Dojo: the JavaScript Toolkit with Industrial-Strength Mojo

 in
Featuring a rich standard library, an extensive collection of turn-key widgets, a unit-testing framework and build tools for minifying your source files, it's no wonder that Dojo is a key part of products from industry giants, such as AOL, Sun Microsystems, BEA and others.
Object-Oriented JavaScript

JavaScript is an object-oriented programming language, but unlike the class-based languages of Java or C++, it uses prototypal inheritance as its mechanism of choice instead of a class-based paradigm. Consequently, mixing properties into object instances as part of a “has-a” relationship is often a far more natural pattern than attempting to mimic class-based patterns that espouse an “is-a” relationship. Consider the following example that adds in a collection of properties to an object instance all at once using dojo.mixin:

var obj = {prop1 : "foo"}

/* obj gets passed around and lots of 
   interesting things happen to it */

// now, we need to add in a batch of properties...
dojo.mixin(obj, {
  prop2 : "bar",
  prop3 : "baz",
  prop4 : someOtherObject
});

The dojo.extend function works much like dojo.mixin except that it manipulates a constructor function's prototype instead of the specific object instances.

Of course, there are some design patterns that do lend themselves to inheritance hierarchies, and the dojo.declare function is your ticket to mimicking class-based inheritance if you find yourself in a predicament that calls for it. You pass it the fully qualified name of the “class” you'd like to create, any ancestors that it should inherit from, and a hash of any additional properties. The dojo.declare function provides a built-in construction function that gets run, so any parameters that are passed in can be handled as needed. Here's a short example demonstrating a Baz class that multiply inherits from both a Foo and a Bar class:

//create an lj.Foo that doesn't have any ancestors
dojo.declare("lj.Foo", null, 
{
  /* custom properties go here */
  _name : null,
  constructor : function(name) {
    this._name = name; 
  },
  talk : function() {alert("I'm "+this._name);},
  customFooMethod : function() { /* ... */ }
});

//create an lj.Bar that doesn't have any ancestors
dojo.declare("lj.Bar", null, 
{
  /* custom properties go here */
  _name : null,
  constructor : function(name) {
    this._name = name; 
  },
  talk : function() {alert("I'm "+this._name);},
  customBarMethod : function() { /* ... */ }
});

//create an lj.Baz that multiply inherits
dojo.declare("lj.Baz", [lj.Foo, lj.Bar], 
{
  /* custom properties go here */
  _name : null,
  constructor : function(name) {
    this._name = name; 
  },
  talk : function() {alert("I'm "+this._name);},
  customBazMethod : function() { /* ... */ }
});

//parameters get passed into the special constructor function
bartyBaz = new lj.Baz("barty"); 

When each of the dojo.declare statements is encountered, internal processing leaves a function in memory that can be readily used to instantiate a specific object instance with the new operator—just like plain-old JavaScript works. In fact, the bartyBaz object is one such instantiation. It inherits the customFooMethod and customBarMethod from ancestors, but provides its own talk method. In the event that it had not provided its own talk method, the last one that was mixed in from the ancestors would have prevailed. In this particular case, the ancestors were [lj.Foo, lj.Bar], so the last mixed in ancestor would have been lj.Bar. If defined, all classes created with dojo.declare have their parameters passed a special constructor function that can be used for initialization or preprocessing.

Server Communication

No discussion of a JavaScript toolkit would be complete without a mention of the AJAX and server-side communication facilities that are available. Dojo's support for server-side communication via the XMLHttpRequest (XHR) object is quite rich, and the dojo.xhrGet function is the most logical starting point, because it is the most commonly used variant. As you might have suspected, it performs a GET request to the server. Unless you configure it otherwise, the request is asynchronous and the return type is interpreted as text. Here's an example of typical usage:

dojo.xhrGet({
  url : "/foo", //returns {"foo" : "bar"}
  handleAs : "json", // interpret the response as JSON vs text
  load : function(response, ioArgs) {
    /* success! treat response.foo just like a 
       normal JavaScript object */
    return response;
  },
  error : function(response, ioArgs) {
    /* be prepared to handle any errors that occur here */
    return response;
  }
});

A point wasn't made of it, but the reason that both the load and error function returns the response type is because Dojo's I/O subsystem uses an abstraction called a Deferred to streamline network operations. The Deferred implementation was adapted from MochiKit's implementation (which was, in turn, inspired from Python's Twisted networking engine). The overarching concept behind a Deferred is that it provides a uniform interface that drastically simplifies I/O by allowing you to handle asynchronous and synchronous requests the very same way. In other words, you can chain callbacks and errbacks arbitrarily onto a Deferred, regardless of whether the network I/O is in flight, threw an Error or completed successfully. Regardless, the callback or errback is handled the same way. In some situations, Deferreds almost create the illusion that you have something like a thread at your disposal.

Here's an updated example of the previous dojo.xhrGet function call that showcases the dojo.Deferred object that is returned:

var d = dojo.xhrGet({
  url : "/foo", //returns {"foo" : "bar"}
  handleAs : "json", // interpret the response as JSON instead

  load : function(response, ioArgs) {
    /* success! treat response.foo just
       like a normal JavaScript object */
    return response; // pass into next callback
  },
  error : function(response, ioArgs) {
    /* be prepared to handle any errors that occur here */
    return response; //pass into next errback
  }
});

/* The xhrGet function just fired. We have no idea if/when
   it will complete in this case since it's asynchronous.
    The value of d, the Deferred, right now is null since it
     was an asynchronous request */

//gets called once load completes
d.addCallback(function(response) {
  /* Just chained on a callback that
     fires after the load handler with the
     same response that load returned. */
     return response;
});

d.addCallback(function(response) {
  /* Just chained on another callback that
     fires after the one we just added */
      return response;
});

d.addErrback(function(response) {
  /* Just added an errback that
     fires after the default error handler */
     return response;
});

/* You get the idea... */

Again, the beauty of a Deferred is that you treat it as somewhat of a black box. It doesn't matter if, when or how it finishes executing. It's all the same to you as the application programmer.

Just so you're aware, sending data to the server with another HTTP method, such as POST or PUT, entails using the very same kind of pattern and works just as predictably with the dojo.xhrPost function. You even can provide a form node so that an entire form is POSTed to the server in one fell swoop or pass in raw data for those times when you need to transfer some information to the server as part of a RESTful (Representational State Transfer-based) architecture. The dojo.toJson function may be especially helpful in serializing JavaScript objects into a properly escaped JSON string, if the protocol is something along the lines of a JSON-RPC system in which the envelope is expected to be JSON in both directions.

______________________

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Shaguf

Anonymous's picture

Hey Guys,
Author of More Servlets and JSP, Marty Hall is coming to Bangalore this April to speak on Choosing an Ajax/JavaScript Toolkit: A Comparison of the Most Popular JavaScript Libraries, Pure Java Ajax: An Overview of GWT 2.0, Integrated Ajax Support in JSF 2.0 and Ajax Support in the Prototype JavaScript Library. You can get more information on developersummit dot com

Webinar
One Click, Universal Protection: Implementing Centralized Security Policies on Linux Systems

As Linux continues to play an ever increasing role in corporate data centers and institutions, ensuring the integrity and protection of these systems must be a priority. With 60% of the world's websites and an increasing share of organization's mission-critical workloads running on Linux, failing to stop malware and other advanced threats on Linux can increasingly impact an organization's reputation and bottom line.

Learn More

Sponsored by Bit9

Webinar
Linux Backup and Recovery Webinar

Most companies incorporate backup procedures for critical data, which can be restored quickly if a loss occurs. However, fewer companies are prepared for catastrophic system failures, in which they lose all data, the entire operating system, applications, settings, patches and more, reducing their system(s) to “bare metal.” After all, before data can be restored to a system, there must be a system to restore it to.

In this one hour webinar, learn how to enhance your existing backup strategies for better disaster recovery preparedness using Storix System Backup Administrator (SBAdmin), a highly flexible bare-metal recovery solution for UNIX and Linux systems.

Learn More

Sponsored by Storix