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, Toolkit Overview; 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, “Quick reminder about dojo.byId” 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 existsType 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
trueifvalueis aString.-
isArray(/*Any*/ value) Returns
trueifvalueis anArray.-
isFunction(/*Any*/ value) Returns
trueif value is aFunction.-
isObject(/*Any*/ value) Returns
trueifvalueis anObject(including anArrayandFunction) ornull.-
isArrayLike(/*Any*/ value) Returns
trueif value is anArraybut also allows for more permissive possibilities. For example, the built-inargumentsvalue that can be accessed from within a Function object is especially an oddball in that it does not support built-in methods such aspush; however, it is array-like in that it is a list of values that can be indexed.-
isAlien(/*Any*/ value) Returns
trueif value is a built-in function or native function such as an ActiveX component but does not respect the normal checks such as theinstanceof 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 [].
Tip
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.
Tip
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
stringmodule provides a slightly more efficient version oftrimthat 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.
Tip
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, Toolkit Overview 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
Warning
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 }); //trueIterating 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.
Warning
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 twoWarning
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 itemsBecause 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, “Namespace organization with dojo.setObject” 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 23The 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.
Tip
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, “Defining a simple simple module (dtdg.foo)”,
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);
}Tip
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, “Using a local module with XDomain bootstrappping” 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, “Defining a simple simple module (dtdg.foo)”.
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.
Tip
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.
Warning
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.
Tip
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, “Using dtdg.foo with a local toolkit installation” 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, “The implementation for a magic genie module” 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, “The implementation for a magic genie module”.
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.
Tip
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, “A web page utilizing the magic genie”.
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”.
JavaScript Object Utilities
Three of Base's language utilities facilitate operations you may
need to routinely perform on objects: mixin, extend, and clone.
Tip
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, Simulated Classes and Inheritance.
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.
Tip
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);Warning
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, “The implementation for a magic genie module” 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];
}
});Warning
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); // bazManipulating 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.
Warning
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( ); //111Hitching 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 AnyAnd 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 FooTo 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( );
Tip
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, AJAX and Server Communication 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, Simulated Classes and Inheritance 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, “Base function for manipulating and handling the DOM”, 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 |
|---|---|---|
|
|
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)
Tip
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, “Base functions for style handling” summarizes the various facilities for manipulating the appearance of a node.
Table 2.2. Base functions for style handling
|
Name |
Comment |
|---|---|
|
|
Provides a means of getting and setting specific style values on a node. |
|
|
Returns |
|
|
Adds a particular class to a node. |
|
|
Removes a particular class from a node. |
|
|
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, “Base functions for manipulating node attributes” lists the available functions.
Table 2.3. Base functions for manipulating node attributes
|
Name |
Comment |
|---|---|
|
|
Provides a means of getting and setting attributes for a node. |
|
|
Returns |
|
|
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, “Placing a node”, 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 |
|---|---|
|
|
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.
Tip
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, “The behavior of width and height as defined by CSS 2.1 Box Model”, which conveys the basic idea.
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.
Tip
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, “The sample page in the browser” 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=6Like 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, “Box model properties” lists all the properties for working with the box model.
Table 2.5. Box model properties
|
Name |
Return type |
Comment |
|---|---|---|
|
|
Object |
Returns an |
|
|
Object |
Returns an |
|
|
Object |
Returns margin box
data for a node, including absolute positioning data. In
addition to the |
Tip
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, “dojo.cookie functions” outlines the basic API.
Table 2.6. dojo.cookie functions
|
Name |
Comment |
|---|---|
|
| Acts as a "getter"
for a cookie value (returned as
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.
|
|
|
Returns a |
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 barBack 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, “dojo.back functions” describes the API.
Table 2.7. dojo.back functions
|
Name |
Comment |
|---|---|
|
|
Needs to be called
from a |
|
|
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 |
|
| Provides a way of
establishing a particular state via the
|
Warning
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, “Example of Back button handling” 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.
Tip
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
djConfigto register module paths and be aware of the various other options you can pass into this structure to configure the bootstrap processUnderstand how to use
dojo.addOnLoadanddojo.addOnUnloadfunctions and be aware of howdojo.addOnLoadcan protect you from creating race conditions in your codeBe able to construct and namespace your own modules with
dojo.provideanddojo.requireUnderstand how (and when) to use the
map,filter, andforEachfunctionsKnow the difference between and be able to effectively use
mixinandextendEffectively use Dojo's utilities for manipulating style with
hasClass,removeClass,addClass, andtoggleClassUnderstand the basics of the CSS box model and be able to use functions like
coordsandmarginBoxto manipulate the placement of DOM nodesBe aware of Base's
Arrayprocessing utilitiesBe able to wire up arbitrary connections of
Objectand DOM eventsBe 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.








Add a comment



Add a comment