Can't "save" a feature from QueryTask. Argh!

962
10
Jump to solution
10-03-2012 12:10 PM
SteveCole
Frequent Contributor
This is probably pretty easy but I can't seem to get what I'm doing wrong. I have a function, kicked off via a button click, that queries a particular layer, and then does some additional queries & data retrieval against three other layers. Basically, I have one location and I want to extract information from three other datasets that overlap its spatial extent.

Anyways, what I've tried to do is an initial query to my layer which has my spatial location (using an FID which I pass to the function). Once I query that layer, I was to "save" the feature to a variable so I can quickly use it in my subsequent queryTask calls. Here's my simple code:

function getProjectReport(theFid) {  var theParam = theFid.split(",");   var query = new esri.tasks.Query();      query.where = "FID=" + theParam[0];  query.outFields = ["*"];  query.returnGeometry = true;    var theGraphic;    //Specify the appropriate point/line map service depending on what was clicked  switch (theParam[1]) {   case "point":    var queryTask = new esri.tasks.QueryTask("URL #1");      break;   case "polyline":    var queryTask = new esri.tasks.QueryTask("URL #2");      break;  }          queryTask.execute(query,function(featureSet) {     dojo.forEach(featureSet.features, function(feature) {      theGraphic = feature;                 });  });     console.log(theGraphic.attributes.PROJECT); }


Now, the queryTask finds my record but when it gets to my "console.log" line, it errors out and tells me that my variable theGraphic is undefined. I've defined my variable at the beginning of my function so why doesn't my assignment persist after the end ofmy forEach loop? How do I fix this so I can re-use theGraphic later on in my function?

Thanks again!
Steve
0 Kudos
1 Solution

Accepted Solutions
WyattPearsall
New Contributor II
The variable should be persisting, you are just accessing it before it gets set.
When you execute a query, your code continues running and the callback isn't
called until the request is ready. This is good, because it doesn't lock your screen
when you make a request, but you have to recognize that until your callback is actually
called, none of the code within it has taken place. This is what is happening to your
variable assignment.

The callback of the query is the ideal place to do work that depends on a variable in the query,
because you are guaranteed that the query has completed. Thus, nesting your queries in callback
functions will allow you to take advantage of asynchronous without running into timing problems.

View solution in original post

0 Kudos
10 Replies
BillDaigle
Occasional Contributor III
You're passing the graphic to the console before the execute task has finished running.  Place the console.log inside the callback for the execute function.

queryTask.execute(query,function(featureSet) {  
  dojo.forEach(featureSet.features, function(feature) {
     theGraphic = feature;       
                });
                console.log(theGraphic.attributes.PROJECT);
 }); 


Just FYI, based on the above code, theGraphic is only going to represent the last item in the featureSet.
0 Kudos
SteveCole
Frequent Contributor
Hi Bill,

There is only one item return via the query so that part is fine. The reason I included the console.log code line outside of the loop was to reinforce the point that my variable is not persisting beyond the scope of the forEach loop. This is something I want it to do.
0 Kudos
__Rich_
Occasional Contributor III
Bill's right, put some console.logs in around the code and you'll see the order that things get executed.

Don't forget the QueryTask.execute() is async and won't block on execution...

Here's your original code, complete with bug but with some log outputs:
function getProjectReport(theFid) {
 var theParam = theFid.split(",");

 var query = new esri.tasks.Query();    
 query.where = "FID=" + theParam[0];
 query.outFields = ["*"];
 query.returnGeometry = true;
 
 var theGraphic;
 
 //Specify the appropriate point/line map service depending on what was clicked
 switch (theParam[1]) {
  case "point":
   var queryTask = new esri.tasks.QueryTask("URL #1");  
   break;
  case "polyline":
   var queryTask = new esri.tasks.QueryTask("URL #2");  
   break;
 }

        console.log("Calling execute()");
        queryTask.execute(query,function(featureSet) {
               console.log("Task callback called");
  dojo.forEach(featureSet.features, function(feature,i) {
                          console.log("Looping through features, current feature index =" + i + " about to set theGraphic variable");
     theGraphic = feature;
                          console.log("Have set theGraphic variable for feature index = " + i);
                });
 }); 
 console.log("Trying to log theGraphic stuff, this will fail...");
 console.log(theGraphic.attributes.PROJECT);
}
0 Kudos
SteveCole
Frequent Contributor
Yes- I understand what Bill (and you) are saying.

What I'm not doing a good job of saying is that I want/need to access theGraphic variable AFTER the conclusion of the queryTask.Execute routine. I need to re-use the geometry and some of it's attributes for subsequent queries on additional datasets.

As my code currently exists, I can't- and I don't know what to change to get it to behave in the manner I need. I thought by declaring theGraphic as a variable at the beginning of the function it would persist and be accessible throughout the entire function but it's not (or I'm using it wrong).
0 Kudos
WyattPearsall
New Contributor II
The variable should be persisting, you are just accessing it before it gets set.
When you execute a query, your code continues running and the callback isn't
called until the request is ready. This is good, because it doesn't lock your screen
when you make a request, but you have to recognize that until your callback is actually
called, none of the code within it has taken place. This is what is happening to your
variable assignment.

The callback of the query is the ideal place to do work that depends on a variable in the query,
because you are guaranteed that the query has completed. Thus, nesting your queries in callback
functions will allow you to take advantage of asynchronous without running into timing problems.
0 Kudos
KellyHutchins
Esri Frequent Contributor
Steve,

Are you trying to query multiple layers at the same time? If so perhaps this sample will help:

http://help.arcgis.com/en/webapi/javascript/arcgis/help/jssamples/query_deferred_list.html


The sample shows how to work with a dojo deferred list to get notified when multiple query tasks have completed.
0 Kudos
__Rich_
Occasional Contributor III
Yes- I understand what Bill (and you) are saying.

What I'm not doing a good job of saying is that I want/need to access theGraphic variable AFTER the conclusion of the queryTask.Execute routine. I need to re-use the geometry and some of it's attributes for subsequent queries on additional datasets.

As my code currently exists, I can't- and I don't know what to change to get it to behave in the manner I need. I thought by declaring theGraphic as a variable at the beginning of the function it would persist and be accessible throughout the entire function but it's not (or I'm using it wrong).

...and we understand what you are saying.

You can access theGraphic variable, no problem there, it's just then when you try it hasn't been populated yet.

See:
function getProjectReport(theFid) {
 var theParam = theFid.split(",");

 var query = new esri.tasks.Query();    
 query.where = "FID=" + theParam[0];
 query.outFields = ["*"];
 query.returnGeometry = true;
 
 var theGraphic = {attributes:{PROJECT:"nothing set yet"}};
 
 //Specify the appropriate point/line map service depending on what was clicked
 switch (theParam[1]) {
  case "point":
   var queryTask = new esri.tasks.QueryTask("URL #1");  
   break;
  case "polyline":
   var queryTask = new esri.tasks.QueryTask("URL #2");  
   break;
 }

        console.log("Calling execute()");
        queryTask.execute(query,function(featureSet) {
               console.log("Task callback called");
  dojo.forEach(featureSet.features, function(feature,i) {
                          console.log("Looping through features, current feature index =" + i + " about to set theGraphic variable");
     theGraphic = feature;
                          console.log("Have set theGraphic variable for feature index = " + i);
                });
 }); 
 console.log("Trying to log theGraphic stuff, this will output the initial value as the callback (probably, i.e. race conditions) hasn't fired yet...");
 console.log(theGraphic.attributes.PROJECT);
}


You must wait for the QueryTask callback to do it's thing before trying to use any values it sets.

Why can't you trigger whatever code needs to use the graphic from inside the callback?  You could even pass the graphic out to another function - a callback from your callback if you like 😉
0 Kudos
SteveCole
Frequent Contributor
Ok, my apologies. The light bulb went off with Wyatt's description as to what's happening. So my problem is waiting until the callback finishes and it sounds like the deferredList that Kelly was suggesting might be the way to do this?

Here's my attempt to incorporate deferredList-

function getProjectReport(theFid) { 
 var theDocument, theGraphic 
 
 theGraphic = returnTipShape(theFid);
 
 console.log(theGraphic.attributes.PROJECT);
        .
        .
        .
}

function returnTipShape(param) {
 var theParam = param.split(",");

 var query = new esri.tasks.Query();    
 query.where = "FID=" + theParam[0];
 query.outFields = ["*"];
 query.returnGeometry = true;
 var theShape; 
 
 //Specify the appropriate point/line map service depending on what was clicked
 switch (theParam[1]) {
  case "point":
   var queryTask = new esri.tasks.QueryTask("http://dmc-arcgis.snoco.co.snohomish.wa.us/SnocoGISdev/rest/services/transportation/tipProjectsPoint/MapServer/0");  
   break;
  case "polyline":
   var queryTask = new esri.tasks.QueryTask("http://dmc-arcgis.snoco.co.snohomish.wa.us/SnocoGISdev/rest/services/transportation/tipProjectsLine/MapServer/0");  
   break;
 }

        dQuery = queryTask.execute(query); 

 var dList = new dojo.DeferredList([dQuery]);
 dList.then(function(results) {
  var featureSet = results[0];
  dojo.forEach(featureSet.features, function(feature) {
     theShape = feature;       
        });
 });
 
 return theShape;
}


I thought that if I put the queryTask and deferredList calls into a function, that would isolate the process until it finishes. Of course, I get the same error so it looks like I've just moved the same issue around.
0 Kudos
BillDaigle
Occasional Contributor III
You're still not waiting for the response to be done before proceeding.  You could do what your trying to do by nesting the second step inside a callback function:


function getProjectReport(theFid) { 
  var theDocument, theGraphic 
  
  returnTipShape(theFid,function(returnedShape){
    console.log(theGraphic.attributes.PROJECT);
        .
        .
        .
  });
  
  
}

function returnTipShape(param,callback) {
  var theParam = param.split(",");

  var query = new esri.tasks.Query();    
  query.where = "FID=" + theParam[0];
  query.outFields = ["*"];
  query.returnGeometry = true;
  var theShape; 
  
  //Specify the appropriate point/line map service depending on what was clicked
  switch (theParam[1]) {
    case "point":
      var queryTask = new esri.tasks.QueryTask("http://dmc-arcgis.snoco.co.snohomish.wa.us/SnocoGISdev/rest/services/transportation/tipProjectsPoint/MapServer/0");   
      break;
    case "polyline":
      var queryTask = new esri.tasks.QueryTask("http://dmc-arcgis.snoco.co.snohomish.wa.us/SnocoGISdev/rest/services/transportation/tipProjectsLine/MapServer/0");    
      break;
  }

  dQuery = queryTask.execute(query); 

  var dList = new dojo.DeferredList([dQuery]);
  dList.then(function(results) {
    var featureSet = results[0];
    dojo.forEach(featureSet.features, function(feature) {
        theShape = feature;       
        });
    callback(theShape);
  });
  
  //return theShape;
}


Also, you don't really need a deferredList in this case since you're only doing one call.  Deferred lists are really only necessary when you need to wait for the response from multiples requests.
0 Kudos