Login Register

Top 10 things you should know about dojo.

A few months ago I started hanging out in #dojo and noticed that there were a lot of common questions. With some input from phiggins, I compiled a top-10 collection of the most frequently stumbled-upon concepts in The Dojo Toolkit. You'll probably never see this on the Late Show but here's number 10:

10. #dojo

Dojo has a very helpful community (http://dojotoolkit.org/community) with some very good resources for those getting started and also for those who have been using dojo for a while. Some very useful "go to" websites are:

These websites are great but what could be better than getting live help from the dojo developers and other developers who use dojo every day? Did you know that you can often get help at #dojo on irc.freenode.net? The help is free and plentiful, but some say you get what you paid for, which is why SitePen offers paid commercial support:

9. Asynchronous calls with dojo (addCallback, etc.)

Not everything in JavaScript can reasonably execute line-by-line. Often times, some function sends the execution into a "thread", whose data will become ready at some point in the future. For instance, loading a HTML fragment from the server:

var a = 1;
dojo.xhrGet({
    url:"frag.html",
    // use hitch() here to control where this callback is called (scope)
    load:function(data){
          console.log(a);
    }
});
a = 2;

The load: function is executed when the frag.html file is loaded. It is likely the variable a will have a value of 2 when the load: function executes, as the xhrGet call is asynchronous, and the line following is executed immediately. Then, later, the load: executes and logs the "closed" value of a.

load: in XHR is just a convenient way of adding a callback to the Deferred Chain. All async operations in Dojo revolve around a dojo.Deferred, which is a contract between the user and the code - something will be returned at some point in time: either a callback or an errorback (in the case of failure). The above could be written as:

var def = dojo.xhrGet({ url:"frag" });
def.addCallback(function(data){ console.log(data); return data.toUpperCase(); });
def.addCallback(function(data){ console.log(data); });

Here, we registered two callbacks. The second will be passed an upper-cased version of the original data. No other callbacks will fire because the second did not return the data along the callback chain.

The most common caveat experienced when using asynchronous callbacks is the scope in which the callback executes. If you have a Class object (where you refer to this as the object itself within it's members) that loads an asynchronous callback, the scope of the callback will not be this unless you explicitly hitch() it:

dojo.declare("my.XhrThing", null, {
      state:"unloaded",
      loader: function(url){
            dojo.xhrGet({ url:url, load: dojo.hitch(this, "handler") });
      },
      handler: function(data){
            this.state = "loaded";
            this.data = data;
      }
});

We execute "handler" in the scope of this by passing that as a reference to the load: parameter, so when handler is executed, it has access to this as you would expect: within the scope of the XhrThing instance.

8. Scope: dojo.hitch, dojo.partial

Sometimes, you want to call a method of an object in response to a certain action, but the context of that function call is not what you want it to be and the this keyword refers to something other than the object you expected it to refer to. Never fear, dojo.hitch() is here! With dojo.hitch() you can set the scope of the function call, for example in this call:

someWidget.onChange = anotherWidget.update;

When someWidget.onChange() is called, anotherWidget.update() is the code that is executed, however, this will likely refer to someWidget and the code from anotherWidget.update will not have the desired result. This can be fixed with dojo.hitch:

someWidget.onChange = dojo.hitch(anotherWidget, "update");

Now, when someWidget.onChange() is called, the code for anotherWidget.update is executed with the scope of anotherWidget which means that this will refer to anotherWidget.

You can also pass functions rather than strings directly to dojo.hitch as the 2nd parameter:

dojo.hitch(anotherWidget, "update");
// or
dojo.hitch(anotherWidget, anotherWidget.update);
// or
dojo.hitch(anotherWidget, function(){this.update()});

A cousin to dojo.hitch() is dojo.partial(). It works in a similar way "except that the scope object is left to be whatever the execution context eventually becomes." For functions executed globally, this is handy:

// create a partial function:
var curry = dojo.partial(dojo.style, "someNodeId", "opacity");

curry(); // returns opacity value of someNodeId
curry(0.75); // sets someNodeId opacity to 0.75

Like hitch(), partial() simply returns a reference to a function without executing it so it is safe to use in place of a callback, like in Animation:

dojo.fadeOut({
    node:"someNodeId",
    onEnd: dojo.partial(dojo.style, "someNodeId", "display", "none")
});

7. Object Hashes (kwArgs)

A pattern used throughout Dojo and Dijit is the kwArgs object hash, used in place of ordered parameters for simplicity and ambiguity. It is a nice way to provide sane defaults and to limit the complexity of calling a function. For instance, dojo.xhrPost only accepts one parameter: an object-hash of properties for the XHR call. Here, we supply the required url: param, and pass two optional params timeout: and load: callback.

dojo.xhrPost({ url:"...", load:function(data){ ... }, timeout:3000 });

Animations, Ajax functions, and many DOM utility functions use this pattern as well. In the case of dojo.style for instance, the node is passed as an ordered parameter, and the second argument is an object hash of multiple styles to apply:

dojo.style(node, "display", "none"); dojo.style(node, "opacity", 1);
// can be re-written as:
dojo.style(node, { display:"none", opacity:1 });

This also gives you a nice way to ambiguously call functions by forming up the object hash prior to execution.

var sizes = { width:200, height:300 };
// defined at difference times/areas
var args = {
     node:"foo", duration:3000, properties: dojo.mixin({ height:100 }, sizes)
};
dojo.animateProperty(args).play();

In the example, if the sizes object lacks a height: key, the default 100 is used.

6. dojo.declare, dojo.extend, dojo.mixin, dojo.delegate

"Out of the box" javascript doesn't provide a structured hierarchy for organizing and packaging your code, such as classes and packages, inheritance, etc. However, in dojo, we are able to build a hierarchy of classes using dojo.declare to create objects that behave as classes.

Once we have these classes, then it's likely that at some point we're going to need to make a change to these classes - add extra functionality, change the behavior, subclass, etc. For this, we have dojo.extend, dojo.mixin and dojo.declare. Each one of these will enable you to customize your classes but each one works in a different way.

dojo.extend will allow you to make a change to the prototype of a class. The overall result of this is that any instance of that class created in that page will now have the changes made by dojo.extend.

dojo.mixin is used on a specific instance of a class. The result of this is that only the specific instance that dojo.mixin is applied to will have the changes made by dojo.mixin. All other instances of that class will be unchanged.

dojo.declare is used to declare an object with a new namespace that is an extension of an existing object (or null). With dojo.declare, you can even extend multiple classes. The new class that is declared will have all the properties and methods of the classes that it is subclassed from and provides a way to override or extend the default behaviors of the classes we're extending.

Finally, when you're passing around all these instances of classes, you can't be sure that someone else's code won't manipulate your object somehow and you want to be respectful of objects that are passed to your code and not make changes to those instances. dojo.delegate is an extremely efficient way to wrap instances in a layer of protection so that "copies" can be made and manipulated without changing the original instance.

5. dijit.byId("foo").containerNode, dijit.byId("foo").domNode

A Dijit is made up of DOM nodes and javascript. The DOM nodes provide the visual interface (via the browser) to the javascript object. A Dijit has a number of DOM nodes and probably the 2 of these that are referenced the most in javascript are the domNode and containerNode. The domNode is the outermost DOM node (top-level) of the Dijit and is used as a handle to the widget when you are interacting directly with the DOM - eg. placing the widget. The containerNode is the node that holds the children/content of the Dijit. So, if you want to move the widget around, use the domNode and if you want to add children or other content, use the containerNode.

4. onChange, onchange (dijit event handler, DOM event)

DOM events such as onchange (all lower case) are low-level events that are generated by DOM nodes. Dijit events such as onChange (camelCase) are higher level "events" that are generated by widgets. Connecting to the onchange of a dijit (or a node that is part of a dijit) might not have the desired results. For more details about events (DOM and dijit) and how to override or connect to them take a look at the links above.

3. dojo.addOnLoad and dojo.require

Script tags are synchronous, meaning all other execution is blocked when the browser encounters a <script> tag in the document. You commonly load a script tag with dojo.js as one tag, then provide a second tag for your custom code.

All code on the page typically MUST be called or triggered from within an addOnLoad function. On the simplest level, it is an indication the DOM is ready for manipulation.

Beyond document.ready, addOnLoad also provides a safe way to ensure any modules loaded externally with dojo.require() are ready. Simply list your dojo.require() modules, and register an addOnLoad(function(){}), and the code will execute when the required code is available.

2. Theme Loading - "class" tag

dijit comes with 3 standard themes - Tundra, Soria and Nihilo. All 3 themes are intentionally unobtrusive and designed to work with most color schemes you might already be using. A very common mistake when using the theme is forgetting to include the "class" attribute somewhere. The most common place to use the class tag is on the <body> element.

<body class="tundra">

This will apply the Tundra theme to the whole document. However, it's possibly not as well known, but you could choose to only apply the theme to selected parts of the document and use different themes for different parts.

<body>
    <div class="tundra">
        ...
    </div>
    <div class="soria">
        ...
    </div>
</body>

This document explains themes in more detail http://docs.dojocampus.org/dijit-themes and this cookie explains how you can also override parts of a theme http://dojocampus.org/content/2008/02/26/overwriting-themes/

The major shortcoming of not putting the class on the <body> element is that popup widgets place themselves as direct children of <body>, and thus lose styling without class="themeName" on the <body>.

and the number 1 thing you should know about dojo

1. dojo.byId, dijit.byId, jsId

One of the most common mistakes to make in dojo is using dojo.byId("foo") to try and get the foo widget. dojo.byId("foo") will return the DOM node with the "foo" id, but dijit.byId("foo") is what you need to use to get a reference to the foo dijit. For convenience, you can declare your widget in markup with a jsId attribute and shortcut dijit.byId. The above cookies will help explain them better and hopefully you won't confuse them ever again.

dojo.hitch example

Thanks for the listing, I never knew about dojo.partial.

I think there might be a slight mistype in the hitch example,
should this:
dojo.hitch(anotherWidget, anotherWidget.update());
be this:
dojo.hitch(anotherWidget, anotherWidget.update);

fixed

@richo13 - you're right. i fixed it. thanks for picking that up.