Dev.Opera - Follow the standards, break the rulesDev.Opera - Follow the standards, break the rules

Login

Lost password?

Introducing The Dojo Toolkit

By SitePen, Inc. · 12 Dec, 2008

Published in: , , , ,

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."

Article categories