Chapter 2. Language and Browser Utilities

This chapter formally introduces the language utilities that you'll find in Base. The language utilities are designed to streamline some of the most commonly worked-around problems in JavaScript programming, and they're designed to be ultra-portable and highly optimized. Regardless of whether you use anything else in the entire toolkit, the constructs presented in this chapter are worth a hard look because they provide augmentation that is difficult to imagine living without once you've gotten used to using them. Manipulating arrays, cloning nodes, adding and removing classes, and calculating margin and content boxes for DOM nodes are among the topics included in this chapter.

Looking Up DOM Nodes

The previous chapter introduced dojo.byId, a toolkit-specific mechanism for looking up DOM nodes in a manner that is more portable and predictable than document.getElementById. Although dojo.byId absolutely pervades Dojo development, there is little value in repeating the previous discussion from Chapter 1; refer back to the previous chapter for a detailed outline involving some of the issues with document.getElementById and how dojo.byId alleviates them. As a reminder, though, here's the full API call for dojo.byId :

dojo.byId(/*String*/ id | /*DomNode*/ node, /*DomNode*/doc)  // Returns a DOM Node

Example 2-1 lists some other common use patterns.

Example 2-1. Quick reminder about dojo.byId
var foo = dojo.byId("foo"); //returns the node with id=foo if one exists
dojo.byId(foo).innerHTML="bar"; //the lookup is a no-op since foo is
                         //a node; then sets innerHTML to "bar"
var bar = dojo.byId("bar", baz); //returns the node with id=bar in document
                         //referenced by baz if one exists

Type Checking

In a language with dynamic typing like JavaScript, it's often necessary (and a very good idea) to test the type of variable before performing any operations on it. Although it might not seem like much to test for the type of a variable, it isn't always a freebie, and in practice can simply result in annoyances and bugs because of subtle differences. Base provides a few handy functions to simplify the nuances entailed. Like the other issues we've touched on so far, there are subtleties amongst various browsers involving some of the finer points. The following list summarizes:

isString(/*Any*/ value)

Returns true if value is a String.

isArray(/*Any*/ value)

Returns true if value is an Array.

isFunction(/*Any*/ value)

Returns true if value is a Function.

isObject(/*Any*/ value)

Returns true if value is an Object (including an Array and Function ) or null.

isArrayLike(/*Any*/ value)

Returns true if value is an Array but also allows for more permissive possibilities. For example, the built-in arguments value that can be accessed from within a Function object is especially an oddball in that it does not support built-in methods such as push ; however, it is array-like in that it is a list of values that can be indexed.

isAlien(/*Any*/ value)

Returns true if value is a built-in function or native function such as an ActiveX component but does not respect the normal checks such as the instanceof Function.

Duck Typing

A concept commonly involved in dynamic programming languages like Python and JavaScript called duck typing provides a common thread for many of the functions just introduced. Duck typing is based upon the saying that if it walks like a duck and quacks like a duck, then it's a duck. Basically, what that means is that if a particular data member exhibits the minimal necessary properties to be a particular data type, then that's good enough to assume that it is that particular data type.

For example, the built-in arguments member qualifying as an array via the isArrayLike function hopefully ties this theme together. When you consider the inherent dynamism in a language that does not require you to declare a particular variable to always be a particular data type (dynamic binding), duck typing is a great vehicle to inspect the type of an object when necessary.

For example, invoking the typeof operator on an ordinary array such as [] returns object while Base's isArray function performs some duck type checks behind the scenes and returns true for an array such as [].

Duck typing is a fundamental programming concept in JavaScript and much of the toolkit, so this discussion is more practical to your day-to-day programming than you might imagine at first glance.

The bottom line is that Base's type checking functions can save you time and spare you from nonintuitive browser inconsistencies, so use them well, and use them often.

String Utilities

Trimming any existing whitespace from a string is an increasingly common operation. The next time you need to do this, use Base's trim function instead of writing your own.

There can be subtle performance issues with even the seemingly most trivial utility functions, and using the toolkit provides you with the benefits and collective knowledge of a community that has given careful consideration to such issues.

Here's an example of trim at work:

var s = "  this is a value with whitespace padding each side    ";
s = dojo.trim(s); //"this is a value with whitespace padding each side"

Core's string module also includes a few other useful string functions. Each of these examples assumes that you have already fetched the dojo.string module via a dojo.require statement.

dojo.string.pad

Pads a string value and guarantees that it will exactly fill a particular number of characters. By default, padding fills in on the left. An optional parameter causes padding to fill in from the right:

dojo.string.pad("", 5); // "00000"
dojo.string.pad("", 5, " "); // "     "
dojo.string.pad("0", 5, "1"); // "11110"
dojo.string.pad("0", 5, "1", true); // "01111"
dojo.string.substitute

Provides parameterized substitution on a string, optionally allowing a transform function and/or another object to supply context:

//Returns "Jack and Jill went up a hill."
dojo.string.substitute("${0} and ${1} went up a hill.", ["Jack", "Jill"]);

//Returns "Jack and Jill went up a hill."
dojo.string.substitute("${person1} and ${person2} went up a hill.", {person1
: "Jack", person2: "Jill"});
//"*Jack* and *Jill* went up a hill."
dojo.string.substitute("${0} and ${1} went up a hill.", ["Jack", "Jill"],
function(x) {
  return "*"+x+"*";
});
dojo.string.trim

At the cost of a little more size than Base's implementation, Core's string module provides a slightly more efficient version of trim that can be used when performance really matters:

dojo.string.trim( /* your string value */);

Array Processing

Arrays are one of the most fundamental data structures in any imperative programming language, including JavaScript. Unfortunately, however, standardized array operations are not supported by all mainstream browsers, and as long as that is the case, it's immensely helpful to have a toolkit that protects you from the bare metal. For that matter, even if the next version of each major browser supported arrays in a completely uniform manner, there would still be way too many people out there using older browsers to begin thinking about going back to the bare metal anytime soon.

You may find it interesting that the various language tools have been optimized for performance, providing wrapped usage of the native Array implementations wherever possible, but emulating functionality for browsers like IE when it is missing.

Fortunately, Dojo strives to keep up with Mozilla's feature rich implementation of the Array object (http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference). As long as you have the toolkit with you, you'll never be caught defenseless again. And in case you have already forgotten from our discussion of dojo.byId in Chapter 1 that you really can't take much for granted in the current browser ecosystem, the next section should reinvigorate your enthusiasm and might even surprise you.

Finding Locations of Elements

Two very routine array operations involve finding the index of an element, which is really one and the same as determining if an element exists at all. Base facilitates this process with two self-explanatory operations, dojo.indexOf and dojo.lastIndexOf. Each of these functions returns an integer that provides the index of the element if it exists; the value -1 is returned if the element was not found at all. These function signatures also include an additional parameter that indicates the value that should be used as an initial location in case you don't want to start from the very beginning or end of the array. The signature is the same for each function:

dojo.indexOf(/*Array*/ array, /*Any*/ value, /*Integer?*/ fromIndex)
//returns Integer

dojo.lastIndexOf(/*Array*/ array, /*Any*/ value, /*Integer?*/ fromIndex)
//returns Integer

If you've been primarily developing with Firefox for a while, you may be surprised to learn that native Array objects in IE6 and IE7 do not even support the indexOf method. Unfortunately, this kind of semantic misunderstanding about something that may seem so obvious can be one of the hardest kinds of bugs to track down.

The following code snippet illustrates some basic usage of these methods:

var foo = [1,2,3];
var bar = [4,5,6,5,6];
var baz = [1,2,3];

dojo.indexOf([foo, bar], baz); // -1
dojo.indexOf(foo, 3); // 2
dojo.indexOf(bar, 6, 2); // 2
dojo.indexOf(bar, 6, 3); // 4

dojo.lastIndexOf(bar, 6); // 4

A more subtle point about these methods is that they perform shallow comparisons, which in the case of complex data types like Array, means that the comparison is by reference. The following snippet clarifies with a concrete example:

bop = [4,5,6,5,6, foo]; // bop contains a nested Array
dojo.indexOf(bop, foo); //5, because (a reference to) foo is contained in bop
dojo.indexOf(bop, [1,2,3]); //-1, because foo is not the same object as [1,2,3]

Testing Elements for a Condition

It is often the case that you may be interested in knowing if each element of an array meets a particular condition, or if any element of an array meets a particular condition. Base provides the every and some functions for performing this kind of testing. The input parameters are the array, a function that each element of the array is passed into, and an optional parameter that can supply the context (this ) for the function:

dojo.every([2,4,6], function (x) { return x % 2 == 0 }); //true
dojo.every([2,4,6,7], function (x) { return x % 2 == 0 }); //false

dojo.some([3,5,7], function f(x) { return x % 2 == 0 }); //false
dojo.some([3,5,7,8], function f(x) { return x % 2 == 0 }); //true

Iterating Over Elements

The forEach function passes each element of the array into a function that takes up to three parameters and does not return any value at all. The first parameter is the current element of the array being iterated over, the second (optional) parameter is the index of the current element within the array, and the third (optional) parameter is the entire array itself. The forEach function is generally used to iterate over each element of an array as an ordinary for loop. Here's the signature:

dojo.forEach(/*Array*/ array, /*Function*/ function) // No return value

In its simplest form forEach works like so:

dojo.forEach([1,2,3], function(x) {
  console.log(x);
});

Some obvious benefits of forEach is that it introduces less clutter than explicitly introducing a for loop and requiring you to manage a counter variable and also allows you to introduce the Array variable inline. However, perhaps the most important thing that it does is leverage the closure provided by the function as the second parameter to protect the immediate context from the counter variable and other variables that may be introduced in the loop's block from persisting. Like other utility functions, forEach provides an optional parameter that can supply the context for the inline functions.

To illustrate how forEach can save you from unexpected consequences, consider the following snippet of code:

var nodes = getSomeNodes(  );

for(var x=0; x<nodes.length; x++){
    nodes[x].onclick = function(  ){
        console.debug("clicked:", x);
    }
}

Which value of "x" would you expect here? Since the enclosure is over the lexical variable x and not the value of x, all calls get the last value. forEach gets us out of this handily by creating a new lexical scope. This variation illustrates how to iterate over the array and produce the expected value:

var nodes = getSomeNodes(  );
var idx = 0;
dojo.forEach(nodes, function(node, idx){
    node.onclick = function(  ){
        console.debug("clicked:", idx);
    }
});

Transforming Elements

While the map and filter functions have the same function signature as forEach, they're very different in that they apply some custom logic to each element of the array and return another array without modifying the original one.

While you could technically modify the original array through the custom map and filter functions, it's generally expected that map and filter will be free of side effects. In other words, introduce side effects with a lot of discretion and an explicit comment saying as much.

As programmers from functional programming languages (or even programming languages with functional extensions like Python) know all too well, map and filter grow on you quickly because they provide so much functionality with such concise syntax.

The map function might almost sound mysterious if you haven't encountered it before; it's actually named self-descriptively because it builds a mapping from the array you give it via a transform function. The following example illustrates:

var z = dojo.map([2,3,4], function(x) {
  return x + 1
}); //returns [3,4,5]

For comparison purposes, consider how you might compute the value for z in the example above without the map function:

var a = [2,3,4];
var z = [];
for (var i=0; i < a.length; i++) {
  z.push(a[i] +1);
}

Like forEach, one of the benefits of using map directly is that the overall expression is clearer, resulting in more maintainable code. You also get the same kind of anonymous function benefit in that a closure surrounds the code block, whereas variables introduced via intermediate calculations would pollute the context without the closure.

The filter function is also a self-descriptive function in that it filters an array according to a function's criteria. Here it is at work:

dojo.filter([2,3,4], function(x) {
  return x % 2 == 0
}); //returns [2,4]

Implementing a block of equivalent code is relatively simple but does require more bookkeeping and clutter—and more opportunity for typos and bugs:

var a = [2,3,4];
var z = [];
for (var i=0; i < a.length; i++) {
  if (a[i] % 2 == 0)
    z.push(a[i]);
}

Like the other array functions provided by Base, you can also provide additional parameters that supply context for or map or filter if you need them:

function someContext(  ) { this.y = 2; }
var context = new someContext;
dojo.filter([2,3,4], function(x) {return x % this.y==0}, context); //returns [2,4]

String-As-Function Style Arguments

Base also provides the ability to create the shorthand "string-as-function" type arguments for the forEach, map, filter, every, and some functions. In general, this approach is less verbose than writing a function wrapper and is especially handy for really simple cases where you're doing a quick transform. Basically, you just provide a string value with the function body in it versus the entire function. Three special keywords have special context if they appear in the string:

item

Provides a reference to the item that is currently being processed

array

Provides a reference to the entire array that is being processed

index

Provides a reference to the index of the item that is currently being processed

Consider the following example, which demonstrates two equivalent approaches for achieving the same end:

var a = new Array(1,2,3,...);

//A lot of extra typing for very little purpose
a.forEach(function(x) {console.log(x);}); //approach one

//A lot less typing so that you can get work done quickly
a.forEach("console.log(item)"); //approach two

Using the shortened string-as-function approach to array-like methods can make your code more concise, but it may make it difficult to track down bugs, so use discretion. For example, consider the following variation of the previous code snippet:

var a = new Array(1,2,3,...);
a.forEach("console.log(items)"); //oops...extra "s" on items

Because there's an extra "s" on the special term item, it won't act as the iterator anymore, effectively rendering the forEach method as a no-op. Unless you have an especially good eye for tracking down these types of misspellings, this could cost you debugging time.

Managing Source Code with Modules

If you've programmed for any amount of time, you've been exposed to the concept of grouping related pieces of code into related blocks, whether they be called libraries, packages, or modules, and pulling in these resources when you need them via a mechanism like an import statement or a #include preprocessor directive. Dojo's official means of accomplishing the same kind of concept is via dojo.provide and dojo.require, respectively.

In Dojo parlance, reusable chunks of code are called resources and collections of related resources are grouped into what are known as modules. Base provides two incredibly simple constructs for importing modules and resources: dojo.require and dojo.provide. In short, you include a dojo.provide statement as the first line of a file that you want to make available for a dojo.require statement to pull into a page. As it turns out, dojo.require is a lot more than just a placeholder like a SCRIPT tag; it takes care of mapping a module to a particular location on disk, fetching the code, and caching modules and resources that have previously been dojo.require d. Given that each dojo.require statement incurs at least one round trip call to the server if the resource is not already loaded, the caching can turn out to be a tremendous optimization; even the caching that you gain from requiring a resource one time and ensuring it is available locally from that point forward is a great optimization.

Motivation for Managing the Mayhem

For anything but the smallest of projects, the benefits of using this approach are irrefutable. The ease of maintenance and simplicity gained in extending or embedding code in multiple places is a key enabler to getting work done quickly and effectively. As obvious as it may sound, importing code in the manner just described hasn't always been obvious to web developers, and many web projects have turned into difficult-to-maintain monstrosities because of improper source code management in the implementation. For example, a typical workflow has been to take a JavaScript file that's in a static directory of a web server and to insert it into the page using a SCRIPT tag like so:

<script src="/static/someScript.js" type="text/javascript"></script>

OK, there's probably nothing wrong with that for one or two script tags—but what about when you have multiple pages that need the same tools provided by the scripts? Well, then you might need to include those SCRIPT tags in multiple pages; later on down the road you might end up with a lot of loose scripts, and when you have to start manually keeping track of all of them, the situation can get a little bit unwieldy. Sure, back in the day when a few hundred lines of JavaScript might have been all that was in a page, you wouldn't have needed a more robust mechanism for managing resources, but modern applications might include tens of thousands of lines of JavaScript. How can you possibly manage it all without a good tool for fetching on demand and lazy loading?

In addition to mitigating the configuration management nightmare that might otherwise await you, the dojo.provide and dojo.require abstraction also allows the build tools that are provided in Util to do pretty amazing things like condense multiple files (each requiring a synchronous request) into a single file that can be requested and endure much less latency. Without the right abstractions that explicitly define dependences, build tool features that could be freebies suddenly become impossibilities.

A final benefit of a well-defined system like dojo.provide and dojo.require is the ability to manage related resources by clustering them into namespaces so that overall naming collisions are minimized and code is more easily organized and maintained. Even though dojo namespaces are really just hierarchies of nested objects simplified with dot notation, they are nonetheless quite effective for organizing namespaces and accomplish the very same purpose.

In fact, organizing resources by namespace is so common that Dojo provides a Base function called dojo.setObject. This function works by accepting two arguments. The first argument is an object hierarchy that will be automatically created, and the second value is what will be mapped to the hierarchy:

dojo.setObject(/* String */ object, /* Any */ value, /* Object */ context)
//returns Any

Example 2-2 illustrates.

Example 2-2. Namespace organization with dojo.setObject
var foo  = {bar : {baz : {qux : 1}}}; //nest some objects the 'long' way
console.log(foo.bar.baz.qux); //displays 1

//Or you could opt to do it in one crisp statement without matching all of
the braces...
dojo.setObject("foo.bar.baz.qux", 1); //crisper syntax
console.log(foo.bar.baz.qux); //displays 1

//If you supply an optional context, the Object is set relative to the
context instead of
//the global context, dojo.global
var someContext = {};
dojo.setObject("foo.bar.baz.qux", 23, someContext);
console.log(someContext.foo.bar.baz.qux); //displays 23

The use of dojo.setObject is nothing more than syntactic sugar, but it can significantly declutter code and the tediousness of matching braces, etc., whenever you do need it.

The OpenAjax Alliance (http://www.openajax.org ), a consortium of vendors banding together to promote openness and standards amongst advanced web technologies, strongly encourages the practice of using dotted object notation to organize namespaces.

Custom Module Example Over XDomain

A short concrete example is in order to put dojo.require and dojo.provide into perspective. First, consider a simple module that provides a trivial function, such as Fibonacci. In Example 2-3, the resource is also associated with a module. Although grouping resources into modules is not strictly necessary, it is almost always good practice. Throughout this book, you'll commonly see dtdg (for Dojo: The Definitive Guide ) used to denote a generic namespace for modules.

Example 2-3. Defining a simple simple module (dtdg.foo)
/*
   The dojo.provide statement specifies that this .js source file provides a
   dtdg.foo module. Semantically, the dtdg.foo module also provides a namespace for
   functions that are included in the module On disk, this file
   would be named foo.js and be placed inside of a dtdg directory.
*/
dojo.provide("dtdg.foo");

//Note that the function is relative to the module's namespace
dtdg.foo.fibonacci = function(x) {
  if (x < 0)
    throw Exception("Illegal argument");

  if (x <= 1)
    return x;

  return dtdg.foo.fibonacci(x-1) + dtdg.foo.fibonacci(x-2);
}

You will almost always want to group your resources into logical modules and associate them with a namespace. In addition to being a good implementation practice, it also prevents you from inadvertently clobbering symbols in the global namespace as well as preventing anyone else from doing the same to you. After all, that's one of the motivators for using dojo.provide and dojo.require in the first place!

In another page somewhere, you determine that you want to use your dtdg.foo module to amaze the elementary school math junkies. Instead of rewriting your well-tested function from scratch and potentially making a mistake that could lead to embarrassment, you instead decide to reuse it via dojo.require. Example 2-4 shows how you would use a local module in conjunction with the rest of the toolkit being loaded over the CDN. This example assumes that the following HTML file is saved alongside a directory called dtdg that contains the module from Example 2-3.

Example 2-4. Using a local module with XDomain bootstrappping
<html>
    <head>
      <title>Fun With Fibonacci!</title>

      <script
        type="text/javascript"
        src="http://o.aolcdn.com/dojo/1.1/dojo/dojo.xd.js"
        djConfig="baseUrl:'./'">
      </script>

      <script type="text/javascript">
        dojo.registerModulePath("dtdg", "./dtdg");
        dojo.require("dtdg.foo");
        /* at this point, the dojo.require is being satisfied asynchronously
        because we're using an Xdomain build of Dojo. Better wrap any references
        to dtdg.foo in an addOnLoad block */

        dojo.addOnLoad(function(  ) {
            dojo.body(  ).innerHTML = "guess what? fibonacci(5) = " +
dtdg.foo.fibonacci(5);
        });
      </script>

    </head>
    <body>
    </body>
</html>

The key concept to take away from the previous listing is that it's fairly straightforward to dojo.require a resource into the page and then use it. However, there are a few finer points worth highlighting.

For local installations, Dojo looks in the root level directory of the toolkit for modules—but when you're performing XDomain loading, the "real" root directory of the toolkit would be somewhere off on AOL's server. Thus, a special configuration switch, baseUrl is passed into djConfig in order to designate the starting point for looking up local modules—dtdg.foo in this case.

djConfig is simply a means of providing specific configuration parameters to the toolkit. It is covered in the next section, but for now, just roll with it.

The dojo.registerModulePath function simply associates a top-level namespace, its first parameter, to a physical directory that is relative to baseUrl, its second parameter.

Don't forget to take extra care when configuring module paths with dojo.registerModulePath. It is not uncommon to be off by one level in the directory structure if you forget that the relative directory is specified to the dojo.js file—not the root level of the toolkit. Additionally, ending a module path with a trailing forward slash has been known to intermittently cause problems, so you should take care to avoid that practice as well.

Everything that is defined in the custom module is made available via the call to dojo.require. For example, if the dtdg.foo module had contained additional functions or symbols, they would be available after the dojo.require("dtdg.foo") statement. As usual, we didn't reference anything provided by dtdg.foo outside of the addOnLoad block.

There is not necessarily a one-to-one mapping between .js source files and the functions that are dojo.provide d in them, but it is generally enforced as a matter of style. Exceptions include cases where some functions might not be exposed because they are considered private as far as an API is concerned.

You may have also noticed a call to dojo.body( ) in the previous code listing. Essentially, this call is just a shortcut for returning the body of the current document—as opposed to document.body, which is considerably less convenient.

Fibonacci Example with Local Toolkit Installation

For comparison purposes, Example 2-5 shows the very same example, but this time it uses a local installation of Dojo with the dtdg module located at the root level of the toolkit alongside the dojo directory that contains Core so that no reference to baseUrl or a call to registerModulePath is necessary. This convenience is available because Dojo automatically searches for modules in the directory alongside Core, which is a logical and convenient location to maintain them.

Example 2-5. Using dtdg.foo with a local toolkit installation
<html>
    <head>
      <title>Fun With Fibonacci!</title>

      <script
        type="text/javascript"
        src="your/relative/path/from/this/page/to/dojo/dojo.js" >
      </script>

      <script type="text/javascript">
        dojo.require("dtdg.foo");
        /* we use an addOnLoad block even though it's all local as a matter of habit*/
        dojo.addOnLoad(function(  ) {
            dojo.body(  ).innerHTML = "guess what? fibonacci(5) = " +
dtdg.foo.fibonacci(5);
        });
      </script>

    </head>
    <body>
    </body>
</html>

Building a Magic Genie Example Module

As an example of some of the concepts from this chapter, let's build a module. Because life can be so hard at times, a magic genie that can give us answers whenever we need it would be a godsend. (Dojo might simply be pure automation that seems like magic, but genies are real magic.)

To get started building a module, recall that it's a good idea to namespace it. Example 2-6 sticks with the dtdg namespace, which we've been using so far in this book, and associates a Genie resource with it. If you don't already have a local directory called dtdg, go ahead and create one now. Inside of it, open up a new file called Genie.js, where we'll include the magic shown in Example 2-6.

Example 2-6. The implementation for a magic genie module
//always include the dojo.provide statement first thing
dojo.provide("dtdg.Genie");

//set up a namespace for the genie
dtdg.Genie = function(  ) {}

//wire in some predictions, reminiscent of a magic 8 ball
dtdg.Genie.prototype._predictions = [
        "As I see it, yes",
        "Ask again later",
        "Better not tell you now",
        "Cannot predict now",
        "Concentrate and ask again",
        "Don't count on it",
        "It is certain",
        "It is decidedly so",
        "Most likely",
        "My reply is no",
        "My sources say no",
        "Outlook good",
        "Outlook not so good",
        "Reply hazy, try again",
        "Signs point to yes",
        "Very doubtful",
        "Without a doubt",
        "Yes",
        "Yes - definitely",
        "You may rely on it"
    ];

//wire in an initialization function that constructs the interface
dtdg.Genie.prototype.initialize = function(  ) {

    var label = document.createElement("p");
    label.innerHTML = "Ask a question. The genie knows the answer...";

    var question = document.createElement("input");
    question.size = 50;

    var button = document.createElement("button");
    button.innerHTML = "Ask!";
    button.onclick = function(  ) {
        alert(dtdg.Genie.prototype._getPrediction(  ));
        question.value = "";
    }

    var container = document.createElement("div");
    container.appendChild(label);
    container.appendChild(question);
    container.appendChild(button);

    dojo.body(  ).appendChild(container);
}

//wire in the primary function for interaction
dtdg.Genie.prototype._getPrediction = function(  ) {
    //get a number betweeen 0 and 19 and index into predictions
    var idx = Math.round(Math.random(  )*19)
    return this._predictions[idx];
}

Essentially, the listing does nothing more than provide a Function object called dtdg.Genie that exposes one "public" function, initialize.

In Dojo, the convention of prefixing internal members that should be treated as private with a leading underscore is common and will be used throughout this book. It's important to really respect such conventions because "private" members may be quite volatile.

The listing is laden with comments, and from a web development standpoint, the logic should hopefully be easy enough to follow. (If it's not, this would be a great time to review some HTML and JavaScript fundamentals elsewhere before reading on.)

To actually put the magic genie to use, we'll need to modify the basic template, as shown in Example 2-7.

Example 2-7. A web page utilizing the magic genie
<html>
    <head>
        <title>Fun With the Genie!</title>

        <script
          type="text/javascript"
          src="http://o.aolcdn.com/dojo/1.1/dojo/dojo.xd.js">
          djConfig="modulePaths:{dtdg:'./dtdg'},baseUrl:'./'">
        </script>

        <script type="text/javascript">
            // require in the module
            dojo.require("dtdg.Genie");

            // safely reference dtdg.Genie inside of addOnLoad
            dojo.addOnLoad(function(  ) {

                //create an instance
                var g = new dtdg.Genie;

                //fire it up, which takes care of the rest
                g.initialize(  );
            });
        </script>
    </head>
    <body>
    </body>
</html>

This example illustrates the reusability and portability of the dtdg.Genie module. You simply require it into the page and, once it's initialized, it "just works." (And so long as the user doesn't read the source code, it truly remains magical.) A finer point worth clarifying is the use of djConfig to configure Dojo before bootstrapping: the modulePaths is inlined to qualify the location of the module relative to baseUrl, which is defined as the current working directory. Thus, from a physical standpoint, the file structure might look like Figure 2-1.

The way that your module should be laid out on disk
Figure 2-1. The way that your module should be laid out on disk

JavaScript Object Utilities

Three of Base's language utilities facilitate operations you may need to routinely perform on objects: mixin, extend, and clone.

Dojo uses mixin and extend in its dojo.declare implementation, which is the toolkit's mechanism for simulating class-based inheritance. dojo.declare is covered extensively in Chapter 10.

Mixins

JavaScript allows you to approximate lightweight classes by housing a collection of properties and methods inside a constructor function. You may then create object instances of these classes by invoking the constructor function with the new operator. As you might expect, it can sometimes be quite convenient to add additional properties to an object, whether it be to make something dynamic happen on-the-fly or as part of a well crafted design that maximizes code reuse. Either way, mixin provides a compact way of handling the implementation details for you.

In terms of object-oriented design with JavaScript, you'll see mixin used extensively throughout the toolkit to reuse blocks of code.

The API for using the toolkit's mixin functions entails providing an indeterminate number of objects, where the first objects gets the other objects mixed into it:

dojo.mixin(/*Object*/ o, /*Object*/ o, ...) //Returns Object

Here's an example of mixin:

function Man(  ) {
  this.x = 10;
}

function Myth(  ) {
  this.y = 20;
}

function Legend(  ) {
  this.z = 30;
}

var theMan = new Man;
var theMyth = new Myth;
var theLegend = new Legend;

function ManMythLegend(  ) {}
var theManTheMythTheLegend = new ManMythLegend;

//mutate theManTheMythTheLegend by mixing in the three objects
dojo.mixin(theManTheMythTheLegend, theMan, theMyth, theLegend);

Note that all parameters to mixin are actual object instances—not function declarations.

Extending Object Prototypes

Base's extend function works just like mixin except that it adds all properties and methods of the mixins to a constructor function's prototype so that all future instances created with the constructor will automatically include these new properties and methods:

dojo.extend(/*Function*/constructor, /*Object*/props, ... ) //Returns Function

Here's an example:

function Man(  ) {
  this.x = 10;
}

function Myth(  ) {
  this.y = 20;
}

function Legend(  ) {
  this.z = 30;
}

var theMan = new Man;
var theMyth = new Myth;
var theLegend = new Legend;

function ManMythLegend(  ) {}

var theManTheMythTheLegend = new ManMythLegend;

dojo.extend(ManMythLegend, theMan, theMyth, theLegend);

var theTheManTheMythTheLegend = new ManMythLegend;

Thus, the primary difference to remember is that mixin produces a single object instance that is the result of mixing in additional objects while extend actually modifies a function's prototype.

Another great application for extend is the ability to create classes in a more lightweight fashion than would normally be required when wiring everything up via the prototype property of an Object. Using extend in this way is somewhat a matter of style, although the end result is usually more compact. Here's a retake of our magic genie example from Example 2-6 that illustrates:

dojo.provide("dtdg.Genie");

//define the object
dtdg.Genie = function(  ) {}

//and now extend it
dojo.extend(dtdg.Genie, {
    _predictions : [
        "As I see it, yet",
        "Ask again later",
        "Better not tell you now",
        "Cannot predict now",
        "Concentrate and ask again",
        "Don't count on it",
        "It is certain",
        "It is decidedly so",
        "Most likely",
        "My reply is no",
        "My sources say no",
        "Outlook good",
        "Outlook not so good",
        "Reply hazy, try again",
        "Signs point to yes",
        "Very doubtful",
        "Without a doubt",
        "Yes",
        "Yes - definitely",
        "You may rely on it"
    ],

    initialize : function(  ) {
        var label = document.createElement("p");
        label.innerHTML = "Ask a question. The genie knows the answer...";

        var question = document.createElement("input");
        question.size = 50;

        var button = document.createElement("button");
        button.innerHTML = "Ask!";
        button.onclick = function(  ) {
            alert(dtdg.Genie.prototype._getPrediction(  ));
            question.value = "";
        }

        var container = document.createElement("div");
        container.appendChild(label);
        container.appendChild(question);
        container.appendChild(button);

        dojo.body(  ).appendChild(container);
    },

    getPrediction : function(  ) {
        //get a number betweeen 0 and 19 and index into predictions
        var idx = Math.round(Math.random(  )*19)
        return this._predictions[idx];
    }
});

Don't accidentally forget and leave a trailing comma after the final element of the Object, which is quite common when refactoring and massive cut/paste operations occur. While Firefox silently forgives you, it may actually do more harm than good because IE will just outright bust.

Cloning Objects

Although JavaScript performs shallow copies in assignments involving JavaScript objects and DOM nodes, you may often find yourself needing to clone, or perform deep copies, of object hierarchies. Base's clone function is a highly efficient ticket to achieving just that. Consider the following simple example:

function foo(  ) {
    this.bar = "baz";
}

var foo1 = new foo;
var foo2 = foo1; //shallow copy

console.log(foo1.bar);
console.log(foo2.bar);

foo1.bar = "qux"; //changing foo1 also changes foo2

console.log(foo1.bar);  // qux
console.log(foo2.bar);  // qux

foo3 = new foo
foo4 = dojo.clone(foo3); //deep copy

foo3.bar = "qux";

console.log(foo3.bar); // qux
console.log(foo4.bar); // baz

Manipulating Object Context

Although the global window object provides the outermost layer of context for a web application, there may be times when you need to swap out the default context for another one. For example, you may want to persist the exact state of a session when the user exits an application, or you might have a custom execution environment that's already been preconfigured for a particular circumstance. Instead of having code that manually iterates over sets of conditions to configure the environment each time, you might opt to use Base's window facilities to swap out the existing context for another one.

The following function allows you to change out the dojo.global object and dojo.doc at will. Note that while dojo.doc is simply a reference to the window.document by default, it does provide a uniform mechanism for identifying the context's current document, which again can be quite useful for situations in which managed object contexts are involved. dojo.body() is a shortcut for obtaining the body of a document.

The body element is not explicitly defined for a strict XHTML document and some other documents you may encounter.

At a minimum, you should be aware of the following three functions from Base for manipulating context:

dojo.doc //Returns Document
dojo.body(  ) //Returns DomNode
dojo.setContext(/*Object*/globalObject, /*Document*/globalDocument)

Finally, in the spirit of flexibility, Base also provides two functions that allow you to evaluate a function in the context of either a different dojo.global environment or a different dojo.doc than the one that currently exists:

dojo.withGlobal(/*Object*/globalObject, /*Function*/callback, /*Object*/thisObject,
/*Array*/callbackArgs)
dojo.withDoc(/*Object*/documentObject, /*Function*/callback, /*Object*/thisObject,
/*Array*/callbackArgs)

It should be noted that using a Dojo function to operate extensively in another document or window is not a well-tested usage of the toolkit, so you may encounter support issues if going down that route. Standard usage normally entails loading Dojo into every document where you plan to use it. For lightweight operations, however, the context functions discussed in this section should work fine.

Partially Applying Parameters

Base's partial function allows you to partially apply parameters to a function as they become available and perform final execution of the function at a later time. Or, you might just need to apply all of the parameters at once and then pass around a function reference that can be executed—which is a little less messy than passing around the function and the parameters all at the same time and a pattern that is commonly used throughout the toolkit. Here's the API for partial:

dojo.partial(*/Function|String*/func /*, arg1, ..., argN*/) //Returns Any

To illustrate, here's a simple example of partial being used to partially apply parameters to a function that adds a series of numbers:

function addThree(x,y,z) { console.log(x+y+z);}

//apply two args now
f = dojo.partial(addThree, 100,10);

//apply the last one later
f = dojo.partial(f, 1);

//now evaluate
f(  ); //111

Hitching an Object to a Specific Context

Base's hitch function is quite similar to partial in that it allows you partially apply parameters to a function, but it also has the interesting twist that it also allows you to permanently bind (or hitch) a function to a specific execution context, regardless of whatever the final execution context becomes. This can be especially handy for situations in which you have callback functions and will never fully know what the final execution context (and thus, this ) will be. Here's the API:

dojo.hitch(/*Object*/scope, /*Function||String*/method /*, arg1, ... , argN*/)
//Returns Any

And to illustrate, here's a simple example that rewires an Object method:

var foo = {
    name : "Foo",
    greet : function(  ) {
        console.log("Hi, I'm", this.name);
    }
}

var bar = {
    name : "Bar",
    greet : function(  ) {
        console.log("Hi, I'm", this.name);
    }
}

foo.greet(  ); //Hi, I'm Foo
bar.greet(  ); //Hi, I'm Bar

/* Bind bar's greet method to another context */
bar.greet = dojo.hitch(foo, "greet");

/ * Bar is now an impersonator */
bar.greet(  ); // Hi, I'm Foo

To be clear, because the greet function explicitly references a context with this, the following code would not have successfully rewired the greet method:

bar.greet = foo.greet;
bar.greet(  );

You might find it interesting to know that with respect to implementation, hitch provides the basis for partial and calling hitch with null as the scope is the functional equivalent of calling partial.

The section "Hitching Up Callbacks" in Chapter 4 provides an example of using hitch to manage the context for data that is used within an asynchronous callback function—one of its most common use cases because the callback function has a different this context than the containing Object.

Delegation and Inheritance

Delegation is a programming pattern that entails one object relying on another object to perform an action, instead of implementing that action itself. Delegation is at the very heart of JavaScript as a prototype-based language because it is the pattern through which object properties are resolved in the prototype chain. Although delegation is at the very crux of JavaScript's inheritance implementation, which relies on the prototype chain being resolved at runtime, delegation as a pattern is very different from inheritance in true class-based programming languages like Java and C++, which often (but not always) resolve class hierarchies at compile time instead of runtime. In that regard, it is especially noteworthy that as a runtime feature, delegation necessarily relies on dynamic binding as a language feature.

Dojo's delegate function wraps up the details of dispatching delegation of an Object 's function through the following API:

dojo.delegate(/*Object*/delegate, properties) //Returns Object

Building on the previous example, the following blurb demonstrates how you might use delegation to get an Object that dispatches responsibility for a function to its delegate:

function Foo(  ) {
    this.talk = function(  ) {console.log("Hello, my name is", this.name);}
}

// Get a Function object back that has the name property
// but dispatches, or delegates, responsiblity for the talk function
// to the instance of Foo that is passed in.
var bar = dojo.delegate(new Foo, {name : "Bar"});

// The talk method is resolved through the Foo delegate
bar.talk(  );

Chapter 10 is devoted to the inheritance pattern facilitated by the toolkit's dojo.declare function, which can be used to simulate class hierarchies with JavaScript; the chapter also includes additional discussion on various approaches to accomplishing inheritance patterns.

DOM Utilities

Recall that Dojo intentionally does not attempt to replace core JavaScript functionality; on the contrary, it only augments it where value can be added so that you can write portable code and incur less boilerplate. For this reason, you won't see direct replacements for common DOM operations such as appendChild, removeChild, and so on. Still, there are many utilities that could make DOM manipulation a lot simpler, and this section is all about how Base helps to make that happen.

Ancestry

Base packs several useful functions that augment and supplement common DOM functions. The first of these functions, isDescendant, shown in Table 2-1, is self-descriptive. You provide it two arguments (id values or actual nodes), where the first argument is the node of interest and the second argument is a potential ancestor. If the node of interest is in fact a member of the potential ancestor's DOM tree, the function returns true.

Table 2-1. Base function for manipulating and handling the DOM

Name

Return type

Comment

dojo.isDescendant(/*String | DomNode*/node, /* String | DomNode*/potentialAncestor)

Boolean

Returns a Boolean value indicating if a node has a particular ancestor or not and works in nested hierarchies as would be expected.

Selectability

The need to make a text on the page unselectable via the cursor is not uncommon and sometimes can actually enhance usability. Virtually every browser has a specific way of accomplishing this task, but no need to worry—you have Dojo. Whenever the need arises, just use the dojo.setSelectable function. Here's the self-descriptive API:

dojo.setSelectable(/*String | DomNode*/node, /*Boolean*/selectable)

Hopefully, it goes without saying that no client-side operation should ever be relied on to protect sensitive content because if something is being viewed in the browser as a native display, it can and will be reverse-engineered.

Styling Nodes

Base's dojo.style function provides a comprehensive means of getting or setting individual style values for a particular node. Simply provide the node and a style value in DOM-accessor format (e.g., borderWidth, not border-width ) to fetch a particular style value. Providing style value in DOM-accessor format as a third argument causes the function to act as a setter method instead of a getter method. For example, dojo.style("foo", "height") would return the height of element with an id of "foo", while dojo.style("foo", "height", "100px") would set its height to 100 pixels. You can also set multiple style properties at the same time by using an Object as the second parameter, like so:

dojo.style("foo", {
    height : "100px",
    width : "100px",
    border : "1px green"
});

While many applications benefit from dojo.style 's ability to manipulate specific style attributes, there is just as common a need for adding, removing, toggling, and checking for the existence of a particular class. Base's suite of functions for manipulating class can do just that, and they all share a common function signature. The first parameter is the DOM node of interest, and the second parameter is a string value indicating the class to manipulate. For example, adding a class to a node is as simple as dojo.addClass("foo", "someClassName"). Note that the class name does not include a leading dot as would define it in the stylesheet.

Table 2-2 summarizes the various facilities for manipulating the appearance of a node.

Table 2-2. Base functions for style handling

Name

Comment

dojo.style(/*DomNode|String*/ node, /*String?|Object?*/style, /*String?*/value)

Provides a means of getting and setting specific style values on a node.

dojo.hasClass(/*DomNode*/node, /*String*/classString)

Returns true only if node has a particular class applied to it.

dojo.addClass(/*DomNode*/node, /*String*/classString)

Adds a particular class to a node.

dojo.removeClass(/*DomNode*/node, /*String*/classString)

Removes a particular class from a node.

dojo.toggleClass(/*DomNode*/node,/*String*/classString)

Adds a class if a node does not have it; removes a class if it does have it.

Manipulating Attributes

Mimicking the same approach as the previous section discussed for styling nodes, Base also provides functions for normalizing the ability to set, get, check for the existence of, and remove attributes. Table 2-3 lists the available functions.

Table 2-3. Base functions for manipulating node attributes

Name

Comment

dojo.attr(/*DOMNode|String*/node, /*String?|Object?*/attrs, /*String?*/value)

Provides a means of getting and setting attributes for a node.

dojo.hasAttr (/*DOMNode|String*/node, /*String*/name)

Returns true only if node has a particular attribute.

dojo.removeAttr (/*DOMNode|String*/node, /*String*/name)

Removes an attribute from a node.

The dojo.attr function works just like dojo.style in that it can set values for individual attributes or multiple attributes depending on whether you use the second and third parameters to specify an attribute and its value, or if you provide an associative array as the second parameter that contains a collection of attributes and values. The hasAttr and removeAttr functions are self-descriptive and work just as you would expect.

Placing Nodes

The built-in methods for manipulating DOM content such as appendChild, insertBefore, and so on can get the job done, but sometimes it's a lot more convenient to have a uniform means of placing nodes, and the dojo.place function, documented in Table 2-4, provides just that. In a nutshell, you give it three parameters: a node to be placed, a reference node, and a position that defines the relative relationship. The position parameter may take on the values "before", "after", "first", and "last". The values "before" and "after" may be used to for relative placement in a lateral context, while "first" and "last" may be used for absolute placement in a context that assumes the reference node is the parent of the node being placed. Position may also be supplied as an Integer value, which refers to the absolute position that the node to be placed should have in the reference node's child nodes.

Table 2-4. Placing a node

Name

Comment

dojo.place(/*String|DomNode*/node, /*String|DomNode*/refNode, /*String|Number*/position)

Augments DOM functionality by providing a uniform function for inserting a node relative to another node. Returns a Boolean.

The Box Model

The CSS box model is a fairly simple topic, but because there are so many inconsistent implementations of it that are available on the Web, things get messy pretty quickly. This short section does little more than scratch the surface, because you really do want to turn to an authoritative reference such as Eric Meyer's CSS: The Definitive Guide (O'Reilly) to really get to the bottom of it all.

If various inconsistent implementations of the box model aren't enough, there's also the issue of keeping the CSS2 box model and the CSS3 box model straight. You can read about the CSS2 box model in the CSS2 Specification at http://www.w3.org/TR/REC-CSS2/box.html, while the CSS3 working draft is at http://www.w3.org/TR/css3-box/.

The ultra-condensed version of the story, however, is that the box model was designed as a way of providing flexible visual formatting that controls the height and width of content by arranging a series of nested boxes around a page element. Before any more dialogue, take a look at Figure 2-2, which conveys the basic idea.

The behavior of width and height as defined by CSS 2.1 Box Model
Figure 2-2. The behavior of width and height as defined by CSS 2.1 Box Model

To summarize the differences between content, margin, padding, and border boxes, review the following relevant blurb from the specification:

The margin, border, and padding can be broken down into left, right, top, and bottom segments (e.g., in the diagram, "LM" for left margin, "RP" for right padding, "TB" for top border, etc.). The perimeter of each of the four areas (content, padding, border, and margin) is called an "edge," so each box has four edges:

1 - content edge or inner edge

The content edge surrounds the element's rendered content.

2 - padding edge

The padding edge surrounds the box padding. If the padding has 0 width, the padding edge is the same as the content edge. The padding edge of a box defines the edges of the containing block established by the box.

3 - border edge

The border edge surrounds the box's border. If the border has 0 width, the border edge is the same as the padding edge.

4 - margin edge or outer edge

The margin edge surrounds the box margin. If the margin has 0 width, the margin edge is the same as the border edge.

As it turns out, two different means of realizing the box model emerged, which is where the divergence begins: the content-box and the border-box. The basic difference between the two approaches can be captured by asking what defines how margins and borders are applied to the content area. With the content-box approach, any area incurred by padding and borders is accounted for outside of the explicit width and height of the content, whereas the border-box approach calls for any padding and borders to be accounted for inside the explicit height and width of the content area. In other words, the content-box approach associates a height/width strictly with only the content, whereas the border-box approach associates a height/width with the border inward.

Many modern browsers support two modes: standards mode and quirks mode. The content-box approach is associated with standards mode while the border-box approach is associated with quirks mode.

If you're not doing anything very fancy and just want to space out some content, the differences may not be apparent, and you can generally get the same net effect in a number of ways. If you need to achieve a very specific look and feel, however, your decisions may already be made for you—and achieving the same look and feel across browsers is exactly where the (lack of) fun begins.

Dojo attempts to normalize the differences in calculating various facets of the box model by exposing the dojo.boxModel attribute, which can take on a value of "content-box" or "margin-box" as well as the dojo.marginBox property and dojo.contentBox function, which can be used to retrieve the coordinates for the boxes. By default, dojo.boxModel is set to "content-box". In all cases, the box parameters provided in the following table refer to an Object containing values for width and height, along with an upper-left coordinate that defines the box's area. A sample margin box would look something like { l: 50, t: 200, w: 300: h: 150 } for a node offset from its parent 50px to the left, 200px from the top with a margin width of 300px, and a margin-height of 150px.

To try it out for yourself, copy the following example into a local file and open it up in Firefox:

<body style="margin:3px">
      <div id="foo" style="width:4px; height:4px; border:solid 1px;"></div>
</body>

Here's some sample output you'd see in Firebug if you copied over the page and experimented with it, and Figure 2-3 shows what it would look like in the browser:

console.log("box model", dojo.boxModel); // content-box
console.log("content box", dojo.contentBox("foo")); // l=0 t=0 w=4 h=4
console.log("margin box", dojo.marginBox("foo")); // l=3 t=3 w=6 h=6
The sample page in the browser
Figure 2-3. The sample page in the browser

Like other functions you've seen in this chapter, calling the functions with only one parameter corresponding to a node returns a value, while calling it with an additional second parameter sets the value for the node. Table 2-5 lists all the properties for working with the box model.

Table 2-5. Box model properties

Name

Return type

Comment

dojo.marginBox(/*DomNode|String*/node, /*Object?*/box)

Object

Returns an Object containing the margin box for a node.

dojo.contentBox(/*DomNode|String*/node, /*Object?*/box)

Object

Returns an Object containing the content box for a node.

dojo.coords(/*HTMLElement*/node, /*Boolean*/includeScroll)

Object

Returns margin box data for a node, including absolute positioning data. In addition to the t, l, w, and h values, additional x and y values indicate the absolute position of the element on the page; these values are offset relative to the viewport if includeScroll is set to true. dojo.coords does not act as a setter.

Dijit uses the box model facilities extensively to produce portable widgets across browsers.

Browser Utilities

This section provides an overview of the toolkit's utilities for managing cookies and the browser's Back button—two topics that are quite common in any modern web application. Because both of these topics are provided by Core, you must dojo.require them into the page before trying to use them.

Cookies

Because HTTP is a stateless protocol, as soon as a web server finishes serving up a page, it knows nothing else about you. While this aspect of the Web is magnificent in many respects, it is less than ideal for situations in which an application could personalize a page based upon preferences you've already defined. For example, it might be nice for a weather-related site to remember your zip code so that you don't have to enter it every single time you visit the page.

Cookies are a concept originally devised by Netscape that mitigate this kind of problem and give browsers a limited form of short-term memory. In short, web page designers can use JavaScript or server-side scripts to create a cookie that contains name-value pairs about your visit to the page. When you visit the page again, scripts can be used to fetch the cookie and dynamically affect your experience. Cookies generally have an expiration date and are always associated with the specific domain from which they originated.

One of the issues with managing cookies from pure JavaScript is that you have to remember the somewhat strict syntax that is expected and build up the string for yourself. For example, to set a cookie for the default domain that consists of a name/value pair of foo=bar with a particular expiration date, you would do this:

document.cookie ='foo=bar; expires=Sun, 15 Jun 2008 12:00:00 UTC; path=/'

Of course, that's the easy part. When you want to read back out cookie values, you get to parse the String yourself, which might contain lots of name/value pairs.

Dojo provides a basic wrapper around cookie operations that's a lot easier to remember how to use. Table 2-6 outlines the basic API.

Table 2-6. dojo.cookie functions

Name

Comment

dojo.cookie(/*String*/name, /*String*/value, /*Object?*/properties)

Acts as a "getter" for a cookie value (returned as String ) when you provide only the first argument, which is the name for a cookie value. Providing the first two values acts as a "setter," which sets the name to the value. The final parameter, properties, may contain the following key/value pairs for specific cookie properties:

expires (Date|String|Number)

If this is a number, it indicates the days from today at which the cookie expires; if a date, it provides the date past which the cookie expires (and if expires is in the past, the cookie is deleted); if expires is omitted or is 0, the cookie expires when the browser closes.

path (String)

The path to use for the cookie.

domain (String)

The domain to use for the cookie.

secure (Boolean)

Whether to send the cookie only on secure connections.

dojo.cookie.isSupported( )

Returns a Boolean value indicating if the browser supports cookies.

For example, you might set and retrieve a cookie value like so:

dojo.cookie("foo","bar", {expires : 30});
//set a foo/bar key-value pair to expire 30 days from now
dojo.cookie("foo"); //get back the value for foo, which is bar

Back Button Handling

For modern web applications, it is pretty much the norm that the entire app lives in a single page that never reloads, and one issue that immediately comes up is managing the Back button so that your application can properly respond to state and potentially even bookmarking. The Core module back provides a simple utility that facilitates the state-tracking portion of the work by allowing you to explicitly define states and respond accordingly when the Back or Forward button is pressed. Table 2-7 describes the API.

Table 2-7. dojo.back functions

Name

Comment

init( )

Needs to be called from a SCRIPT tag that exists inside of the page BODY because of a nuance with Internet Explorer. If you know for sure that your application will not need to run on IE, you can optionally ignore calling this function.

setInitialState(/*Object*/args)

Used to define the callback function that should be executed when the page returns to its "first" state. In general, it is recommended that this function be called first thing in addOnLoad.

addToHistory(/*Object*/args)

Provides a way of establishing a particular state via the args that provides callbacks for when the Back and Forward buttons are pressed, as well as an optional identifier in the URL that may be used for convenient bookmarking. Specifically, args has the following form:

back (Function)

The callback function to execute when the state is entered via the Back button being pressed.

forward (Function)

The callback function to execute when the state is entered via the Forward button being pressed.

changeUrl (Boolean|String)

If true, a random identifier is inserted into the URL and used internally for tracking purposes. If a String is provided, the string is inserted into the URL and used for the same purpose with the nicety that it also provides convenient bookmarking. Do not mix and match Boolean and String values; use one or the other.

Be consistent and use either Boolean values or String identifiers for the changeUrl property for the args Object that is passed to addToHistory.

Example 2-8 illustrates a trivial usage of back to produce callback functions that could provide custom behavior, which hopefully gets the basic idea across. Note that the emphasized lines inside of the body tags are necessary to ensure that IE behaves as expected.

In case you're wondering why the SCRIPT that is included inside of the BODY looks really awkward, it's because of a specific problem with IE that requires a document.write to execute, which cannot happen after the page loads. It's not elegant, but it does work across all browsers and gets you Back button functionality.

Example 2-8. Example of Back button handling
<html>
    <head>
         <title>Fun with Back!</title>


        <link rel="stylesheet" type="text/css"
          href="http://o.aolcdn.com/dojo/1.1/dojo/resources/dojo.css" />


         <script
             type="text/javascript"
             src="http://o.aolcdn.com/dojo/1.1/dojo/dojo.xd.js"
             djConfig="dojoIframeHistoryUrl:'iframe_history.html',isDebug:true"
         ></script>

         <script type="text/javascript">

             dojo.addOnLoad(function(  ) {
                initialState = {
                    back: function(  ) { console.log("Back to initial state"); }
                };
                state1 = {
                    back: function(  ) { console.log("Back to state 1"); },
                    forward: function(  ) { console.log("Forward to state 1"); },
                    changeUrl : true // could also be an id like "state1"
                };
                state2 = {
                    back: function(  ) { console.log("Back to state 2"); },
                    forward: function(  ) { console.log("Forward to state 2"); },
                    changeUrl : true // could also be an id like "state2"
                };

                //set the initial state and move forward two steps in history
                dojo.back.setInitialState(initialState);
                dojo.back.addToHistory(state1);
                dojo.back.addToHistory(state2);
            });
         </script>
     <head>
        <body>
            <script type="text/javascript"
                src="http://o.aolcdn.com/dojo/1.1/dojo/back.js"></script>
            <script type="text/javascript">dojo.back.init(  );</script>

            Press the back button and have a look at the console.
        </body>
 </html>

Summary

After reading this chapter, you should:

  • Understand Base's general feature set

  • Be able to configure djConfig to register module paths and be aware of the various other options you can pass into this structure to configure the bootstrap process

  • Understand how to use dojo.addOnLoad and dojo.addOnUnload functions and be aware of how dojo.addOnLoad can protect you from creating race conditions in your code

  • Be able to construct and namespace your own modules with dojo.provide and dojo.require

  • Understand how (and when) to use the map, filter, and forEach functions

  • Know the difference between and be able to effectively use mixin and extend

  • Effectively use Dojo's utilities for manipulating style with hasClass, removeClass, addClass, and toggleClass

  • Understand the basics of the CSS box model and be able to use functions like coords and marginBox to manipulate the placement of DOM nodes

  • Be aware of Base's Array processing utilities

  • Be able to wire up arbitrary connections of Object and DOM events

  • Be able to manage cookies

  • Be able to use Core's facilities for managing the back button for a single page app

Next, we'll take a look at event listeners and pub/sub communication.