Select to view content in your preferred language

Returning a value from dojo.connect

2411
11
Jump to solution
07-31-2014 08:09 AM
FilipKrál
Frequent Contributor

Hi,

I have quite a few calls like this in my JavaScript:

dojo.connect(map, "onLoad", {map: map, do:buildScalebar}, "do");

and I would like to simplify to something like this:

var scalebar = buildScalebar({ map: map });

// the retuned value should be the scale bar widget so I could do:

scalebar.hide();

scalebar.show(); // and so on

What is the best way to do it?

Here is the buildScalebar function from the second case, which however returns null.

    function buildScalebar(opts){

      /* Build the scalebar widget with options:

      * .map -- map for which to load the scalebar

      * .unit -- scalebarUnit, dual|metric|english, default is "dual"

      * .anchor -- top-right|bottom-right|top-center|bottom-center|bottom-left|top-left, default value is "bottom-left"

      *

      * Returns the widget. (BUT IT DOESN'T!!!)

      */

      var o = this;

      if ('map' in opts) { o = opts; }

      var map = opts.map;

      var anchor = 'anchor' in opts ? opts.anchor : "bottom-left";

      var unit = 'unit' in opts ? opts.unit : "dual";

      var scalebar = null;

     

      dojo.connect(map, "onLoad", {

        "map": map,

        "attachTo": anchor,

        "scalebarUnit": unit,

        "do": function () {

          var options = {"map": this.map, "attachTo": this.attachTo, "scalebarUnit": this.scalebarUnit }

          scalebar = new esri.dijit.Scalebar( options );

        }

      }, "do");

      return scalebar;

    }

I would like to use this pattern for other widgets and functionality, not just the scale bar widget.

Is it a good idea? Do I need some kind of Deferreds here? Can you help me with an example?

Cheers,

Filip.

0 Kudos
1 Solution

Accepted Solutions
ReneRubalcava
Honored Contributor

Using a Promise such as dojo/Deferred will probably be the most reliable to accomplish close to what you are asking. The main sticking point is that you are delegating the responsibility of waiting for the map to load to the createScalebar methods, which is an asynchronous process. The Scalebar widget will internally already wait for the map to load, so there is really isn't a need for you to do it also in this case.

What the Scalebar does not do at the moment unfortunately is emit a load event. Someone mentioned in a thread somewhere that in a future release, all widgets will emit load events. If you leave out the waiting for the map to load, your original code works fine.

With widgets, when you writing your own and they are dependent on the map, then you should be concerned about waiting for the map to load, similar to how this esri dijit for the geocoder works on github.

If you really need to wait for the load of the map in that create method, I think a Promise is a good way to do it.


  function buildScalebar(opts) {
    var def = new Deferred();
    var _map = opts.map;
    var anchor = ('anchor' in opts) ? opts.anchor : "bottom-left";
    var unit = ('unit' in opts) ? opts.unit : "dual";
    _map.on('load', function() {
      var sb = new Scalebar({
        map: _map,
        attachTo: anchor,
        scalebarUnit: unit
      });
      def.resolve(sb)
    }, def.reject);


    return def.promise;
  }
  buildScalebar({
    map: map
  }).then(function(scalebar) {
    scalebar.hide();
    scalebar.show();
  });

Hope that helps a bit.

View solution in original post

0 Kudos
11 Replies
TimWitt2
MVP Alum

Filip,

I would suggest using the AMD style in your javascript app. Here is a little example.

<!DOCTYPE html>

<html>

<head>

  <title>Create a Map</title>

  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

  <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">

  <link rel="stylesheet" href="http://js.arcgis.com/3.10/js/dojo/dijit/themes/claro/claro.css">

  <link rel="stylesheet" href="http://js.arcgis.com/3.10/js/esri/css/esri.css">

  <style>

    html, body, #mapDiv{

      padding: 0;

      margin: 0;

      height: 100%;

    }

  </style>

  <script src="http://js.arcgis.com/3.10/"></script>

  <script>

    var map;

    require(["esri/map", "esri/dijit/Scalebar", "dojo/on", "dojo/domReady!"], function(Map, Scalebar, on) {

      map = new Map("mapDiv", {

        center: [-56.049, 38.485],

        zoom: 3,

        basemap: "streets"

      });

      //Create the Scalebar

        var scalebar = new Scalebar ({

          map: map,

          attachTo: "top-right"

        });

        // When clicked on map scale bar will disappear

        map.on("click", clickHandler);

        function clickHandler(){

          scalebar.hide();

        };

    });

  </script>

</head>

<body class="claro">

  <div id="mapDiv"></div>

</body>

</html>

Hope this helps!

FilipKrál
Frequent Contributor

Hi,

Thanks for the sample, Tim. I still have the same question though. Below is an extended version of Tim's code sample to illustrate the issue better.

It is not really about creating scale bars, but about structuring the code so I don't need to keep everything in one file. Let me rephrase the quesion:

You have function A that calls function B when an event occurs. How do you make function A return the value returned by function B?

<!DOCTYPE html>

<html><head>

  <title>Create a Map</title>

  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

  <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">

  <link rel="stylesheet" href="http://js.arcgis.com/3.10/js/dojo/dijit/themes/claro/claro.css">

  <link rel="stylesheet" href="http://js.arcgis.com/3.10/js/esri/css/esri.css">

  <style> html, body, #mapDiv{ padding: 0;  margin: 0;  height: 100%; } </style>

  <script src="http://js.arcgis.com/3.10/"></script>

  <script>

    var map;

    require(["esri/map", "esri/dijit/Scalebar", "dojo/on", "dojo/domReady!"], function(Map, Scalebar, on) {

      map = new Map("mapDiv", { center: [-56.0, 38.0],  zoom: 3,  basemap: "streets"});

    

      function createScalebar(opts){

        //Create a Scalebar for opts.map

        var themap = this.map;

        var options = { map: themap, attachTo: "top-right" };

        var scalebar = new Scalebar (options);

        return scalebar;

      }

    

      function createScalebarWhenMapLoads(opts){

        //Create a Scalebar for opts.map on the opts.map.load event

        var themap = this.map;

        var scalebar = null;

        // maybe you don't need to wait for map.onLoad with scale bars but imagine something else

        themap.on('load', function(){

          var options = { map: themap, attachTo: "bottom-left", scalebarUnit: "dual" };

          scalebar = new Scalebar (options);

        });

        return scalebar;

      }

    

      var scalebar = createScalebar({map:map});

      var scalebar2 = createScalebarWhenMapLoads({map:map});

    

      // When clicked on map scale bar will disappear

      map.on("click", clickHandler);

      function clickHandler(){

        scalebar.hide();

      };

      // But scalebar2 is undefined (understandably), so it won't disappear

      map.on("click", clickHandler2);

      function clickHandler2(){

        scalebar2.hide();

      };

    

    });

  </script>

</head>

<body class="claro"><div id="mapDiv"></div> </body>

</html>

Also, if you use the 'on' method, is there a way how to change the context (what the 'this' keyword means)? Can the dojo.hitch function help here? In other words, how do you rewrite the above code to use only one clickHandler?

With dojo.connect you could do something like this:

function clickHandler(){ this.scalebar.hide(); }

dojo.connect(map, 'click', {scalebar: scalebar, "callme": clickHandler}, "callme");

dojo.connect(map, 'click', {scalebar: scalebar2, "callme": clickHandler}, "callme");

I hope it is clear what I want to know.

Filip.

0 Kudos
RobertScheitlin__GISP
MVP Emeritus

Filip,

  I am not sure if this is what you are after or not but I was able to do exactly what you did using connect but with on instead.

<!DOCTYPE html>

<html><head>

  <title>Create a Map</title>

  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

  <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">

  <link rel="stylesheet" href="http://js.arcgis.com/3.10/js/dojo/dijit/themes/claro/claro.css">

  <link rel="stylesheet" href="http://js.arcgis.com/3.10/js/esri/css/esri.css">

  <style> html, body, #mapDiv{ padding: 0;  margin: 0;  height: 100%; } </style>

  <script src="http://js.arcgis.com/3.10/"></script>

  <script>

    var map;

    require(["esri/map", "esri/dijit/Scalebar", "dojo/on", "dojo/domReady!"], function(Map, Scalebar, on) {

      map = new Map("mapDiv", { center: [-56.0, 38.0],  zoom: 3,  basemap: "streets"});

    

      function createScalebar(opts){

        //Create a Scalebar for opts.map

        var themap = this.map;

        var options = { map: themap, attachTo: "top-right" };

        var scalebar = new Scalebar (options);

        return scalebar;

      }

    

      function createScalebar2(opts){

        //Create a Scalebar for opts.map on the opts.map.load event

        var themap = this.map;

        var options = { map: themap, attachTo: "bottom-left", scalebarUnit: "dual" };

        var scalebar = new Scalebar (options);

        return scalebar;

      }

    

      var scalebar = createScalebar({map:map});

      var scalebar2 = createScalebar2({map:map});

    

      // When clicked on map scale bar will disappear

      map.on("click", dojo.hitch(this, function(vscalebar){vscalebar.hide();}, scalebar));

    

      // But scalebar2 is undefined (understandably), so it won't disappear

      map.on("click", dojo.hitch(this, function(vscalebar){vscalebar.hide();}, scalebar2));

    

    });

  </script>

</head>

<body class="claro"><div id="mapDiv"></div> </body>

</html>

0 Kudos
RiyasDeen
Frequent Contributor

Hi Filip,

If you want to access the scalebar object outside the context of map event, as you mentioned dojo.hitch is the answer for you. I have modified your code, hope this helps you.

<!DOCTYPE html>  

<html><head>  

  <title>Create a Map</title>  

  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">  

  <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">  

  <link rel="stylesheet" href="http://js.arcgis.com/3.10/js/dojo/dijit/themes/claro/claro.css">  

  <link rel="stylesheet" href="http://js.arcgis.com/3.10/js/esri/css/esri.css">  

  <style> html, body, #mapDiv{ padding: 0;  margin: 0;  height: 100%; } </style>  

  <script src="http://js.arcgis.com/3.10/"></script>  

  <script>  

  this.map = null;

  this._scaleBar = null;

  require(["esri/map", "esri/dijit/Scalebar", "dojo/on", "dojo/domReady!"], function(Map, Scalebar, on) { 

  this.map = new Map("mapDiv", { center: [-56.0, 38.0],  zoom: 3,  basemap: "streets"});

  dojo.connect(this.map, "onLoad", dojo.hitch(this, function () {

  var options = {"map": this.map, "attachTo": "bottom-left", "scalebarUnit": "dual" }

  this._scaleBar = new Scalebar (options); 

  }));

  dojo.connect(this.map, "onClick", dojo.hitch(this, function () {

  this._scaleBar.hide();

  }));

  });

  </script>  

</head>  

<body class="claro"><div id="mapDiv"></div> </body> 

</html> 

0 Kudos
RiyasDeen
Frequent Contributor

<!DOCTYPE html>  

<html><head>  

  <title>Create a Map</title>  

  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">  

  <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">  

  <link rel="stylesheet" href="http://js.arcgis.com/3.10/js/dojo/dijit/themes/claro/claro.css">  

  <link rel="stylesheet" href="http://js.arcgis.com/3.10/js/esri/css/esri.css">  

  <style> html, body, #mapDiv{ padding: 0;  margin: 0;  height: 100%; } </style>  

  <script src="http://js.arcgis.com/3.10/"></script>  

  <script>  

  this.map = null;

  this._scaleBar = null;

  require(["esri/map", "esri/dijit/Scalebar", "dojo/on", "dojo/domReady!"], function(Map, Scalebar, on) { 

  this.map = new Map("mapDiv", { center: [-56.0, 38.0],  zoom: 3,  basemap: "streets"});

  dojo.connect(this.map, "onLoad", dojo.hitch(this, function () {

  var options = {"map": this.map, "attachTo": "bottom-left", "scalebarUnit": "dual" }

  this._scaleBar = new Scalebar (options); 

  }));

  dojo.connect(this.map, "onClick", dojo.hitch(this, function () {

  this._scaleBar.hide();

  }));

  });

  </script>  

</head>  

<body class="claro"><div id="mapDiv"></div> </body> 

</html> 

RobertScheitlin__GISP
MVP Emeritus

Filip,

  This one does the scalebar2 creation just like you had it after the map is loaded and uses "on" and "hitch" to hide both scalebars when the map is clicked.

<!DOCTYPE html>

<html>

<head>

  <title>Create a Map</title>

  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

  <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">

  <link rel="stylesheet" href="http://js.arcgis.com/3.10/js/dojo/dijit/themes/claro/claro.css">

  <link rel="stylesheet" href="http://js.arcgis.com/3.10/js/esri/css/esri.css">

  <style>

    html,

    body,

    #mapDiv {

      padding: 0;

      margin: 0;

      height: 100%;

    }

  </style>

  <script src="http://js.arcgis.com/3.10/"></script>

  <script>

    var map;

    require(["esri/map", "esri/dijit/Scalebar", "dojo/on", "dojo/aspect", "dojo/domReady!"], function (Map, Scalebar, on, aspect) {

      map = new Map("mapDiv", {

        center: [-56.0, 38.0],

        zoom: 3,

        basemap: "streets"

      });

      function createScalebar(opts) {

        //Create a Scalebar for opts.map

        var themap = this.map;

        var options = {

          map: themap,

          attachTo: "top-right"

        };

        var scalebar = new Scalebar(options);

        return scalebar;

      }

      function createScalebarWhenMapLoads(opts) {

        //Create a Scalebar for opts.map on the opts.map.load event

        var themap = this.map;

        var scalebar = null;

        // maybe you don't need to wait for map.onLoad with scale bars but imagine something else

        themap.on('load', dojo.hitch(this,

          function (xscalebar) {

            var options = {

              map: themap,

              attachTo: "bottom-left",

              scalebarUnit: "dual"

            };

            xscalebar = new Scalebar(options);

            map.on("click", dojo.hitch(this, function (vscalebar) {

              vscalebar.hide();

            }, xscalebar));

          }, this.scalebar));

        return scalebar;

      }

      var scalebar = createScalebar({

        map: map

      });

      var scalebar2 = createScalebarWhenMapLoads({

        map: map

      });

      // When clicked on map scale bar will disappear

      map.on("click", dojo.hitch(this, function (vscalebar) {

        vscalebar.hide();

      }, scalebar));

    });

  </script>

</head>

<body class="claro">

  <div id="mapDiv"></div>

</body>

</html>

FilipKrál
Frequent Contributor

Hi,

Robert, both scale bars disappear when I click the map, but only because you binded the hide scalebar functionality to the second scalebar within the createScalebarWhenMapLoads function (lines 53-55 of your code).

I want to be able to write similar lines on line 66, but that won't work because on line 66 scalebar2 is null. See, if I later decide the scalebar should do something else when I click the map, I don't want to modify the createScalebarWhenMapLoads function. Or maybe I will decide that the scalebar should hide when I do something else than clicking the map.

I extended the example again with 3rd scale bar, which is loaded using deferreds.

http://filipkral.com/dev/gisdesk/return-value-from-dojo-on.html

I think the defferds version would be the most reliable, although I am not convinced it would always work in this example.

If the scalebar widget had an event like 'loaded', I would resolve the deferred when this event occurs

scalebar.on('loaded', function(){deferred.resolve(scalebar); });

Anyhow, all those examples seem terribly complicated for what I am trying to do so I feel like I must be missing something obvious.

I would be really glad if someone could explain to me how to do this kind of thing once for all.

Cheers,

Filip.

0 Kudos
FilipKrál
Frequent Contributor

Thank you all for your responses about accessing variables out of event handlers. After some more trials I have to say though that none of your suggestions fully answered my question. I'll post back here if I find a solution to my problem.

Filip.

0 Kudos
RobertScheitlin__GISP
MVP Emeritus

Filip,

   I will be very interested to see what you might find. Thanks in advance for offer to post.

0 Kudos