Tag Archives: require

ArcGIS JavaScript API 3.0, Dojo 1.7 and AMD Modules

Diving in

For a while now, I have been keeping my JavaScript development modular by using Require.js as I have discussed before. Require.js provides AMD module loading for JavaScript projects. If you are not familiar with AMD, take a look here. Dojo 1.7 is AMD compliant and uses require/define methods to load modules. Up until now, the ArcGIS JavaScript API has been using Dojo < 1.7 for its core. I posted before how I used Require.js to load the API into my applications. Recently, the ArcGIS JavaScript API has been updated to 3.0 and is now using Dojo 1.7 for it’s core. I was pretty excited about this, mainly for AMD modules.

As soon as I saw that 3.0 was out, I dived right in, went straight past go, updated the CDN url in one of my apps and crossed my fingers. Of course it didn’t work. So I did what any dev should do. RTFM. That gave me a little insight, but I figured there must be something in my Require.js config I need to set up, so I went to check out the How to use RequireJS with Dojo page. I know I’ve seen samples of Require.js loading Dojo modules, so I set out to hack away at it. The Require.js/Dojo template was a good read, but has since been abandoned in favor of a non-Require.js template.

Trimming it down

After some fiddling around, I found out the issue of using Require.js with Dojo 1.7 via a CDN was that you can’t and you shouldn’t. When you load Dojo via a CDN, the dojo loader will create a global define and require, negating the need to use Require.js in this case. If I was loading Dojo locally, I could continue using Require.js and load individual Dojo modules as I was initially expecting to do. Now that we know that Require.js is no longer required (pun intended), we can remove that from our upgrade path equation.

I broke it

Let’s return to the ArcGIS JS 3.0 migration page. It explains how to load custom modules while using the CDN. I tried this, and hit a snag. jQuery was broken, which broke my Backbone lib too. I use Backbone a lot. So ok, more digging. I went through this page on Using Custom Modules with a CDN. It seemed pretty straightforward, but jQuery was still broken and other modules were not working.
Part of my problem was I was trying to define my package like this.

1
2
3
4
5
packages: [
{
name: "module",
location: location.pathname.replace(/\/[^/]+$/, "") + "src/moduleFolder/moduleFile"
} ]

Where “moduleFile” is actually a JavaScript file. You don’t need to add the *.js, the require function knows what to do. The regex is needed when using the CDN so that the path will point to the server your app sits on, or else it looks in the CDN directories and you just get lots of “file not found” errors. I thought I could bypass the regex with “./src” and so on, don’t bother, do it this way, don’t be dumb like me. So I went back and read up on some more documentation. Turns out, I am dumb. Packages simply defines the path to a folder. If you want the name to point to a specific file, you need to define the main field.

1
2
3
4
5
6
packages: [
{
name: "module",
location: location.pathname.replace(/\/[^/]+$/, "") + "src/moduleFolder",
main: "moduleFile"
} ]

I fixed it

At this point, I think I’m good to go, but jQuery is still broken and I’m getting pretty pissed, but I was on a roll, so I plowed right through with various config options, then I found this article. I followed this and it worked. jQuery worked, Backbone worked, everything was working. Now I had to know why. It turns out this was the magic piece to get jQuery to work.

1
2
// Add this before first require to launch app
define.amd.jQuery = true;

There is even a page explaining about using jQuery and AMD loaders.

I have put together a sample project using ArcGIS JavaScript API 3.0, jQuery and Backbone. You can find the source on github. I add a lot of comments to the config file, but I use CoffeeScript, so look at those files for comments. To load configs, like you would inside an HTML page creating a dojoConfig, you call require passing it an object with your configs defined.

1
require({ async: true, packages: [{...}] });

Caveats and Tips

So what does all this mean to ArcGIS JavaScript developers. It means you should write your modules as AMD compliant. As of right now, a few ESRI modules work as AMD compliant, meaning you can do this.

1
2
3
define(['esri/dijit/Popup'], function(){
    return new esri.dijit.Popup({...});
});

BUT, it doesn’t work everywhere. Not all ESRI modules are AMD compliant, so for now I would suggest using the standard dojo.require() method.

If you are used to using the text plugin for Require.js to load html templates, have no fear, Dojo has you covered with dojo/text. I just had to change my modules where I called text! to dojo/text!.

1
2
3
4
5
6
7
define(['text!templates/home.html'], function(template){
    ...
});
// Became this
define(['dojo/text!templates/home.html'], function(template){
    ...
});

I tried creating an alias for this in my dojo config, but couldn’t get it to work. If someone does, I’d love to hear about it. I was able to fix this by forming my alias as a proper array or arrays in my config.
In the config, especially if you are using jQuery, async: true is an important parameter. jQuery won’t work without it. This specifies that Dojo should be loaded asynchronously. You can also use it in your require/define methods similar to the order! plugin for Require.js.

1
2
3
4
// 1 means true, 0 means false
require({async:1},['modules/myModule'], function(myModule){
    ...
});

I have not tried this method yet, but if I have a need, now I know.

Go forth and update…

The biggest hurdle for me was getting the config to work as I needed it to work. Now that I have a better understanding of how Dojo defines packages and loads them, I’ll be moving forward with the ArcGIS JavaScript API 3.x updates. Future updates when the ESRI modules become AMD compliant should be less painful. My old Require.js modules will load just fine now. Most people may not need to move on from Require.js, so you should have a clean slate to work from. Hopefully some of these tips help get you on the track to creating modular apps with the new API.

Getting modular with the ArcGIS JavaScript API

Maybe it’s all the time I’ve spent using frameworks like Swiz and Robotlegs in my Flex development, but I usually strive to try and keep my code modular. Breaking it up into smaller manageable pieces, not necessarily so that I could reuse files but mostly for my own sanity.

Have you ever caught yourself starting a simple application, just keeping everything in a single file or two only to find it has grown into a beast where you get an error on line 292 but it could have carried over from something on line 5? That’s a clue you probably should look into breaking up your code. If you’re not quite sure about modular JavaScript, please go read up on it here. It’s worth it and every JavaScript developer should read it.

The first piece to this little bit I’d like to introduce is Require.js. Require.js uses the AMD (Asynchronous Module Definition) API to load JavaScript files or modules as they are called. If you have used Dojo 1.7+, you might recognize it. It’s a pretty nifty way of loading your modules. It will even work with CommonJS typed files if you need it to.  I won’t get int to AMD vs CommonJS, pick one and go with it, you’ll probably use both at some point anyway. I picked AMD. Require.js files will typically look like the following

1
2
3
4
5
6
7
define(['tools/myTool'], function(MyTool) {

var tool = new MyTool()

/// do stuff

})

Easy sauce. Simple and clean. You can read about the details on their API page.

Moving on, the next piece to modular nirvana is Backbone.js. Let me start off by saying that I was not a big fan of Backbone the first time I looked at it. It seemed to be too much trouble for little return. Then when I looked at samples of using with Require.js I was flat out ready to just say forget it. But I powered through the initial learning curve and now, I can say that Backbone.js is my most favorite little framework since jQuery. But it’s not jQuery and I’ll get to that in a second. The only hard dependency of Backbone is Underscore, which has a bunch of neat little utilities one of which is a template generator (if that’s the correct way to describe it). Backbone provides a framework to build your applications using models, views and collections. Backbone really shines when you tie your models to REST endpoinst, but that is beyond the scope of this little intro. It does not do DOM manipulation, but the views components can use jQuery to bind to a view that is rendered on the DOM. Once you start using it, it will kick in and you’re going to want to send me lots of beer for encouraging you to use it.

The next step, which took the most brain matter for me to really kind of tear apart and piece back together to make sense of it was on how to use these tools together with the ArcGIS JavaScript API. Well, as usual with the internet there’s a page for that. I followed this model to build my first test applications. That link goes over in detail on how to load external scripts and set up your bootstrapping for your application. Basically what you end up doing is creating aliases to library paths and use the aliases.

So you can follow along, I placed the code for this article on github. Here is my run.js bootstrap file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
#This is essentially where you do
#your bootstrapping for your application.
#Define aliases for the paths to your
#JavaScript libraries or other folders
#you may use.
*/


require({
  baseUrl: 'javascripts',
  paths: {
    loader: 'libs/backbone/loader',
    jQuery: 'libs/jquery/jquery',
    Underscore: 'libs/underscore/underscore',
    Backbone: 'libs/backbone/backbone',
    dojo: 'libs/esri/dojo',
    templates: '../templates'
  },
  cach: {}
}, ['main']);

The interesting stuff then happens in the loader.js file.

1
2
3
4
5
6
7
8
define(['order!http://serverapi.arcgisonline.com/jsapi/arcgis/?v=2.7', 'order!libs/jquery/jquery.min', 'order!http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js', 'order!libs/underscore/underscore-min', 'order!libs/backbone/backbone-min'], function() {
  return {
    Backbone: Backbone.noConflict(),
    _: _.noConflict(),
    $: jQuery.noConflict(),
    dojo: dojo
  };
});

This loader file defines where your libraries come from, local files or external, doesn’t matter. When the files are loaded, the loader will return an object that contains a reference to the libraries like Backbone, jQuery or dojo. The link above that I based this off of explains these concepts in more detail, the important thing to know is that the loader loads libraries. One thing to note is that the only time you ever have to reference the ESRI API url. When the API is loaded anywhere, it goes global, so dojo is available throughout your whole application now. There is a special case for this, but I won’t go into detail on that, if it happens to you, you’ll know.

I know this seems like a lot of upfront effort, but it’s really not so bad when you get the idea of what is happening. Once I’ve bootstrap the application, the ‘main.js’ file gets loaded. I use this file to make a couple of tweaks to the way Underscore does it’s templating and I start the application.

1
2
3
4
5
6
7
8
9
define(['require'], function(require) {
  return require(['app', 'Underscore'], function(app, _) {
    _.templateSettings = {
      interpolate: /\{\{(.+?)\}\}/g,
      evaluate: /\{\#(.+?)\}\}/g
    };
    return app.start();
  });
});

I won’t go into detail on the app.js file, but I do want to move on to some specific Backbone stuff. The sample application I have put together shows a map of the United States. There is a sidebar that list the name o each state. Clicking on a name will zoom you in to that state on the map. Pretty straightforward operation.

Here is my Backbone model for the state item.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
define(['Backbone'], function(Backbone) {
  var State;
  return State = Backbone.Model.extend({
    initialize: function(state) {
      this.state = state;
      return {
        defaults: {
          name: "",
          graphic: null,
          map: null
        }
      };
    },
    zoom: function() {
      var extent;
      this.state.map.graphics.clear();
      this.state.map.graphics.add(this.state.graphic);
      extent = this.state.graphic.geometry.getExtent();
      return this.state.map.setExtent(extent, true);
    }
  });
});

This model has a reference to a graphic and the map. It’s not a copy, just a reference, so it’s not like you’re using up extra memory here. This allows the model to have it’s own zoom function to zoom in to itself on the map. I use this technique a lot when I need to populate a list with items from a map, click on the list and zoom to it. It’s a pretty straightforward function and this pattern simplifies it for you.

But wait, you don’t need to interact directly with the model, there’s a Backbone collection for that.

1
2
3
4
5
6
7
8
9
10
11
12
define(['Backbone', 'models/State'], function(Backbone, State) {
  var StateCollection;
  StateCollection = Backbone.Collection.extend({
    model: State,
    zoomByCid: function(cid) {
      var state;
      state = this.getByCid(cid);
      return state.zoom();
    }
  });
  return new StateCollection;
});

The cid is a unique id that the collection will assign to each model that is added to it. You can use this cid in the collection find the correct model and access it’s zoom function that we assigned to it. But how do I interact with the collection? Bear with me a second. If this seems a little confusing, don’t worry, it took me a while to kind of grasp how the pieces all fit together.

We can access the collection from our view.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
define(['jQuery', 'Underscore', 'Backbone', 'collections/StateCollection', 'text!templates/forms/StateListView.html'], function($, _, Backbone, stateCollection, viewTemplate) {
  /*
            # This list view will
            # handle rendering and events
            # of the sidebar list
  */

  var ListView;
  ListView = Backbone.View.extend({
    el: $("#sidebar>ul"),
    tagName: "ul",
    initialize: function() {
      return this.collection = stateCollection;
    },
    events: {
      "click a": "clicked"
    },
    clicked: function(evt) {
      var cid;
      evt.preventDefault();
      cid = $(evt.currentTarget).data("id");
      return this.collection.zoomByCid(cid);
    },
    render: function() {
      var data, template;
      data = {
        states: this.collection.models,
        _: _
      };
      template = _.template(viewTemplate, data);
      $(this.el).html("");
      return $(this.el).append(template);
    }
  });
  return new ListView;
});

This view holds a reference to the collection that we are using. When the view is rendered, we use Underscore.js to load a template and render it to the view.

1
template = _.template(viewTemplate, data);

There are a couple of things happening in the view. First you’ll notice this line in out define.

1
'text!templates/forms/StateListView.html'

This will load an html file that has a little function to load each data from each model into the view. This view also handles the events of the view using jQuery. When an item in the list is clicked, it will find the cid we discussed earlier by searching for the data id attribute tag we assigned in the template and use the collection function we made called zoomByCid() to zoom to that location. It all fits together in a neat little package.

Just to give you an idea of what it looks like, here is template.

1
2
3
4
5
{# _.each(states, function(state) { }}
<li>
  <a data-id='{{ state.cid }}' href='#'>{{ state.get("name") }}</a>
</li>
{# }); }}

This uses an Underscore utility function to iterate the array of models and render each one to a list item and passes model information into the anchor. If you’ve done jQuery templating this look very familiar to you. As a matter of fact you could use jQuery templates in the view if that’s what you’re comfortable with.

You can see a working demo of this application here.

Ok, I think I’ve covered a the basics of how you might use Require.js and Backbone.js with the ArcGIS JavaScript API. I know this might all seem like a lot of work, but imagine building a more complicated application with multiple views, maybe a couple of more collections and you can see how this pattern can simplify that task for you. There is definitely more to Require.js and Backbone.js that you could learn about and I encourage you to look up more examples. For example, I recently used Backbone.js with .NET MV3 REST endpoints and the ArcGIS JavaScript API and the workflow is incredibly simple. Backbone.js shines with REST endpoints.

By the way, the source for this example uses CoffeeScript and Sass/Compass for css. Don’t worry about that, I’ll get to Sass/Compass stuff later.

I hope I was able to introduce you to some tools that you can add to your development toolbox and use wisely. Go forth, and make cool shit.