Other Functions

In a toolkit as comprehensive as Dojo, the designers had to be vigilant about not duplicating functionality. Virtually all facets of the toolkit need low-level functions - like JSON support, array features, node list processing, etc. And because these functions do not exist in standard JavaScript, or are available only in incompatible calls across browsers, Dojo programmers wrote a lot of low-level code themselves.

Luckily for us Dojo users, they expose these low-level functions. Instead of writing a lot of the annoying little JavaScript utility methods ourselves, we can use Dojo base functions instead. These API's are fast, flexible, and well-thought out. And they are consistent with the higher layers of Dojo's design. Can you dig it?

High-Level Data Format Conversion

Dojo relies on JavaScript objects and arrays for most of its internal communication. Unfortunately, the world doesn't work solely in JavaScript and you must convert data formats into JavaScript before Dojo can use them. Specifically:

  • HTML Forms keep data for display and entry for the browser. Its contents live in a DOM tree.
  • URL Query Parameters are used to pass form data to a server via HTTP GET.
  • JSON is a popular XML alternative optimized for performance, and is often used where you can control the web service.

Dojo, being the diplomat that it is, can convert to and from most of these formats. Why not all? Because strictly speaking, the formats are not equivalent. JavaScript and JSON model arrays with ordered indexes, while HTML forms and URL queries model them as unordered collections. To illustrate:

Source format Comments Example
HTML Form The array b is not ordered, so we don't know if 2 or 3 appears first. Therefore, converting from an ordered array like in JavaScript loses information. Converting to JavaScript is impossible unless we make a "guess" on the ordering. Also, the heirarchy is restricted to two levels - the form "x" at the root, and all other data elements below it, with arrays adding at most one level. You cannot nest any further. /* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<form name="x">
   <input type="text" name="a" value="1">
   <input type="text" name="b" value="2">
   <input type="text" name="b" value="3">
</form>
URL Query Same limitations as HTML form. ?a=1&b=2&b=3
JavaScript Allows arbitrary nesting and grouping. Arrays are ordered.
var x = {a: 1, b: [2, 3] };
JSON Same limitations as JavaScript.
{"x": {"a": 1, "b": [2, 3] }}

Given those limitations, here are the dojo conversion functions. All are available in Dojo Base, so no required's are necessary.

Source format To HTML Form To URL Query To JavaScript To JSON
HTML Form N/A dojo.formToQuery() dojo.formToObject() dojo.formToJson()
URL Query None N/A dojo.queryToObject() None
JavaScript None dojo.objectToQuery() N/A dojo.toJson()
JSON None None dojo.fromJson() N/A

Communication Between Threads - dojo.Deferred

JavaScript has no threads, and even if it did, threads are hard.  Deferreds are a way of abstracting non-blocking events, such as the final response to an XMLHttpRequest. Deferreds create a promise to return a response a some point in the future and an easy way to register your interest in receiving that response.

How Does it Work?

Imagine a whiteboard in a math classroom.  One professor knows that the maintenance person for this particular classroom is a math genius.  So the professor writes on the board a question:

"What is the Shortest Proof for Fermat's Last Theorem?  Call x.2591 when you have the answer."

That night, the maintenance worker finds the question on the whiteboard.  It's an interesting problem.  So he continues to mop the floors thinking about it.  (Uhh, does this sound like a movie with Ben Affleck and Matt Damon?  Could be...)

Now suppose students would also like the answer.  (It'd make a helluva term paper!)  They scribble notes underneath like "Also call 555-8244.  Also email mathlunkhead@aol.com," etc.

Finally, 8 nights later, the maintenance worker writes down the answer on the whiteboard and calls x.2591, 555-8244, and emails "mathlunkhead@aol.com".

dojo.Deferred is like the whiteboard.  At least one person has a question that needs answering by some other entity.  In Web 2.0 applications, this is often a server process called by XmlHTTPRequest.  Unfortunately, we don't know when the answer will come back.  We can either call the process synchronously (we wait by the whiteboard for the answer) or asynchronously (we leave the room and asked to be called). 

The email addresses and phone numbers symbolize handlers.  A handler is simply a Javascript function called when the answer is complete.  These are split between callbacks and errbacks which handle normal completion and errors respectively. 

Using Deferreds

   

The most important methods for Deferred users are:    

  • addCallback(handler)
  • addErrback(handler)
  • callback(result)
  • errback(result)

In general, when a function returns a Deferred, users then "fill in" the second half of the contract by registering callbacks and error handlers. You may register as many callback and errback handlers as you like and they will be executed in the order registered when a result is provided. Usually this result is provided as the result of an asynchronous operation. The code "managing" the Deferred (the code that made the promise to provide an answer later) will use the callback() and errback() methods to communicate with registered listeners about the result of the operation. At this time, all registered result handlers are called with the most recent result value.

Deferred callback handlers are treated as a chain, and each item in the chain is required to return a value that will be fed into successive handlers. The most minimal callback may be registered like this:

                var d = new dojo.Deferred();
                d.addCallback(function(result){ return result; });

Perhaps the most common mistake when first using Deferreds is to forget to return a value (in most cases, the value you were passed).

The Deferred also keeps track of its current status, which may be one of three states:

  • -1: no value yet (initial condition)
  • 0: success
  • 1: error

A Deferred will be in the error state if one of the following three conditions are met:

  1.  The result given to callback or errback is an object whose class or superclass is Error, e.g. "instanceof Error" is true.,
  2.  The previous callback or errback raised an exception while executing
  3.  The previous callback or errback returned a value whose class or superclass is "Error"

Otherwise, the Deferred will be in the success state. The state of the Deferred determines the next element in the callback sequence to run.

When a callback or errback occurs with the example deferred chain, something equivalent to the following will happen (imagine that exceptions are caught and returned):

                 d.callback(result) or d.errback(result)
                if(!(result instanceof Error)){
                    result = myCallback(result);
                }
                if(result instanceof Error){
                    result = myErrback(result);
                }
                result = myBoth(result);
                if(result instanceof Error){
                    result = myErrback(result);
                }else{
                    result = myCallback(result);
                
}

The result is then stored away in case another step is added to the callback sequence. Since the Deferred already has a value available, any new callbacks added will be called immediately.

There are two other "advanced" details about this implementation that are useful:

  • Callbacks are allowed to return Deferred instances themselves, so you can build complicated sequences of events with ease.
  • The creator of the Deferred may specify a canceller.  The canceller is a function that will be called if Deferred.cancel is called before the Deferred fires. You can use this to implement clean aborting of an XMLHttpRequest, etc. Note that cancel will fire the deferred with a CancelledError (unless your canceller returns another kind of error), so the errbacks should be prepared to handle that error for cancellable Deferreds.

Deferred objects are often used when making code asynchronous. It may be easiest to write functions in a synchronous manner and then split code using a deferred to trigger a response to a long-lived operation. For example, instead of register a callback function to denote when a rendering operation completes, the function can simply return a deferred:

callback style:
function renderLotsOfData(data, callback) {
   var success = false;
   try{
      for(var x in data) {
         renderDataitem(data[x]);
     }
     success = true;
   }catch(e){ }
   if(callback){
      callback(success);
   }
}
using callback style:
renderLotsOfData(someDataObj, function(success){
   //handles success or failure
   if (!success){
      promptUserToRecover();
   }
});

NOTE: there's no way to add another callback here!!

Using a Deferred doesn't simplify the sending code any, but it provides a standard interface for callers and senders alike, providing both with a simple way to service multiple callbacks for an operation and freeing both sides from worrying about details such as "did this get called already?". With Deferreds, new callbacks can be added at any time.

Deferred style:
function renderLotsOfData(data, callback){
   var d = new dojo.Deferred();
   try {
      for (var x in data) {
         renderDataitem(data[x]);
      }
      d.callback(true);
   } catch(e) { 
      d.errback(new Error("rendering failed"));
   }
   return d;
}
using Deferred style
renderLotsOfData(someDataObj).addErrback(function(){
   promptUserToRecover();
});
      

NOTE: addErrback and addCallback both return the Deferred again, so we could chain adding callbacks or save the deferred for later should we need to be notified again.

In this example, renderLotsOfData is syncrhonous and so both versions are pretty artificial. Putting the data display on a timeout helps show why Deferreds rock:

Deferred style and async func:

    
                 Deferred style 
                function renderLotsOfData(data, callback){
                    var d = new dojo.Deferred();
                    setTimeout(function(){
                        try{
                            for(var x in data){
                                renderDataitem(data[x]);
                            }
                            d.callback(true);
                        }catch(e){ 
                            d.errback(new Error("rendering failed"));
                        }
                    }, 100);
                    return d;
                }
                     using Deferred style
                renderLotsOfData(someDataObj).addErrback(function(){
                    promptUserToRecover();
                });
                Note that the caller doesn't have to change his code at all to             handle the asynchronous case.  

Author: Alex,  with Good Will Hunting analogy provided by Craig Riecke (and Ben and Matt)

Cookies

The dojo.cookie module has one method, dojo.cookie, a one-stop shop for all your HTML cookie needs. /* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<head>
<title>Cookie Demo</title>
    <script type="text/javascript"
                src="http://o.aolcdn.com/dojo/1.0.0/dojo/dojo.xd.js">
</script>
    <script type="text/javascript">
        dojo.require("dojo.cookie");
        dojo.addOnLoad(function() {
            // Calling dojo.cookie with two parameters sets a cookie
            dojo.cookie("online-book.cookie","oatmeal");
                    
           // With one parameter, reads the cookie
           console.debug("cookie is "+dojo.cookie("online-book.cookie"));
                    
           // Third parameter is an object with other cookie options.  Here, setting the expires
           // property to -1 deletes the cookie
           dojo.cookie("online-book.cookie","Value Doesn't matter",{expires: -1});
           console.debug("cookie should now be null: "+dojo.cookie("online-book.cookie"));
        });        
    </script>
</head>

Rumor has is that Brad Neuberg calls cookies "Dojo Offline Lite."

Array Functions

Along with dojo.forEach, Dojo provides other array functions which mimic JavaScript 1.6 functionality. And that's good news for IE users, who don't have the luxury of JavaScript 1.6 yet.

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
            "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Array Examples</title>
        <script type="text/javascript"
            src="http://o.aolcdn.com/dojo/1.0.0/dojo/dojo.xd.js">
</script>
        <script type="text/javascript">
       dojo.addOnLoad(function(){
           // JavaScript is so important, it's listed twice!
           var webLanguages = ["JavaScript", "PHP", "Rails", "JavaScript", "JSP", "ASP.NET"];
           
           // indexOf and lastIndexOf are like the String functions
           console.debug(dojo.indexOf(webLanguages, "JavaScript")); // = 0
           console.debug(dojo.indexOf(webLanguages, "Javascript")); // = -1 - case sensitive!
           console.debug(dojo.lastIndexOf(webLanguages, "JavaScript")); // = 3
           
           // Apply function to each element and return a matching output array.
           console.dir(
               dojo.map(webLanguages,
                  function(elem) { return elem.length; }
               )
           );   // Returns [10, 3, 5, 10, 3, 7]
           
           // Filter applies a boolean function and returns an array of elements that match
           console.dir(
               dojo.filter(webLanguages,
                  function(elem) { return elem.substring(0,1) == 'J'; }
               )
           );   // Returns ["JavaScript", "JavaScript", "JSP"]
           
           // 'Every' and 'some' applies a boolean function to each element and returns true
           // if function is true for all (every) or at least one (some)
           console.debug(
               dojo.every(webLanguages,
                  function(elem) { return elem > 'BASIC'; }
               )
           );   // Returns false because ASP.NET is less than
                // BASIC (technical arguments aside...)
           console.debug(
               dojo.some(webLanguages,
                  function(elem) { return elem.length < 4; }
               )
           );   // Returns true because length of JSP and PHP are less than  4
       });
     </script>

</head>
</html>

Checking Object Types

JavaScript's "instanceof" operator checks an object for class information. But there are nuances that make it difficult in practice. For example, a DOM NodeList can be accessed like an array with [] subscripts, but "instanceof Array" applied to a NodeList returns false nonetheless. These Dojo base functions shield those nasty details from you.

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
            "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Type Checking Demo</title>
        <script type="text/javascript"
                    src="http://o.aolcdn.com/dojo/1.0.0/dojo/dojo.xd.js">
</script>
        <script type="text/javascript">
            //
            dojo.addOnLoad(function() {
                var allScripts = dojo.query("script");
                var iceCreamObject = { flavor: "vanilla", scoops: 2};
                
                    // NodeLists are like arrays
                    console.debug(dojo.isArray(allScripts));  // True
                    // An arbitrary object.  Arrays are objects too.
                    console.debug(dojo.isObject(iceCreamObject));  // True
                    console.debug(dojo.isObject(allScripts));  // True
                    
                    console.debug(dojo.isFunction(dojo.query)); // True
                    console.debug(dojo.isFunction(dojo)); // False
                    
                    //  This next example hurts my head!
                    console.debug(dojo.isFunction(dojo.isFunction));  // True, but weird
                    
                    // This is a common idiom for doing "nullable" arguments in the Dojo source code.
                    // Here, you can pass either myFn(var, function, var) or myFn(var, var);
                    var myFn = function() {
                       if (dojo.isFunction(arguments[1])) {
                          return arguments[1](arguments[0], arguments[2]);
                       } else {
                          return arguments[0]+arguments[1];
                       }
                    }
                    console.debug(myFn(1,2));  // 3
                    console.debug(myFn(1,Math.max,2));  // 2 = Math.max(1,2);
        });        
            
    </script>
</head>
</html>

Date Functions

The date functions in Dojo are like those little wrapped mints on your pillow. Surprising, little and fabulous!

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
            "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Date Examples</title>
        <script type="text/javascript"
                src="http://o.aolcdn.com/dojo/1.0.0/dojo/dojo.xd.js">
</script>
        <script type="text/javascript">
           dojo.require("dojo.date");
           dojo.require("dojo.date.stamp");
          
       dojo.addOnLoad(function(){
           // We'll demonstrate these functions on today's date, so you'll have to think
           // for a minute to verify the functions work!
           var today = new Date();
           console.debug("today is "+today);
           
           // Is this year a leap year?
           console.debug("leap year: "+dojo.date.isLeapYear(today));
           
           // How many days in this month?
           console.debug("days in month: "+dojo.date.getDaysInMonth(today));
           
           // Convert to/from ISO format
           var dojo0_9Release = dojo.date.stamp.fromISOString("2007-08-20");
           
           // Do some arithmetic. 
           console.debug(
               dojo.date.difference(dojo0_9Release, today)+
               " days since Dojo 0.9 release"
           );
           
           var oneYearAnniversary = dojo.date.add(dojo0_9Release,"year",1);
           var isOneYearYet = dojo.date.compare(oneYearAnniversary, today);
           if (isOneYearYet > 0) {
                   console.debug(
                       dojo.date.difference(today, oneYearAnniversary)+
                       " days until one year anniversary of 0.9 release"
                   );
               }
           
       });
   </script>
</head>
</html>

String Functions

Dojo includes some string operations. Here are a few examples:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
            "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Array Examples</title>
        <script type="text/javascript"
                    src="http://o.aolcdn.com/dojo/1.0.0/dojo/dojo.xd.js">
</script>
        <script type="text/javascript">
           dojo.require("dojo.string");
          
       dojo.addOnLoad(function(){
           // Pads * onto the left hand side
           console.debug(dojo.string.pad("9.99",10,"*"));
           
           // With true on the end, pads on the right
           console.debug(dojo.string.pad("Ninety-Nine and 75/100",25,"-",true));
           
           // Dojo has two versions of trim, functionally equivalent.  The first
           // is in base so you don't have to dojo.require anything.
           console.debug("[" + dojo.trim("    spaced    ") + "]");
           
           // The second is dojo.string you need to dojo.require("dojo.string"), but
           // it's faster.
           console.debug("[" + dojo.string.trim("    spaced    ") + "]");
           
           // Substitute does simple ${var} template substitution, much like you see in
           // _Templated widgets.  You can use it with arrays:
           console.debug(
               dojo.string.substitute(
                   "${2}ft ${0} cable - ${1}",
                   [ "red", "fiber", 7]
               )
           );
           
           // Or, even better, with objects:
           console.debug(
               dojo.string.substitute(
                   "${length}ft ${color} cable - ${media}",
                   {color: "red", media: "fiber", length: 7}
               )
           );
           
           // Even nested objects, using dot notation
           console.debug(
               dojo.string.substitute(
                   "Ooops, spilled ${condiment.ketchup}",
                   {burger: "boca",
                       condiment: { ketchup: "heinz", mustard: "french's"},
                    bun: "wonder"}
              )
           );
       });
   </script>
</head>
</html>