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

White Paper
Linux Management with Red Hat Satellite: Measuring Business Impact and ROI

Linux has become a key foundation for supporting today's rapidly growing IT environments. Linux is being used to deploy business applications and databases, trading on its reputation as a low-cost operating environment. For many IT organizations, Linux is a mainstay for deploying Web servers and has evolved from handling basic file, print, and utility workloads to running mission-critical applications and databases, physically, virtually, and in the cloud. As Linux grows in importance in terms of value to the business, managing Linux environments to high standards of service quality — availability, security, and performance — becomes an essential requirement for business success.

Learn More

Sponsored by Red Hat

White Paper
Private PaaS for the Agile Enterprise

If you already use virtualized infrastructure, you are well on your way to leveraging the power of the cloud. Virtualization offers the promise of limitless resources, but how do you manage that scalability when your DevOps team doesn’t scale? In today’s hypercompetitive markets, fast results can make a difference between leading the pack vs. obsolescence. Organizations need more benefits from cloud computing than just raw resources. They need agility, flexibility, convenience, ROI, and control.

Stackato private Platform-as-a-Service technology from ActiveState extends your private cloud infrastructure by creating a private PaaS to provide on-demand availability, flexibility, control, and ultimately, faster time-to-market for your enterprise.

Learn More

Sponsored by ActiveState