I have a map with a slide out panel that houses a list of elevation certificates, the certificates are grouped into collapsing menus by year issued. My problem is that to build the menu out takes two queries since there are 1755 records. Being asynchronous by design JS does both in quick succession and builds out my menu in the order the queries come back. That means it may start with the second batch first. How can I make it do the first then the second? I'm already using execute(). then(). If I cant do it with the query logic but can combine the two feature sets somehow I could sort that and then build off the master feature set but I don't see how to do that either. If there is a better way to page through the feature class than using the start parameter of the query I'm open to completely reformulating this.
var queryTask = new QueryTask({
url: "https://gis.baycountyfl.gov/arcgis/rest/services/ElevationCertificates/MapServer/0"
});
var query = new Query();
query.returnGeometry = true;
query.outFields = ["DateIssued","Address","OBJECTID"];
query.where = "1=1";
query.orderByFields = ["DateIssued"];
query.outSpatialReference = 3857;
query.num = 1000;
queryTask.executeForCount(query).then(function(count){
for (var step=0; step < count; step += 1000){
query.start = step;
queryTask.execute(query).then(function(results){
for (var i=0; i < results.features.length; i++) {
let recordDate = new Date(results.features[i].attributes.DateIssued);
let recordYear = recordDate.getFullYear()
if (!$("#content" + recordYear).length){
$("#addressListDiv").append('<button type="button" class="collapsible" id="button' + recordYear + '">' + recordYear + '</button>');
$("#addressListDiv").append('<div class="content" id="content' + recordYear + '"></div>');
$("#button" + recordYear).on("click", function() {
this.classList.toggle("active");
var content = this.nextElementSibling;
if (content.style.display === "block") {
content.style.display = "none";
} else {
content.style.display = "block";
}
});
}
let OBJECTID = results.features[i].attributes.OBJECTID
let address = results.features[i].attributes.Address
$("#content" + recordYear).append('<p class="address" id="' + OBJECTID + '">' + address + '</p>')
let center = results.features[i].geometry;
$("#" + OBJECTID).on("click", function(){
view.center = center;
view.zoom = 18;
})
}
})
}
})
});
Solved! Go to Solution.
I actually figured out another way to do it. By feeding the promises returned by the query execution to a list and then that list into Promise.all you can make the script wait for those promises to resolve. In this case they resolve into the results objects and I could combine the results.features arrays, sort the array, and build out the menu. It blocks the code but the queries come back quick enough you don't notice.
var queryTask = new QueryTask({
url: "https://gis.baycountyfl.gov/arcgis/rest/services/ElevationCertificates/MapServer/0"
});
var query = new Query();
query.returnGeometry = true;
query.outFields = ["DateIssued","Address","OBJECTID"];
query.where = "1=1";
query.outSpatialReference = 3857;
query.num = 1000;
var certificates = [];
var promises = []
queryTask.executeForCount(query).then((count) => {
for (var step=0; step < count; step += 1000){
query.start = step;
promises.push(queryTask.execute(query))
}
}).then(() => {
Promise.all(promises).then((results) => {
for (var i=0; i < results.length; i++){
certificates = certificates.concat(results[i].features);
}
certificates.sort((a, b) => (a.attributes.DateIssued > b.attributes.DateIssued) ? 1 : (a.attributes.DateIssued === b.attributes.DateIssued) ? ((a.attributes.Address > b.attributes.Address) ? 1 : -1) : -1 );
for (var i=0; i < certificates.length; i++) {
let recordDate = new Date(certificates[i].attributes.DateIssued);
let recordYear = recordDate.getFullYear()
if (!$("#content" + recordYear).length){
$("#addressListDiv").append('<button type="button" class="collapsible" id="button' + recordYear + '">' + recordYear + '</button>');
$("#addressListDiv").append('<div class="content" id="content' + recordYear + '"></div>');
$("#button" + recordYear).on("click", function() {
this.classList.toggle("active");
var content = this.nextElementSibling;
if (content.style.display === "block") {
content.style.display = "none";
} else {
content.style.display = "block";
}
});
}
let OBJECTID = certificates[i].attributes.OBJECTID
let address = certificates[i].attributes.Address
$("#content" + recordYear).append('<p class="address" id="' + OBJECTID + '">' + address + '</p>')
let center = certificates[i].geometry;
$("#" + OBJECTID).on("click", function(){
view.center = center;
view.zoom = 18;
})
}
})
})
Chris,
The simple way is to execute the second query in the results handler function of the first query.
That would be the easiest but hard coding in the second query would not allow for growth beyond 2000 records in the table which will eventually happen. I feel like the menu is a bit unwieldy with so many items in it but that's how they want it built. Looking at the browser console I didnt realize that a featureset.features is just an array. Ill just concat the resulting arrays into a new one and then write a custom sort.
Chris,
Then an approach like i use in my PagingQueryTask would be appropriate then.
My PagingQueryTask class is a little complicated to follow but maybe these blocks of code would work for explaining what code path you need to take.
1. query the record ids.
this.iStart = 0;
this.iMaxRecords = 0;
this.featuresProcessed = 0;
this.featuresTotal = 0;
this.query.where = "1=1";
this.query.returnGeometry = false;
this.queryTask.executeForIds(this.query, lang.hitch(this, this.onSearchIdsFinish), lang.hitch(this, this.onSearchError));
onSearchIdsFinish: function (results) {
if (results.length > 0) {
this.objectIdsArray = results;
this.featuresTotal = this.objectIdsArray.length;
this.query.where = null;
this.query.text = null;
this.query.objectIds = this.objectIdsArray.slice(0, this.maxRecordCount);
this.queryTask.execute(this.query, lang.hitch(this, this.onSearchFinish), lang.hitch(this, this.onSearchError));
} else {
console.error('onSearchIdsFinish returned zero length');
this.isQuerying = false;
this.esc = false;
this.emit('pagingFault');
}
},
2. query the first 1000 records using the query.objectids
3. then loop through all the remaining records 1000 at a time.
onSearchFinish: function (featureSet) {
//do something with the returned query results
// check to see if max records has been determined.
// add the max records to the start index as these have already been queried.
if (this.iMaxRecords === 0) {
this.iMaxRecords = this.featuresProcessed;
this.iStart += this.iMaxRecords;
}
// Query the server for the next lot of features
// Use the objectids as the input for the query.
// Do not continue if the esc is true.
if (this.iStart < this.objectIdsArray.length && this.esc === false) {
//If we get this far we need to requery the server for the next lot of records
this.isQuerying = true;
this.query.objectIds = this.objectIdsArray.slice(this.iStart, this.iStart + this.iMaxRecords);
this.queryTask.execute(this.query, lang.hitch(this, this.onSearchFinish), lang.hitch(this, this.onSearchError));
this.iStart += this.iMaxRecords;
}
}
},
I actually figured out another way to do it. By feeding the promises returned by the query execution to a list and then that list into Promise.all you can make the script wait for those promises to resolve. In this case they resolve into the results objects and I could combine the results.features arrays, sort the array, and build out the menu. It blocks the code but the queries come back quick enough you don't notice.
var queryTask = new QueryTask({
url: "https://gis.baycountyfl.gov/arcgis/rest/services/ElevationCertificates/MapServer/0"
});
var query = new Query();
query.returnGeometry = true;
query.outFields = ["DateIssued","Address","OBJECTID"];
query.where = "1=1";
query.outSpatialReference = 3857;
query.num = 1000;
var certificates = [];
var promises = []
queryTask.executeForCount(query).then((count) => {
for (var step=0; step < count; step += 1000){
query.start = step;
promises.push(queryTask.execute(query))
}
}).then(() => {
Promise.all(promises).then((results) => {
for (var i=0; i < results.length; i++){
certificates = certificates.concat(results[i].features);
}
certificates.sort((a, b) => (a.attributes.DateIssued > b.attributes.DateIssued) ? 1 : (a.attributes.DateIssued === b.attributes.DateIssued) ? ((a.attributes.Address > b.attributes.Address) ? 1 : -1) : -1 );
for (var i=0; i < certificates.length; i++) {
let recordDate = new Date(certificates[i].attributes.DateIssued);
let recordYear = recordDate.getFullYear()
if (!$("#content" + recordYear).length){
$("#addressListDiv").append('<button type="button" class="collapsible" id="button' + recordYear + '">' + recordYear + '</button>');
$("#addressListDiv").append('<div class="content" id="content' + recordYear + '"></div>');
$("#button" + recordYear).on("click", function() {
this.classList.toggle("active");
var content = this.nextElementSibling;
if (content.style.display === "block") {
content.style.display = "none";
} else {
content.style.display = "block";
}
});
}
let OBJECTID = certificates[i].attributes.OBJECTID
let address = certificates[i].attributes.Address
$("#content" + recordYear).append('<p class="address" id="' + OBJECTID + '">' + address + '</p>')
let center = certificates[i].geometry;
$("#" + OBJECTID).on("click", function(){
view.center = center;
view.zoom = 18;
})
}
})
})
Chris,
Yep, I have used that type of method as well with promise.all but in most of my cases I want to start working with the first 1000 records as soon as they are available, thus my paging query task.
I had a similar quandary and solved it with composing promises.
https://community.esri.com/thread/222084-multiple-querytask-operations-with-maxrecordcount