One of the biggest problems Dojo has when trying to attract new users is the fact that our community rarely promotes one method of writing code as THE WAY. Following suit, I’m not going to say that one method of doing things is better than the next. Ultimately, arguing that there is one catch-all solution to the problems coders face when developing for the web is one way to drive away users as soon as they realize that they want something more.

With this in mind, I’ll outline three strategies available with The Dojo Toolkit. Each offers different levels of control and different benefits. Hopefully, some of the negativity aimed toward some of these strategies is only because of a lack of understanding about their purpose. After some of that has been dispelled, I hope to kill any remaining bad feelings by showing that some of the things you might feel that the toolkit imposes on you can be fixed — with either a simple alias or a simple function.

These three strategies come from three different areas of The Dojo Toolkit: Dojo Base, Dojo Core, and Dijit. Dojo Base is what you get in dojo.js, out of the box. Dojo Core and Dijit modules appear in the dojo and dijit directories respectively, and you’ll be able to use a dojo.require statement to load them.

Base: Write a dojo.query “Plugin”

By far one of the most beloved, and likely one of the most popular, patterns for development is the one favored by many jQuery developers. I found the rules outlined in Learning jQuery‘s article on A Plugin Development Pattern to be pretty sage advice.

Putting it simply, this pattern focuses around finding a set of nodes and executing some functionality on each node. Passing a property bag of options to this function, as suggested by the Learning jQuery article, is also typical of this pattern. To better visualize it, see the following code:

dojo.addOnLoad(function(){
  dojo.query('.hilight').hilight({
    background: "blue",
    foreground: "white"
  }); 
});

To begin with, one of the ideas here is that not only is this pattern good for adding functionality to dojo.query, but also as a namespace. In this case, aliasing dojo.NodeList.prototype to dojo.fn creates a much more appropriate namespace. We’ll quickly define this:

dojo.fn = dojo.NodeList.prototype;

Now, if you’re familiar with the syntax, the following code will look familiar. If you’re not, go through that article, it’s full of good stuff. It’s specific to jQuery, but looks almost exactly the same with Dojo. Below is a version of the article’s final code snippet, adapted for Dojo.

dojo.fn.hilight = function(options) {
  var opts = dojo.mixin({}, dojo.fn.hilight.defaults, options);

  return this.forEach(function(node) {
    $node = dojo.query(node);

    $node.style({
      backgroundColor: opts.background,
      color: opts.foreground
    });

    node.innerHTML = dojo.fn.hilight.format(node.innerHTML);
  });
};
 
dojo.fn.hilight.format = function(txt) {
  return '' + txt + '';
};
 
dojo.fn.hilight.defaults = {
  foreground: 'red',
  background: 'yellow'
};

Benefits

With this format, everything can be found in one place: hooks for functionality, exposed defaults, and easy access through dojo.query. It’s also extremely lightweight and will work with just a simple script include of dojo.js.

Try This

Create a directory next to your local dojo directory, call it query. Create a file named hilight.js and add dojo.provide("query.hilight"); to the top of the file, followed by the code we wrote above.

When using this in your page, you can either point a script tag at the file, or you can use dojo.require("query.hilight"); in your JavaScript to dynamically include it.

In Action

See it here

Core: Write a Behavior

On one hand, writing a behavior is just a different way of using dojo.query. Where the query syntax breaks down is when you’re adding or replacing HTML in your page, and you have to start writing extra code to make sure that you can apply your plugins without accidentally re-applying them. dojo.behavior fixes this for you.

We make the dojo.behavior module available in our source using the following call:

dojo.require("dojo.behavior");

We can then take the plugin that we’ve created above and adapt it again to dojo.behavior. Like we did above with the query directory, we’ll create a directory called plugins and use the file hilight.js.

dojo.provide("plugins.hilight");

plugins.hilight.behavior = {
  ".hilight": {
      found: function(node){
        var opts = plugins.hilight.defaults;

        $this = dojo.query(node);

        $this.style({
          backgroundColor: opts.background,
          color: opts.foreground
        });

        node.innerHTML = plugins.hilight.format(node.innerHTML);
      }
  }
};

plugins.hilight.format = function(txt) {
  return '' + txt + '';
};
 
plugins.hilight.defaults = {
  foreground: 'red',
  background: 'yellow'
};

You might have noticed a couple of differences between this and the dojo.query version. Our selector and options are hard wired. It might be improper to say that these are drawbacks, since the very purpose of this syntax is to make sure that a page is upgraded a certain way, and that even if we change some HTML in the page, we can make sure we don’t end up with mismatched upgrades. Using this behavior, and showing how it can be re-applied is shown in the code below:

dojo.require("dojo.behavior");
dojo.require("plugins.hilight");

dojo.behavior.add(plugins.hilight.behavior);

dojo.addOnLoad(function(){
  dojo.body().innerHTML += '
Highlight me!
'; dojo.behavior.apply(); });

Benefits

Behaviors use a much tighter contract than a dojo.query plugin does. You don’t have to worry if the DOM has been loaded when adding a behavior, the module takes care of it for you. Finally, it’s easy to keep a document up to date without having to keep track of everything yourself.

Try This

A great pattern used in many projects is to use an XHR call (or a stored string) to inject HTML into a node, followed by a standard pattern of replacing variables, adding events, and showing or hiding nodes. Write a behavior designed to manipulate this HTML injection to really get a feel of why this pattern is so well suited to certain tasks.

In Action

See it here

Dijit: Write a Widget

Unfortunately, when new users explore Dojo, they head straight over to Dijit and think that this potentially heavyweight system is the only way to upgrade their HTML. They see that some HTML nodes have custom attributes and how you have to build real objects in order to use it — and it all gets a little overwhelming.

We have to build a real object because there’s a new concept introduced through Dijit. Not only are we upgrading the node, but we’re creating an object that’s bound to the node, can hold extra data for us, and exposes an API for manipulating the node.

Have you guessed what the widget is going to do? The answer is, of course, highlighting. We’ll create it in widgets/Hilight.js (uppercased to indicate that it’s meant to be instantiated).

You’ll often see widgets created using dojo.declare. You could create the same object structure by hand, but the Dijit widget system depends on a lot of variables getting set and functions getting called that you might otherwise forget about.

dojo.provide("widgets.Hilight");
dojo.require("dijit._Widget");

dojo.declare("widgets.Hilight", dijit._Widget, {
  background: "yellow",
  foreground: "red",
  constructor: function(props, node){
    props = dojo.mixin({}, this, props);

    dojo.style(node, {
      backgroundColor: props.background,
      color: props.foreground
    });

    node.innerHTML = this.format(node.innerHTML);

    dijit._Widget.call(this, props, node);
  },
  format: function(txt) {
    return '' + txt + '';
  }
});

There are so many ways to now use this object that it can be a little scary. You’ll mainly see the following syntax used:



Highlight me!
Highlight me!

All of this working together means that these nodes will automatically get marked up when the DOM is loaded. Not only that, but you can get the object bound to each node by using dijit.byNode(node); And finally, the parser uses the foreground and background attributes to create the props object passed to the constructor.

For those that don’t want to use these custom HTML attributes, you can just create this object programatically. We’ll keep the syntax we’ve been using and use a class instead. You might be surprised at how easy this is:

dojo.addOnLoad(function(){
  dojo.query(".hilight").instantiate(widgets.Hilight, { foreground: "black" });
});

Benefits

When your node doesn’t stand on its own, having an object bound to the node is massive upgrade in power. It can keep track of state, it can provide all sorts of functions for manipulating both the underlying DOM and any data it’s keeping track of — and best of all, since it’s a real object we can create a new object that inherits properties and functionality instead of having a single global defaults object.

Try This

The dijit._Widget constructor adds an instance field, domNode, that references the bound node. Knowing this, we can add functions that manipulate the node well after construction.

widgets.Hilight.prototype.setForeground = function(color){
  dojo.style(this.domNode, "color", color);
}
widgets.Hilight.prototype.setBackground = function(color){
  dojo.style(this.domNode, "backgroundColor", color);
}

Adding an ID to our node, and using dijit.byId, we can see how this would be used:

setTimeout(function(){
  var instance = dijit.byId("changeme");
  instance.setForeground("blue");
  instance.setBackground("orange");
}, 10000);
Highlight me!

In Action

See the html upgrades demo in action