The Dojo Toolkit in Practice

>An Ajax library for more than “Prototype-ing”

PDF Version of Article | Download Source Code

Introduction

When you start to build an Ajax application, you quickly run into situations where you feel like you are reinventing the wheel. The XMLHttpRequest object is what a lot of developers jump on when they think about Ajax, but that is just the start, and is the easy part.

There are lots of annoyances when you build JavaScript rich applications. Browser compatibility and degradation, messing with the DOM, and dealing with bleeding edge hacks such as offline storage all come up.

This article is going to introduce you to a toolkit that is much more than an XHR wrapper. It is the type of toolkit that everyone should be using if they are developing a rich Ajax application. Without it, you are a crippled developer.

Rather than listing out the APIs available in The Dojo Toolkit, we will look at a simple slice of an application, to see parts and pieces of the library in action.

We will discuss:

  • What this Dojo thing is?
  • Setting up Dojo
  • DOM and HTML Effects in practice
  • Ajax remote calls via dojo.io.bind()
  • Drag and Drop operations

Let’s get to it!

The Dojo Toolkit: The Kitchen Sink of JavaScript Development

What is the Dojo Toolkit? Is it a framework? Is it a library? What do those names even mean? Alex Russell, one of the creators of the toolkit thinks that it is a library that you can use to build JavaScript applications.

It is more than you probably think because it contains such a lot of functionality in one set of packages, within one top level project.

Dojo contains sub packages of JavaScript code to take care of all of the infrastructure work that you normally have to write yourself when you build a JavaScript application. It encapsulates cross-browser annoyances so you don’t have to worry about them. It helps you with the DOM. It makes the XMLHttpRequest object degrade in various browsers, whilst giving you the simplest of APIs to use.

JavaScript isn’t just about the browser. It can run in various host environments, and Dojo supports that too. If you want to write JavaScript on the server-side, you can still use Dojo to help make that code simpler.

To give you a sense of the breadth of this framework, let’s list out the packages within:

  • dojo. collections: Useful Collection data structures (List, Queue, Set, Stack, Dictionary, …)
  • dojo. crypto: Cryptographic API (Blowfish, MD5, Rijndael, SHA*)
  • dojo.date: No need to write the ugly code to munge between date formats.
  • dojo. dnd: Drag and Drop helper API. We will build a drag and drop enabled application
  • dojo. event: Event driven API that has AOP abilities, and topic/queue functionality
  • dojo. lfx: Effects in HTML and SVG . We will use effects in the example.
    • dojo. animation: Animation package based off of Dan Pupius’ work on Animations (http://pupius.co.uk/js/Toolkit.Drawing.js) [deprecated in favour of dojo.lfx]
    • dojo.fx: deprecated in favour of dojo.lfx
  • dojo. io: Various IO pipelines. From cookies to IFrames, to pub/sub functionality. The magic Ajax work lives here
  • dojo. lang: Functionality that enhances your entire JavaScript environment. Features that you wish you had such as mixins, closure based functions, and much much more
  • dojo. logging: Logging framework
  • dojo. math: Mathematic routines (curves, points, matrices)
  • dojo. reflect: Reflection routines
  • dojo. rpc: Talk to back end services such as JSON aware Web services
  • dojo. storage: Save data to local storage (e.g. using Flash trick in the browser)
  • dojo. string: Now you can trim, capitalize, encode, escape, pad, and more.
  • dojo. undo: Stack manager for undoing actions
  • dojo. uri: Handle URIs
  • dojo. widget: A widget framework that lets you build reusable HTML/JavaScript widgets that can be used with simple HTML (e.g.
    ). This allows markup based application development (think: XAML, XUL).
  • dojo. xml, dojo.dom: Helpers to help you manipulate the DOM and other XML help. We will use dojo.dom in this example.
  • dojo.style: CSS functions such as getting access to style sizes, working with the box model, and more.

Phew! That is a long list, and it doesn’t do justice to the work that lives in this framework. If you use Dojo, and start to write some infrastructure code, you quickly learn to search for it within the framework first, as chances are someone else has done it for you!

What you quickly find is that if you are building any non-trivial Ajax application, Dojo will have a lot to offer you. It helps you make the jump from hacking away on a few onclick events, to writing professional quality JavaScript applications.

Setting up and Configuring Dojo

How do you normal setup and configure a JavaScript library? Erm, don’t you just grab the JavaScript file and put it on the web server, and then access it via <script src=…?, I hear you cry.

That is the case for simple libraries, and CAN be done with Dojo. However, it is better to know the entire picture.

Choosing the right Dojo build

Since the toolkit has so many parts and pieces, there are a set of builds for you to choose from. Why add this complication and not just offer one download?

In a lot of our environments, we can be liberal on this issue, but with JavaScript in the browser, every kilobyte that we feed the browser matters. Why send down the entire set of packages if you just want to do the odd Ajax call? We don’t want to shoot the bits around for no reason.

When you download an “Edition” of Dojo (Grab the zip file at: http://download.dojotoolkit.org, or get anonymous SVN via http://svn.dojotoolkit.org/dojo/trunk), you will choose between:

  • I/O (XHR) Edition: All you need is to chat back to the server Ajax style? Come right ahead.
  • Event + I/O Edition: The I/O Edition plus the event system
  • Ajax Edition: Here you get not only the I/O and eventing features, but also killer visual effects.
  • Widget Edition: You love separating CSS, content, and JavaScript, so you take it to the next level with the widget system
  • “Kitchen Sink” Edition: I don’t want to think about it. I just want to grab every bit of this framework and make my users suck it down. This edition will give you just that.

Although the kitchen sink approach allows you to grab everything and move on without having to make changes later on if you pull in a package that you didn’t foresee earlier in a project, it is not advised. Not only because you are forcing all of your users to download a large set of JavaScript, but it can actually cause some issues. For example, with one of the latest releases, if you used the kitchen sink, some users will see their browser status bar saying “Still loading…” forever. This can break the entire page very easily. The reason is that the kitchen sink includes every package, and the dojo.storage.* package in particular. That package uses Flash 8 to allow storage, and can cause issues on IE without Flash 8 installed. Bad news.

To start using a Dojo build, simply take the dojo.js file from the build that you downloaded, and simply load it in the normal way:

javascript
< view plain text >
  1. <script type="text/javascript" src="http://yoursite.com/scripts/dojo.js"></script>

Loading pieces dynamically

Wouldn’t it be nice to programmatically ask for packages, and have them available to you? I mean, we do this all the time in our other languages. We include our C, import our Java, use our Perl, and require our Ruby. The Dojo team wanted just this, and they baked it into the platform. If you require a package that isn’t in your download bundle, you do just that: require it! In our example we will require the drag and drop, event system, and fx packages:

javascript
< view plain text >
  1. <script type="text/javascript" src="http://yoursite.com/scripts/dojo.js"></script>
  2. <script type="text/javascript">// < ![CDATA[
  3. // < ![CDATA[
  4. // < ![CDATA[
  5. // < ![CDATA[   dojo.require("dojo.dnd.*");     dojo.require("dojo.event.*");   dojo.require("dojo.lfx.*"); // now we are ready to work with any of these packages
  6. // ]]></script>

The require call will go out and dynamically retrieve the JavaScript code needed for those packages and load them up in the page. You can also write your own packages, and load up your namespaces too. Each package has some metadata to tell the require loader what it really needs.

For example, here is the __package__.js file that holds that metadata for the drag and drop package:

javascript
< view plain text >
  1. dojo.hostenv.conditionalLoadModule({
  2. common: ["dojo.dnd.DragAndDrop"],
  3. browser: ["dojo.dnd.HtmlDragAndDrop"]
  4. });
  5. dojo.hostenv.moduleLoaded("dojo.dnd.*");

You will notice that the runtime can load different source files depending on the host environment. If you are not running Dojo in a browser, you do not need to load the HtmlDragAndDrop source.

Custom builds

Dynamically, and lazily loading the required JavaScript code can be great, but after development is done, you probably want to build one JavaScript file that has all of the code needed for your application.

Luckily Dojo has a build system that allows you to do just that. It requires that you grab the source and work from there. The source is kept in Subversion, with the repository of: http://svn.dojotoolkit.org/dojo/trunk (for the HEAD of the project).

We could talk about doing custom builds, but let’s leave that for another time, and instead let us get to our application.

The Application: Travel Itinerary Editor

Now we know how to setup Dojo, let’s use it, and some of its packages in a real application. The page we will work with is an itinerary editor that lets you:

  • Add and remove days for your trip
  • Add and remove sites to visit in those days
  • Clone sites
  • Drag and drop sites between days

All of these activities will be done in an Ajax interface. One page, many actions. This page is step two in the process, in that the user has already picked sites and has done the initial placement.

The screen looks like this:

Let’s get it working.

DOM and HTML Effects

What shall we do first? How about building the functionality to add a new day to your itinerary? To do this, we will need to build up the HTML needed for a new day. The header, the ‘empty…’ collection since there are no sites, etc.

We could build this up using the DOM, but it will be easier if we could just do a copy. To do that we can have an empty table in our HTML, but not shown.

This is a set of div’s with lists, and template code to act as a placeholder (such as EMPTY DAY instead of the day number header, and special id’s for us to grab):

  1. <div id="empty-new-day" class="itinerary-day" style="display: none;">
  2. <div class="itinerary-day-title day-8"><span style="float: right;"><a onclick="return deleteDay(this);" href="javascript:void(0);"><img src="images/but_edit_delete_white.gif" alt="Delete this entire day" width="16" height="16" border="0" /></a></span>EMPTY DAY</div>
  3. <ol id="empty-new-day-ol" class="itinerary">
  4.     <li id="empty-new-day-li">empty...</li>
  5. </ol>
  6. </div>

Mucking around with the DOM

To create the new day, we will need to make a copy of that HTML, and place it at the right place in the document.

To do this, we can use some helper methods from Dojo:

  • dojo.byId: I am sure you have typed document.getElementById(..) more times than you have had hot dinners. This little helper not only makes the typing a little less, but it also lets you pass in a text id, or a node itself. This is a piece of pragmatism that means that you can forget what you actually have and it all just works. Some of you may think that this looks like the dollar-sign function in Prototype $(id). It is. Why did Dojo decide not to use a short name such as $? They do not want to pollute namespaces. $ is great to use, but what if another defines $ too, and they do it slightly differently? Problems. The $() function does have another nice feature, in that it can take more than one id/node and return them all back. You can do this in Dojo with dojo.byIdArray(). The reason for the “dojo.” before most of the calls that you see is that Dojo enables package semantics to make sure that we don’t have these kind of collisions.
  • dojo.dom.insertBefore(newElement, elementBefore); dojo.dom has a large set of dom helper methods to make it a bit nicer to deal with (isn’t DOM a pain in the….?). We use this function to place the new day before the last one (the unused sites).

The full code for the addDay() method is below. It is a bit verbose as it has to reset the ids for the new day number, increment the total number of days, and more:

javascript
< view plain text >
  1. function addDay() {
  2. var lastDay = dojo.byId("unused-sites");
  3. var emptyDayTemplate = dojo.byId("empty-new-day");
  4.  
  5. numberOfDays++; // we have one more day now
  6.  
  7. var newDay = emptyDayTemplate.cloneNode(true);
  8. newDay.id = "fullday-" + numberOfDays;
  9.  
  10. // set the title
  11. newDay.childNodes[1].childNodes[1].nodeValue = "day " + numberOfDays;
  12.  
  13. // set the new ol id
  14. var ol = newDay.childNodes[3];
  15. ol.id = "day-" + numberOfDays;
  16.  
  17. // set the new empty li id
  18. var li = ol.childNodes[1];
  19. li.id = "empty-day-" + numberOfDays;
  20.  
  21. setupDayForDND(newDay); // drag and drop explained later
  22.  
  23. dojo.dom.insertBefore(newDay, lastDay);
  24. dojo.fx.wipeIn(newDay, 500); // effect below
  25. }

You will see a lot of normal DOM methods such as node.cloneNode(true), which is the call that copies the template from the empty day, and then we get to work updating the fields within. The true flag that we pass in tells DOM to copy the nodes within the node. Go deep.

What other dojo.dom helpers are there?

You can probably work out what these cross browser helpers do:

  • dojo.dom.isNode
  • dojo.dom.getTagName
  • dojo.dom.firstElement
  • dojo.dom.lastElement
  • dojo.dom.nextElement
  • dojo.dom.prevElement
  • dojo.dom.moveChildren (srcNode, destNode, trim)
  • dojo.dom.copyChildren (srcNode, destNode, trim)
  • dojo.dom.removeChildren(node)
  • dojo.dom.replaceChildren(node, newChild)
  • dojo.dom.removeNode(node)
  • dojo.dom.getAncestors
  • dojo.dom.getAncestorsByTag
  • dojo.dom.innerXML
  • dojo.dom.createDocumentFromText
  • dojo.dom.prependChild
  • dojo.dom.insertAfter
  • dojo.dom.insertAtPosition
  • dojo.dom.textContent: implement the DOM Level 3 attribute

Dojo HTML Effects

We use dojo.dom to plop in the new day, but that just isn’t classy. We don’t want to jolt the eyes of our users, we want to caress them, and show them where the document is changing at the same time.

Script.aculo.us is known for its killer effects, but a lot of people don’t know that Dojo has them too.

We chose to dojo.lfx.wipeIn() the node which makes it roll in like blinds coming down the window frame.

We didn’t have to go with a wipe though. Maybe you would prefer a fade (In/OutShow/Hide), a slide (By, To), a colorFade/highlight, or an explode/implode? They are all there, and they are all one liners, with the ability for you to add callbacks to the effects.

These effects may seem like lipstick, but they add the polish to give your users the wow factor, and they can do their job with respect to usability.

The warning though is that you don’t want to explode every movement on the page. If we do that, people will think that we are Flash developers ;)

Ajax remote calls with dojo.io.bind()

Let’s add some Ajax into the mix ☺ We want to give the users the ability to delete days and sites in a responsive manner. We don’t need no sinking page reload, we just want to nuke the day or site from the UI and we are happy.

However, we can’t JUST delete the element from the screen without telling the backend, else in reality the day/site will still exist in the backend state.

This means that we follow a common pattern in which we:

  • Offer a simple UI change dynamically in the DOM
  • Send an Ajax request to the backend to tell it of the change (e.g. delete day 5 for this guy will you?)

Here is the high level function that will handle the deletion of a day:

javascript
< view plain text >
  1. function deleteDay(removeDay) {
  2. if (confirm('Are you sure you want to delete this entire day?')) {
  3. // get the actual li site node from the remove button
  4. var day = removeDay.parentNode.parentNode.parentNode;
  5.  
  6. fadeAndRemove(day);
  7.  
  8. // call the back-end
  9. remoteDeleteDay(day);
  10.  
  11. return true;
  12. } else {
  13. return false;
  14. }
  15. }

We make sure that the user wants to remove the day (versus hitting the mouse button by mistake), and then we are off to the races.

Fade and Remove

First we remove the day from the UI gracefully via fadeAndRemove(day):

javascript
< view plain text >
  1. function fadeAndRemove(e) {
  2. dojo.lfx.fadeHide(e,1000,dojo.lfx.easeIn,function(nodes) {
  3. dojo.lang.forEach(nodes, dom.removeNode);
  4. }).play();
  5. }

This shows off the combination of dojo.dom and dojo.fx one more time. We ask Dojo to fade out the given element (in this case the day), and pass in a callback for him to call when it has faded. That closure gets given the node, which fadeHide nicely removes for you when done. The forEach method shows off the iterators that are available to you via dojo.lang.

The new dojo.lfx package has a new dojo.lfxpropertyAnimation that allows you to pass in many properties, allowing you to do custom animation in a simple way.

For example, to do a simple fade in you may use:

javascript
< view plain text >
  1. dojo.lfx.fadeIn(node, 200, dojo.lfx.easeOut, callback).play();

At a lower level you could write this as:

javascript
< view plain text >
  1. dojo.lfx.propertyAnimation(node, [
  2. { property: opacity, start: 0, end: 1 }
  3. ], 200).play();

Now you can add more and more properties to the array and you are off on a wild animation ride.

Use XMLHttpRequest to tell the server what’s happening

The remoteDeleteDay() call will use Dojo to (probably) use XMLHttpRequest to talk back to the server.

When you see people talk about Ajax, you often see lots of routines to wrap getting the XMLHttpRequest object cross browser, and strange status codes like “4”.

Dojo takes that all away from you, and gives you a nice clean API that looks like:

javascript
< view plain text >
  1. dojo.io.bind({
  2. url: "http://path/to/server",
  3. load: function(type, data, evt) { … do something onload …},
  4. error: function(type, error) { … handle an error … },
  5. mimetype: "text/plain"
  6. // and many more options!
  7. });

Basically, all remote calls go through the one dojo.io.bind() method call. This method does a lot more for you than you may think:

  • If the XMLHttpRequest is chosen (probably the case) Dojo will find that object for the given browser
    • Use the key: transport: “X” to explicitly use XMLHttpRequest, or IFrameIO, or …
    • 99% of the time you can leave this out and Dojo will work out the best solution for you!
  • If XHR is not supported, Dojo can drop down to using a hidden iframe to get the remote call
  • XHR object caching is done for you
    • Use the key: useCache: true
  • Bookmarkability is supported
    • Use the key: changeURL: true (for an automatic value) or “ajaxian” for your own value
  • Back/Forward buttons on the browser are supported
    • Use the keys backButton: code, forwardButton: code. For example, in our book Pragmatic Ajax, we use the example of:
      javascript
      < view plain text >
      1. backButton: function() {
      2. saveCityState();
      3. cleanCityState();
      4. },
      5. forwardButton: function() {
      6. setupCityState();
      7. }
  • You can use XHR to submit a form: Use key: formNode: dojo.byId(‘yourFormId’)
  • Uploading of a an entire file is supported. First you need to make sure that iframe support is loaded (dojo.require(“dojo.io.IframeIO”)), and then tell Dojo about the file.
javascript
< view plain text >
  1. file: {
  2. name: "upload.txt",
  3. contentType: "plain/text",
  4. content: "look ma! no form node!"
  5. }

So, to complete remoteDeleteDay() you would just have a simple dojo.io.bind() call that hits the backend URL of your choice, which should be a service that understands what you are doing.

You won’t need to do much in the load: handler, because in this case we are not changing the UI based on anything that comes back. However, in the error: case you could potentially undo the change that you just made and put up an error message telling the user that the day couldn’t be removed (using an error message sent back from the server).

Drag and Drop Operations

Web users are not used to drag and drop operations in this medium. For that reason, we need to be careful about offering a drag and drop enabled interface, and probably need to make available an interface that doesn’t rely solely on it.

Offering drag and drop support can make some tasks a lot easier to use. In our application, it would be nice to visually move sites around in our itinerary rather than have to go into some edit mode and move them around in weird lists.

Configuring Drag and Drop with Dojo is actually simpler than you may think. We will go through three phases:

  1. Wire up the day elements to tell the system what can be dragged, and what can be dropped
  2. Given a day, wire up its sub-elements
  3. Tell the browser to do this wiring up

Wire up all days

The initial function will go through all of the days, and tell each day to get ready for drops, and then setup all of the sites as draggable objects. Once the days are setup, also configure the unused sites area.

javascript
< view plain text >
  1. function enableSiteMoving() {
  2. for (var d=1; d &lt; = numberOfDays; d++) {
  3. var dl = dojo.byId("day-" + d);
  4.  
  5. setupDayForDND(dl);
  6.  
  7. var lis = dl.getElementsByTagName("li");
  8. for(var x=0; x new dojo.dnd.HtmlDragSource(lis[x], "daylist");
  9. }
  10. }
  11.  
  12. // configure the unused sites
  13. var unusedSitesDiv = dojo.byId("unused-sites");
  14.  
  15. var lis = unusedSitesDiv.getElementsByTagName("li");
  16. for (var x=0; x new dojo.dnd.HtmlDragSource(lis[x], "daylist");
  17. }
  18. }

Wire up individual day

A lot of magic is in the following few lines of code. To setup the day, we take the day list that was passed to it via enableSiteMoving(), and then we use the dojo.event API for the first time.

This API allows you to connect, and intercept methods on DOM nodes, AND on any JavaScript function too. This is where the AOP-ness comes in. If you wanted to log every method call, you could grab the function objects and connect() on it with the code required to run each time to log.

In our case we want to connect the TARGETS of a drag and drop. When you drop the site on the day list, we configure the onDrop() function to do its work. When the site is dropped we use the Dojo Effects package to wipe-in the moved site, we check to see if this day was previously empty, and delete the comment “empty…” if it is, and then we call the Ajax function remoteMoveSiteOntop() to tell the server about the move, passing in the id of the nodes that have moved.

We chose special IDs for the nodes in the list. The format is “siteid-occurance”, and allows us to know on the backend exactly what is being shoved around.

javascript
< view plain text >
  1. function setupDayForDND(dl) {
  2. var dt = new dojo.dnd.HtmlDropTarget(dl, ["daylist"]);
  3. dojo.event.connect(dt, "onDrop", function(e) {
  4. dojo.fx.wipeIn(e.dragObject.domNode.id, 300);
  5. checkEmptyDay(dl.id.substring(3));
  6. remoteMoveSiteOntop(e.dragObject.domNode.id, e.target.id);
  7. });
  8. }

onLoad

When you want to load something into the browser, after the DOM is loaded up, you normally set the onLoad handle via:

Or:

javascript
< view plain text >
  1. window.onload = function() { … }

This works in some cases, but what if more than one library uses this approach? You get collisions, and the last library to setup the window.onload handler wins. The code that was in that handler will never be called.

To get around this you can use the handy Dojo helper and use:

javascript
< view plain text >
  1. dojo.addOnLoad(function() {
  2. enableSiteMoving();
  3. });

addOnLoad will make sure that all functions that are added in this way will be queued up, and will ALL fire.

There is another callstyle to addOnLoad: dojo.addOnLoad(object, ‘functionName’). The functionName given will be called on the object that you passed in.

Conclusion

Hopefully you can now grok this Ajax-enabled, drag and drop-able interface that requires no page refreshes to accomplish a lot of edit functionality.

We have scratched the surface of what Dojo offers you, as it is a full toolkit that has it all. I hope that you see the depth of the library, and how we need to take JavaScript development seriously.

If we want to hack up a couple of events, this may not be for you. If you want to be professional grade applications where the ugly work is done for you by smart JavaScripters, give Dojo a try.

Check out the widget toolkit as a great example of markup based development.