timing issue -- anyone who is good with deferreds?

3422
21
Jump to solution
11-12-2013 06:05 AM
KaitlynnDavis
Occasional Contributor
I'm building an editing application, and I use a custom drop-down menu for one of the fields in my attribute inspector. The drop down is populated by a query, so it's very important that the query function completes before the function that builds the attribute inspector. My problem is timing the query. My map listener map.on("layers-add-result", initEditor); fires initEditor well before my query has had the chance to complete and as a result my map features become disabled. I can fix the problem by pausing the execution of initEditor with an alert box, but obviously this can't be a permanent solution.

I believe a deffered would help ensure that my query task has returned before populating my attribute inspector, but I am weak on the concept. Is there a programmatic way to say map.on("layers-add-result", initEditor) after query returns results?
0 Kudos
1 Solution

Accepted Solutions
KaitlynnDavis
Occasional Contributor
The big picture is that I want an attribute inspector field that draws from a feature layer called "reports", so people can easily associate the map feature they're adding with the report that it comes from. We'll be continuously adding to the reports dataset, so I can't have a domain with finite values. I couldn't find a way to create a domain in ArcMap that is formulated from a query, just domains where you have to hard code your possible options. So that's why I ended up creating a custom select dijit in my JavaScript.

I was able to resolve my issue with the deferred pattern. As was suggested earlier I want to control when my map layers are getting added to the map, so that the completion of my queries are prioritized before firing the editor init function. So in my init function for the map I created a new Deferred and the instruction to wait until it's resolved to add my map layers

deferred = new dojo.Deferred();  deferred.then(function(value){         console.log(value);  map.addLayers([needPoints, needRoutes, needAreas]); });


Then, at the end of my query I resolved my deferred:

deferred.resolve("success"); return deferred.promise;


This was a tricky one and I very much appreciate the help you gave!

View solution in original post

0 Kudos
21 Replies
JohnGravois
Frequent Contributor
why not just name the function you want to call using a QueryTask event like 'complete'?

ie:
myQuery.on('complete', initEditor);
0 Kudos
KaitlynnDavis
Occasional Contributor
That would be a good choice except then my layers will load before my query's callback gets the chance to finish. That's important because the callback is where the dropdown menu is populated. As far as I can tell there is no event listener in the API for the completion of a callback function...

queryTaskDocs = new esri.tasks.QueryTask("server_path");
var queryDocs = new esri.tasks.Query();
queryDocs.outFields = ["DOC_TITLE2"];
queryDocs.returnGeometry = false;
queryDocs.where  = "DOC_TITLE2 <> ''"
queryTaskDocs.execute(queryDocs,function(results){
 //Populate the dropdown list box with unique values from DOC_TITLE2 field
 var docs;
 var values = [];
 var testVals={};
 var features = results.features;
 dojo.forEach (features, function(feature) {
     docs = feature.attributes.DOC_TITLE2;
     if (!testVals[docs]) {
   testVals[docs] = true;
   values.push({name:docs});
  }
     });        
 store1 = new dojo.store.Memory({data:values});
 dijit.byId("docSelect").set("store", store1);  
});
dojo.connect(queryTaskDocs, "onComplete", map.addLayers([featurePoints, featureRoutes, featureAreas]));
0 Kudos
JohnGravois
Frequent Contributor
i have to confess i don't quite understand the code flow, but you should be able to call map.addLayers() as the last line within your existing QueryTask callback.

queryTaskDocs.execute(queryDocs,function(results){
 //...
 dijit.byId("docSelect").set("store", store1);
 map.addLayers([featurePoints, featureRoutes, featureAreas])); 
});

//no need for this anymore
//dojo.connect(queryTaskDocs, "onComplete", map.addLayers([featurePoints, featureRoutes, featureAreas]));


if not, please layout the entire sequence of asynchronous calls being made and their dependencies and i should be able to walk you through using existing events to control the timing.
0 Kudos
KaitlynnDavis
Occasional Contributor
I tried your suggestion and placing map.addLayers at the end of my callback just caused the runtime to skip over most of the callback before adding the layers...

I reproduced my problem using esri data (apologies for the extra indents), placing log statements throughout to monitor the timing of my code. It should work so:

After loading my map and layers in init, I call a query function that obtains all the results for the req_type field, and then fires a callback that loops through the results and places the unique values into an object store. This store is used to populate a drop down that appears in the attribute inspector. So its basically a plain editing app that has a custom drop down for one of its attribute inspector fields

My problem is that the initEditor function that sets up the attribute inspector starts as soon as my map layers are added, and completes before my callback has the chance to complete. When I place an alert though, it halts initEditor, allowing the populateList callback to complete. I am just wondering if I can make the populateList a deferred that when resolved adds my map layers which in turn starts my editor initialization...

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=7, IE=9, IE=10">
    <!--The viewport meta tag is used to improve the presentation and behavior of the samples 
      on iOS devices-->
    <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
    <title>Add New Needs</title>

 <link rel="stylesheet" type="text/css" href="http://serverapi.arcgisonline.com/jsapi/arcgis/3.5/js/dojo/dijit/themes/claro/claro.css">
    <link rel="stylesheet" type="text/css" href="http://serverapi.arcgisonline.com/jsapi/arcgis/3.5/js/esri/css/esri.css" />
 <link rel="stylesheet" type="text/css" href="http://serverapi.arcgisonline.com/jsapi/arcgis/3.5/js/dojo/dojox/grid/resources/Grid.css">
 <link rel="stylesheet" type="text/css" href="http://serverapi.arcgisonline.com/jsapi/arcgis/3.5/js/dojo/dojox/grid/resources/claroGrid.css">
    <style>
      html,body{height:100%;width:100%;margin:0;overflow:hidden;}
      #map{
        padding:0;
      }
      .dj_ie .infowindow .window .top .right .user .content { position: relative; }
      .dj_ie .simpleInfoWindow .content {position: relative;}
    </style>
 <script>var dojoConfig = { parseOnLoad:true };</script>
    <script src="http://serverapi.arcgisonline.com/jsapi/arcgis/3.5/"></script>
    <script>
      dojo.require("dijit.dijit"); // optimize: load dijit layer
      dojo.require("dijit.layout.BorderContainer");
      dojo.require("dijit.layout.ContentPane");
      dojo.require("esri.dijit.Geocoder");
      dojo.require("dijit.layout.StackContainer");
      dojo.require("dijit.MenuBar"); 
     dojo.require("dijit.PopupMenuBarItem");
     dojo.require("dijit.DropDownMenu");
     dojo.require("dijit.MenuItem");
     dojo.require("esri.layers.FeatureLayer");
     dojo.require("dojo.parser");
    dojo.require("dijit.form.ComboBox");
    dojo.require("esri.dijit.editing.Editor-all");
      dojo.require("esri.SnappingManager");
   dojo.require("esri.dijit.editing.AttachmentEditor");
   dojo.require("dijit.form.Select");
   dojo.require("dijit.form.ComboBox");
   dojo.require("dojo.store.Memory");
   dojo.require("dojo.Deferred");


      var map;
      
      function init() {
   
   console.log("drop line 1");
   esriConfig.defaults.io.proxyUrl = "/proxy"; 
   //This service is for development and testing purposes only. We recommend that you create your own geometry service for use within your applications. 
   esri.config.defaults.geometryService = new esri.tasks.GeometryService("http://tasks.arcgisonline.com/ArcGIS/rest/services/Geometry/GeometryServer");
  
   var customExtentAndSR = new esri.geometry.Extent({"xmin": -134.060026464775,"ymin": 32.885241223675,"xmax": -110.305411069225,"ymax": 42.2934163323251,"spatialReference": {"wkid": 4326}});
        
   map = new esri.Map("map", {extent:customExtentAndSR, zoom: 11});
   dojo.connect(map, "onLayersAddResult", initEditor);

   var basemap = new esri.layers.ArcGISTiledMapServiceLayer("http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer");
   
   var needPoints = new esri.layers.FeatureLayer("http://sampleserver3.arcgisonline.com/ArcGIS/rest/services/SanFrancisco/311Incidents/FeatureServer/0");
   map.addLayers([basemap, needPoints]);
   
   var queryTaskDocs = new esri.tasks.QueryTask("http://sampleserver3.arcgisonline.com/ArcGIS/rest/services/SanFrancisco/311Incidents/MapServer/0");
    var queryDocs = new esri.tasks.Query();
    queryDocs.outFields = ["req_type"];
    queryDocs.returnGeometry = false;
    queryDocs.where  = "req_type <> ''"
    queryTaskDocs.execute(queryDocs,populateListDocs);
    
   console.log("drop line 3");

   }
   
   function populateListDocs(results) {
  console.log("drop line 4");
     //Populate the dropdown list box with unique values
     var type;
     var values = [];
     var testVals={};
     var features = results.features;
     dojo.forEach (features, function(feature) {
       type = feature.attributes.req_type;
       if (!testVals[type]) {
      testVals[type] = true;
      values.push({name:type});
       }
     });        
     store1 = new dojo.store.Memory({data:values});
     dijit.byId("docSelect").set("store", store1);
     def.resolve();
     console.log("store is set");
   };
 
   function initEditor(results) {
   alert("this is just here for now");
   console.log("drop line 5");
   var templateLayers = dojo.map(results,function(result){
    return result.layer;
   });
   console.log("drop line 6");
   var templatePicker = new esri.dijit.editing.TemplatePicker({
    featureLayers: templateLayers,
    grouping: false,
    rows: 'auto',
    columns: 3
   },'templateDiv');
   templatePicker.startup();
    
   //widget used for the attribute inspector custom field
    var docSelect = new dijit.form.ComboBox({
     id: "docSelect",
     name: "docSelect",
     store: store1,
    }, "docSelect");

     var layers = arrayUtils.map(evt.layers, function(result) {
    return { featureLayer: result.layer,
      showDeleteButton:true,
      fieldInfos:[
        {fieldName: "req_id", visible: true, 'label':"id", 'customField': docSelect},
        {fieldName: "req_type", visible: true, 'label':"req type", 'customField': docSelect}
        ]
      };
     });
    
    var settings = {
     map: map,
     templatePicker: templatePicker,
     layerInfos:layers,
     toolbarVisible: true,
     createOptions: {
      polylineDrawTools:[ esri.dijit.editing.Editor.CREATE_TOOL_FREEHAND_POLYLINE],
      polygonDrawTools: [ esri.dijit.editing.Editor.CREATE_TOOL_FREEHAND_POLYGON,
      esri.dijit.editing.Editor.CREATE_TOOL_CIRCLE,
      esri.dijit.editing.Editor.CREATE_TOOL_TRIANGLE,
      esri.dijit.editing.Editor.CREATE_TOOL_RECTANGLE
      ]
     },
     toolbarOptions: {
      reshapeVisible: true
     }
    };
    console.log("drop line 8");
    var params = {settings: settings};
    //Create the editor widget 
     var myEditor = new esri.dijit.editing.Editor(params,'editorDiv');
    //define snapping options
    var symbol = new esri.symbol.SimpleMarkerSymbol(esri.symbol.SimpleMarkerSymbol.STYLE_CROSS, 15, new esri.symbol.SimpleLineSymbol(esri.symbol.SimpleLineSymbol.STYLE_SOLID, new dojo.Color([255, 0, 0, 0.5]), 5), null);
    map.enableSnapping({
      snapPointSymbol:symbol,
      tolerance:20,
      snapKey:dojo.keys.ALT
    });
    
    myEditor.startup();
   }
   console.log("drop line 9");
      dojo.ready(init);
    </script>
  </head>
  <body class="claro">
    <div id="main" data-dojo-type="dijit/layout/BorderContainer" data-dojo-props="design:'headline'" style="height:width:100%;height:100%;">
      <div data-dojo-type="dijit/layout/ContentPane" id="header" data-dojo-props="region:'top'">
        Edit Hydrography
      </div>
      <div data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region:'left'" style="width: 300px;overflow:hidden;">
        <div id="templateDiv"></div>
        <div id="editorDiv"></div>
      </div>
      <div data-dojo-type="dijit/layout/ContentPane" id="map" data-dojo-props="region:'center'"></div>
    </div>
  </body>
</html>
0 Kudos
KenBuja
MVP Esteemed Contributor
Have you tried initialize the attribute editor in the populateListDocs function, after the store is built, instead of in the onLayersAddResult function?
0 Kudos
KaitlynnDavis
Occasional Contributor
thanks for the suggestion. Placing initEditor() at the end of my query callback introduces problems because initEditor needs to take as an argument the loaded map layers in order to apply the editor widgets to them. So when initEditor(results) is invoked it is taking the result of "onLayersAddResult". So I tried this to store the result in a global to use later:

dojo.connect(map, "onLayersAddResult", function(results){myResults = results;});


Then I connected my query function to the layer onLoad event and, as you suggested, placed initEditor(myResults); at the last line of my query callback. Doing this breaks my code at that line, so I don't think I called initEditor properly.
0 Kudos
JohnGravois
Frequent Contributor
i apologize if im missing something, but if you defined a domain for the appropriate field in the feature layer itself, wouldnt the editor widget pick up on it and show corresponding descriptions to users automatically?
0 Kudos
KaitlynnDavis
Occasional Contributor
The big picture is that I want an attribute inspector field that draws from a feature layer called "reports", so people can easily associate the map feature they're adding with the report that it comes from. We'll be continuously adding to the reports dataset, so I can't have a domain with finite values. I couldn't find a way to create a domain in ArcMap that is formulated from a query, just domains where you have to hard code your possible options. So that's why I ended up creating a custom select dijit in my JavaScript.

I was able to resolve my issue with the deferred pattern. As was suggested earlier I want to control when my map layers are getting added to the map, so that the completion of my queries are prioritized before firing the editor init function. So in my init function for the map I created a new Deferred and the instruction to wait until it's resolved to add my map layers

deferred = new dojo.Deferred();  deferred.then(function(value){         console.log(value);  map.addLayers([needPoints, needRoutes, needAreas]); });


Then, at the end of my query I resolved my deferred:

deferred.resolve("success"); return deferred.promise;


This was a tricky one and I very much appreciate the help you gave!
0 Kudos
JohnGravois
Frequent Contributor
thanks for taking the time to explain your steps.  i guess i'm still not sure what you mean by 'from a query', but i can definitely understand the trouble of working with a growing domain.

if it would be helpful, perhaps you could use a gp tool like table to domain as some sort of scheduled task instead of entering items manually?
0 Kudos