Introducing The Dojo Toolkit

By SitePen, Inc.

Introducing Dojo page 1 : Introducing Dojo page 2 : Introduction to JavaScript toolkits

Introduction

The Dojo Toolkit is a collection of uniform JavaScript components to assist with any of your web development needs. The Base dojo.js provides a collection of 'must have' APIs for your most common needs, and provides an entire library of functionality built around a "use at will" philosophy. Dojo is completely free, and is dual-licensed under the AFL and new-BSD Open Source Licenses, providing peace of mind about the history and future of the project.

You can download Dojo now, or simply get started by including a single <script> tag in an existing page:

<script type="text/javascript" charset="utf-8" src="http://ajax.googleapis.com/ajax/libs/dojo/1.2/dojo/dojo.xd.js"></script>

That's it! The line will load the Dojo Toolkit (1.2 at the time of this writing) from the Google Ajax API Content Distribution Network, which uses edge-caching to deliver the library from the closest possible geographic location. The entire Dojo Toolkit is available on both the AOL CDN, and Google's new Ajax API CDN - all components, styles and images, available at your fingertips with zero overhead.

You can see a fairly complete demonstrative overview of available components, as well as a complete API reference to get familiar with the potential functionality. The Dojo QuickStart guide covers several core concepts in depth as well, and DojoCampus makes a great community-driven learning center, providing documentation, tutorials, and examples around every corner. A community driven Wiki is in place at docs.dojocampus.org, and will soon become the definitive on-line documentation resource for Dojo.

Dojo is the "less Magic" JavaScript library, a motto adopted after the 0.9 release. The API is clear, concise, consistent, namespaced and entirely extensible, though it makes few (if any) assumptions about "what you want" to happen. Every component is "use at will". Every feature is additive and optional.

Facts about Dojo:

  • Dojo is lightweight - 26KB in size when compressed, with more advanced options to shrink to as little as 6KB on the wire.
  • Dojo supports all CSS3 selectors in its query engine, designed with a forward-looking API.
  • Dojo supports all major web browsers: Opera 9+, FF2+, Safari 3+, and IE6+
    • Dijit (the UI portion of the Dojo Toolkit) is currently not supported in Opera 9, though it works. Small keyboard and accessibility features in Opera prevent Dijit from claiming "official" support, though per Dojo's open development, patches to enhance support are always welcome with a CLA.
  • Dojo has a large group of core developers working together in relative harmony from all across the globe.
  • The Dojo Build and Package systems takes the guesswork out of optimization, including the automated creation of optimized "layers" of JavaScript, as well as inlining CSS @imports and comment removal.
  • Dojo is dual-licensed under the New BSD or the AFL, allowing for a true Open Source, Open Project experience, and piece of mind about IP purity.
  • DojoX provides countless plugins, all similarly licensed, supported, and included 'in the box'. Client-side charting, Graphics API, Advanced IO, Countless dojo.data Stores, and more.
  • Not only is Dojo backed by a thriving community providing support (on the Dojo Forums, #dojo on irc.freenode.net, and DojoCampus), it also has commercial support options from available SitePen for guaranteed results. Dojo also has the support and backing of a number of prominent companies within the web industry.

Base overview: what do I get?

On the client-side, the lightweight dojo.js file provides a vast amount of functionality. dojo.js is referred to as Base Dojo - the most stable, useful, and common functionality for all web developers. Without getting into too much detail (it would make for a very long article), below is an overview of the tools available in Base Dojo.

dojo.addOnLoad

Registers some function to run when the page is ready. This includes any additional components loaded in through the Dojo package system as well. dojo.addOnLoad accepts a function as follows:

dojo.addOnLoad(function() {
  console.log("The Page is ready!")
});

This is the quintessential "first step" when working with the Document Object Model, or DOM. Note that you should never directly add an onLoad event handler to the body element when using Dojo (or most any other toolkit).

dojo.require

Loads namespaced modules or components. For example, to load the advanced Animations and easing plugins, you'd do the following:

dojo.require("dojo.fx"); 
dojo.require("dojo.fx.easing");
dojo.addOnLoad(function() {
  console.log("The Page and all Dojo dependencies are ready!")
});

All modules/packages/plugins, however you prefer to call them, will be loaded and have their dependencies loaded as well. dojo.addOnLoad fires after everything has been resolved.

Alternate useful package tools: dojo.requireIf (conditional loading), dojo.provide (to alert the package system a module has been provided, and to not re-load).

Combining dojo.require and dojo.addOnLoad wrapped within each other provides a unique way to lazy-load resources. Simply include the base dojo.js in your page, and call dojo.require() from within an addOnLoad function:

// with just dojo.js, this is basically document.ready:
dojo.addOnLoad(function() {
  // page is rendered, add extra code:
  dojo.require("dijit.Dialog");
  dojo.addOnLoad(function() {
    // Dialog and all (if any) dependencies solved:
    var thinger = new dijit.Dialog( { 
      title:"A Modal Dialog",
      href:"remote.html" 
    });
    thinger.startup();
    // show it:
    thinger.show();
  });
});

In this example, we introduced the dijit namespace. Dijit is an add-on to the Dojo Core, and entirely optional. The above example needs a theme to "look right", which we'll cover later, but if you are impatient, the following will do:

<head>
<link rel="stylesheet"  href="http://ajax.googleapis.com/ajax/libs/dojo/1.2.0/dijit/themes/tundra/tundra.css" />

<script type="text/javascript" charset="utf-8" src="http://ajax.googleapis.com/ajax/libs/dojo/1.2.0/dojo/dojo.xd.js"
></script>
</head>
<body class="tundra">
<h2>Hello, Dijit</h2>

</body>

We simply added a CSS file, and class="tundra" to the body. This enables the 'tundra' theme for the entire page. Two other themes, soria and nihilo are available by default with Dojo, though themes are entirely CSS and images, so with some design work you can easily create your own.

dojo.byId

This is an alias to document.getElementById, but works in the few cases where getElementById does not. dojo.byId simply returns the native DOM Node, which can you manipulate directly. It is shorter to type, too.

dojo.addOnLoad(function() {
  dojo.byId("someNode").innerHTML = "I just replaced the content.";
});

You'll notice all of the examples are wrapped in an addOnLoad call, which prevents the code from executing before the DOM is actually ready.

dojo.connect - the event connection maker

or: "one-to-one" communication. This function can connect any DOM Event to any node and give you a powerful API for manipulating scope. The events are normalized across browsers and in some cases synthesized. For example, to connect an onclick handler to a single node:

dojo.addOnLoad(function() {
  var node = dojo.byId("someNode");
  dojo.connect(node, "onclick", function(event) {
    console.log("the node was clicked: ", event.target);
  });
});

Dojo connect also allows you to connect to any object. For instance, to execute a function anytime the method dojo.require() is issued, as in the following example:

var handle = dojo.connect(dojo, "require", function(arg) {
  console.log("require() called with: ", arg)
  dojo.disconnect(handle);
});
dojo.require("dojo.io.iframe");

dojo.connect passes the parameters the connected function is called with to the callback, illustrated above as an anonymous function. By calling dojo.disconnect with the return value of the dojo.connect, we insure this listener is only called once ever.

dojo.connect handles much more than just DOM Events. Any method or function can act as an 'event':

var myObj = {
  foo:"bar",
  baz: function(e) {
    console.log(this.foo);
},
bam: function(e) {
  this.foo = "barbar";
}
};
// call myObj.bam() in scope when baz() is run
dojo.connect(myObj, "baz", myObj, "bam");
myObj.baz();

The third parameter is a scope to execute the function from. We can pass a named function (as seen above), or an anonymous function to call in scope:

var myObj = {
  foo:"bar",
  baz: function(e){
  console.log(this.foo);
},
bam: function(e) {
  this.foo = "barbar";
}
};
// call anon function in myObj scope when bam() is run
dojo.connect(myObj, "bam", myObj, function() {
  // this is know as "after advice", and is run after bam
  this.foo = "otherbar";
  this.baz();
});
myObj.bam();

Connect uses dojo.hitch() under the covers to provide the scope-switching magic, which is a very powerful and useful feature once you understand the concept, and the frustration of the default behavior of events firing in the scope of the window and not the point at which the event handler is defined.

Topics: dojo.publish, dojo.subscribe

An extremely convenient method of sending information to and from ambiguous objects. After dojo.subscribe()'ing to a named channel, the function registered will be called any time some other function dojo.publish()es something on the same channel.

var subscription = dojo.subscribe("/system/alerts", function(msg) {
  console.log(msg);
});	
// and later:
dojo.publish("/system/alerts", ["You've been logged out"]);

Indeed, this is a very handy way for sections of a page to update themselves without prior knowledge of the other components on a page. In the above example, we save the handle of the dojo.subscribe call in a variable. Like dojo.disconnect, we can stop the subscription at any time by passing that handle to dojo.unsubscribe:

var handle = dojo.subscribe("/foo/bar", function(msg) {
  console.log("In this example, I'll never run.")
});
dojo.unsubscribe(handle);
dojo.publish("/foo/bar", ["Baz"]);

Topics are used in places throughout the Dojo API. For instance, dojo.dnd (the Drag and Drop component in Core) uses them to notify ambiguous events like "/dnd/move/start", and "/dnd/move/stop". The Dijit component dijit.layout.TabContainer uses them to notify itself about events like addChild, selectChild, removeChild, and so on.

Array utilities

dojo.map, dojo.filter, dojo.every, dojo.some, dojo.forEach, dojo.indexOf and so on... Dojo wraps all the native Array utilities found in JavaScript 1.6, providing a very functional approach to most problems.

The most common would be forEach, to run a function on each element in an array:

var arr = ["one","two","three","four"]; dojo.forEach(arr, function(elm, index, theArray) {
  // elm is the item in the array:
  console.log(elm);
  // index is where in the array we are:
  console.log('run ', index, ' times');
  // theArray is the full array, should you need a reference to it internally:
  console.log(elm == theArray[index]); 
  // should return 'true'
});

An optional third parameter to forEach is scope, which again utilizes dojo.hitch to scope your function to another object.

var arr = ["one","two","three","four"];
var obj = {
  log: function(elm) {
    console.log(elm);
  }
};
dojo.forEach(arr, function(elm) {
   // elm is the item in the array:
   this.log(elm);
}, obj);

Filter reduces an array based on a return value from a function:

var arr = ["one","two","three","four"];
var newArr = dojo.filter(arr, function(elm, i) {
  // only do even numbers:
  return i % 2 !== 0;
});
console.log(newArr);

Map creates a new array of elements based on a return value from a function:

var arr = ["a","b","c","d"];
var other = ["one", "two", "three", "four"];
var newArr = dojo.map(arr, function(elm, i) {
  if(i % 2 == 0) {
    // only odds elements from a different array
    return other[i];
  }
});
console.log(newArr);

indexOf and lastIndexOf return integer values for matches within an array, returning -1 if no match was found:

var arr = ["one","two","three","four","five","one"];
console.log(dojo.indexOf(arr, "two")); // 1
console.log(dojo.lastIndexOf(arr, "one")); // 5
console.log(dojo.indexOf(arr, "one")); // 0
console.log(dojo.indexOf(arr, "unknown")); // -1

DOM and CSS utilities

dojo.place, dojo.clone, dojo.attr, dojo.style, dojo.addClass/removeClass/toggleClass, dojo.marginBox, and dojo.coords are just some of the convenience functions above and beyond normal DOM manipulation common in the JavaScript DOM.

dojo.place will place a node in a position relative to another node:

// place a new <li> as the first in the ul id="miUl":
var li = dojo.doc.createElement('li');
dojo.place(li, "myUl", "first"); 
// give it some content, too:
li.innerHTML = "Hi!";

dojo.clone clones a node, returning the newly created node:

var input = dojo.query(".cloneMe")[0];
dojo.query("#someButton").onclick(function(e) {
  // add a new <input /> every time you click on someButton
  dojo.clone(input);
  dojo.place(input, e.target, "last");
});

dojo.attr handles all cross-browser attribute getting and setting:

// getter:
dojo.attr("someId", "title"); // title="bar"

// setter, single:
dojo.attr("someId", "title", "A new Title");

// setter, multiple:
dojo.attr("someId", {
  "tabindex": 2, // add to tab order
  "onclick": function(e) {
    // add a click event to this node
  }
});

dojo.style works with the same getter/setter API as dojo.attr, but for CSS styles:

// getter:
dojo.style("someId", "height"); 

// setter, single:
dojo.style("someId", "padding", "8px");

// setter, multiple:
dojo.style("someId", {
  "fontSize": "14pt", 
  "color": "#333"
});

Simple utilities for dynamically altering a node's class="" attribute include dojo.addClass, dojo.removeClass, dojo.toggleClass and dojo.hasClass. All follow the same API pattern: pass the function a string ID or DOM Node reference, and act on the node, adding, removing, or toggling a class name. dojo.hasClass returns a boolean, true if a node has a specified class name.

Locating a node in the page, or setting/getting a Node's size and position can be done as follows:

// returns x, y, t, l, w, and h values of id="someNode"
var pos = dojo.coords("someNode");
console.log(pos); 

// includes any potential scroll offsets in t and l values
var abs = dojo.coords("someNode", true);
console.log(abs); 

// get just the marginBox, set another node to identical size
var mb = dojo.marginBox("someNode");
dojo.marginBox("otherNode", mb);

Dojo's DOM Utility functions aim to take the pain out of working around cross browser quirks, and provide an easy-to-use API for common tasks.

dojo.query - Dojo's CSS3 selector query engine

Or: "Get DOMNodes, and do something with them".

Most of the core API (where relevant) are wrapped by dojo.query. In other words, anything you can do to a single node with Dojo can be applied to all matching nodes, using the same conventions. For instance, the above onclick example can be rewritten as:

dojo.addOnLoad(function() {
  dojo.query("#someNode").connect("onclick", function(e) {
    console.log('the node dojo.byId("someNode") was clicked', e.target);
  });
});

For convenience, shorthand methods more or less aliasing .connect() are mixed into dojo.NodeList (the super-Array returned by dojo.query()):

dojo.query("#someNode").onclick(function(e) {
  console.log('the node dojo.byId("someNode") was clicked', e.target);
});

All of the DOM Level 2 events are normalized and mixed: .onclick, .onmouseenter, .onmouseleave, .onmousemove, .onmouseover, .onmouseout, .onfocus, .onblur, .onkeypress, .onkeydown, .onkeyup, .onsubmit and .onload. dojo.query methods are also chainable, returning the same NodeList instance back after (most) calls.

dojo.addOnLoad(function() {
  // connect mouseenter and mouseleave functions to all div's with the class "hoverable":
  dojo.query("div.hoverable")
  .connect("onmouseenter", function(e) {
    dojo.style(e.target, "opacity", 1);
  })
  .connect("onmouseout", function(e) {
    dojo.style(e.target, "opacity", 0.42);
  })
  .style( {
    // set the initial opacity to 0.42, as if a mouseout has happened
    opacity: 0.42,
    // and the color:
    backgroundColor:"#ededed"
  });
});

Extending dojo.query (or "creating a plugin") is exceptionally easy:

dojo.extend(dojo.NodeList, {
  makeItRed: function() {
    return this.style({ color:"red" });
  },
  setColor: function(color) {
    return this.style({ color: color });
  }
});
// run the makeItRed function across all nodes
dojo.query(".greenNodes").makeItRed();
// set the color: property of .redNodes to a greyish tone:
dojo.query(".redNodes").setColor("#ededed");

Sometimes it is handy to work directly with a Node reference. For instance, when you register an onclick event handler on a div, any node within that div when clicked on will be the event.target, provided the event bubbles to the div. Sometimes you explicitly want to manipulate the original node, like so:

dojo.query("li").forEach(function(n) {
  dojo.connect(n,"onclick",function(e) {
    if(n == e.target) {
      console.log('same node');
    } else {
      console.log('bubbling up from different node');
    }
    // ensure we only ever add the class to the LI element, regardless of target
    dojo.addClass(n, "beenClicked");
  });
});

Dojo doesn't pollute the global namespace, nor compete for any shorthand convenience variables. If you desire aliases for commonly used functionality, you are capable of making that decision. dojo.query can be aliased trivially by creating a scope out of an anonymous function:

(function($) {
  $("a[href^=http://]").onclick(function(e) {
    // confirm all external links before leaving the page
    if(!confirm("Visit" + $(e.target).attr("href") + "?")) {
      e.preventDefault();
    }
  });
})(dojo.query);

Or by a convention seen throughout the Dojo source code:

(function() {
  var d = dojo;
  d.addOnLoad(function() {
    d.connect(d,"loaded",function() {
      console.log("obfuscated some");
    })
  });
})();

JavaScript is a very flexible language. A more advanced use case can be seen in Neil Robert's "Creating your Own $" Article on DojoCampus, ultimately mapping the entire Dojo Toolkit around a single $ variable.

dojo.query / dojo.NodeList follows the same "use at will" philosophy as the rest of Dojo - specific use-case methods are left out of the Base dojo.js, allowing you to optionally mix them in should you need them, by issuing a simple dojo.require. For instance:

dojo.require("dojo.NodeList-fx"); // adds Animation support to dojo.query
dojo.require("dojox.fx.ext-dojo.NodeList"); // adds DojoX addon Animations to dojo.query
dojo.require("dojo.NodeList-html"); // adds advanced HTML utility functions to dojo.query 

The naming convention on these packages are slightly different. The hyphen, being an invalid character in normal "dotted notation" is here used to indicate "cross package manipulation". For example, dojo.NodeList-fx adds "fx" to NodeList, but you will never be able to call a dojo.NodeList-fx method directly. Instead, such methods are simply injected into the dojo.NodeList. dojox.fx.ext-dojo.NodeList reads semantically as "This module in DojoX FX project extends Dojo's NodeList Class directly, and is only the convention of this type used within The Toolkit."

Introducing Dojo page 1 : Introducing Dojo page 2 : Introduction to JavaScript toolkits

Ajax: transporting data

dojo.xhr provides a simple, powerful API for using Ajax. In newer versions of Dojo, the simple dojo.xhr() call wraps the existing methods from previous versions: dojo.xhrGet, dojo.xhrPost, dojo.xhrPut and dojo.xhrDelete.

dojo.xhrGet( {
  url:"/path/to/remote.html",
  load:function (data) {
    dojo.byId("updateArea").innerHTML = data;
  }
});

Many things are simple to achieve with such calls, like retrieving the contents of a remote file by issuing a GET on the server, and injecting the result into a node (as seen above) or progressively trapping a native Form and using Ajax to POST the results to the server, as seen below:

<!-- a simple form: -->
<form id="sampleForm" action="submit.php" method="POST">
<input type="text" name="username" />

<button type="submit">login</button>
</form>
<script type="text/javascript">
dojo.addOnLoad(function() {
  var form = dojo.byId("sampleForm"); // save ref to form
  dojo.connect(form, "onsubmit", function(e) {
    // connect to, and disable the submit of this form
    e.preventDefault();
    // post the form with all it's defaults
    dojo.xhrPost( {
      form: form,
      load: function(data) {
        // set the form's HTML to be the response
        form.innerHTML = data;
      }
    });
  });
});
</script>

A simple PHP script to process the POST and send back some text would look like so:

<?php

  print "You Sent:";
  print_r($_POST);

?>

All the dojo.xhr* methods use a single object hash, or property bag, as the only parameter, so there's no need to remember the order of parameters - just the common names and what they do:

  • url: The endpoint to target.
  • handleAs: defaults to text, but allows you to modify the way the callback receives the data. Valid built-in options are: text, javascript, json, or xml
  • timeout: time (in milliseconds) to wait before throwing a failure, should the data not come back.
  • load: a function to call when the data arrives. The data is passed to the function as the first parameter.
  • error: an error handler function to define.
  • sync: A Boolean to toggle whether or not this XMLHttpRequest is blocking or runs in the background. This defaults to false, which indicates asynchronous operation.
  • handle: A function that is fired in the result of error or load success. The data object passed is either the data, or typeof "Error". It is provided as a convenience.
  • form: A domNode (or string ID of a node) to use as the content when submitting. As seen in the example above, the url: parameter is retrieved from the form's action attribute. You may specify an alternate url by passing both form: and url: parameters.
  • content: A JSON object of data to send to the url.

Later in this article, we'll show the "magic" behind dojo.hitch and dojo.partial, which give you more flexibility over the scope in which your load:, error: and handle: callbacks are called.

FX: A powerful, flexible animation API

Base dojo.js includes simple fade methods, and a powerful animateProperty method, which animates any CSS property. All animation methods return an instance of dojo._Animation, the core object providing control over the sequence. To create and run a fade animation, we'd do the following:

dojo.addOnLoad(function() {
  dojo.fadeOut( { 
    node:"someNode", // a node ref, byId
  }).play();
});

dojo._Animation instances have play(), stop(), status(), gotoPercent(), and pause() methods for control. With the exception of dojo.anim, all use a single object hash for defining the options. Some of the more useful options include:

var anim = dojo.fadeOut( {
  node: "someNode", // node to manipulate
  duration: 3000, // time in ms to run the animation
  easing: function(n) {
    // a linear easing function, alter the progression of the curve used
    return n;
  },
  delay: 400, // time in ms to delay the animation when calling .play()
  rate: 10 // a framerate like modifier. 
});
anim.play();

There are 30+ available easing functions in the optional dojo.fx.easing component. Simply dojo.require() it to use them:

dojo.require("dojo.fx.easing");
dojo.addOnLoad(function() {
  dojo.fadeOut( {
    node:"someNode",
    easing: dojo.fx.easing.bounceOut // bounce towards the end of the animation
  }).play();
});

They also fire synthetic events at various stages of the cycle. What follows is an involved example illustrating all of them:

dojo.addOnLoad(function() {
  dojo.fadeOut( { 
    node:"someNode",
    beforeBeing: function() {
      console.log("the animation will start after I've executed");
    },
    onBegin: function() {
      console.log('the animation just started');
    },
    onEnd: function() {
      console.log('the animation is done now');
    },
    onPlay: function() {
      console.log('the animation was started by calling play()');
    },
    onStop: function() {
      console.log('the animation was stopped');
    }
    onAnimate: function(val) {
      // fired at every step of the animation
    console.log('current value: ', val);
    }
  })
});

The most commonly used event is onEnd. For instance, supposed you want to fade out some content, replace it via Ajax, and fade it back in:

var n = dojo.byId("someNode");
dojo.fadeOut( { 
  node: n,
  onEnd: function() {
    dojo.xhrGet( {
      url: "newContent.html",
      load: function(data) {
        n.innerHTML = data;
        dojo.fadeIn({ node: n }).play();
      }
    })
  }
}).play();

The node: parameter can either be a DOM Node reference, or a string to be passed through dojo.byId. In this example, we stored the reference as "n", and reuse it in our callback.

You can also use dojo.connect for advanced usage with animations, the _Animation instance simply being an object to connect to:

// create a simple loop
var fadein = dojo.fadeIn({ node: "someNode" });
var fadeOut = dojo.fadeOut({ node: "someNode" });
// call fadeout.play() anytime fadein's onEnd is fired:
// and re-play fadein when fadeout's onEnd is fired:
dojo.connect(fadein, "onEnd", fadeout, "play");
dojo.connect(fadeout, "onEnd", fadein, "play");
// start the loop
fadeout.play();

Fading is great, and useful, but is only provided as a convenience wrapper around dojo.animateProperty. The property undergoing the animation is opacity:

// simulate fadeIn
dojo.animateProperty( {
  node:"someNode",
  properties: {
    opacity: 1
  }
}).play();
// as opposed to:
// dojo.fadeIn({ node: "someNode" }).play();

But animateProperty is entirely more robust and flexible. With it, you can animate any number of properties across a single node:

dojo.animateProperty( {
  node:"someNode",
  properties: {
    // end is assumed:
    opacity:0,
    // define a start AND end value:
    marginLeft: {
      start:5, end:250
    }
    // start is calculated, use unit "em"
    padding: {
      end:5, unit:"em"
    }
  }
}).play();

Of course all the same dojo._Animation events and configuration options still apply. The properties hash accepts several formats. When passed a start: value and and end: value, the node is forced to those properties. When only passing an end: value, the start: value is calculated based on the node's current state. When only passed an integer, it is used as an end: value. The unit: parameter is assumed to be "px" unless otherwise specified, though use it with caution as some browsers do not convert "em" and "pt" to pixel values very well, or not at all.

Notice marginLeft in the example above. In CSS the value would be margin-left:, though the hyphen is illegal in JavaScript. A camelCase version is used instead per standard CSS to JavaScript property name translations, eg marginLeft.

You'll probably notice the animateProperty syntax is relatively verbose, as well as most every example so far has immediately called .play() on the returned _Animation. A shorthand function in Base Dojo exists to wrap common conventions (though it breaks the convenience of the "object hash" paradigm):

dojo.anim("someNode", { 
  opacity:0,
  marginLeft: { start:5, end:250 },
  padding: 50
});

This example produces the same results as the much longer example above. You sacrifice flexibility for convenience with dojo.anim, as the parameters are ordered, and the animation is automatically play()ed. This really only scratches the surface of the Dojo Animation API.

Advanced JavaScript utilities

Including object-oriented helpers like dojo.declare and dojo.mixin, as well as native prototypical inheritance helpers like dojo.extend, and dojo.delegate is extremely helpful. There are also useful scope-manipulation functions - like the often-used dojo.hitch and the elegant dojo.partial - included for your convenience.

By far the most magical of functions is dojo.hitch, which creates a function that will only ever execute in a give scope.

var foo = function() {
  var bar = function(arg) {
    console.log("was passed: ", arg);
  }
  dojo.fadeOut( { 
    node: "nodeById",
    onEnd: dojo.hitch(this,"bar")
  }).play();
}

The important thing to note here is the function created by hitch isn't executed immediately. We retain the scope of this in the example, calling a local function. There are a few great dojo.hitch articles available on DojoCampus, further exploring the potential of Javascript scope-manipulation.

dojo.partial behaves similarly to dojo.hitch, though it assumes a global scope. Using hitch or partial, you can 'curry' in JavaScript.

var style = dojo.partial(dojo.style,"someNodeId");
// anytime we execute this function, we'll style a node with id="someNodeId"
style("opacity",0.5);
style("backgorundColor","#fff");
// it also acts as a getter:
var val = style("width");
console.log(val);

dojo.mixin simply mixes objects together from right to left:

var obj = { a: "foo", b: "bar" };
dojo.mixin(obj, { b: "baz", c: "bam" });
console.log(obj);
// Object a=foo b=baz c=bam

We lose the initial value of b:, having mixed in a new value.

To create a new object, and simply add properties to it, we can use dojo.clone (which also works on DOM Nodes):

var obj = { a: "foo", b: "bar" };
var newObj = dojo.clone(obj); 
dojo.mixin(newObj, { b: "baz", c: "bam" });
console.log(obj, newObj);
// Object a=foo b=bar Object a=foo b=baz c=bam

declare is Dojo's Class creator. Without delving too deeply into its powerful API, we will say that it allows you to create reusable objects in an object-oriented manner, wrapped around JavaScript's prototypical nature. In the below example we create a Person class, and then create an instance of that Person:.

dojo.declare("Person", null, {
  constructor: function(nick, name, age) {
    this.nick = nick;
    this.name = name;
    this.age = age;
    this.location = null;
  },
  setLocation:function(loc) {
    this.location = loc; 
  },
  getLocation:function() {
    return this.location;
  }
});
var dante = new Person("dante","Peter Higgins", 28);
dante.setLocation("Tennessee");
console.log(dante.getLocation());

We can use mixins within declare to create new classes that inherit from other classes. Below we make an Employee class that inherits from Person. Employees will receive additional relevant fields:

dojo.declare("Person", null, {
  constructor: function(nick, name, age) {
    this.nick = nick;
    this.name = name;
    this.age = age;
    this.location = null;
  },
  setLocation:function(loc) {
    this.location = loc; 
  },
  getLocation:function() {
    return this.location;
  }
});
dojo.declare("Employee", Person, {
  employeeId: 0,
  setId: function(id) {
    this.employeeId = id;
  }
})
// I am employed:
var dante = new Employee("dante","Peter Higgins", 28);
dante.setLocation("Tennessee");
dante.setId(42);
console.log(dante.employeeId);

This way, we can create People and Employees, and differentiate them by their properties and/or methods.

Using dojo.mixin, we can add custom properties to instances of a class, as the instances are just decorated objects:

dojo.declare("Person", null, {
  constructor: function(nick, name, age) {
    this.nick = nick;
    this.name = name;
    this.age = age;
    this.location = null;
  }
});
var dante = new Person("dante","Peter Higgins", 28);
dojo.mixin(dante, {
  employeeId: 42
});
console.log(dante.employeeId); // 42

Using dojo.extend, we can modify the class directly. The extended properties will be available in all instances defined after the extend() occurred:

dojo.declare("Person", null, {
  constructor: function(nick, name, age) {
    this.nick = nick;
    this.name = name;
    this.age = age;
    this.location = null;
  }
});
// add Eye-color functions to the Person Class
dojo.extend(Person, {
  eyeColor:"adefault",
  setEyeColor: function(color) {
    this.eyeColor = color;
  }
});
var dante = new Person("dante","Peter Higgins", 28);
console.log(dante.eyeColor); // default
dante.setEyeColor("brown");
console.log(dante.eyeColor); // brown

The flexibility provided by dojo.declare, dojo.mixin and dojo.extend is visible throughout the entire Dojo Toolkit. Every aspect of Dojo, Dijit, or DojoX can be extended, modified, reused or otherwise hacked up as you see fit. For instance, all Dijits inherit from a base class named dijit._Widget, which is subject to all the rules of standard dojo.declare extension points mentioned above.

Built-in namespacing support

Never worry about the location of your code again! The namespaces dojo, dijit, and dojox are all assumed to be sibling folders of each another, and are located ../namespace relative to dojo/. You can create a custom namespace to encapsulate your own code simply by adding a sibling folder:

+ dojo-1.2/
+ dojo/
  + dojo.js
+ dijit/
+ dojox/
+ custom/
  + kernel.js

All that is required to notify Dojo about your page is dojo.provide(). In dojo-1.2/custom/kernel.js:

dojo.provide("custom.kernel");
dojo.require("dojo.io.iframe");
custom.init = function() {
  // comments get removed as part of the Build process
  console.log("I am custom code!");
}
dojo.addOnLoad(custom.init);

In your pages, you simply dojo.require your package:

dojo.require("custom.kernel");

If you'd like your code tree to live outside the dojo-1.2 folder, simply register the path relative to dojo.js:

+ dojo-1.2/
  + dojo/
    + dojo.js
  + dijit/
  + dojox/
+ custom/
  + templates/
    + Template.html
  + kernel.js

Then in your HTML, register the module path. In this case, custom is two-folders below dojo.js:

dojo.registerModulePath("custom", "../../custom");
dojo.require("custom.kernel");

The most important item is locating dojo.js on your web server. Everything "just works" after that. Once the module path has been registered, you can access any file within that path with ease:

dojo.require("dijit._Widget");
dojo.require("dijit._Templated");
dojo.declare("custom.Widget", [dijit._Widget, dijit._Templated], {
  templatePath: dojo.moduleUrl("custom", "templates/Template.html");
});

Summary

This tutorial barely scratches the surface of the tools Dojo provides for building your web application or enhancing your web site. To get more help, visit the Dojo web site. Not only is Dojo backed by a thriving community providing support (on the Dojo Forums, #dojo on irc.freenode.net, and DojoCampus), it also has commercial support options from available SitePen for guaranteed results. Dojo also has the support and backing of a number of prominent companies within the web industry. We also encourage you to get involved to help us continue to make Dojo even better.

SitePen (www.sitepen.com) exists to develop web applications. Your organization either needs one, almost has one, or is struggling to fix the one its got. We've simplified your problem, now let us simplify and deliver the answer.

As co-creators of the Dojo Toolkit and proponents of the efficiencies of open source software, our services provide solutions to any project dilemma and include web application development, Dojo & JavaScript support and Dojo training. The best part? Whether you choose just one or a combination of our services, you will always receive the trusted advice and expert consulting that our clients have come to rely on.

Headquartered in downtown Palo Alto, in the heart of the Silicon Valley, SitePen has been successfully launching web applications since 2000 and we're proud to employ an unsurpassed development and design team that leads the industry in innovation and skill.


This article is licensed under a Creative Commons Attribution, Non Commercial - Share Alike 2.5 license.

Comments

The forum archive of this article is still available on My Opera.

No new comments accepted.