Want to create a module for this QueryTask functionality

4320
26
Jump to solution
08-10-2015 02:29 PM
TracySchloss
Frequent Contributor

I have two external tables that contain an ID and a description.  They are the basis for a set of menus, and since they are very similar in their schema, I'm attempting to create one tool  for creating an array of code and description pairs.  In addition, I'm using this as a learning tool for defining modules and it's not going so well. These these will eventually be input for menus, as well as look

These are two different tables and the field names are not  identical, so I need to account for that as well.

In my Map module, myMap.js, I have defined this variable, which is a table I have loaded as a featureLayer, along with several other layers.

        var specUrl = specialtyTable.url;

added a listener for the map load event:

myPopulateCodeList.populateCode('spec',specUrl);

The function populateCode is getting called, but I need to keep the codeList array as a variable, because I'll use it again in more than one place.

myPopulateCodeList.js

 define([
  "dojo/on", 
  "esri/tasks/QueryTask", 
  "esri/tasks/query"
  ], function(
  on,QueryTask, Query){
  var codeList = [];
      return {
        populateCode: function(codeType, url){
          switch (codeType) {
            case "spec":
              codeAtt = 'ID_SPECIALTY_PK';
              specAtt = 'TX_SPECIALTY_DESC';
              break;
            default:
              codeAtt = 'ID_PROV_TYPE_PK';
              specAtt = 'TX_PROV_DESC';
              break;
          }
          var queryTask = new QueryTask(url);
          var query = new Query();
          query.outFields = ["*"];
          query.where = "1=1";
          query.returnGeometry = false;
          on(queryTask, 'complete', this.codeResultsHandler);
          on(queryTask, 'error', this.errorHandler);
          queryTask.execute(query);
        },
        
        //builds codeList object containing codes and descriptions 
        codeResultsHandler: function(results){
          var numResults = results.featureSet.features.length;         
            for (var j = 0; j < numResults; j++) {
              var code = results.featureSet.features.attributes[codeAtt];
              var desc = results.featureSet.features.attributes[specAtt];
              codeList.push({
                code: code,
                desc: desc
              });
            }
            console.log('end of codeList array build');
            return codeList;
          },
        
        errorHandler: function(err){
          console.log("error in myPopulateCodeList, error: " + err.details);
        }
        
      }
    return codeList;
     
    });

I can see that codeList is getting populated with values from the results handler of the queryTask, but I can't figure out how to pass this result back to the calling function.  This doesn't work:

var specCodeList = myPopulateCodeList.populateCode('spec',specUrl);

When I had it all in one big file, this was working, but I'm trying to learn how to break my work up into smaller bits.

0 Kudos
1 Solution

Accepted Solutions
RobertWinterbottom
Occasional Contributor

Yes seems like your getting a reference to myPopulateCodeList.codeList.  If your going to store those codeLists in the global scope then I would recommend modifying the module a little so codeList is not a property of myPopulateCodeList to prevent issues like this.  Here is an example of the original sample I posted with some changes to make codeList not a property of myPopulateCodeList module.

define([    
  "dojo/on",    
  "esri/tasks/QueryTask",    
  "esri/tasks/query",    
  "dojo/_base/lang",  
  "dojo/Deferred"    
  ], function(    
  on, QueryTask, Query, lang, Deferred){    
  var mo = {    
    //codeList: [],   We don't want this as a property here, it should be a local variable in function below  
    populateCode: function(codeType, url){    
      var codeAtt, specAtt, codeList = [];  // Added local codeList here
      var deferred = new Deferred();    
      switch (codeType) {    
        case "spec":    
          codeAtt = "ID_SPECIALTY_PK";    
          specAtt = "TX_SPECIALTY_DESC";    
          break;    
        default:    
          codeAtt = "ID_PROV_TYPE_PK";    
          specAtt = "TX_PROV_DESC";    
          break;    
      }    
      var queryTask = new QueryTask(url);    
      var query = new Query();    
      query.outFields = ["*"];    
      query.where = "1=1";    
      query.returnGeometry = false;    
      queryTask.execute(query, lang.hitch(this,function(results){    
        var numResults = results.featureSet.features.length;    
        for (var j = 0; j < numResults; j++) {    
          var code = results.featureSet.features.attributes[codeAtt];    
          var desc = results.featureSet.features.attributes[specAtt]; 
          // push to the local codeList and not this.codeList   
          codeList.push({    
            code: code,    
            desc: desc    
          });    
        }    
        console.log("end of codeList array build");   
        deferred.resolve(codeList);  
      }), function(err){    
        console.log("error in myPopulateCodeList, error: " + err.details);  
        deferred.reject(err);    
      });   
      return deferred;   
    }    
  };    
  return mo;    
});  

What was happening was that when you called the populateCode function, it was updating the myPopulateCodeList.codeList property. So when you call populateCode function it just updates the same property no matter what you pass into it.  Doing it this way returns a new codeList each time and then you can append it to your global codeLists

View solution in original post

0 Kudos
26 Replies
SteveCole
Frequent Contributor

Is there any reason why you just don't declare codeList as a global variable? Stands to reason this is why it probably worked when everything was in one long JS file.

Either declare it at the top of your myMap.js file (outside of the requires):

var codeList = [];

or if you don't like lots of individual global variables,  create a single global object and then create/update codeList as you need to at the module level:

Create global object in myMap.js before requires:

var app = [];
app.codeList = [];

Create/update values down at the module level:

app.codeList.push({...

I've been really getting my feet wet lately with migrating my legacy JS over to AMD and, although it's entirely possible that I'm doing this wrong, I'm not returning anything from my created AMD modules:

define ([
  "dojo/_base/declare",
  "esri/tasks/PrintTemplate",
  "esri/tasks/PrintParameters",
  "esri/tasks/PrintTask"
], function (
  declare, PrintTemplate, PrintParameters, PrintTask
) {
    return declare(null,{...

I've had some issues when I want to pass a value WITHIN a module from one function to another (i.e. two levels down and trying to use this) but I generally find my module approach and the app.[object] approach works well enough for what I've been trying to do. Again, maybe I'm missing something or being a bit too simplistic.

0 Kudos
TracySchloss
Frequent Contributor

I'm trying not to have too many global variables, because once I have this code broken up, I know I'll have problems keeping track of where I'm settnig them.

I have two tables I need to process, and both arrays needs to be persistent.  I have a field for specialty code and a field for provider type and each has a corresponding lookup table.  Since that's the case, I can't just re-use codeList.  I need something like specCodeList and providerCodeList. 

Technically you don't have to have your code divided into modules to use AMD syntax.  I switched from legacy to AMD a couple years ago.  When I did it, though, I left it all in one file the same as I had it before.   My projects aren't generally very big, so I felt OK about leaving that at that state.   The project I have now is working just great, but it is  now 1200 lines long and I'm tired of scrolling.  Even if I've not fully embraced AMD, if I could get it into manageable chunks, I'd be better off.

0 Kudos
RobertScheitlin__GISP
MVP Emeritus

Tracy,

  This is how I would code your module:

define([
  "dojo/on",
  "esri/tasks/QueryTask",
  "esri/tasks/query",
  "dojo/_base/lang"
  ], function(
  on, QueryTask, Query, lang){
  var mo = {
    codeList: [],
    populateCode: function(codeType, url){
      var codeAtt, specAtt;
      switch (codeType) {
        case "spec":
          codeAtt = "ID_SPECIALTY_PK";
          specAtt = "TX_SPECIALTY_DESC";
          break;
        default:
          codeAtt = "ID_PROV_TYPE_PK";
          specAtt = "TX_PROV_DESC";
          break;
      }
      var queryTask = new QueryTask(url);
      var query = new Query();
      query.outFields = ["*"];
      query.where = "1=1";
      query.returnGeometry = false;
      queryTask.execute(query, lang.hitch(this,function(results){
        var numResults = results.features.length;
        for (var j = 0; j < numResults; j++) {
          var code = results.fetures.attributes[codeAtt];
          var desc = results.features.attributes[specAtt];
          this.codeList.push({
            code: code,
            desc: desc
          });
        }
        console.log("end of codeList array build");
        return this.codeList;
      }), function(err){
        console.log("error in myPopulateCodeList, error: " + err.details);
      });
    }
  };
  return mo;
});
0 Kudos
TracySchloss
Frequent Contributor

I would expect  the 'mo' that is returned to be the codeList array, but when I call it from myMap.js as 

var specCodeList = myPopulateCodeList.populateCode('spec',specUrl);

it is still undefined.

0 Kudos
RobertWinterbottom
Occasional Contributor

I think it returning undefined looks to be the correct behavior the way that Robert Scheitlin, GISP defined it.  Calling that populateCode function does not actually return anything it is just updating the codeList property. So I think you could try something like this:

myPopulateCodeList.populateCode('spec', specUrl);

console.log(myPopulateCodeList.codeList);

logging myPopulateCodeList.codeList should see some things have updated, but remember populateCode is an async function and may take time to complete so the data may not be available right away.  Async operations are great to use dojo's deferred library.

RobertWinterbottom
Occasional Contributor

Here is an example of Robert Scheitlin, GISP​ code but with deferred in it

define([  
  "dojo/on",  
  "esri/tasks/QueryTask",  
  "esri/tasks/query",  
  "dojo/_base/lang",
  "dojo/Deferred"  
  ], function(  
  on, QueryTask, Query, lang, Deferred){  
  var mo = {  
    codeList: [],  
    populateCode: function(codeType, url){  
      var codeAtt, specAtt;
      var deferred = new Deferred();  
      switch (codeType) {  
        case "spec":  
          codeAtt = "ID_SPECIALTY_PK";  
          specAtt = "TX_SPECIALTY_DESC";  
          break;  
        default:  
          codeAtt = "ID_PROV_TYPE_PK";  
          specAtt = "TX_PROV_DESC";  
          break;  
      }  
      var queryTask = new QueryTask(url);  
      var query = new Query();  
      query.outFields = ["*"];  
      query.where = "1=1";  
      query.returnGeometry = false;  
      queryTask.execute(query, lang.hitch(this,function(results){  
        var numResults = results.featureSet.features.length;  
        for (var j = 0; j < numResults; j++) {  
          var code = results.featureSet.features.attributes[codeAtt];  
          var desc = results.featureSet.features.attributes[specAtt];  
          this.codeList.push({  
            code: code,  
            desc: desc  
          });  
        }  
        console.log("end of codeList array build"); 
        deferred.resolve(this.codeList);
      }), function(err){  
        console.log("error in myPopulateCodeList, error: " + err.details);
        deferred.reject(err);  
      }); 
      return deferred; 
    }  
  };  
  return mo;  
});

Then you can use this in your code like so:

myPopulateCodeList.populateCode('spec', specUrl).then(function (codeList) {
  console.log(codeList);
}, function (err) {
  console.error(err);
});

Note this is not tested but should work fine.  Anytime you want to make asynchronous requests, dojo/Deferred can be a great utility to have if you know how to use it.  There is a lot of information on them here: dojo/Deferred — The Dojo Toolkit - Reference Guide

TracySchloss
Frequent Contributor

Maybe I need to also incorporate the idea that Steve brought up.  I need these codeList to continue to be available throughout the application.  I'm actually using them as a lookup table, so every time I create a grid or info tag, I need them to look up what the description is to go with the code.

In my index.html I have defined

var app = {};

I guess I need to continue to make this available to my other .js files by passing it as a parameter?

0 Kudos
SteveCole
Frequent Contributor

Tracy Schloss

If you follow what I was throwing out there, it should be

var app = [];

not

var app = {};

0 Kudos
TracySchloss
Frequent Contributor

I can definitely see that I need to handle this is in a my async manner.  The calling script myMap.js is getting to the console.log ('UR here') way before it finishes the results of the queryTask.  I should mention the external table is a DB2 business table, nothing in geodatabase format, and I always operate under the assumption it's going to take longer to process any query.

Listing myPopulateCodeList.codeList in the console log displays an empty array.

I should also mention for anyone who wants to use this code for themselves that the queryTask results must be referenced as results.features, not results.featureSet.features.  In my original posting, I had a separate results handler.  When you call a separate results handler, that's when you need to results.featureSet.features to get to the individual features.

0 Kudos