Part 1: Life With Dojo - Dojo and Dijit Application Examples

Dojo applications look good, but their primary benefit is in helping real people solve real interaction problems in real web application. Dojo makes it easy to design a more usable web experience for the intended audience.

The following personas illustrate how people with different goals and skill sets can make Dojo work for them. We will follow them working through an example. The personas and examples are made-up, but broadly represent who the toolkit is built for and each section of the book is designed to help solve problems for each of them, sometimes more for one than the others, but always for their users:

John Walsh is a Web Developer. He’s been out of college for 3 years and he works for a small company that creates web sites for clients. He lives and breathes HTML and CSS. He has some basic JavaScript experience, for example with click handlers.  He knows a lot about Photoshop, but if you ask him most days, he doesn’t really consider himself to be a Designer. Several of his older co-workers would call themselves Designers and only incidentally Web Developers. John and his co-workers care greatly about how an interface looks, they are completely sold on CSS, and they want their tools to work the way they think they should.

Andy Tso has been doing the "startup thing" for nearly a decade. He's seen it all and is a very discerning consumer of technology. He couldn't get enough of his CS and math courses when he was at Stanford. After graduation Andy didn't really know where he wanted to go, so he started on an advanced CS degree at MIT but dropped when some of his other friends left to found an e-commerce thing in '98. It imploded quickly but by that time he'd caught the startup bug. His current startup is pushing the edges of what you can (or should) do in a browser and when they started investigating Dojo, they saw it wasn't everything they needed, but certainly a good starting point. Andy is the kind of guy who could have written Dojo but is wise enough not to. He might contribute patches, though. Andy's main problem is getting through the gunk to the hard tech docs and giving his junior Developers something to work from.

Laura Allen is an Enterprise IT Developer. She has worked for the same (medium sized) company over the past 15 years as it has been bought out twice and renamed three times. She supports internal development sites and relies on tools and frameworks all day long. For her, Web2.0 is tremendously exciting. She didn't know you could do much of anything in a browser, but things that aren't Java/PHP scare her a bit. She’s heard that Microsoft mentioned a toolkit too but her manager saw a Dojo demo at a conference and now he’s pushing his teams to investigate Dojo.

Example 1: Why Doesn't Anyone Fill Out Their Tax Forms?

John Walsh lives in the country of Googolica. This country is the first in the world to require its citizens to file their taxes online. Here's the form they use:

2007 Tax Form

The Sovreign Nation of Googolica, In Search We Trust

First Name:
Last Name:
Email Address:

  1. Please Enter Your 2007 Gross Income
  2. Please enter the value from line 1. This is your 2007 tax
  3. Would you like to contribute an extra $3 to the Presidential Campaign Fund?


To the astonishment of the Googolican government, people have not been using the form. The Parliament investigated. The problem, they first thought, might be the brutal tax percentage - 100%. This theory was quickly dismissed.

The final report concluded the web page is faulty, thus discouraging it use. Among its problems:

  • It's ugly. Really ugly.
  • The form does nothing to help the user. It does not clean up mistakes. And it reports errors after the form has been submitted, leaving the user to stare at a blank form again.
  • Googolica has two official languages, English and Snobol. Snobol-speakers are out of luck. And bifurcating the page into an English and a Snobol version makes development twice as difficult.
  • People with special needs - low vision, motor problems that discourage mouse use - are out of luck.
  • MORE...

Fortunately, the Googolican IT department employs John Walsh. And John has just downloaded Dojo and Dijit. Things are looking up!

Adding Dijit

Let's look at the HTML code for that 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: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<head>
    <title>Taxes, The Surest Thing Next to Death!</title>
</head>
<body>
<h1>2007 Tax Form</h1>
    The Sovereign Nation of Googolica,  In Search We Trust
<form>
    First Name: <input type="text" length="20" name="first"><br>
    Last Name: <input type="text" length="20" name="last"><br>
    Email Address: <input type="text" length="20" name="email"><br>
<hr>
<ol>
    <li>
        Please Enter Your 2007 Gross Income
        <input type="text" length="10" name="grossIncome">
    </li>
    <li>
        Please enter the value from line 1.  This is your <em>2007 tax</em>
        <input type="text" length="10" name="tax">
   </li>
    <li>
        Would you like to contribute an extra $3 to the Presidential Campaign Fund?
        <input type="checkbox" name="campaign" value="Y">
    </li>
    <li>
        Filing Date:
        <input type="text" length="10" name="filingDate">
    </li>
</ol>
<input type="submit" value="Submit">
</form>
</body>

Pretty standard stuff. Using Dojo we can improve the UI just a few lines of Javascript and some extra attributes on our existing markup.

The magic is in Dijit - shorthand for "dojo widgets". Dijit widgets perform all sorts of tasks, from embelllishing a form control, to controlling the layout of sections and beyond.

Preliminaries for Dijit

You must add two snippets of code for every page using dijits:

  • A HEAD snippet which loads the style sheet and Dojo libraries, then calls functions to load individual dijit types.
  • A class to the BODY tag specifying the name of your theme. In our examples, we'll use the "Tundra" theme.

Our examples will use the America Online hosted version of Dojo, so you don't have to install one bit of Dojo code! Just copy and paste them to a file on your server, and they will work. So here's the HEAD snippet:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
            "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Taxes, The Surest Thing Next to Death!</title>
    <style type="text/css">
        @import "http://o.aolcdn.com/dojo/1.0.0/dijit/themes/tundra/tundra.css";
        @import "http://o.aolcdn.com/dojo/1.0.0/dojo/resources/dojo.css"
    </style>
    <script type="text/javascript" src="http://o.aolcdn.com/dojo/1.0.0/dojo/dojo.xd.js"
        djConfig="parseOnLoad: true">
</script>
    <script type="text/javascript">
       dojo.require("dojo.parser");
    </script>
</head>

Alternatively, if you're a do-it-yourselfer running Dojo from your local site, you just make a few changes to the header:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
            "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Taxes, The Surest Thing Next to Death!</title>
    <style type="text/css">
        @import "/dojoroot/dijit/themes/tundra/tundra.css";
        @import "/dojoroot/dojo/resources/dojo.css"
    </style>
    <script type="text/javascript" src="/dojoroot/dojo/dojo.js"
        djConfig="parseOnLoad: true">
</script>
    <script type="text/javascript">
       dojo.require("dojo.parser");
    </script>
</head>

All of the remaining examples will use the AOL version of the header. The important thing is: none of the rest of the code changes. You can use AOL CDN while you're trying out Dojo, then just change the first lines when/if you install you're own.

This is a fairly standard block. If you have access to a server-side language like PHP or ASP, it's handy to place the header in a separate file and include it.

Tundra is the default theme for dijit, a theme being a standard color and design scheme across elements. Themes are discussed at length in Themes, and dijit comes preloaded with a few very nice ones, or you can create your own.

The second snippet is trivial:

/* 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;}
<body class="tundra">

But if you don't do it, your widgets will look very strange on screen. They rely almost entirely on CSS.

Turning Ordinary INPUT Tags into Widgets

Dijit introduces a new attribute "dojoType". You simply add that to the tag you wish to "dijit-ize". /* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}

<form>
First Name: <input type="text" length="20" name="first"  dojoType="dijit.form.TextBox"><br>
Last Name: <input type="text" length="20" name="last"  dojoType="dijit.form.TextBox"><br>
Email Address: <input type="text" length="20" name="email"  dojoType="dijit.form.TextBox">
Filing Date: <input type="text" length="10" name="filingDate" dojoType="dijit.form.DateTextBox">
<hr>
<ol>
<li>Please Enter Your 2007 Gross Income
<input type="text" length="10" name="grossIncome"  dojoType="dijit.form.TextBox"></li>
<li>Please enter the value from line 1.  This is your <em>2007 tax</em>
<input type="text" length="10" name="tax"  dojoType="dijit.form.TextBox"></li>
<li>Would you like to contribute an extra $3 to the Presidential Campaign Fund?
<input type="checkbox" name="campaign" value="Y"  dojoType="dijit.form.CheckBox"></li>
</ol>

We use the dojoTypes dijit.form.TextBox and dijit.form.Checkbox. Now that we know this, we can fill in some code in the head snippet:

/* 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;}
<script type="text/javascript">
    dojo.require("dojo.parser");
    dojo.require("dijit.form.TextBox");
    dojo.require("dijit.form.CheckBox");
    dojo.require("dijit.form.DateTextBox");
</script>

What Did We Get?

The form is looking better already:

First Name:
Last Name:
Email Address:
Filing Date:

  1. Please Enter Your 2007 Gross Income
  2. Please enter the value from line 1. This is your 2007 tax
  3. Would you like to contribute an extra $3 to the Presidential Campaign Fund?

It is now clear which field has the focus: the border turns dark and the gradient fill turns upside down. A small thing, but very helpful to find the usually-tiny insertion point. The checkbox uses a snazzy checkbox icon. It just looks better.

But what is style without substance? Let's add some...

Validating and Assisting

To solve the data entry problems, we can use Dijit validating text boxes. They either make changes to the input for you - like trimming and casifying - or alert the user of invalid input. We'll do this in a few steps.

Trimming and Changing Case

A few extra attributes for the TextBox dijit help the user along:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<FORM>
First Name:
<input type="text" size="20" name="first"  dojoType="dijit.form.TextBox"
           trim="true" propercase="true" />

Last Name:
<input type="text" size="20" name="last"  dojoType="dijit.form.TextBox"
           trim="true" propercase="true"  />

Email Address:
<input type="text" size="20" name="email"  dojoType="dijit.form.TextBox"
           lowercase="true"/>

Setting trim="true" will make dijit.form.TextBox trim the spaces before and after the data value. When the TextBox loses the focus, dojo silently hacks the input and places it back in the box. Trimming is often essential in database work, where leading and trailing spaces often lead to search abnormalities down the road.

The propercase and lowercase attributes are similar. They make the input uppercase-first and lowercase, respectively, when the TextBox loses focus. The propercase attribute only changes lowercase letters - all existing uppercase letters are left alone. Similarly, lowercase changes only uppercase letters.

Currency Input

For valid number input, we employ dijit.form.CurrencyTextBox. This box does little things for you like adding the digit separators (comma and period in the US), and making sure only digits are typed.

/* 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;}
<input type="text"
                maxlength="12"
                class="fillwidth currency"
                id="grossincome" name="grossincome"
                value="0.00"
                dojoType="dijit.form.CurrencyTextBox"
                required="true"
                onChange="updateTotals()"
                currency="USD"/>

And because we haven't added a require for CurrencyTextBox, we add one to the top:

dojo.require("dijit.form.CurrencyText");

Adding Some Help

Googolican citizens complain that the form is too complex. John needs to add some directions, but it seems unfair to punish the intelligent citizens by cluttering up the form. So John decides to include some instructions, tucked away but in easy reach. The Dijit TitlePane is perfect for that.

/* 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="dijit.TitlePane" open="false"
     title="Directions (click to Expand)" style="width:400px;height:300px">

Proin risus. Nullam rhoncus purus id turpis. Praesent aliquam adipiscing ligula. Aenean lorem ante,
accumsan quis, elementum id, cursus eu, lorem. Fusce viverra. Ut tempor nisi at ipsum. Etiam sed nibh.
</div>

Of course, the real text can be filled in later. Latin is not exactly a popular language in Googolica.

For some extra help, John inserts a Dijit Tooltip next to the gross income. Tooltip's can be tied to any DOM node - that is, any HTML tag - with an id attribute:

/* 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;}
<img src="symbol_help.gif" id="helpIncome"/>
<div dojoType="dijit.Tooltip" style="display:none" connectId="helpIncome">
    That's how much <b>money</b> you make.
</div>

The display:none style is not necessary, but it prevents the tooltip from flickering when the screen paints. Why does this happen? It's the way that Dijit handles pages:

  1. First all the HTML is drawn. The dojoType attributes are ignored, since they're not standard HTML.
  2. Once the page is drawn, the Dojo parser runs - that's what parseOnLoad does. The parser replaces all the dojoType'd tags with the widget HTML
  3. The widget then applies styles - including display attributes and colors - to make it look "right".

You can easily hide the 1st step screen-junk by applying display:none to those tags.

And lastly, don't forget the dojo.require's:

dojo.require("dijit.Tooltip");
dojo.require("dijit.TitlePane");

Accessibility (A11y)

The right to pay heavy taxes should not be denied to anyone, regardless of physical ability. John knows how important that is. His selection of Dijit was a good one here, because it handles a lot of the A11y details (A11y = A + 11 letters + y, a standard abbreviation).

One a11y consideration is low vision. Usually this is overcome by high-contrast color schemes. Dijit automatically detects this condition, and renders the page accordingly. You can simulate this by merely changing the body class from "tundra" to "a11y":

/* 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;}
<body class="a11y">

Graphic embellishments, like gradients, are eliminated so as not to blend into the important text. And all graphic symbols are placed with clearer text ones, as in the Title Pane:

Proin risus. Nullam rhoncus purus id turpis. Praesent aliquam adipiscing ligula. Aenean lorem ante, accumsan quis, elementum id, cursus eu, lorem. Fusce viverra. Ut tempor nisi at ipsum. Etiam sed nibh.

Internationalization (i18n)

Up until this point, we haven't had to use JavaScript beyond the dojo.require's. That's about to change. But as you'll see, a small amount of JavaScript goes a long way ... when hooked to Dijit and Dojo.

We're going to make the tax form accessible to readers of both languages: English and Swedish Chef. But first here's the wrong way to do it: make two pages with the same controls, the same styles, and the same wiring, but with different text on each. Very bad. When one page changes the other needs to change too. To add more languages, the job becomes even worse.

Since only the text changes, it be nice to have one copy of the page with textual placeholders. Dojo i18n is perfect for that.

Bundles and Resources

If you've used i18n in other languages like Java, the concepts are pretty similar here. Your application users are partitioned into "locales" - a language and a location and/or dialect. The ISO defines standard identifiers for locales, so that en-us is English in the U.S. and en-au is English in Australia. In our example, we'll be using a functional locale called "sw-chef". All other locales will map to the default locale, which in our case is just plain ol' English.

A bundle is a directory named after a locale, and a Dojo i18n resource is a JavaScript file underneath. As is common in other i18n setups, all of our bundles go under the directory "nls". For our i18n setup, we'll use the resource name "taxform". Here's the way it'll look:

[inline:taxform5.png]

The taxform.js underneath nls is the default resource, which looks like this:

({
first: "First Name",
last: "Last Name",
email: "Email Address",
filingDate: "Filing Date",
grossincome: "Please Enter Your 2007 Gross Income",
deductibles: "Total Deductions",
netincome: "Taxable Income",
taxpaid: "Total Witholding",
refund: "Your Refund",
owed: "Amount You Owe",
campaign: "Would you like to contribute an extra $3 to the Presidential Campaign Fund?"
})

The names before the : are called keys and they are one-word abbreviations for each of the text pieces. The sw-chef resource sw-chef/taxform.js is similar:

({
first: "Furst Neme-a",
last: "Lest Neme-a ",
email: "Emeeel Eddress",
filingDate: "Feeling Dete-a",
grossincome: "Pleese-a Inter Yuoor 2007 Gruss Incume-a",
deductibles: "Tutel Dedoocshuns",
netincome: "Texeble-a Incume-a",
taxpaid: "Tutel Veethulding",
refund: "Yuoor Reffoond",
owed: "Emuoont Yuoo Oove-a",
campaign: "Vuoold yuoo leeke-a tu cuntreeboote-a un ixtra $3 tu zee Preseedentiel Cempeeegn Foond? Bork Bork Bork!"
})

Now we somehow need to pull these names into the right places on the form. For that, we'll build our own custom widget.

An i18n Field Label Widget

Widgets are a lot like macros. You're basically substituting one "fake" tag with a dojoType - called the widget class - for a set of "real" tags. To transform the fake tag to the real tag, you use a template. For example, suppose our goal is to get this tag:

/* 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;}
<label for="first" id="first_label">First Name</label>

To construct the Dijit template, we insert place holders for the parts that will vary:

/* 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;}
<label for="${fieldid}" id="${fieldid}_label"></label>

Then nestle it inside a dijit.Declaration tag. We can place this tag and template anywhere in our HTML file, in theory, but most people place them under the BODY tag.

/* 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;}
<span dojoType="dijit.Declaration" widgetClass="TaxformI18n"
      defaults="{ fieldid: 'none' }">

    <label for="${fieldid}" id="${fieldid}_label"></label>
</span>

The widgetClass parameter gives the name TaxformI18n to our widget class. This will match the dojoType in our actual widgets. The defaults parameter defines all the placeholders and their default values. Any placeholder you use in the template must have a default here.

Having declared this, now the tag:

/* 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="TaxformI18n" fieldid="first"></div>

Will be replaced with

/* 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;}
<label for="first" id="first_label"></label>

Well, that's helpful but we still need our translated text. That's where we'll call Dojo.

The Dojo i18n API

The fieldid in our widget template can be used as a key into our resource. Ah ha! So you've got a label tag connected to first, which will be filled in with the text tied to "first" ... e.g. "First Name" or "Furst Neme-a". Given that the key is in "fieldid", here's the JavaScript to get the translation:

var labelNode = dojo.byId(this.fieldid + "_label");
var taxFormBundle = dojo.i18n.getLocalization("taxformI18n","taxform");
labelNode.innerHTML = taxFormBundle[this.fieldid];

The third line puts the translated text into the tag marked "fieldid_label".

So taxform is the resource name, but what is taxformI18n? That's a package name, which we define outside of the widget:

// This is necessary to i18n routines look in this directory for the nls folder
dojo.registerModulePath("taxformI18n","/online-book/taxform");
dojo.requireLocalization("taxformI18n", "taxform");

These two statements say "my resources are in http://yourserver/online-book/taxform/nls".

Now for the wiring. The dojo.Declaration must have a template, but it can also contain JavaScript code. You might be tempted to just plunk it in there with a <script type="text/javascript"> but that won't work. Instead you use the type "dojo/connect" and the event "startup." 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: #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;}
<span dojoType="dijit.Declaration" widgetClass="TaxformI18n"
    defaults="{ fieldid: 'none' }">

    <label for="${fieldid}" id="${fieldid}_label"></label>
       
    <script type='dojo/connect' event='startup'>
        var labelNode = dojo.byId(this.fieldid + "_label");
        var taxFormBundle = dojo.i18n.getLocalization("taxformI18n","taxform");
        labelNode.innerHTML = taxFormBundle[this.fieldid];
    </script>
</span>

And we're ready to go!

i18n in Action

So here's the complete listing:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
            "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Googolica Tax Form</title>
<style type="text/css">
        @import "http://o.aolcdn.com/dojo/1.0.0/dijit/themes/tundra/tundra.css";
        @import "http://o.aolcdn.com/dojo/1.0.0/dojo/resources/dojo.css"
</style>
<script type="text/javascript" src="/dojoroot/dojo/dojo.js"
        djConfig="parseOnLoad: true">
</script>
<script type="text/javascript">
        dojo.require("dojo.parser");
        dojo.require("dijit.form.TextBox");
        dojo.require("dijit.form.CheckBox");
        dojo.require("dijit.form.DateTextBox");
        dojo.require("dijit.form.CurrencyTextBox");
        dojo.require("dijit.Declaration");
        dojo.require("dijit.TitlePane");
        dojo.require("dijit.Tooltip");
               
        // This is necessary to i18n routines look in this directory for the nls folder
        dojo.registerModulePath("taxformI18n","/online-book/taxform");
        dojo.requireLocalization("taxformI18n", "taxform");
    </script>
</head>
<body class="tundra">
<span dojoType="dijit.Declaration" widgetClass="TaxformI18n"
        defaults="{ fieldid: 'none' }">

        <label for="${fieldid}" id="${fieldid}_label"></label>
       
        <script type='dojo/connect'     event='startup'>
        var labelNode = dojo.byId(this.fieldid + "_label");
        var taxFormBundle = dojo.i18n.getLocalization("taxformI18n","taxform");
        labelNode.innerHTML = taxFormBundle[this.fieldid];
    </script>
</span>
<form>
<div dojoType="TaxformI18n" fieldid="first"></div>
<input type="text" size="20" name="first" dojoType="dijit.form.TextBox"
        trim="true" propercase="true" />
<br>
<div dojoType="TaxformI18n" fieldid="last"></div>
<input type="text" size="20" name="last" dojoType="dijit.form.TextBox"
        trim="true" propercase="true" />
<br>
<span dojoType="TaxformI18n" fieldid="email"></span>: <input type="text"
        length="20" name="email" dojoType="dijit.form.TextBox" lowercase="true" />

<div dojoType="TaxformI18n" fieldid="filingDate"></div>
<input type="text" length="10" id="filingDate" name="filingDate"
        dojoType="dijit.form.DateTextBox">

<hr />
<ol>
        <li>
        <div dojoType="TaxformI18n" fieldid="grossincome"></div>
        <input type="text" maxlength="12" id="grossincome" name="grossincome"
                value="0" dojoType="dijit.form.CurrencyTextBox" required="true"
                currency="USD" />
<img src="symbol_help.gif" id="helpIncome" />
        <div dojoType="dijit.Tooltip" connectId="helpIncome">That's how
        much <b>money</b> you make.</div>
        </li>
        <li>
        <div dojoType="TaxformI18n" fieldid="deductibles"></div>
        <input type="text" dojoType="dijit.form.CurrencyTextBox"
                id="deductibles" name="deductibles" value="0" required="false"
                currency="USD" />
</li>
        <li>
        <div dojoType="TaxformI18n" fieldid="netincome"></div>
        <input type="text" value="0" dojoType="dijit.form.CurrencyTextBox"
                required="false" id="netincome" name="netincome" currency="USD" />
</li>
        <li>
        <div dojoType="TaxformI18n" fieldid="taxpaid"></div>
        <input type="text" value="0" class="fillwidth currency"
                dojoType="dijit.form.CurrencyTextBox" required="false" readonly="true"
                id="taxpaid" name="taxpaid" currency="USD" />
</li>
        <li>
        <div dojoType="TaxformI18n" fieldid="refund"></div>
        <input type="text" value="0" dojoType="dijit.form.CurrencyTextBox"
                required="false" readonly="true" id="refund" name="refund"
                currency="USD" />
</li>
        <div dojoType="TaxformI18n" fieldid="owed"></div>
        <input type="text" value="0" dojoType="dijit.form.CurrencyTextBox"
                required="false" readonly="true" id="owed" name="owed" currency="USD" />

        </li>
        <li>
        <div dojoType="TaxformI18n" fieldid="campaign"></div>
        <input type="checkbox" name="campaign" value="Y"
                dojoType="dijit.form.CheckBox">
</li>
</ol>
<div dojoType="dijit.TitlePane" open="false"
        title="Directions (click to Expand)" style="width:400px;height:300px">

Proin risus. Nullam rhoncus purus id turpis. Praesent aliquam adipiscing
ligula. Aenean lorem ante, accumsan quis, elementum id, cursus eu,
lorem. Fusce viverra. Ut tempor nisi at ipsum. Etiam sed nibh.</div>
</body>
</html>

The browser knows which locale to use based on the web browser installation, and it feeds this directly to Dojo, by default. So unless your locale is "sw-chef", your page will look exactly like the previous examples. But you can easily override this by changing the configuration when loading dojo.js:

And run the result.

[inline:taxform6.png]

It makes you want to throw kitchen utensils in the air with glee! OK, one more design run, and our form will be finished.

Example 2: The Postman Always Clicks Twice

Andy Tso gets a call from Regina Opulence, the head of the new Internet Startup clickdammit.com. "I need to have a mail web site," she said, "one like Google Mail. Can you make one up in a week?"

"Hmmmm." said Andy, "What's your business plan, if I may ask?"

"It's brilliant." she said. "Our backend servers will read the mail they send back and forth, then send them ads based on the things they talk about!"

Andy rolled his eyes to the ceiling. It would do no good to talk about privacy issues, legality, or all that other nonsense. He was just a tech guy, and Regina wanted him to do techie-type things. At least Clickdammit.com paid well. "Lemme give it a whirl. I think I can get you a pretty good demo in a week. "

He hung up the phone and wondered ... how could he get this demo done and propose to his longtime girlfriend on Friday night? He still had to write the speech, after all.

Fortunately there's Dojo! *



* Also fortunately, Dojo has a demo email client that comes in every package! Written by Bill Keese with contributions from Alex Russell and Peter Higgins, this demo covers a lot of Dojo goodness.

Basic Layout

So here's the cocktail napkin view of the email client:

Like most Outlook-inspired email programs, it will have a three pane, split-screen layout with a toolbar on top and status bar on the bottom. The left hand side will use accordion panes that flip up and down like a window blinds. Fortunately, all the layout tools you need are already in Dijit.

Andy likes to build the layout from the inside-out best, so he'll tackle the steps in this order:

  1. Divide the screen into list and messages areas
  2. Add the navigation bars - address book, message list, etc. - to the left hand area
  3. Add the message preview pane
  4. Add the tabbed panes for Compose Mail
  5. Glue on the toolbar and progress bar

Dividing the Screen with SplitContainer

start with the message list and message area first. In between these two is the movable divider bar, called the sizer. In Dijit, you can use the dijit.layout.SplitContainer widget to model this like so:

/* 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="dijit.layout.SplitContainer" id="rightPane"
        orientation="vertical"  sizerWidth="5"  activeSizing="0">

        <div id="listPane" dojoType="dijit.layout.ContentPane" sizeMin="20" sizeShare="20">
              Message List will go here
        </div>
                                       
        <div id="message" dojoType="dijit.layout.ContentPane" sizeMin="20" sizeShare="80">
            Message will go here
        </div>
</div> <!--  End right hand side split container -->

Usually the innermost children in a layout are dijit.layout.ContentPane's. You can place arbitrary HTML in the pane, as we've done here, or lazy load the content from other URL's, like a server-side include.

The dijit.layout.SplitContainer itself can be oriented horiztonally or vertically, as the "orientation" attribute shows. A vertically oriented SplitContainer means the components are stacked vertically. Note that the orientation is opposite of the sizer bar orientation - i.e. if the SplitContainer is vertical, as it is in this case, the sizer orientation is horizontal. The sizer bar here is 5 pixels and changing it does not change the content immediately, as the activeSizing attribute shows.

The Navigation Bars: AccordionContainer

The left hand bar is an Accordion Container, which is easier to show than to describe. In Dijit, the dijit.layout.AccordionContainer holds dijit.layout.AccordionPane's, a special kind of ContentPane.

/* 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="dijit.layout.AccordionContainer" sizeMin="20" sizeShare="20">
        <div dojoType="dijit.layout.AccordionPane" title="Folders">
            Folders will go here
        </div>
        <div dojoType="dijit.layout.AccordionPane" title="Address Book">
            Address Book will go here
        </div>
</div>

The title attributes give the label for the top of the AccordionPane. When the user clicks on this title or the arrow icon next to it, the pane will slide into view.

The Message Preview Pane - SplitContainer

Now we can glue these two pieces together with another SplitContainer, this one oriented horizontally:

/* 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;}
<!-- main section with tree, table, and preview -->
<div dojoType="dijit.layout.SplitContainer"
        orientation="horizontal" sizerWidth="5" activeSizing="0" title="Inbox">

    <div dojoType="dijit.layout.AccordionContainer" sizeMin="20" sizeShare="20">
     ...
    </div>
    <div dojoType="dijit.layout.SplitContainer" id="rightPane"
        orientation="vertical"  sizerWidth="5"  activeSizing="0">

    ...
    </div>
</div>

Tabbed Panes - TabContainer

The whole thing will be situated in a tab named Inbox. Though our cocktail napkin drawing doesn't show other tabs, the idea is to open them for new email composition. This way you can work on many emails at once, clicking on the tabs to switch between them. Dijit has a widget dijit.layout.TabContainer, and like the Split and Accordion containers, it's a container for subscontainers and panes.

/* 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="dijit.layout.TabContainer"  id="tabs" jsId="tabs" layoutAlign="client">
    <div dojoType="dijit.layout.SplitContainer"
            orientation="horizontal" sizerWidth="5" activeSizing="0" title="Inbox">

    ...
    </div>
</div>

The dijit.Layout.TabContainer can hold as many tabs as you want, each marked with a title attribute used to title the tab. This illustrates a familiar pattern in Dijit. Sometimes a widget contains attributes that don't really deal with the widget itself - title, for instance, or sizeMin and sizeShare in earlier examples. They only make sense when that widget is used within other widgets. So an AccordionContainer ignores a sizeMin attribute, but because it's a child of a SplitContainer, the SplitContainer uses it for sizing. Title is similar. In a normal HTML tag, title would display a tooltip, but because the SpilitContainer is inside a TabContainer, the title is used specially.

What is that jsId attribute? jsId sets a global JavaScript variable for the widget, which will make it easy to script widget actions later. For example:

// Note used for step 1 - this is a preview
tabs.closeChild(tabs.selectedChildWidget);

closes the currently selected tab in the tab container. Without even knowing the Dijit API's, it's easy to understand what's going on here. That's object-oriented magic at work.

Gluing On Toolbar and Status Areas - LayoutContainer

To top it all off, we smush these tabs into a dijit.layout.LayoutContainer. A LayoutContainer was pioneered by Borland Delphi and copied in Java AWT and other toolkits. The idea is to split a box into five areas: top, bottom, left, right, and client (the middle). If the box is resized, the client area takes most of the growth or shrinkage - it's the document part of the app.

In our email client, the toolbar will occupy the top, the status bar the bottom, and the TabContainer the client portion:

/* 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="dijit.layout.LayoutContainer" id="main">
        <div dojoType="dijit.Toolbar" layoutAlign="top" style="height:25px;">
           Toolbar will go here
        </div>
               
        <div dojoType="dijit.layout.ContentPane" layoutAlign="bottom"
             id="footer" align="left">

                <span style="float:right;">DojoMail v1.0 (demo only)</span>
                Progress bar will go here
        </div>
        <div dojoType="dijit.layout.TabContainer"
            id="tabs" jsId="tabs" layoutAlign="client">

         ...
        </div>
</div>

The layoutAlign attributes tell LayoutContainer where they should be placed.

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

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
            "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Demo Mail Application</title>
    <script type="text/javascript" src="http://o.aolcdn.com/dojo/1.0.0/dojo/dojo.xd.js"
                djConfig="parseOnLoad: true">
</script>
    <script type="text/javascript">
        dojo.require("dojo.parser");
                dojo.require("dijit.Toolbar");
                dojo.require("dijit.layout.LayoutContainer");
                dojo.require("dijit.layout.SplitContainer");
                dojo.require("dijit.layout.AccordionContainer");
                dojo.require("dijit.layout.TabContainer");
                dojo.require("dijit.layout.ContentPane");
    </script>
        <style type="text/css">
                @import "http://o.aolcdn.com/dojo/1.0.0/resources/dojo.css";
                @import "http://o.aolcdn.com/dojo/1.0.0/dijit/themes/soria/soria.css";
                @import "http://o.aolcdn.com/dojo/1.0.0/dijit/demos/mail/mail.css";
        </style>
</head>
<body class="soria">

There's no JavaScript at all beyond the dojo.require statements. And because he's using CDN, he didn't even have to install Dojo on the server. While Andy takes a moment to write more of his empassioned speech, you can download the code in step1.html below and try it out.

Ready? On to the next step...

The Address Book

Andy thinks, "That was easy. Now what's the next easiest thing to do?" The left-hand side panes, Address Book and Folders, will each have a tree. The Folders tree will have a heirarchy - folders within folders - but the Address Book is nice and flat. Andy figures he'll use the Address Book to learn Dijit Trees, then use his knowledge on the more complex Folders pane later. Here's the cocktail napkin view:

[inline:mail_step2.png]

Back in the old days (i.e. 5 minutes ago), Andy would have drawn up a PHP file that sent all the markup for the Address Book tree - the folder IMG tags, the labels, etc. - over the wire. While this approach worked fine in the old days, he has learned it doesn't work well for Web 2.0 applications like this one. Why?

  • It doesn't scale well. For a few entries, assembling the markup on the server, downloading, parsing and inserting the HTML is no problem. But this Address Book could contain hundreds or thousands of entries. And it will need to be drawn frequently.
  • It requires PHP to generate JavaScript. Since each Book entry needs an onlcik handler, Andy would need to embed onclick handlers into each tag. Mixing languages like this requires him to juggle syntax in his head. His mind is on other things (remember the girlfriend? the proposal?)

Address Book Data - dojo.Data and JSON

He decides to leave the architecture questions alone for a moment, and looks at the Dijit catalog entry for Tree. The easiest data-driven way to approach this uses dojo.data. Dojo.data reminds Andy of ODBC in the Windows world, JDBC in Java, and DBD in PHP (Andy gets around.) Though dojo.data can talk to many different kinds of backing stores formatted in CSV, XML or HTML, Andy picks JavaScript Object Notation, or JSON. A JSON representation of the address book would look like this:

{ 
	identifier: 'id',
	label: 'label',
	items: [
		{ type: 'address', id: 'adam', label: "Adam Arlen" },
		{ type: 'address', id: 'bob', label: "Bob Baxter" },
		{ type: 'address', id: 'carrie', label: "Carrie Crow" }
        ]
}

JSON nicely represents JavaScript objects, bracketed by { and }, and arrays bracketed by [ and ]. The attributes identifier and label conform to dojo.data's metadata specifications. Dojo.data uses the drivers dojo.data.ItemFileReadStore and dojo.data.ItemFileWriteStore to read JSON data in this format.

Andy thinks, "it'll be a snap to write a PHP for that format!" For the demo, he will simply use the above file as mail/mail.json. If the project moves past the demo phase, he can replace it with a PHP that reads mail on the database server and outputs JSON. Good deal.

To connect his demo to the data store, he inserts this below the BODY tag:

/* 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="dojo.data.ItemFileWriteStore" jsId="mailStore"
        url="mail/mail.json">
</div>

He has decided to use the ItemFileWriteStore to write entries back later (maybe not in the demo, but later on.)

Displaying the Address Book - Tree

Now drawing the tree is a snap using dijit.Tree:

/* 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="dijit.Tree" id="addrTree" store="mailStore"
        labelAttr="label" query="{type:'address'}">

        <script type="dojo/method" event="getIconClass" args="item">
                var specifiedIcon = item && mailStore.getValue(item, "icon");
                return specifiedIcon || "mailIconFolderDocuments";
        </script>
</div>

The store attribute connects to the jsId attribute on the ItemFileWriteStore. LabelAttr tells which attribute is used for the labels. Finally, the query tells dojo.data which entries to retrieve. You can equate a dojo.data query with a SQL query - it works with any backing store that dojo.data understands. As you can see here, a dojo.data query is much simpler than a SQL query. Sorting and compound selection are available for later, but for now this simple query will do fine.

Tree Icons - Extension Points

But wow ... what's up with that SCRIPT tag? What is type "dojo/method"?

That came about when Andy learned about tree icons ... the hard way. His first stab at dijit.Tree drew a nice address book but no icons. That wouldn't do. Clickdammit.com really, really likes icons. The trouble is there might be different icons based on the address book entry type.

At first Andy thought he'd have to modify dijit.Tree ... good thing Dojo is open source, right? But he'd been down that road before. You start monkeying around with changing the source and it becomes a never-ending struggle with revision control. So he read over the Dijit catalog entry for Tree and found:

[inline:mail_step2a.png]

An extension point is a point at which you can add, remove or replace functionality. The Dijit designers have built lots of extension points so you can change Dijit instances to your heart's content. And that's what Andy did.

First he looked up the signature for the extension point in the API guide: dijit.Tree.getIconClass:

getIconClass: function(/*dojo.data.Item*/ item){
		// summary: user overridable function to return CSS class name to display icon
	},

Since each of the address book entries was a dojo.data.Item, he had no problem passing something in. Now he can replace the guts of the SCRIPT tag with JavaScript code to read the icon attribute and turn it into a CSS class name. That class name points to the right icon specified in mail/mail.css. Problem solved!

One note about that "item &&" phrase. In Dojo 1.0, Trees always have one and only one root node. There is nothing in dojo.data that specifies items as root nodes, and thus the first item passed to getIconClass is always null. By saying "item &&" we take advantage of JavaScript's short-circuit && (and) operator. It says "if item is a false-y value (= false, 0, null), stop here and don't evaluate what's after the &&."

"Cool!" thought Andy. Extension points made it easy to change widget behavior, and dojo/method calls did the nasty job of wiring JavaScript code.

And with a few more header entries:

dojo.require("dojo.data.ItemFileWriteStore"); 
	dojo.require("dijit.Tree");

step2.html is now a working address book. If you don't believe it, download it and try it yourself! Then we'll move right along...

User Interaction

Pretty nice so far, but this app is long on displaying, short on interaction. So next Andy decides to add some toolbars, tooltips and dialog boxes. "Surely this will involve lots of JavaScript," he thinks.

Here's the cocktail napkin view of the toolbar:

[inline:mail_step3a.png]

We already have the dijit.Toolbar widget in the app as a placeholder. Dijit.toolbar expects its immediate children to be buttons:

  • dijit.form.Button is a plain ol' command button, but better.
  • dijit.form.DropDownButton, when clicked, displays a menu of commands
  • dijit.form.ComboButton combines the two - acting like a button when clicked, or a menu when held-down.

Getting Mail - ComboButton and Tooltip

The New Mail button is a ComboButton:

/* 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;}
<button id="getMail" dojoType="dijit.form.ComboButton"
        iconClass="mailIconGetMail">

        <script type="dojo/method" event="onClick">
                fakeDownload();
        </script>
        <span>Get Mail</span>
        <ul dojoType="dijit.Menu">
                <li dojoType="dijit.MenuItem" iconClass="mailIconGetMail">Yahoo</li>
                <li dojoType="dijit.MenuItem" iconClass="mailIconGetMail">GMail</li>
        </ul>
</button>
<span dojoType="dijit.Tooltip" connectId="getMail">Click to download new mail.</span>

The dijit.Tooltip does what you think it should - it displays a message when the user hovers over the button. It knows this by connecting the connectId to the id attribute of the widget.

At the heart of a ComboButton or DropDownButton is a dijit.Menu, itself composed of dijit.MenuItem objects. Andy chooses not to wire the items - Yahoo and GMail - to an event handler yet. But he does tie the onClick handler of the ComboButton itself.

Like for the Tree, Andy uses a dojo/method script to fire off a snippet of JavaScript. In this case, it's a stubbed out function called fakeDownload(). But wait! Can't you just do 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: #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;}

<!-- It works, but uses DOM Level 0 event model.  Yuck. -->
<button id="getMail" dojoType="dijit.form.ComboButton"
        iconClass="mailIconGetMail"
        onclick="fakeDownload();">

Yes, but you wouldn't want to. Using dojo/method and connecting to the extension point gets you the DOM Level 2 event model for free. DOM Level 2 events pass much more information to the event handler. And before you ask ... yes, this even works in Internet Explorer, which doesn't natively support the DOM Level 2 event model. Dojo adds a nice layer which emulates the model on IE.

Sweet! That's cross-browser functionality for free. Dojo is really, really good at that.

New Mail and Options - Dialog Boxes

So let's look at the other buttons:

/* 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;}
<button
        id="newMsg" dojoType="dijit.form.Button"
        iconClass="mailIconNewMessage">

        New Message
        <script type="dojo/method" event="onClick">
                alert("New message functionality coming soon.");
        </script>
</button>
<span dojoType="dijit.Tooltip" connectId="newMsg">Click to compose new message.</span>
<button id="options" dojoType="dijit.form.Button" iconClass="mailIconOptions">
        Options 
        <script type="dojo/method" event="onClick">
                dijit.byId('optionsDialog').show();
        </script>
</button>
<div dojoType="dijit.Tooltip" connectId="options">Set various options</div>

There's nothing here we haven't seen before ... except the call to optionsDialog. This displays a dialog box, which should look like this:

[inline:mail_step3b.png]

Now this looks like a regular HTML form, and in fact we'll code it as if it were. But ... we'll use a dijit.Dialog to do it. The good news is that unlike HTML forms, a dijit.Dialog acts modally, leaving all the information and page state alone while the user fills in the dialog details. So the server interaction happens in the background.

The code for the dialog box goes at the bottom of our app, just above the /BODY tag, separated away from all the other HTML:

/* 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="dijit.Dialog" id="optionsDialog" title="Options:">
        <table> 
        <tr>
            <td style="text-align:right;"><label for="option1">Transport type:</label></td>
            <td>
                <select id="option1" dojoType="dijit.form.FilteringSelect">
                    <option value="pop3">POP3</option>
                    <option value="imap">IMAP</option>
                </select>
            </td>
        </tr>
        <tr>
            <td style="text-align:right;"><label for="option2">Server:</label></td>
            <td>
                <input id="option2" dojoType="dijit.form.TextBox" type="text">
            </td>
        </tr>
        <tr>
            <td style="text-align:right;">
                <input type="checkbox" id="fooCB" dojoType="dijit.form.CheckBox">
            </td>
            <td><label for="fooCB">Leave messages on Server</label></td>
        </tr>
        <tr>
             <td style="text-align:right;">
                 <input type="checkbox" id="fooCB2" do