4

I have a module with four functions that call one after the other. I am trying to follow the Revealing Module Pattern. One of the functions is public, the remaining are private. It goes like this:

  1. publicMethod is called from another module
  2. queryNames is called from publicMethod
  3. execute(parameters, callback?, errback?) is called from queryNames
  4. addNamesList is called as the callback? argument of execute
  5. Several dijit/form/CheckBox's are created and the method querySegments is triggered onChange
  6. querySegments needs to call a method of an object created in publicMethod.

The problem is in step 6, I can't reach the object created in step 1.

I have tried to use dojo hitch to define the callback? argument in step 3, but I can't get it to work. I tried putting this in its first argument, but even then I can't reach the required scope to call addNamesList.

Here is some code to demonstrate this issue.

define([
  'dojo/dom',
  'dijit/form/CheckBox',
  'esri/layers/ArcGISDynamicMapServiceLayer',
  'esri/tasks/query',
  'esri/tasks/QueryTask',
  'dojo/_base/lang'
],
  function (
    dom,
    CheckBox,
    ArcGISDynamicMapServiceLayer,
    Query, QueryTask,
    lang
  ) {
    // ***************
    // private methods
    // ***************

    // fetch names and call addNamesList to put the list in place
    var queryNames = function (map, mapLayer) {
      // new QueryTask(url, options?)
      var queryTask = new QueryTask("url")
      var query = new Query()
      // execute(parameters, callback?, errback?)
      // this callback passes an argument called featureSet
      queryTask.execute(query, lang.hitch(map, "addNamesList", mapLayer), function(error) {console.log(error)})
    }  

    // callback function of queryNames
    var addNamesList = function (mapLayer, featureSet) {
      console.log('addOplist')
      var namesCount = featureSet.features.length
      for (var i = 0; i <namesCount; i++) {
        // work
        var cbox = new CheckBox({
          id: "cbox_" + i,
          value: featureSet.features[i].attributes["someID"],
          checked: false,
          onChange: function (evt) {
            querySegments(this.value, mapLayer)
          }
        })
        cbox.placeAt("someDiv" + i, "first")
      }
    }

    // triggered by the checkbox event
    var querySegments = function (name, mapLayer) {
        // build the query
        var queryStatement = "someID = " + name
        var layerDefinitions = [queryStatement]
        // call a method of mapLayer
        mapLayer.setLayerDefinitions(layerDefinitions)
    }

    // **************
    // public methods
    // **************
    var publicMethod = function (map) {
      var mapLayer = new ArcGISDynamicMapServiceLayer('restURL')
      map.addLayer(mapServiceLayer)
      queryNames(map, mapLayer)
      return mapLayer
    }

    return {
      publicMethod: publicMethod
    }
  }
)

You can see a more detailed explanation and a working example on this other (and more broad) question that I have put on Code Review.

I am new to JavaScript and I guess I still have a lot of issues with scoping, closures and callbacks.

I will deeply appreciate any input, including how to improve this question.

Edit

With this current implementation (with dojo hitch), no error is thrown. The method addNamesList is not called (nor errback, which I also don't understand why). I think this is because addNamesList is not on map's (hitch first argument) namespace. I tried to put this instead, but it makes no difference.

Before I decided to use hitch, the code looked like this:

var queryNames = function (map, mapLayer) {
  ...
  queryTask.execute(query, addNamesList)
}

var addNamesList = function (featureSet) {
  ...
    ...
      ...
        querySegments(this.value, mapLayer)
}

but then I couldn't reach mapLayer inside the method triggered by the check box event. It would throw Uncaught ReferenceError: mapLayer is not defined. That is why I tried to use hitch.

Community
  • 1
  • 1
iled
  • 2,142
  • 3
  • 31
  • 43
  • Does it throw any errors? I'm not quite into dojo, but it appears yo me that you've kinda messed with callbacks :) – Alexander Mikhalchenko Dec 11 '15 at 22:14
  • @AlexanderM. thank you for your comment and answer. As it is right now, it does not throw any errors. I've edited my question to add some more info. You're right, I am pretty messed up with callbacks. I wish there were a Zen of JavaScript, as there is for [Python](http://www.thezenofpython.com). – iled Dec 12 '15 at 14:13
  • @ilied Well, in fact it's just a matter of getting used to the asynchronous way of thinking. – Alexander Mikhalchenko Dec 12 '15 at 14:30
  • If you google for *callback hell*, you'll probably find some weirder stuff, but the languae is constantly evolving and now we have generators+promises and ES7's async/await in Babel, so everything's OK :) – Alexander Mikhalchenko Dec 12 '15 at 14:30
  • @Alexander M. Thanks. I've read about _callback hell_, I guess that's where I'm going to. I will try to (understand and) apply your suggestion/answer. – iled Dec 12 '15 at 14:54
  • good luck with that! – Alexander Mikhalchenko Dec 12 '15 at 14:57

1 Answers1

3

Javascript is asynchronous, so pretty much data coming from db, http requests or whatever is returned via callbacks. Here's what happens in your code:

  • public method calls queryNames
  • queryNames call addNamesList of map asynchronously and returns nothing
  • public method takes back control, meanwhile some stuff is going on with the addNamesList
  • mapLayer is returned untouched while some stuff is still going on in the background

So, to avoid this, you should return data from public method via callback, so you pass callback as the second parameter to the public method, then to the querySegments. Then, in the success callback of query, when you finally get the result ready, you do:

callback(mapLayer);

So, everything you should do is to pass this callback as deep as needed to the place where you have your mapLayer ready (so you've done with it everything you wanted), and then do a callback(mapLayer);.

This and this would probably explain better.

Best regards, Alexander

Community
  • 1
  • 1
Alexander Mikhalchenko
  • 4,525
  • 3
  • 32
  • 56
  • Thank you @AlexanderM. for taking the time to read and answer this question. I think I understood what you've suggested, I can see how it would work, and for that reason I am accepting your answer. However, meanwhile, I tried using `lang.partial` instead of `lang.hitch` and it worked. I didn't have to define another callback function. Probably this is not the best approach, but is working for now... thanks. – iled Dec 14 '15 at 12:04