Part 4: Testing, Tuning and Debugging

Up until now, we've been looking directly at Dojo and Dijit. Now we'll shift gears for a bit and talk about stuff outside the code itself. When you work with a rich toolkit like Dojo to build Enterprise-grade apps, the old JavaScript development paradigms aren't so hot:

In Part 4, we'll look at ways to combat all of these issues. The Dojo people aren't content with dropping the API in your lap and running. Oh no! In the course of developing Dojo itself, they have built tools and techniques to help you, and they're very generous in sharing them. So let's have a look.

Getting the Code from Source Control

The Dojo team uses Subversion for source control. Those familiar with CVS will find the command line syntax for subversion to be very similar. Regardless, the following instructions are geared to those not familiar with CVS, or even source control in general.

Authoritative documentation on Subversion is available here.

Why Source Control?

Source control is one of those things that one rarely notices they need until it's far too late, usually when you accidentally delete part of your source tree instead of simply moving it to a different location, or when you make a set of complex changes that leave you worse off than were you started (but you can't go back). A source control system solves these problems by keeping copies of each revision of a set of files on a server, while giving you access to a local copy of those files to make changes on. Good source control systems allow multiple people to modify a single file at once, and will try to automatically merge changes between differing sets of modifications. A good source control system will also let you browse the history of a file or set of files (allowing you to "go back in time") and allow you to have acccess to your code from as many systems as you like. Subversion is one of those good source control systems.

General Information

Across the gamut of source control systems, there is quite a bit of confusing (and non-portable) nomenclature surrounding the common actions that you as a developer will preform with the source control system. We will use the terms here that are commonly accepted by CVS and Subversion users and administrators. So what are those terms?

  • Checkout: a "checkout" is a local working copy of a "repository". This is the set of files that you will be working with when making modifications to Dojo.
  • Repository: the logical grouping of project-related files on the server. A subversion server may host multiple repositories, but it is quite likely that your changes will be constrained to a single repository.
  • Checkin: transmitting a set of changes from your local checkout to the server. Your changes will then be available to everyone else who has a checkout of that repository when they update their view.
  • Head: (also called the "main line") the most up-to-date version of the source control tree. Most of your checkins will be to the "head" of the tree, although in some more complex situations, you may be checking in to a "branch".
  • Branch: A clone of the source control repository from some point in time which contains a set of changes which are not shared with the "head" or main line. Branches are one way for a developer to work on a particular feature (usually a large feature) and have intermediate changes versioned without having to worry about whether or not his or her changes will break someone else's code. Changes can then be "merged" back back to the main line when the developer thinks they are stable.
  • Merge: merging is the process of taking several versions of a single file and turning them into one authoritative version. Merging in is often an automated process with Subversion, but you may at times be called upon to merge a set of files manually (when the server cannot automatically take care of it).

Unlike some other source control systems, Subversion manages files on your disk without interjecting itself obtrusively into your workflow. You can change large sets of files
without worrying if anyone else is also modifying those files.

Browsing the repository

You may browser our subversion repository (http://svn.dojotoolkit.org/src/) directly using a web browser or browse using the Bug tracker.

Directory structure

The code is structured with each subproject (dojo, dijit, dojox, util) as a separate directory. Within each subproject you will find the "trunk" as well as "branches" and "tags". You may pull these subprojects separately, or use a special svn "view" which links the subprojects and checks them out with a single command.

Note that the "trunk" directory at the top-level is obsolete. This was used prior to the 0.9 release, when the code was reorganized into the various subprojects.

To do an anonymous, read-only checkout of the Dojo development trunk:
/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .bash .imp {font-weight: bold; color: red;} .bash .kw1 {color: #b1b100;} .bash .kw3 {color: #000066;} .bash .es0 {color: #000099; font-weight: bold;} .bash .br0 {color: #66cc66;} .bash .st0 {color: #ff0000;} .bash .nu0 {color: #cc66cc;} .bash .re0 {color: #0000ff;} .bash .re1 {color: #0000ff;} .bash .re2 {color: #0000ff;} .bash .re3 {color: #808080; font-style: italic;} .bash .re4 {color: #0000ff;} svn co http://svn.dojotoolkit.org/src/view/anon/all/trunk dojotoolkit

Or, to pull a particular release, such as Dojo 1.0.2:
/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .bash .imp {font-weight: bold; color: red;} .bash .kw1 {color: #b1b100;} .bash .kw3 {color: #000066;} .bash .es0 {color: #000099; font-weight: bold;} .bash .br0 {color: #66cc66;} .bash .st0 {color: #ff0000;} .bash .nu0 {color: #cc66cc;} .bash .re0 {color: #0000ff;} .bash .re1 {color: #0000ff;} .bash .re2 {color: #0000ff;} .bash .re3 {color: #808080; font-style: italic;} .bash .re4 {color: #0000ff;} svn co http://svn.dojotoolkit.org/src/tags/release-1.0.2 dojo102

Branches in the Dojo repository

Most Dojo development takes place on the trunk. Branches may be used for development of experimental features or for code migration before being merged back into the trunk. Branches are also used to stabilize major releases.

Making changes to the repository

Anyone may access the Dojo Subversion server. Contributors are encouraged to access code directly from the repository and submit patches using the bug tracker. To submit changes, however, you must have committer status and have already received a system account from the administrator.

To access the repository as a committer, use this URL with subversion:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw3 {color: #000066;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .re0 {color: #0000ff;} .geshifilter .re1 {color: #0000ff;} .geshifilter .re2 {color: #0000ff;} .geshifilter .re3 {color: #808080; font-style: italic;} .geshifilter .re4 {color: #0000ff;}
svn co --username=YOURUSERNAME
https://svn.dojotoolkit.org/src/view/committer/all/trunk dojotoolkit

Using Development Tools

Setup for Eclipse

If you use Eclipse, we recommend the Subclipse plugin. Simply provide a URL for the Dojo repository as discussed above.

Setup for Windows

To access our subversion repository on Windows, please install TortoiseSVN, available at: http://tortoisesvn.tigris.org/ Installing Tortoise requires a reboot since it installs itself as a Windows Explorer shell extension.

Welcome back! Now that you have rebooted, open up a Windows Explorer window (Win-E, or right-click on the "Start" menu and select "Explore"), navigate to whatever directory you would like your Dojo source code to be placed under. Create a new directory there titled "dojo", and then navigate to it.

Right-click in the directory and select "SVN Checkout..." from the context menu. In the resulting dialog box, you will be prompted for a URL for the repository you want to check out. In this dialog box, place the following URL:

Dojo development trunk
https://svn.dojotoolkit.org/src/view/committer/all/trunk dojotoolkit

Click "ok" in the dialog box if a dialog box comes up discussing a host key. You will then be asked for your user login password at dojotoolkit.org. Provide it. You will then see a list of files being retreived from the server, and when it's finished, you will have your very own checkout!

Next, take a minute to update your svn config settings.

You can then check in your changes by right-clicking on the file(s) you want to check in, selecting providing a checkin comment (strongly encouraged). Other operations, including diffing and merging are also available from the context menu.

For more information about TortiseSVN or how to use it, see the documentation at:

http://tortoisesvn.tigris.org/docs/TortoiseSVN_en/index.html

Setup for Linux

Since you're running Linux, it is assumed that you're comfy with your systems package management system and the command line.

Making a checkout is straightforward from the command line. Provided you already have Subversion installed, simply run:

Dojo development trunk
/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw3 {color: #000066;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .re0 {color: #0000ff;} .geshifilter .re1 {color: #0000ff;} .geshifilter .re2 {color: #0000ff;} .geshifilter .re3 {color: #808080; font-style: italic;} .geshifilter .re4 {color: #0000ff;}

svn co --username=YOURUSERNAME
https://svn.dojotoolkit.org/src/view/committer/all/trunk dojotoolkit

Next, take a minute to update your svn config settings.

This will create a local copy ("checkout") of the source tree (under a new directory called "dojotoolkit") in the current directory. Make edits to the files you want to change, then commit them back to the repository with:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .bash .imp {font-weight: bold; color: red;} .bash .kw1 {color: #b1b100;} .bash .kw3 {color: #000066;} .bash .es0 {color: #000099; font-weight: bold;} .bash .br0 {color: #66cc66;} .bash .st0 {color: #ff0000;} .bash .nu0 {color: #cc66cc;} .bash .re0 {color: #0000ff;} .bash .re1 {color: #0000ff;} .bash .re2 {color: #0000ff;} .bash .re3 {color: #808080; font-style: italic;} .bash .re4 {color: #0000ff;} svn commit -m "Commit message here" names/of/files

Setup for OS X

We assume that you are on at least OS 10.3 (Panther).

You may need to install the Developer Tools package in order to get a the most up-to-date JDK (for running custom Dojo builds). It is assumed that operations will be preformed at the command line, and Project Builder/XCode configuration is not covered here. It is, however, recommended that you download the latest set of developer tools from http://connect.apple.com (free registration required).

Once you have the OS X developer tools installed, download the latest 1.x Subversion package (1.3 as of this writing) from:
http://metissian.com/projects/macosx/subversion/

The downloads are an OS X installer package. Install it, at which point the instructions from the Linux section will be sufficient to get you up and running.

Next, take a minute to update your svn config settings.

SVN Config Settings:

You'll need to add a couple config settings to your SVN config file. If you are on Windows, that's located at:

C:\Documents and Settings\YourUserName\Application Data\Subversion\config

And on UNIX/Mac OS X:

~/.subversion/config

Open it up in your favorite text editor. Most configs have some default settings, so locate [miscellany] and enable-auto-props in the file. If they exist, make sure that they are uncommented (remove # from beginning of line), otherwise add them. They line should look like:

[miscellany]
enable-auto-props = yes

Next, located [auto-props]. If it doesn't exist, add it, otherwise you'll probably have to uncomment it. Add the following entries below [auto-props]:

*.js = svn:eol-style=native
*.htm = svn:eol-style=native
*.html = svn:eol-style=native
*.svg = svn:eol-style=native
*.txt = svn:eol-style=native
*.xml = svn:eol-style=native
*.xsl = svn:eol-style=native
*.dtd = svn:eol-style=native
*.css = svn:eol-style=native
*.rest = svn:eol-style=native
*.php = svn:eol-style=native
*.phps = svn:eol-style=native
*.inc = svn:eol-style=native
*.sh = svn:eol-style=native
Makefile = svn:eol-style=native
README = svn:eol-style=native
CHANGELOG = svn:eol-style=native
LICENSE = svn:eol-style=native
INSTALL = svn:eol-style=native
BUILD = svn:eol-style=native

Save that and you should be set! Continue on to committing directions above.

Development Tools

No matter what your development environment, several tools are indispensable when building client-side applications:

  • FireFox
  • FireBug is a web developer's best friend. Also see it's associated performance tuning plugin YSlow.
  • Virtualization software such as VMWare or Parallels can make your job significantly easier by allowing you to run multiple versions of IE side-by-side
  • Safari (for Windows and Mac) and the WebKit nightly builds are great debugging aids.

Integrated Development Environments

At the high end of the spectrum, Integrated Development Environments, or IDEs support a wide range of web development activity, including editing JavaScript and HTML files, deploying code to servers, and integration with existing features like source control. In addition, some include runtime tools and browser integration to assist in debugging.

Cross-Platform, Eclipse-Based

  • Aptana - Extremely strong JavaScript support, good support for many common server-side languages, and integration for Dojo.
  • MyEclipse - MyEclipse Professional contains a JavaScript editor and debugger. The debugger can set breakpoints, inspect variables, and single step within its own Web 2.0 Browser, based on Mozilla/Firefox.
  • ATF - The Ajax Tools Framework contains similar tools as MyEclipse, and adds AJAX toolkit "personalities". These are essentially snippets for the common idioms for a toolkit. The Dojo personality is included, but is not yet updated for 0.9.
  • Adobe JSEclipse, formerly a product of Interakt, is a free JavaScript editor. Like ATF, it includes code completion idioms for Dojo, but does not include the debugger.

Windows

  • Visual Studio .NET bundles the JavaScript editor and debugger with the other languages. This is especially useful when writing data services in ASP.NET and client-side scripts with Dojo.
  • Visual Web Developer Express is a free, scaled down version of the web portion of Visual Studio. JavaScript editing and debugging is supported.
  • Microsoft Script DebuggerAn extremely scaled-down and outdated debugger, but familiar to many.

Browser Addons

Mozilla/Firefox

  • The Firebug is the debugger of choice for Dojo and Firefox. Consult Cheap Debugging for details.
  • JavaScript Debugger, a/k/a Venkman has been around for years, and has adherents. Venkman seems to be dormant, gaining no new features but being ported verbatim to new versions of Firefox.
  • Live HTTP Headers is useful for debugging HTTP traffic . Although Firebug is good for analyzing HTTP traffic within the page, as in XHR, Live HTTP Headers excels at between-page HTTP requests.
  • The Web Developer Toolbar is a favorite of Web Designers, and includes rulers and positioning features that Firebug does not.

Internet Explorer

  • IE Web Developer is a commercial product with Firebug-like features such as JavaScript debugging.
  • IE Developer Toolbar, a free download from Microsoft, is useful for page design.
  • The Microsoft Script Editor integrates with Internet Explorer and is available as part of office 2003. It is not included in the default install but you can get it from the installation cd's.
  • Web Developer Helper - HTTP logging, script debugging and a DOM inspector.

Safari

Drosera is the "native" JavaScript debugger for Safari, and is written by the folks from Webkit, on which Safari is based. The Safari Developer FAQ has some general information about developing with Safari, as well as instructions on how to turn on a debug menu that allows showing a JavaScript console.

Traffic Analyzers

Many problems can be resolved by watching the traffic between the browser and the server. While IDE's and web add-ons analyze traffic, the following standalone programs are more heavy-duty. If you are having any problems with XHR or with js/html/css/jpg files not loading as you would expect, this is often the best way to diagnose them quickly. Also if you are having problems with required files not loading then this is a good starting point to find out why.

  • Fiddler is a fantastic HTTP header and content inspector for Windows. It understands HTTP and presents the information very clearly (once you get used to its slightly quirky interface). Well integrated with IE, but work s with any browser. With FF a useful extension to help use Fiddler is the Switch Proxy extension (scroll down that page to find it).
  • Wireshark (formerly Ethereal) is great for sniffing all kinds of network traffic.

Using Dojo with Maven

Using  maven to build your project is a fantastic for managing your dependencies and avoiding having dependencies (and their dependencies) checked into your own svn. There are maven repositories for dojo snapshots and releases at:
These repositories contain artifacts for:
  • dojo toolkit release as a zip, tar.gz and tar.bz2
  • dojo toolkit packaged as a java war file ready to serve. The war includes a filter that sets the Cache-Control header to encourage browser caching.
  • cometd artifacts for java API and cometd example war

Building Dojo with Maven

To checkout and build dojo with maven is as simple as:
svn co http://svn.dojotoolkit.org/src/view/anon/all/trunk dojo
cd dojo/util/maven
mvn
This will build the standard dojo release and package it as a zip, a tar.gz and tar.gz2 and make them available in your local repository. The groupId is org.dojotoolkit and the artifactId is dojo.

Using Dojo artifacts

The following incantation of the maven dependency plugin will download and unpack these artifacts into your maven project:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>unpack dojo</id>
<phase>generate-sources</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.dojotoolkit</groupId>
<artifactId>dojo</artifactId>
<version>${project.version}</version>
<type>zip</type>
</artifactItem>
</artifactItems>
<outputDirectory>${project.build.directory}/dojo</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>

Using Dojo in a WAR

A WAR file is a standard packaging of a java web application for a java servlet server. Along with the build of the dojo release artifacts, dojo is also packaged as a war file with groupId org.dojotoolkit and artifactId dojo-war. The war produced by this can be directly deployed and used by other web applications on the same server (there is no requirement for js to be served from the same war as your application). Alternately, this war can be used as an overlay by the maven war plugin and merged with your own war project:
<project xmlns="...">
<modelVersion>4.0.0</modelVersion>
<groupId>vom.acme</groupId>
<artifactId>myproject</artifactId>
<packaging>war</packaging>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<overlays>
<overlay></overlay>
<overlay>
<groupId>org.dojotoolkit</groupId>
<artifactId>dojo-war</artifactId>
<excludes>
<exclude>META-INF/**</exclude>
</excludes>
</overlay>
</overlays>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.dojotoolkit.java</groupId>
<artifactId>dojo</artifactId>
<version>1.2-SNAPSHOT</version>
<type>war</type>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>

Using Maven Repositories

Eventually the dojo maven release repositorie will be mirrored in the central maven repository. Until that time, or if you wish to use snapshots, you can access the dojo repositories directly with the following incantation in your pom.xml:
<repositories>
<repository>
<id>dojo</id>
<name>Dojo Maven2 Repository</name>
<url>http://download.dojotoolkit.org/maven2</url>
<releases>
<enabled>true</enabled>
<updatePolicy>daily</updatePolicy>
<checksumPolicy>fail</checksumPolicy>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
<layout>default</layout>
</repository>
<repository>
<id>dojoSnapshots</id>
<name>Dojo Maven2 Snapshot Repository</name>
<url>http://download.dojotoolkit.org/maven2-snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
<checksumPolicy>warn</checksumPolicy>
</snapshots>
<layout>default</layout>
</repository>
</repositories>

Debugging Facilities

A great toolkit like Dojo deserves great tools alongside it. If you're used to chasing down problems with alert() and trial-and-error, it's time to give your development environment an upgrade!

Dojo itself has facilities for easing debugging tasks: an isDebug flag for better tracking down internals and console.debug() statements for logging. We'll cover these in the next sections, plus give you some hints on general JavaScript debugging.

Basic JavaScript Gotchas

Developing AJAX based applications, whether you use the Dojo Toolkit or develop everything the hard way from scratch, can pose quite a challenge to most developers. The difficulties typically arise from two main issues:

  • JavaScript(TM) is an interpreted language and is not compiled into machine code. Therefore, you do not have a compiler to catch common syntax errors.
  • Various Web browsers report errors in different ways. For example, Mozilla Firefox(TM) logs JavaScript errors into its JavaScript console. Microsoft(TM) Internet Explorer might bring up an alert window with the line and error message.

This article presents common problems and how currently available tools might be used to debug them. This section is not intended as an all-encompassing debugging guide for JavaScript problems.

Debugging Issue 1: No Compiler Syntax Checking

Developers who have worked primarily with compiled languages will miss the compiler when working with a completely interpreted language like JavaScript. More often than not, the browser's error message is far from helpful in determining the actual root cause of the error. In addition, the errors reported by many AJAX toolkits are often not very helpful in determining the root cause of the error. For example, if you have written a custom widget based on the Dojo Toolkit that contains a syntax error and attempt to load it, you might get the following error:

FATAL exception raised: Could not load
'some.package.name.WidgetName'; last tried '__package__.js'

Unfortunately, the message does not tell you what the problem was, only that it could not load a particular widget. This error can be caused by many programming errors, but the most common ones are syntax errors in the widget JavaScript. The most common syntax errors that cause the above error are as follows:

  • Using ';' instead of ',' when separating attributes of a JavaScript object definition.  This error is extremely common for novice JavaScript developers who have a background in Java or C++.

    Note: All Dojo Toolkit widgets and extension widgets are JavaScript objects and therefore any custom widgets must use the proper comma separator between properties of the object (these include function declarations as properties).

  • Incorrectly terminating the last attribute definition in a JavaScript object definition with ','. The last property in any JavaScript object definition must never end with a comma.
  • Malformed (unmatched) braces {} .
  • Malformed function definitions.

You can avoid syntax errors by running a lint processor that understands JavaScript syntax on your code as you develop it. The lint processor will check for syntax errors before the code is run. Some of the JavaScript lint processors available on the web include:

Most lint processors are excellent at catching the errors mentioned previously and more. Many of the lint processors also enforce that JavaScript program statements always line terminate with ';' . While terminating with ';' is considered optional in JavaScript, it is highly recommended to do it for strictness and uniformity.

In summary, using lint syntax checking will save you hours of development time and frustration when trying to find the misplaced comma, malformed function, or missing closure.

Debugging Issue 2: Runtime Problems.

Assuming that all the syntactical errors were caught, by running a lint processor on the JavaScript code, and corrected the next set of problems are all related to runtime problems. These issues tend to be tricker to debug, as the various browsers do not report errors in the same way. Unfortunately, this means that, as a JavaScript developer, you must become familiar with debugging tools and browser error reporting mechanisms that are specific to each of the major supported browsers on which your application will be used. The list below outlines how some of the more well-known browsers report errors:

Microsoft Internet Explorer
Errors, such as accessing an undefined reference, are reported as a pop up from the browser with the line number and a simple description of what it believes the error is.
Mozilla 1.7.X and Firefox 1.5.X
The Mozilla and Firefox browsers report all JavaScript errors into a 'JavaScript Console'. So, often on a page that encountered JavaScript problems, the Web application just won't work and it's not always obvious why. So, while developing and testing using these browsers, you should regularly open and check the JavaScript console. This console can be located from the following menu locations:
  • Mozilla 1.7.X: Tools->Web Development->JavaScript Console.
  • Firefox 1.5.X: Tools->JavaScript Console.

The most common problems encountered at runtime tend to be:

  • Referencing undefined variables. For example, trying to access an attribute on a currently undefined object, such as jsObject.someAttribute, where the jsObject variable has not been assigned.
  • Functions defined on a JavaScript object accessing properties on that JavaScript object and failing to use the 'this' reference identifier. For example, the following is incorrect:
    /* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000066; font-weight: bold;} .geshifilter .kw2 {color: #003366; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .co1 {color: #009900; font-style: italic;} .geshifilter .coMULTI {color: #009900; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #3366CC;} .geshifilter .nu0 {color: #CC0000;} .geshifilter .me1 {color: #006600;} .geshifilter .re0 {color: #0066FF;}
    function testOnload() {
           var fooObject = {
                fooAttribute: "This is foo",
                fooFunction: function() {
                    alert("The value of foo is: [" + fooAttribute + "]");
                }
            };
            fooObject.fooFunction();
        }   
    dojo.addOnLoad(testOnload);

    This is the correct way:

    /* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000066; font-weight: bold;} .geshifilter .kw2 {color: #003366; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .co1 {color: #009900; font-style: italic;} .geshifilter .coMULTI {color: #009900; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #3366CC;} .geshifilter .nu0 {color: #CC0000;} .geshifilter .me1 {color: #006600;} .geshifilter .re0 {color: #0066FF;}
    function testOnload() {
           var fooObject = {
                fooAttribute: "This is foo",
                fooFunction: function() {
                    alert("The value of foo is: [" + this.fooAttribute + "]");
                }
            };
            fooObject.fooFunction();
        }   
    dojo.addOnLoad(testOnload);

Using JavaScript Debuggers

Debugging with the Dojo loader

The dojo.require() method of the Dojo loader uses a combination of XHR plus the eval statement to load JavaScript. This is one of the most powerful features in Dojo, however, it raises a few problems with some tools. First, most tools have no way to identify the source of the code. The information about the file or URL source of the buffer passed to eval in the Dojo loader is lost. (Note: the loader puts this metadata at the very bottom of the buffer, which may be inspected or parsed by some tools) Second, the Mozilla/Firefox console as well as most debuggers based on Mozilla's Spidermonkey engine suffer from a serious bug which makes debugging Dojo nearly impossible: all code loaded through an eval statement are identified at the location of the eval statement itself instead of the eval buffer. Not only are you presented with the wrong buffer for the stack frame, but the line number is also incorrect -- it is calculated as the offset of the eval command in the code plus the offset of the line within the eval buffer itself.

Neither Firebug nor Venkman work very well given the Spidermonkey limitation. Thankfully, there is an experimental patch available to Firebug which addresses both of these problems. Unitl then, setting debugAtAllCosts in djConfig can help better identify the error. Alternatively, you can [SCRIPT] include the JavaScript files directly on the page, but that can be very tedious.

Firefox Safe Mode

If you are having weird problems with Firefox, it is often worthwhile running Firefox in safe mode. This is because installed extensions can interfere with the DOM tree, CSS, or even with javascript. In Windows there is a shortcut to start Firefox in safe mode from within the Mozilla Firefox folder, from the Start button.

Javascript debugger statement

Javascript has a debugger keyword that forces a breakpoint to occur. Just insert debugger; and if you have a debugger for your browser, then it will stop at the debugger keyword.

This is especially useful when using Venkman, because otherwise it can be difficult to get Venkman into debugging mode (e.g. you try to click on a line of source and it puts in a [F] future breakpoint).

It also works with Firebug and most other JavaScript debuggers.

There's also a lint program which catches a variety of problems in source files, but also flags certain patterns which are valid JavaScript, but considered by the author to be bad style.

Profiling

Performance problems are bugs too, and Dojo has built-in facilities for identifying slow-running code segments. Some JavaScript debuggers like Firebug give file-loading performance, but often you need information at the code level.

You can cheaply calculate function execution time by taking the difference between two JavaScript dates. It's not a great technique, because you have to make educated guesses as to where the inefficient areas are, and it may take quite a lot of work to home in on the problem area. But it is easy to do if you are measuring a single function:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000066; font-weight: bold;} .geshifilter .kw2 {color: #003366; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .co1 {color: #009900; font-style: italic;} .geshifilter .coMULTI {color: #009900; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #3366CC;} .geshifilter .nu0 {color: #CC0000;} .geshifilter .me1 {color: #006600;} .geshifilter .re0 {color: #0066FF;}
var startTime = new Date();
// call your function here
console.debug("Total time: " + (new Date() - startTime));

Another example of code to do this is on this page.

D.O.H. Unit Testing

Dojo (from 0.9 onward) features a from-scratch unit testing harness, D.O.H., that's both flexible, easy to use, and portable. D.O.H. is designed to run in both browser and command line environments and unlike it's predecessor doesn't need to be run from the build system. D.O.H. has no Dojo dependencies, but can use the Dojo package system if it's available, thereby allowing tests to be run without need of the build tools at all. D.O.H. also includes a browser-based test runner which can execute command-line tests as well as browser-specific tests. When run from the command line, only pure-JS test are run.

Test Registration

D.O.H. defines one global object (doh.*) and code building tests can use APIs defined on it to pass in a test, a group of tests, or a URL to pull a test group from:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000066; font-weight: bold;} .geshifilter .kw2 {color: #003366; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .co1 {color: #009900; font-style: italic;} .geshifilter .coMULTI {color: #009900; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #3366CC;} .geshifilter .nu0 {color: #CC0000;} .geshifilter .me1 {color: #006600;} .geshifilter .re0 {color: #0066FF;}
doh.registerTest(group, testFuncOrObj);
doh.registerTests(group, testFuncOrObjArr);
doh.registerTestNs(nsObj, objName);
doh.registerTestUrl(url);
doh.register(...);

The tests.register() method accepts the function signatures of any of the other registration functions and determines the correct underlying function to dispatch registration to.

The contents of a typical, command-line-only, test file might look something like: /* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000066; font-weight: bold;} .geshifilter .kw2 {color: #003366; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .co1 {color: #009900; font-style: italic;} .geshifilter .coMULTI {color: #009900; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #3366CC;} .geshifilter .nu0 {color: #CC0000;} .geshifilter .me1 {color: #006600;} .geshifilter .re0 {color: #0066FF;}
// file located in core at:
//      tests/moduleToBeTested.js
dojo.provide("tests.moduleToBeTested");
dojo.require("doh.runner");
doh.register("tests.moduleToBeTested",
        [
                // single test, no test fixture
                function assertTrueTest(){
                        doh.assertTrue(true);
                        doh.assertTrue(1);
                        doh.assertTrue(!false);
                },
                // a test fixture
                {
                        name: "thingerTest",
                        setUp: function(){
                                this.thingerToTest = new Thinger();
                                this.thingerToTest.doStuffToInit();
                        },
                        runTest: function(){
                                doh.assertEqual("blah", this.thingerToTest.blahProp);
                                doh.assertFalse(this.thingerToTest.falseProp);
                                // ...
                        },
                        tearDown: function(){
                        }
                },
                // ...
        ]
);

In this example, we see a variant of the test system that uses the doh.addTests() style of add() and registers both independent tests and fixture-driven tests. Note that we give the functions that are registered names even though we could easily provide anonymous functions. This allows the system to more correctly report on what went wrong and where, allowing you to talk more intelligently about your tests passing or failing. The fixture-based example uses the "name" property to provide the same information.

Assertions

D.O.H. exposes a small but adequate number of assertion APIs:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000066; font-weight: bold;} .geshifilter .kw2 {color: #003366; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .co1 {color: #009900; font-style: italic;} .geshifilter .coMULTI {color: #009900; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #3366CC;} .geshifilter .nu0 {color: #CC0000;} .geshifilter .me1 {color: #006600;} .geshifilter .re0 {color: #0066FF;}
doh.assertEqual(expected, actual); // aliased to: doh.is(e, a)
doh.assertTrue(condition); // aliased to: doh.t(condition)
doh.assertFalse(condition); // aliased to: doh.f(condition)

Asynchronous Tests

D.O.H. provides direct support for asynchronous test cases. Writing asynchronous tests depends on a script context that "knows about" asynchronous execution (aka, a browser but not Rhino) and a slightly modified test authoring syntax:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000066; font-weight: bold;} .geshifilter .kw2 {color: #003366; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .co1 {color: #009900; font-style: italic;} .geshifilter .coMULTI {color: #009900; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #3366CC;} .geshifilter .nu0 {color: #CC0000;} .geshifilter .me1 {color: #006600;} .geshifilter .re0 {color: #0066FF;}
doh.register("tests.moduleToBeTested",
        // the async  text fixture
        {
                name: "thingerTest",
                timeout: 2000, // 2 seconds, defaults to half a second
                setUp: function(){
                        this.thingerToTest = new Thinger();
                        this.thingerToTest.doStuffToInit();
                },
                runTest: function(){
                        var testCondition = true;
                        var d = new doh.Deferred();
                        setTimeout(function(){
                                try{
                                        if(testCondition){
                                                d.callback(true);
                                        }else{
                                                d.errback(new Error("we got a failure"));
                                        }
                                }catch(e){
                                        d.errback(e);
                                }
                        }, 100);
                        return d;
                }
        }
);

Note that in the above example, the runTest function explicitly returns a doh.Deferred object. This is how the system knows that you are going to be testing potentially asynchronous conditions. Also, in our delayed call (see the setTimeout), we explicitly catch errors and pass them to the Deferred's errback() function. It is expected that your code will do this if you are testing asynchronous conditions. Lastly, you may specify a timeout in milliseconds as part of the fixture object.

As with the previous examples, you can specify an anonymous or single function in place of a the full fixture used here to handle asynchronous cases. The only caveat is that it must return a Deferred object in order to be treated as an async test. We can also simplify the above by using the tests.Deferred classes getTestCallback() method. Here's a simplified async test case:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000066; font-weight: bold;} .geshifilter .kw2 {color: #003366; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .co1 {color: #009900; font-style: italic;} .geshifilter .coMULTI {color: #009900; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #3366CC;} .geshifilter .nu0 {color: #CC0000;} .geshifilter .me1 {color: #006600;} .geshifilter .re0 {color: #0066FF;}
doh.register("tests.moduleToBeTested", function simplerAsyncTest(){
        var testCondtition = true;
        var d = new doh.Deferred();
        var checkCondition = function(){
                doh.assertTrue(testCondition);
        };
        setTimeout(d.getTestCallback(checkCondition), 100);
        return d;
});

While the test case still needs to pass back a Deferred object, the use of the getTestCallback() to wrap the success or failure test function allows us to stop manually handling exceptions that might be thrown in the callback function, specifically from assertTrue(), assertEqual(), or assertFalse().

Group Registration

Many times, it's advantageous to register an entire group of tests at once. D.O.H. provides a method for doing this as well as for registering group-level setUp and tearDown methods. The tests.registerGroup(name, tests, setUp, tearDown) method lets you handle this in a single call:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000066; font-weight: bold;} .geshifilter .kw2 {color: #003366; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .co1 {color: #009900; font-style: italic;} .geshifilter .coMULTI {color: #009900; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #3366CC;} .geshifilter .nu0 {color: #CC0000;} .geshifilter .me1 {color: #006600;} .geshifilter .re0 {color: #0066FF;}
// file located in core at:
//      tests/fullGroupTest.js
dojo.provide("tests.fullGroupTest");
dojo.require("tests.runner");
doh.registerGroup("tests.fullGroupTest",
        [
                // single test, no test fixture
                function assertTrueTest(t){ t.t(true); },
                // string variant of the same:
                "doh.t(true);",
                // test that uses variable set up by group
                function assertTrueTest(t){
                        t.t(tests.fullGroupTest._localVariable);
                },
                // ...
        ],
        function(){ // setUp
                tests.fullGroupTest._localVariable = true;
        },
        function(){ // tearDown
                tests.fullGroupTest._localVariable = false;
        }
);

Note that when using registerGroup, setUp and tearDown replace existing group-level handlers, but the registered tests are additive to any pre-existing tests registered for the group.

The above example also introduces yet another shorthand for writing tests, the string-only test. This style of test authoring is particularly terse. Tests written this way do not provide explicit fixture names and so the test code itself is used as the test name in reporting. In these tests, there is also always a variable t which is an alias to the global tests variable. This allows for very compact tests to be written in the form:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000066; font-weight: bold;} .geshifilter .kw2 {color: #003366; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .co1 {color: #009900; font-style: italic;} .geshifilter .coMULTI {color: #009900; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #3366CC;} .geshifilter .nu0 {color: #CC0000;} .geshifilter .me1 {color: #006600;} .geshifilter .re0 {color: #0066FF;}
[
        "doh.t(true);",
        "doh.f(!true);",
        "doh.is('thinger', 'thing'+'er')",
        // ...
]

With group registration, this style of test authoring requires very little typing. Just mind your string quotes!

URL-based Testing

Being developed explicitly to test JavaScript applications, D.O.H. includes features for browser-based test harnesses to load sub-documents which may run a set of tests explicitly on a browser-provided DOM. This lets you automate UI testing and isolate browser-specific bugs by writing tests once and quickly running them through the unified test harness UI. To support this, browser runtimes for D.O.H. provide an implementation for tests.registerUrl(groupName, url). On other environments, this may be a no-op.

A real example from Dojo Core:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000066; font-weight: bold;} .geshifilter .kw2 {color: #003366; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .co1 {color: #009900; font-style: italic;} .geshifilter .coMULTI {color: #009900; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #3366CC;} .geshifilter .nu0 {color: #CC0000;} .geshifilter .me1 {color: #006600;} .geshifilter .re0 {color: #0066FF;}
doh.registerUrl("tests._base.NodeList", dojo.moduleUrl("tests", "_base/NodeList.html"));

This example uses Dojo to normalize the tested URL with relationship to the loading code, but you can just as easily specify a full URL manually. Just be aware that in order for D.O.H. to be able to record the results of tests from this page, it must be hosted on the same domain as the hosting test harness.

But what does the page itself look like? Here's a snapshot of the page referenced above:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<html>
        <head>
                <title>testing dojo.NodeList</title>
                <script type="text/javascript" src="../../dojo.js"
                        djConfig="isDebug: true">
</script>
                <script type="text/javascript">
                        <b>dojo.require("doh.runner");</b>
                        dojo.addOnLoad(function(){
                                doh.register("t",
                                        [
                                                function ctor(){
                                                        var nl = new dojo.NodeList();
                                                        nl.push(dojo.byId("c1"));
                                                        doh.assertEqual(1, nl.length);
                                                },
                                                // ...
                                        ]
                                );
                                <b>doh.run();</b>
                        });
                </script>
        </head>
        <body>
                <h1>testing dojo.NodeList</h1>
                <div id="t">
                        <span id="c1">c1</span>
                </div>
        </body>
</html>

The above code has several important features (in bold). First, we ensure that the test system itself is loaded into the tested page. Without this, the tests won't run. D.O.H. is smart enough to know if it's being loaded into a child frame or as a parent document. If you load this file into a normal browser window, the tests will still run, but you won't get the pretty D.O.H. chrome or audio feedback. Instead, the results of only the tests from this page will be sent to whatever console facility is available.

The second important feature of our tested URL is that it manually calls tests.run(), in this case after the page has been loaded and tests have been registered (a good time to do it). There are Dojo-isms in the test page, but they don't affect the important bits of the system. You can still load the test system with <script> tags and hard-wired URLs and this file would participate in the larger test group correctly.

Since testing on loaded pages may take a long time (relatively), a default timeout of 10 seconds per URL is provided. If your tested page requires more (or less) time, you can pass an explicit timeout parameter to the tests.registerUrl method:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000066; font-weight: bold;} .geshifilter .kw2 {color: #003366; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .co1 {color: #009900; font-style: italic;} .geshifilter .coMULTI {color: #009900; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #3366CC;} .geshifilter .nu0 {color: #CC0000;} .geshifilter .me1 {color: #006600;} .geshifilter .re0 {color: #0066FF;}
doh.registerUrl(
        "tests._base.NodeList",
        dojo.moduleUrl("tests", "_base/NodeList.html"),
        5000); // 5000ms, or 5 seconds

Running the tests in Rhino

Here are some instructions for running the tests in Rhino. Ideally they should run in any Rhino version 1.6R4 or later, however it has only be verified to work with the custom_rhino.jar in Dojo's util repository (it is in util/buildscripts/lib). Steps:

> svn svn co http://svn.dojotoolkit.org/dojo/view/anon/all/trunk dojo_0.9
> cd dojo_0.9
> cd util/doh
> java -jar ../buildscripts/lib/custom_rhino.jar runner.js

NOTE:Do not use a built version of dojo when trying to run DOH through the Rhino command line. The built versions of Dojo that are available for download are optimized for use in the browser, and they do not have the auto-detection logic to load the right environment code for Rhino. So be sure to use a source distribution when running the DOH tests in Rhino.

Including Tests From Custom Modules

FIXME: TODOC

Including Tests Without Dojo

FIXME: TODOC

Performance Optimization

So you've built an application using Dojo and it "feels slow". Now what? Unfortunately, you can't send the latest Dual Core processors to your users But fortunately, experience has taught us that in JavaScript, smaller = faster, no matter what the client. So basically, you want to concentrate on the size of your pages. The smaller they are, the less time it takes to download, parse and execute them.

By default, all dojo.require() statements try to find the module in memory first. If it's not present, it will ask the server for a "full sized" version of the code in a synchronous manner , and incurring network I/O overhead. Synchronous network requests from a browser are necessary for loading external code because it's the only way to block execution of JavaScript while dependencies are satisfied. Once the code is fecthed it is then eval()'d and execution picks up after the dojo.require() statement.

Unfortunately synchronous IO requests also have the effect of "locking" the UI of the browser, preventing loading other resources affecting the layout of the page. Each request is serial. The package system doesn't know enough to try to request multiple files at once. To be fair, scripts included in a page via the <script> tag also suffer from the same serial behavior.

But there are still plenty of things you can do:

  • Use Dojo's custom build system. A custom build of Dojo will improve the download of the page by grouping related modules into one script, and optimizing it for fast parsing. See The Package System and Custom Builds for more information.

  • Configure the web server to cache JavaScript files aggressively and send status information quickly. Servers that don't send adequate cache information may find that UIs are very slow even when the script content isn't sent. Instead, browsers may be checking to see if the file has changed since it was last seen. This "has it changed?" check is synchronous and serial and so we want to eliminate it if possible.
  • Use data compression and application program structure as your server permits. See the article Improving performance of Dojo-based web applications which contains useful information on these topics.
  • If you find IE is repeatedly downloading the same image, read this article for advice. This also helps get rid of ugly image flickering in IE.
  • Reduce then number of tags on a page.

    It sounds obvious, but many web pages have a lot of unnecessary markup. For example, instead of /* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}

    <table ...><BR></BR>  <tr>
    <td>Hello World</td>
    </tr>
    </table>
    just do: /* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
    <div class="foo">Hello World</div>
    To achieve this, you definitely want to leverage CSS. The web standards movement has championed this approach, and Dijit uses it for reducing the widget download time.
  • Instead of downloading your whole page at once, defer portions until the user needs them. The judicious use of ContentPanes nested inside containers /* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
    <div dojoType="TabContainer">
        <a dojoType="LinkPane" href="tab1.jsp">Tab #1</a>
        <a dojoType="LinkPane" href="tab2.jsp">Tab #2</a>
    </div>

The Package System and Custom Builds

A Dojo custom build speeds performance by doing the following:

  1. First, it groups together modules into layers. A layer, which is one big .js file, loads faster than the individual .js modules that comprise it
  2. Second, it interns external non-JavaScript files. This is most important for Dijit templates, which are kept in a separate HTML file. Interning pulls the entire file in and assigns it to a string.
  3. Third, it smooshes the layer down with ShrinkSafe. ShrinkSafe removes unneeded whitepsace and comments, and compacts variable names down to smaller ones. This file downloads and parses faster than the original.
  4. Finally, it copies all non-layered scripts to the appropriate places. While this doesn't speed anything up, it ensures that all Dojo modules can be loaded, even if not present in a layer. If you use a particular module only once or twice, keeping it out of the layers makes those layers load faster.

The catch? You have to designate the modules in each layer with a profile, which is something like a Makefile or Ant script. But that's not too hard if you know your app well.

So the input of the build system is the Dojo source tree, plus any source trees for custom stuff you wish to include ... plus the profile. The output is a Dojo distribution tree which you can copy to your web server. Sweet!

Prerequisites

You need the following installed on your computer to run Dojo's build system:

Creating a Custom Profile

In the util/buildscripts/profiles directory, you will create a profile build file called foo.profile.js like this:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000066; font-weight: bold;} .geshifilter .kw2 {color: #003366; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .co1 {color: #009900; font-style: italic;} .geshifilter .coMULTI {color: #009900; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #3366CC;} .geshifilter .nu0 {color: #CC0000;} .geshifilter .me1 {color: #006600;} .geshifilter .re0 {color: #0066FF;}
dependencies ={
    layers:  [
        {
        name: "mydojo.js",
        dependencies: [
            "dijit.Button",
            "dojox.wire.Wire",
            "dojox.wire.XmlWire",
            "explosive.space.Modulator"
        ]
        }
    ],
    prefixes: [
        [ "dijit", "../dijit" ],
        [ "dojox", "../dojox" ],
        [ "explosive", "../../explosive" ]
    ]
};

The dependencies section within the layer lists all the modules you call directly. Any referenced modules will also be included, so you don't have to trace back the dependency tree. Also, Dojo base modules are an implicit dependency, so you don't need to list things like "dojo.query". (Dojo core modules, however, do need to be listed.)

The modules for that layer are gathered together to make the "layer" file, in our example: "mydojo.js". Then you just load this layer file in your pages with a SCRIPT tag. Easy!

The prefixes section list any modules that need inclusion. Note our "explosive" module, which is located away from the Dojo tree. You need to list these if you use them, even if you don't want any modules from it in your layer file.

For the 1.0+: If you choose to optimize the JS files in a prefix directory (via the optimize= build parameter), you can choose to have a custom copyright text prepended to the optimized file. To do this, specify the path to a file that contains the copyright info as the third array item in the prefixes array. For instance:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000066; font-weight: bold;} .geshifilter .kw2 {color: #003366; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .co1 {color: #009900; font-style: italic;} .geshifilter .coMULTI {color: #009900; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #3366CC;} .geshifilter .nu0 {color: #CC0000;} .geshifilter .me1 {color: #006600;} .geshifilter .re0 {color: #0066FF;}
prefixes: [
    [ "explosive", "../../explosive", "../../explosive/copyright.txt"]
]

If no copyright is specified in this optimize case, then by default, the dojo copyright will be used.

Running The Build

After specifying a profile file as shown above that statically specifies the resources you want to include, and saving it as /buildscripts/profiles/foo.profile.js, you run the Rhino interpreter on it and specify the profile name as a parameter. For example, from the buildscripts directory:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw3 {color: #000066;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .re0 {color: #0000ff;} .geshifilter .re1 {color: #0000ff;} .geshifilter .re2 {color: #0000ff;} .geshifilter .re3 {color: #808080; font-style: italic;} .geshifilter .re4 {color: #0000ff;}
$ cd util/buildscripts
$ build.sh profile=foo action=release

On Windows PC's, substitute build.bat for build.sh. For both platforms, you may also specify additional build options. Run build.sh to see a list of all supported options. Here is a sample of the supported options:

profile The name of the profile to use for the build. It must be the first part of the profile file name in the profiles/ directory. For instance, to use base.profile.js, specify profile=base. Default: base
profileFile A file path to the the profile file. Use this if your profile is outside of the profiles directory. Do not specify the "profile" build option if you use "profileFile" Default: "",
action The build action(s) to run. Can be a comma-separated list, like action=clean,release. The possible build actions are: clean, release Default: "help",
version The build will be stamped with this version string Default: "0.0.0.dev",
localeList The set of locales to use when flattening i18n bundles Default: "en-gb,en-us,de-de,es-es,fr-fr,it-it,pt-br,ko-kr,zh-tw,zh-cn,ja-jp",
releaseName The name of the release. A directory inside 'releaseDir' will be created with this name Default: "dojo",
releaseDir The top level release directory where builds end up. The 'releaseName' directories will be placed inside this directory Default: "../../release/",
loader The type of dojo loader to use. "default" or "xdomain" are acceptable values." defaultValue: "default",
internStrings Turn on or off widget template/dojo.uri.cache() file interning Default: true,
optimize Specifies how to optimize module files. If "comments" is specified, then code comments are stripped. If "shrinksafe" is specified, then the Dojo compressor will be used on the files, and line returns will be removed. If "shrinksafe.keepLines" is specified, then the Dojo compressor will be used on the files, and line returns will be preserved. If "packer" is specified, Then Dean Edwards' Packer will be used Default: "",
layerOptimize Specifies how to optimize the layer files. If "comments" is specified, then code comments are stripped. If "shrinksafe" is specified, then the Dojo compressor will be used on the files, and line returns will be removed. If "shrinksafe.keepLines" is specified, then the Dojo compressor will be used on the layer files, and line returns will be preserved. If "packer" is specified, Then Dean Edwards' Packer will be used Default: "shrinksafe",
copyTests Turn on or off copying of test files Default: true,
log Sets the logging verbosity. See jslib/logger.js for possible integer values Default: logger.TRACE,
xdDojoPath If the loader=xdomain build option is used, then the value of this option will be used for the path to Dojo modules. The dijit and dojox paths will be assumed to be sibilings of this path. The xdDojoPath should end in '/dojo' Default: "",

Cross Domain (XDomain) Builds

Doing an xdomain build allows you to load Dojo and your custom modules from another domain.

Benefits

  • You get more connections in MSIE, since you can load from another domain. Faster loading.
  • You get increased cacheability/startup if many of your applications use the same installation.
  • Resource loading does not block the rest of the page from filling in as with Dojo's normal, synchronous loading.
  • With a local install, your ISP may charge you for all of those Dojo bits that you are serving.

Implications/Limitations

  • Not all external resources can be xdomain loaded, in particular some support files that need to be loaded from the same domain as the HTML page. See module-specifc notes below.
  • Requires a "xdomain" build of Dojo (see below for more info on how to make a xdomain build).
  • Asynchronous loading. You MUST use dojo.addOnLoad() to register a callback function to get notification of package loading. This can be used even after the initial page load. Just do the dojo.require()s that you need, and then call dojo.addOnLoad() with a callback function, and once those new packages are loaded (or if they are already loaded), then the callback will be called. This technique works even for the normal Dojo loader, so this is a good practice to use even when not using an xdomain build.
  • Avoid using document.write(): Since module can load asychnously, after the page is loaded, document.write can cause problems.

Module-specific Notes

dojo.io.iframe.create(), dijit.Editor, dijit._editor.RichText: You need to save dojo/resources/blank.html to the same domain as your HTML page and set djConfig.dojoBlankHtmlUrl to the path on that domain. Note: The dijit.Editor/dijit._editor.RichText has a bug in release 1.1.0 where it was not using this djConfig parameter. It is fixed in Dojo 1.1.1 and later.

dojo.back: You need to save dojo/resources/blank.html to the same domain as your HTML page and set djConfig.dojoIframeHistoryUrl to the path on that domain.

dojox.flash.Info() : It uses document.write() which will cause problems if dojox.flash is loaded via dojo.require().

Doing xdomain builds

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw3 {color: #000066;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .re0 {color: #0000ff;} .geshifilter .re1 {color: #0000ff;} .geshifilter .re2 {color: #0000ff;} .geshifilter .re3 {color: #808080; font-style: italic;} .geshifilter .re4 {color: #0000ff;}
$ cd util/buildscripts
$ build.sh profile=foo loader=xdomain xdDojoPath=http://my.server.com/path/to/buildoutputdir action=release

xdDojoPath is optional. It just burns in the location of dojo, dijit and dojox into the built dojo.js. If you do not specify that option, then you will need to use djConfig.modulePaths/dojo.registerModulePath() in your HTML page to set the xdomain locations for dojo, dijit and dojox. For your own custom modules, you will have to set djConfig.modulePaths/dojo.registerModulePath() even if you us the xdDojoPath build option.

For Dojo 0.9 through 1.1.x there is a bug about loading dojox.gfx with an xdomain build.. This is fixed in Dojo 1.2. If you want to use dojox.gfx with an xdomain build of Dojo 0.9-1.1.x, there are some workarounds until the bug gets fixed:

  1. Include dojox/gfx.js directly in your page with a script tag in the HTML source, after the dojo.js script tag (do not use gfx.xd.js, use gfx.js).
  2. Include dojox.gfx in a layer file that you load via a script tag in the HTML source (load the .js layer file, not the .xd.js layer file).

How to use xdomain builds in web pages

  • In djConfig, add useXDomain = true.
  • In djConfig, add a modulePaths object that maps where to find your modules.
  • Register a callback function to get notification of when the packages are loaded by using dojo.addOnLoad().
  • Optional: set a wait time in milliseconds (djConfig.xdWaitSeconds) that specifies how long the resource loader should wait for a resource to load until returning an error. Since script elements do not give information about failed or long-running requests, this timeout is used to prevent infinite waiting in the browser. An exception will be thrown to indicate a load error. The default xdWaitSeconds is 15.

XDomain Example

Here is an example showing how to load local modules along with an xdomain-loaded dojo and dijit. You can download this example.

Alternative Host Environments: Adobe AIR

Introduction to Adobe AIR and Dojo

Adobe AIR is a new runtime by Adobe. Adobe AIR allows web applications permission to the local file system that traditional browser-based web applications do not normally receive due to security policies.

As of Dojo Toolkit 1.1.0 Beta 1, Dojo-based applications will now run successfully in Adobe AIR's secure application sandbox.

Getting Started

  1. Download and install the Adobe AIR runtime and Adobe AIR SDK
  2. Download the latest version of Dojo
  3. Read the documentation below:

Modifications were required throughout the Dojo Toolkit to accommodate Adobe AIR's security policies for local web applications. Changes to Dojo were made in the following areas:

  • dojo.require() pre-onload
  • dojo.require() post-onload
  • dojo.addOnLoad()
  • dojo.query()
  • dojo.parser
  • dojo.fx
  • Dijit widget system
  • AIR dojox.storage provider

Creating the Development Environment

After installing the Adobe AIR runtime and the Adobe AIR SDK, you will find a bin directory inside the SDK containing the adl command used to execute an AIR application. By adding this bin folder to your path, you'll be able to execute the adl command from anywhere.

Start by creating an empty directory, and add Dojo to that directory.

Dojo's Package System

At the heart of the Dojo Toolkit is a package system that allows code to be loaded on demand. There are two loader mechanisms in Dojo: a default loader and a cross-domain (XDomain) loader. The default loader uses XmlHttpRequest (XHR) to retrieve the code. Once the code is downloaded, it is evaluated using eval(). This method is synchronous, so when a package is downloaded, its dependencies are also retrieved and executed. The XDomain loader uses script tags to retrieve dependencies, eliminating the need for evaluating the code after downloading. This method is asynchronous, so the XDomain loader has additional functionality for checking when the script has completely downloaded and to defer the declaration of the package until its dependencies have been loaded.

No debugAtAllCosts

Dojo has a configuration parameter called “debugAtAllCosts” which forces the use of the XDomain loader. This feature does not work properly with apps running on Adobe AIR. It is not required for developing Dojo applications, but needs to be disabled for applications to run on Adobe AIR.

dojo.require(): Using the default loader in the Adobe AIR Application Sandbox

Pre-onload

The default loader uses XHR to read in the contents of a package, then evaluates it. This functionality works as expected on Adobe AIR.

Post-onload

When the default loader gets the contents of a package, it evaluates the contents. Because eval() is prohibited post-onload in Aodbe AIR, dojo.require() cannot function post-onload. If dojo.require() is absolutely necessary post-onload, the XDomain loader is your only option. Otherwise simply define all your dojo.require()'s before or during onload which is analogous to other programming languages which require you to define all external code dependencies via an import/include at the beginning of the code file.

dojo.require() Using the default loader in the Adobe AIR Non-Application Sandbox

The default loader functions properly both pre-onload and post-onload.

dojo.require() Using the XDomain Loader

In order to use XDomain loader with apps running inside Adobe AIR, you must perform a custom build. Note the change that was required to make the XDomain loader work with Adobe AIR:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */
.geshifilter {font-family: monospace;}
.geshifilter .imp {font-weight: bold; color: red;}
.geshifilter .kw1 {color: #000066; font-weight: bold;}
.geshifilter .kw2 {color: #003366; font-weight: bold;}
.geshifilter .kw3 {color: #000066;}
.geshifilter .co1 {color: #009900; font-style: italic;}
.geshifilter .coMULTI {color: #009900; font-style: italic;}
.geshifilter .es0 {color: #000099; font-weight: bold;}
.geshifilter .br0 {color: #66cc66;}
.geshifilter .st0 {color: #3366CC;}
.geshifilter .nu0 {color: #CC0000;}
.geshifilter .me1 {color: #006600;}
.geshifilter .re0 {color: #0066FF;}

if(dojo.isAIR){
        this._xdTimer = setInterval(function(){dojo._xdWatchInFlight();}, 100);
}else{
        this._xdTimer = setInterval(dojo._scopeName + "._xdWatchInFlight();", 100);
}

Additionally, the _loadUri() function has to be modified to strip the app:/ protocol from the beginning of the URI since the app:/ protocol is unavailable in the non-application sandbox.

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */
.geshifilter {font-family: monospace;}
.geshifilter .imp {font-weight: bold; color: red;}
.geshifilter .kw1 {color: #000066; font-weight: bold;}
.geshifilter .kw2 {color: #003366; font-weight: bold;}
.geshifilter .kw3 {color: #000066;}
.geshifilter .co1 {color: #009900; font-style: italic;}
.geshifilter .coMULTI {color: #009900; font-style: italic;}
.geshifilter .es0 {color: #000099; font-weight: bold;}
.geshifilter .br0 {color: #66cc66;}
.geshifilter .st0 {color: #3366CC;}
.geshifilter .nu0 {color: #CC0000;}
.geshifilter .me1 {color: #006600;}
.geshifilter .re0 {color: #0066FF;}

if (dojo.isAIR) {
        xdUri = xdUri.replace("app:/", "/");
}

Next you need to define a build profile which tells the build system which files to bundle in the build. We are not concerned with combining packages into a single file as much as we are in the XDomain loader and generation of .xd versions of each package. The difference in the .xd files is the package's contents are wrapped in a function call that allows the package's execution to be deferred until it's dependencies have been loaded.

A build profile may look like the following:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */
.geshifilter {font-family: monospace;}
.geshifilter .imp {font-weight: bold; color: red;}
.geshifilter .kw1 {color: #000066; font-weight: bold;}
.geshifilter .kw2 {color: #003366; font-weight: bold;}
.geshifilter .kw3 {color: #000066;}
.geshifilter .co1 {color: #009900; font-style: italic;}
.geshifilter .coMULTI {color: #009900; font-style: italic;}
.geshifilter .es0 {color: #000099; font-weight: bold;}
.geshifilter .br0 {color: #66cc66;}
.geshifilter .st0 {color: #3366CC;}
.geshifilter .nu0 {color: #CC0000;}
.geshifilter .me1 {color: #006600;}
.geshifilter .re0 {color: #0066FF;}

// my-build-profile.js
dependencies = {
        layers: [
                {
                        name: "dojo.js",
                        dependencies: [
                                "dojo.parser",
                                "dijit.dijit"
                        ]
                },
        ],
        prefixes: [
                [ "dijit", "../dijit" ],
                [ "dojox", "../dojox" ]
        ]
}

To run the build, execute the following from the command line:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */
.geshifilter {font-family: monospace;}
.geshifilter .imp {font-weight: bold; color: red;}
.geshifilter .kw1 {color: #b1b100;}
.geshifilter .kw3 {color: #000066;}
.geshifilter .es0 {color: #000099; font-weight: bold;}
.geshifilter .br0 {color: #66cc66;}
.geshifilter .st0 {color: #ff0000;}
.geshifilter .nu0 {color: #cc66cc;}
.geshifilter .re0 {color: #0000ff;}
.geshifilter .re1 {color: #0000ff;}
.geshifilter .re2 {color: #0000ff;}
.geshifilter .re3 {color: #808080; font-style: italic;}
.geshifilter .re4 {color: #0000ff;}

cd dojo/util/buildscripts
./build.sh profileFile=../../../my-build-profile.js action=clean,release
releaseDir=../../../ releaseName=MyBuild loader=xdomain xdDojoPath=app:/MyBuild

Now you can reference the build in your code by including the following:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */
.geshifilter {font-family: monospace;}
.geshifilter .imp {font-weight: bold; color: red;}
.geshifilter .kw1 {color: #b1b100;}
.geshifilter .kw2 {color: #000000; font-weight: bold;}
.geshifilter .kw3 {color: #000066;}
.geshifilter .coMULTI {color: #808080; font-style: italic;}
.geshifilter .es0 {color: #000099; font-weight: bold;}
.geshifilter .br0 {color: #66cc66;}
.geshifilter .st0 {color: #ff0000;}
.geshifilter .nu0 {color: #cc66cc;}
.geshifilter .sc0 {color: #00bbdd;}
.geshifilter .sc1 {color: #ddbb00;}
.geshifilter .sc2 {color: #009900;}

<script type="text/javascript" src="/MyBuild/dojo/dojo.xd.js.uncompressed.js" />

dojo.require() will now work pre-onload and post-onload.

dojo.addOnLoad()

dojo.addOnLoad() registers callback functions to be executed when the page loads. The default loader properly executes the callback functions before the page's onload event is fired. The XDomain loader on the other hand fires the onload callbacks after the body's onload has fired. This causes problems for the XDomain loader in the application sandbox if parseOnLoad is enabled since Dojo will be unable to parse and eval() the data post-onload.

dojo.query()

Dojo has a query function that is used to search the DOM for nodes. The default query function works for simple queries such as “div” or “li > a”. However, the default query function is unable to find nodes based on a specific attribute. For example, the default query function is unable to locate the element /* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */
.html4strict .imp {font-weight: bold; color: red;}
.html4strict .kw1 {color: #b1b100;}
.html4strict .kw2 {color: #000000; font-weight: bold;}
.html4strict .kw3 {color: #000066;}
.html4strict .coMULTI {color: #808080; font-style: italic;}
.html4strict .es0 {color: #000099; font-weight: bold;}
.html4strict .br0 {color: #66cc66;}
.html4strict .st0 {color: #ff0000;}
.html4strict .nu0 {color: #cc66cc;}
.html4strict .sc0 {color: #00bbdd;}
.html4strict .sc1 {color: #ddbb00;}
.html4strict .sc2 {color: #009900;}
<button dojoType=”dijit.form.Button”>Click Me</button> using the
query “[dojoType]”. This was fixed by the following change to Dojo:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */
.geshifilter {font-family: monospace;}
.geshifilter .imp {font-weight: bold; color: red;}
.geshifilter .kw1 {color: #000066; font-weight: bold;}
.geshifilter .kw2 {color: #003366; font-weight: bold;}
.geshifilter .kw3 {color: #000066;}
.geshifilter .co1 {color: #009900; font-style: italic;}
.geshifilter .coMULTI {color: #009900; font-style: italic;}
.geshifilter .es0 {color: #000099; font-weight: bold;}
.geshifilter .br0 {color: #66cc66;}
.geshifilter .st0 {color: #3366CC;}
.geshifilter .nu0 {color: #CC0000;}
.geshifilter .me1 {color: #006600;}
.geshifilter .re0 {color: #0066FF;}

// uncomment to disable XPath for testing and tuning the DOM path
_getQueryFunc = getStepQueryFunc;

dojo.fx

Dojo's FX library works as expected in both Adobe AIR's application and non-application sandboxes as well as with both the default and XDomain loaders.

dojo.parser

Dojo's parser scans the DOM using dojo.query() for declarative markup. If markup is found, it is evaluated. The parser can either be invoked automatically when the page loads or manually. The parser can be executed onload by specifying the following in the djConfig:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */
.html4strict .imp {font-weight: bold; color: red;}
.html4strict .kw1 {color: #b1b100;}
.html4strict .kw2 {color: #000000; font-weight: bold;}
.html4strict .kw3 {color: #000066;}
.html4strict .coMULTI {color: #808080; font-style: italic;}
.html4strict .es0 {color: #000099; font-weight: bold;}
.html4strict .br0 {color: #66cc66;}
.html4strict .st0 {color: #ff0000;}
.html4strict .nu0 {color: #cc66cc;}
.html4strict .sc0 {color: #00bbdd;}
.html4strict .sc1 {color: #ddbb00;}
.html4strict .sc2 {color: #009900;}
<script type="text/javascript" src="path/to/dojo.js" djConfig="parseOnLoad:true" />

The parser will then scan the DOM for declarations such as:

<button id="myButton" dojoType="dijit.form.Button">Click me!>/button> 
<script type="text/javascript"< 
dojo.declare("tests.parser.SaySomething", null, { 
	sayWhat: "", 
	constructor: function(args, node){ dojo.mixin(this, args); }, 
	sayIt: function() { alert(this.sayWhat); } 
}); 
</script> 
<div dojoType="tests.parser.SaySomething" jsId="obj" sayWhat="Hello World!"> 
<script type="dojo/method" event="sayItReversed">
	alert(this.sayWhat.split("").reverse().join("")); 
</script> 
<script type="dojo/connect" event="sayIt"> 
	alert("sayIt() was called!"); 
</script> 
</div>

The parser works in both application and non-application sandboxes as well as with the default loader. When using the XDmain loader, the parser works as expected in the non-application sandbox. However, the XDomain loader fails in the application sandbox because the parser cannot
eval() post-onload inside Adobe AIR.

Dijit: The Dojo Widget System

Dijit widgets work both declaratively and programatically using the both the default loader in both application and non-application sandboxes. Dijit does not work properly with the XDomain loader since parser doesn't fire correctly.

AIR dojox.storage Providers

There are 3 new storage providers that leverage the AIR's API:

  • dojox.storage.AirFileStorageProvider
  • dojox.storage.AirDBStorageProvider
  • dojox.storage.AirEncryptedLocalStorageProvider

When using the XDomain loader inside Adobe AIR, dojox.storage is unavailable until onload unless dojox.storage and the providers you wish to use are included in the XDomain build.

These 3 AIR specific storage providers are only available in the application sandbox. It would take a large amount of abstraction to each provider with respect to the AIR API calls plus a significant sized snippet of code in the parent window to perform the storage operation.

AirFileStorageProvider

The AirFileStorageProvider store the data in flat files in the app-storage directory. For example, a key of “MyKey”, value of “Hello World!”, and namespace of “MyNamespace” would create the file app-storage:/__DOJO_STORAGE/MyNamespace/MyKey which contains the text “Hello World!”. This storage provider allows for large amounts of data to be stored. Not only can strings be stored, but also objects thanks to AIR's serialize/de-serialize functionality. On the downside, data is not encrypted on disk. It would be trivial however to encrypt the information using dojox.crypto before storing the data.

AirDBStorageProvider

The AirDBStorageProvider leverages AIR's embedded database. When the provider is initialized, a database file is created in the app-storage directory and the Dojo storage table is created. The table holds the namespace, key, and value. This provider is similar to the AirFileStorageProvider in which it can store large amounts, but it cannot store serialized objects because there is no way to de-serialize them due to eval() being unavailable post-onload. The database file is not encrypted, but you could encrypted the data using dojo.crypto prior to storing the data. One advantage of the AirDBStorageProvider is there is only one file written to disk whereas the AirFileStorageProvider writes a file for each key/value.

AirEncryptedLocalStorageProvider

The AirEncryptedLocalStorageProvider uses AIR's encrypted local data store functions. Data, such as passwords, will be encrypted when being stored and decrypted when being retrieved. Similar to the AirEncryptedLocalStorageProvider, objects cannot be stored because they cannot be
de-serialized with an eval(). One limitation of the AIR's encrypted local data store is it does not provide a way to enumerate keys. To solve this, the AirEncryptedLocalStorageProvider creates a registry using AIR's encrypted local data store to track the namespaces and keys.

Default vs. XDomain Loader

The default loader cannot dojo.require() post-onload, however the XDomain loader can.
The XDomain loader requires a custom build, which in turn requires Java runtime environment installed. The default loader does not require a build, but would work if it was a custom build. If you are using the XDomain loader, unless the package you are requiring is already in the build, you cannot reliably access it until onload is fired.

There may be caching issues with the Xdomain loader pulling in code that not already in the build on subsequent requests, which causes out of order execution of code before dependencies have loaded. It is recommended to bundle all needed packages into the XDomain build. The XDomain loader fires dojo.addOnLoad() callbacks after onload which causes Dojo's parser in the application sandbox to fail because it cannot eval() post onload inside Adobe AIR.

Application vs. Non-Application Sandbox

The non-application sandbox does not recognize the app-storage:/ and app:/ protocols. Due to the lack of eval() post-onload, the XDomain loader doesn't work in the application sandbox when the parser is used.

application.xml

Here is an example of an application.xml file for a Dojo-based web application:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */
.geshifilter {font-family: monospace;}
.geshifil