Search code examples
javascriptjquerygoogle-maps-api-3requirejsamd

Scopes in Javascript AMD pattern


I'm totally new to Javascript from Java, and I have had to start directly for the most difficult thing for me... developing an app using the Asynchronous Module Definition pattern...

I'm using the platform Weejot, which provides a framework to develop javascript web apps that uses AMD pattern. All documentation here. You probably don't know it, but maybe you can help anyway if you know the AMD pattern...

Basically I have a module MyController.js that looks like this (note that most of the code is automatically generated, I only wrote the commented lines):

define([
    "js/util",
    "framework/Controller"
], function (
  util,
  Controller
){      
  function MyController(environment, connector) {
    Controller.call(this, environment);
    this.settings = environment.siteApplication.getSettings();
    this.connector = connector;        
    //Variable where I have some value stored
    this.myVariable = "hello";
  }    

  MyController.prototype = Object.create(Controller.prototype);

  util.extend(MyController.prototype, {
    //Function to show a map
    showMap: function (request, render) {
      //Get google maps script
      $.getScript("http://maps.googleapis.com/maps/api/js?key=KEY&sensor=false");          
      //Render is a framework object that basically shows the view "map.html" in full page mode
      render({name: "map", mode: "fullPage"});
    }    
  });

  return MyController;
});

This doesn't work fine, because after the line of $.getScript(...), the script doesn't seem to be loaded, since I can't access the object google.maps, which should have been created...

I made it working by adding a callback function to the script url, like this:

$.getScript("http://maps.googleapis.com/maps/api/js?key=KEY&sensor=false&callback=drawMap");

The problem is that this callback function must be a global function, so I added the following function to the previous module (just before the return statement above):

window.drawMap = function () {
  var mapOptions = {
    center: new google.maps.LatLng(-34.397, 150.644);,
    zoom: 8,
    mapTypeId: google.maps.MapTypeId.ROADMAP
  };
  var map = new google.maps.Map(document.getElementById("map_canvas"), mapOptions);
}

And somehow, I get the map rendered perfectly... BUT, my problem is that I need to access the variable myVariable from the function window.drawMap, and I can't... is there any way to do it? or I just doing everything wrong from the beginning?

As I said I'm new to all this, and I'm getting crazy and in my try to make it working I've been adding and changing things to the point that I don't know what I'm actually doing... Any suggestion will be appreciated...


EDIT: if it's useful, this is the response when getting the Google Maps script (copied from Chrome's console):

window.google = window.google || {};
google.maps = google.maps || {};
(function() {

  function getScript(src) {
    var s = document.createElement('script');

    s.src = src;
    document.body.appendChild(s);
  }

  var modules = google.maps.modules = {};
  google.maps.__gjsload__ = function(name, text) {
    modules[name] = text;
  };

  google.maps.Load = function(apiLoad) {
    delete google.maps.Load;
    apiLoad([0.009999999776482582,[[["http://mt0.googleapis.com/vt?lyrs=m@216000000\u0026src=api\u0026hl=es-ES\u0026","http://mt1.googleapis.com/vt?lyrs=m@216000000\u0026src=api\u0026hl=es-ES\u0026"],null,null,null,null,"m@216000000"],[["http://khm0.googleapis.com/kh?v=128\u0026hl=es-ES\u0026","http://khm1.googleapis.com/kh?v=128\u0026hl=es-ES\u0026"],null,null,null,1,"128"],[["http://mt0.googleapis.com/vt?lyrs=h@216000000\u0026src=api\u0026hl=es-ES\u0026","http://mt1.googleapis.com/vt?lyrs=h@216000000\u0026src=api\u0026hl=es-ES\u0026"],null,null,"imgtp=png32\u0026",null,"h@216000000"],[["http://mt0.googleapis.com/vt?lyrs=t@131,r@216000000\u0026src=api\u0026hl=es-ES\u0026","http://mt1.googleapis.com/vt?lyrs=t@131,r@216000000\u0026src=api\u0026hl=es-ES\u0026"],null,null,null,null,"t@131,r@216000000"],null,null,[["http://cbk0.googleapis.com/cbk?","http://cbk1.googleapis.com/cbk?"]],[["http://khm0.googleapis.com/kh?v=75\u0026hl=es-ES\u0026","http://khm1.googleapis.com/kh?v=75\u0026hl=es-ES\u0026"],null,null,null,null,"75"],[["http://mt0.googleapis.com/mapslt?hl=es-ES\u0026","http://mt1.googleapis.com/mapslt?hl=es-ES\u0026"]],[["http://mt0.googleapis.com/mapslt/ft?hl=es-ES\u0026","http://mt1.googleapis.com/mapslt/ft?hl=es-ES\u0026"]],[["http://mt0.googleapis.com/vt?hl=es-ES\u0026","http://mt1.googleapis.com/vt?hl=es-ES\u0026"]],[["http://mt0.googleapis.com/mapslt/loom?hl=es-ES\u0026","http://mt1.googleapis.com/mapslt/loom?hl=es-ES\u0026"]],[["https://mts0.googleapis.com/mapslt?hl=es-ES\u0026","https://mts1.googleapis.com/mapslt?hl=es-ES\u0026"]],[["https://mts0.googleapis.com/mapslt/ft?hl=es-ES\u0026","https://mts1.googleapis.com/mapslt/ft?hl=es-ES\u0026"]]],["es-ES","US",null,0,null,null,"http://maps.gstatic.com/mapfiles/","http://csi.gstatic.com","https://maps.googleapis.com","http://maps.googleapis.com"],["http://maps.gstatic.com/intl/es_es/mapfiles/api-3/12/11","3.12.11"],[2244818506],1.0,null,null,null,null,0,"blabla",null,null,0,"http://khm.googleapis.com/mz?v=128\u0026","AIzaSyAgUZ1LkdjZ9jsRivdSB4cDLmUAOMOMi34","https://earthbuilder.googleapis.com","https://earthbuilder.googleapis.com",null,"http://mt.googleapis.com/vt/icon"], loadScriptTime);
  };
  var loadScriptTime = (new Date).getTime();
  getScript("http://maps.gstatic.com/intl/es_es/mapfiles/api-3/12/11/main.js");
})();

Solution

  • EDIT: I looked at the script, and its not jsonp, so I modified my answer to use requirejs.

    EDIT again: It appears that there is some more asychronous jazz going on here. Another edit to bind the global function to the module scope.

    Looking at the code returned by that url, it appears that within the code it makes another asynchronous call to retrieve additional data from the api, and then calls your callback. The only way I can think to do it is to define a global function within your module and use that to handle the return value from the api call. Its not pretty, but it should work:

    define([
        "js/util",
        "framework/Controller"
    ], function (
      util,
      Controller
    ){      
      function MyController(environment, connector) {
        Controller.call(this, environment);
        this.settings = environment.siteApplication.getSettings();
        this.connector = connector;        
        //Variable where I have some value stored
        this.myVariable = "hello";
      }    
    
      MyController.prototype = Object.create(Controller.prototype);
    
      util.extend(MyController.prototype, {
        //Function to show a map
        createRenderer : function(){
           var self = this;
           window.drawMap = function(){
              var mapOptions = {
              center: new google.maps.LatLng(-34.397, 150.644);,
              zoom: 8,
              mapTypeId: google.maps.MapTypeId.ROADMAP
            };
            var map = new google.maps.Map(document.getElementById("map_canvas"), mapOptions);
            // Do stuff with self.myVariable and, render, ect...
          };
        },
        showMap: function (request, render) {
          //Get google maps script
          this.createRenderer();
          $.getScript("http://maps.googleapis.com/maps/api/js?key=KEY&sensor=false&callback=drawMap");                     }    
      });
    
      return MyController;
    });
    

    Explanation:

    When you call this.createRenderer(), the function is called in the context of your object. Since we declare a variable, self = this, within the this.createRenderer() function call, then define window.drawMap within that function call as well, we create a closure, which stores the self reference to the MyController object. When window.drawMap is called later, the self reference is available in the closure scope. So now, within the window.drawMap function call, we can access self.myVariable. See How do JavaScript closures work? for more information.

    As an alternative, you could consider using Miller Medeiros's async plugin to load the Google Maps api as a dependency to your module. This would be a much cleaner way to do it.