The Book of Dojo

Welcome to the Book of Dojo. This book covers versions 0.9, 1.0, and all follow-on 1.x releases; additions from minor releases are clearly marked for your enjoyment. Please use the forums for support questions, but if you see something missing, incomplete, or just plain wrong in this book, please leave a comment.

For an offline version, click on the Printer-Friendly Page link at the bottom. This assembles the entire book into one long HTML page, which you can then save offline. Caveats: it takes a long time to load, you will get approximately 150 JavaScript errors (which you can ignore), and images won't appear. However, all the text and code will be intact.

Table of Contents


The Book of Dojo
Quick Installation
Hello World - Dojo for the Attention-Impaired
Debugging Tutorial
Introduction
Licensing
History
Why Dojo?
Part 1: Life With Dojo - Dojo and Dijit Application Examples
Example 1: Why Doesn't Anyone Fill Out Their Tax Forms?
Example 2: The Postman Always Clicks Twice
Example 3: Chatting With Tech Support
Part 2: Dijit - The Dojo Widget Library
Dijit at a Glance
Common Features
Form, Validation, Specialized Input
Form Widget
CheckBox, RadioButton, ToggleButton
ComboBox
FilteringSelect
InlineEditBox (0.9)
NumberSpinner
Slider
Textarea
TextBox family: Validation, Currency, Number, Date, Time
Layout
AccordionContainer
BorderContainer (1.1)
ContentPane
LayoutContainer
SplitContainer
StackContainer
TabContainer
Command Control
Button, ComboButton, DropDownButton
Menu
Toolbar
User Assistance and Feedback
ProgressBar
Tooltip
Dialog and TooltipDialog
TitlePane
Advanced Editing and Display
ColorPalette
InlineEditBox (1.0)
Editor
Tree
Themes and Design
Common Elements
Overriding and Combining Themes
Writing Your Own Theme
Accessibility
Web Accessibility Issues
Dojo Accessibility Strategy
Dojo Accessibility Resources
Part 3: JavaScript Programming With Dojo and Dijit
Functions Used Everywhere
Object Orientation
Modules
Creating and Scripting Widgets
Writing Your Own Widget Class
The Event System
XMLHttpRequest (XHR)
Drag and Drop
Using dojo.data
Selecting DOM Nodes with dojo.query
Internationalization (i18n)
Back Button
Other Functions
Multiple Versions of Dojo in a Page
Part 4: Testing, Tuning and Debugging
Getting the Code from Source Control
Development Tools
Debugging Facilities
D.O.H. Unit Testing
Performance Optimization
The Package System and Custom Builds
Part 5: DojoX - Experimental and Specialized Extensions
Cometd (client)
DojoX Charting
DojoX Collections
DojoX Cryptography
DojoX Data
DojoX DTL (Django Template Language)
DojoX FX
DojoX GFX
DojoX Grid
DojoX I/O
DojoX Image
DojoX Layout
DojoX Offline
DojoX Presentation
DojoX String Utilities
DojoX Timing
DojoX UUID
DojoX Validate
DojoX Widgets
DojoX Wire
DojoX XML Utilities

Contributors

Simon Bates David Bolter Matt Bowen Pete Brunet Dipen Chaudhary
Jeff Chimene Lance Duivenbode Sam Foster Becky Gibson Peter Higgins
Martin Humphreys Bill Keese Carla Mott Brad Neuberg Shane O'Sullivan
Shelita Overton Ashish Patil Adam Peller Leesa Payne Craig Riecke, editor
Alex Russell Gerwood Stewart Peter Waegener

Quick Installation

There are three main ways to install Dojo:

  1. Install nothing! Use Dojo from AOL's Content Distribution Network (CDN).
  2. Install the latest release on your server
  3. Install directly from source control

Use Dojo from CDN

This method is quick and painless! You simply load Dojo through <script> tags pointing to the AOL CDN. You don't need to invest any of your own server disk space or resources nor will you need to install Dojo locally in many cases. Instructions are available on using Dojo from the CDN.

All of the Dijit examples in this book load Dojo through this method. You can literally copy and paste any example in Parts 1 and 2 onto your own web server and it will work as-is! In Part 3, where examples are most often code fragments, we'll tell you any modifications needed to run the example on CDN.

Use Dojo from your Own Server

For you traditionalists out there, you can download, install and use Dojo the old fashioned way.

  1. Download the latest build from http://dojotoolkit.org/downloads.
  2. Uncompress the file onto your web server. Assuming you install it under the directory /js, when you're done, the file system should look something like this:
    [inline:dir_list.png]
  3. With your browser, open http://yoursite.com/js/dojo-0.9.0/dijit/themes/themeTester.html You should see a page like this:
    [inline:themeTester.png]

You've got a working Dojo!

Getting the Nightly Build

Finally, for those of you who live on the edge ... you can get the latest, greatest code directly from the Subversion code repository.

Hello World - Dojo for the Attention-Impaired

The purpose of this tutorial is to provide a starting point for users who are new to Dojo. Whilst every effort is made to introduce as much as possible about Dojo, it is impossible to include more of the detailed information since to do so would be counterproductive and confusing to new users. For more information on the concepts introduced here, please see the links to other resources at the end of this document (Finding More Resources).

Requirements

Obviously, you need Dojo first! You can get the latest stable build from http://download.dojotoolkit.org. Next you need a web server. Whether it's hosted offsite or onsite, on Linux or Windows or Mac ... matters naught. The Dojo JavaScript library is simply pulled from your web server to the browser as needed. However, the AJAX examples in this document require a server-side scripting language like PHP or ASP.

The Dojo and Dijit code, which runs on the client browser, is certified to run on IE 6 and 7, Firefox 2, and Safari.

Setting Up Dojo

First, you should create a directory on the web server. We'll call ours HelloWorldTutorial. Then create a directory called dojoroot underneath it. Finally, use your favorite unzipping tool to unzip Dojo into /HelloWorldTutorial/dojoroot. It'll look like this when you're done:

[inline:debugging9.png]

Getting Started

Once we have setup the directory and file structure for the tutorial, we will need to setup the JavaScript component of our HTML page. Have a look at the code below:

/* 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>Dojo: Hello World!</title>

    <!-- SECTION 1 -->
    <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>
  </head>

  <body class="tundra">
  </body>
</html>

As it can be seen above, the page is a just a standard HTML skeleton with three things:

Creating a Button Widget

Ok, now for the exciting part! In this example we're going to create a Button widget with the text 'Hello World!'. In the case of the Button widget, three visual states (mouseOut, mouseOver, and mouseDown) are available which means that we are able to enhance the user's experience somewhat.

The first step in creating the widget is telling Dojo to load the appropriate modules. In the header, add another section (hereafter referred to as section 2) below section 1 as follows:

/* 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;}

<!-- SECTION 2 -->
    <script type="text/javascript">
       // Load Dojo's code relating to the Button widget
       dojo.require("dijit.form.Button");
    </script>

The dojo.require line instructs Dojo to load the Button widget. If you were to omit this line, the markup code for the button would not be evaluated by Dojo upon loading, resulting in a plain HTML button instead of what you expect.

After making the changes, insert the following code into the body section of the 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;}

<button dojoType="dijit.form.Button" id="helloButton">Hello World!</button>

The key attribute of this HTML element to notice is the dojoType attribute. The dojoType attribute is responsible for instructing Dojo on how to process the element when the page is loading. In this case we've used a button element for the button though we could have used an input element - Dojo will work with either as long as the dojoType attribute is present. It is worth noting that if we did use an input element, we would have to specify the button's text by using adding a caption attribute that contained the desired text.

Connecting an Event to the Widget

A button is all well and good, but what about getting it to do something when it's clicked? We could just specify an onClick event handler for the button, but there's another, more efficient way - the Dojo event system!

The easiest way to attach an event to a button is through a script tag. But not just any script tag ... this one has a type of dojo/method, 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;}

<button dojoType="dijit.form.Button" id="helloButton">
        Hello World!
        <script type="dojo/method" event="onClick">
           alert('You pressed the button');
        </script>
    </button>
    }

Pretty simple, eh? Putting the script inside the tag body makes a good deal of sense. And you can harness the full power of DOM Level 2 events inside the script. That means you can detect SHIFT and CTRL keys, get all sorts of event properties, and bubble events up through the HTML tree. If you've ever used Level 2 events, you know how IE and Firefox use different syntax. In Dojo, the same functions work in any supported browser. That's powerful stuff!

Reading Data from the Server

Having an alert pop up when we press the button is great, but what if we want to retrieve some data from the server? Again, Dojo comes to the rescue with an easy method of accomplishing this - dojo.xhrGet. For easy reference, the code for this section is available as HelloWorld-Section5.html and response.txt in the attachments section.

To get started, we first need a callback function to handle the data to be returned from the server. Insert the following code into 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;}

<script>
       function helloCallback(data,ioArgs) {
          alert(data);
       }       
       function helloError(data, ioArgs) {
          alert('Error when retrieving data from the server!');
       }
</script>

The two arguments to the functions (data, and ioArgs) are important - don't leave any of them out! The first argument (data) contains the data sent back from the server, whilst the second argument contains a Dojo I/O Bind object. Only the first concerns us right now.

The next step is to link the click of the button to the server request. To do this, modify the following code:

/* 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="dojo/method" event="onClick">
    alert('You pressed the button');
</script>

To 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;}

<script type="dojo/method" event="onClick">
   dojo.xhrGet({
        url: 'response.txt',
        load: helloCallback,
        error: helloError
   });
</script>

The above code basically tells Dojo to query the URL specified by url and to use the function specified by handler to process the response from the server.

Finally, we need to create another file in the same directory as HelloWorld.html called response.txt. In this file, place the text 'Welcome to the Dojo Hello World Tutorial'.

Now, when the button is clicked, a JavaScript alert should display the text from the response.txt file. Dojo-Easy!

Next, we'll look at doing something meaningful with that server request.

Sending Data to the Server Using GET

It's all well and good retrieving static data from the server, but it is hardly a widely used situation in real life. So, instead of simply requesting data from the server we also will send it some information for it to process. In this section, we'll use the GET method whilst in the next section we'll use the POST method. For easy reference, the code for this section is available as HelloWorld-Section6.html in the attachments section. Server side code is also available as HelloWorldResponseGET. where type is ASP ('.asp'), PHP ('.php'), ColdFusion ('.cfm'), or Java ('.jsp').

Firstly, in the markup section of the HelloWorld.html file (i.e. the body section), we need to add another element - an input element. So, change the code in this section 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: #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 dojoType="Button" widgetId="helloButton">
    <script type="dojo/method" event="onClick">
    dojo.xhrGet({
        url: 'response.txt',
        load: helloCallback,
        error: helloError
    });
    </script>
</button>

to:

/* 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 dojoType="dijit.form.Button" id="helloButton">
        Hello World!
        <script type="dojo/method" event="onClick">
        dojo.xhrGet({
           url: 'HelloWorldResponseGET.php',
           load: helloCallback,
           error: helloError,
           content: {name: dojo.byId('name').value }
        });
        </script>
     </button>
     Please enter your name: <input type="text" id="name">

Before we go any further - it is important to mention that the url property in the dojo.xhrGet function call must be set to the file that is appropriate to your environment. If you are using an ASP server then the value must read 'HelloWorldResponseGET.asp' instead of 'HelloWorldResponseGET.php' Likewise, if you are using a ColdFusion server then the value must read 'HelloWorldResponseGET.cfm' instead of 'HelloWorldResponseGET.php'. Finally, if you are using a Java server (JSP) then the value must read 'HelloWorldResponseGET.jsp' instead of 'HelloWorldResponseGET.php', or if you are using a Perl server then the value must read 'HelloWorldResponseGET.pl' instead of 'HelloWorldResponseGET.pl'. The code for these files is in the sections below, and is also available as attachments to this tutorial.

In the code above, you will notice that there is a new property that has been passed to the dojo.xhrGet function. This property - content - allows the programmer to send arbitary values to the server as parameters. In this case, since we are using the default method of dojo.io.bind which is GET, the server side script will have the value of the textbox available to it as a the GET parameter 'name'. It is worth mentioning that if the script expected the parameter under a different name (such as 'myName'), we would simply change the content property to be (note the change of 'name' to 'myName' on the left of the assignment operator ':'):

content: {myName: dojo.byId('name').value }

Since we've not used it before, it is also worth noting the call dojo.byId('name').value. Quite simply, this call is a shortcut for the standard document.getElementById(..) function.

Finally, if you enter your name into the text box and you click the 'Hello World' button, an alert box should appear with the message 'Hello , welcome to the world of Dojo!' where is the name you entered into the text box.

Here are the server side scripts. A few of them are downloadable at the bottom of this page (the website content management system doesn't allow .jsp or .cfm files).

Using a PHP Server

/* 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 .co1 {color: #808080; font-style: italic;}
.geshifilter .co2 {color: #808080; font-style: italic;}
.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 .me1 {color: #006600;}
.geshifilter .me2 {color: #006600;}
.geshifilter .re0 {color: #0000ff;}
.geshifilter .re1 {color: #ff0000}

<?php
  /*
  * HelloWorldResponseGET.php
  * --------
  *
  * Print the name that is passed in the
  * 'name' $_GET parameter in a sentence
  */


  header('Content-type: text/plain');
  print "Hello {$_GET['name']}, welcome to the world of Dojo!\n";
?>

Using an ASP Server

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */
.geshifilter {font-family: monospace;}
.geshifilter .imp {font-weight: bold; color: red;}
.geshifilter .kw1 {color: #990099; font-weight: bold;}
.geshifilter .kw2 {color: #0000ff; font-weight: bold;}
.geshifilter .kw3 {color: #330066;}
.geshifilter .co1 {color: #008000;}
.geshifilter .co2 {color: #ff6600;}
.geshifilter .coMULTI {color: #008000;}
.geshifilter .es0 {color: #000099; font-weight: bold;}
.geshifilter .br0 {color: #006600; font-weight: bold}
.geshifilter .st0 {color: #cc0000;}
.geshifilter .nu0 {color: #800000;}
.geshifilter .me1 {color: #9900cc;}

<%
  '
  ' HelloWorldResponseGET.asp
  ' --------
  '
  ' Print the name that is passed in the
  ' 'name' GET parameter in a sentence
  '

  response.ContentType="text/plain"
  response.write("Hello " & request.querystring("name") & ", welcome to the world of Dojo!\n")
%>

Using a ColdFusion Server


Hello, #url.name#, welcome to the world of Dojo!

Using a Java Server (JSP)

<%
  /*
  ' HelloWorldResponseGET.jsp
  ' --------
  '
  ' Print the name that is passed in the
  ' 'name' GET parameter in a sentence
  */

  response.setContentType("text/plain");
%>
Hello <%= request.getParameter("name") %> , welcome to the world of Dojo!

Using a Perl Server

#!/usr/bin/perl
#
#  ' HelloWorldResponseGET.pl
#  ' --------
#  '
#  ' Print the name that is passed in the
#  ' 'name' GET parameter in a sentence
#
use strict;
use CGI;
my $cgi = CGI::new();
print $cgi->header(-type => "text/html; charset=utf-8");
print "Hello " . $cgi->param('name') . ", welcome to the world of Dojo!\n";

Sending Data to the Server Using POST

Using GET data is all well and good, but sometimes you want to use Dojo to improve the user's experience when using a traditional HTML form. As usual, Dojo has a very nice way of making this easier. Again, the code for these files is in the sections below, and are also available as attachments to this tutorial. Additionally, as with the last section, you will need to change the 'url' property to point to the file that is appropriate to your environment.

First, we need to change the markup in the body of HelloWorld.html 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: #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;}

<br>
    Please enter your name: <input type="text" id="name">

to:

/* 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;}

<br>
    <form id="myForm" method="POST">
      Please enter your name: <input type="text" name="name">
    </form>

Next we need to change the dojo/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: #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="dojo/method" event="onClick">
        dojo.xhrGet({
           url: 'HelloWorldResponseGET.php',
           load: helloCallback,
           error: helloError,
           content: {name: dojo.byId('name').value }
        });
</script>

to:

/* 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="dojo/method" event="onClick">
   // Don't forget to replace the value for 'url' with
   // the value of appropriate file for your server
  // (i.e. 'HelloWorldResponsePOST.asp') for an ASP server
    dojo.xhrPost({
        url: 'HelloWorldResponsePOST.php',
        load: helloCallback,
        error: helloError,
        form: 'myForm'
   });
</script>

As can be seen from the code above, we've changed dojo.xhrGet to dojo.xhrPost. We removed the 'content' property and replaced it with a new property 'form'. This basically informs the dojo.xhrPost function that it needs to use the form 'myForm' as the source for the data in the call.

As with the last section, entering your name and clicking 'Hello World!' should yield a message such as 'Hello , welcome to the world of Dojo!' where is the name you entered into the text box.

Using a PHP Server

/* 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 .co1 {color: #808080; font-style: italic;}
.geshifilter .co2 {color: #808080; font-style: italic;}
.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 .me1 {color: #006600;}
.geshifilter .me2 {color: #006600;}
.geshifilter .re0 {color: #0000ff;}
.geshifilter .re1 {color: #ff0000}

<?php
  /*
  * HelloWorldResponsePOST.php
  * --------
  *
  * Print the name that is passed in the
  * 'name' $_POST parameter in a sentence
  */


  header('Content-type: text/plain');
  print "Hello {$_POST['name']}, welcome to the world of Dojo!\n";
?>

Using an ASP Server

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */
.geshifilter {font-family: monospace;}
.geshifilter .imp {font-weight: bold; color: red;}
.geshifilter .kw1 {color: #990099; font-weight: bold;}
.geshifilter .kw2 {color: #0000ff; font-weight: bold;}
.geshifilter .kw3 {color: #330066;}
.geshifilter .co1 {color: #008000;}
.geshifilter .co2 {color: #ff6600;}
.geshifilter .coMULTI {color: #008000;}
.geshifilter .es0 {color: #000099; font-weight: bold;}
.geshifilter .br0 {color: #006600; font-weight: bold}
.geshifilter .st0 {color: #cc0000;}
.geshifilter .nu0 {color: #800000;}
.geshifilter .me1 {color: #9900cc;}

<%
  '
  ' HelloWorldResponsePOST.asp
  ' --------
  '
  ' Print the name that is passed in the
  ' 'name' $_POST parameter in a sentence
  '

  response.ContentType="text/plain"
  response.write("Hello " & request.form("name") & ", welcome to the world of Dojo!\n")
%>

Using a ColdFusion Server


Hello, #form.name#, welcome to the world of Dojo!

Using a Java Server (JSP)

<%
  /*
  ' HelloWorldResponsePOST.jsp
  ' --------
  '
  ' Print the name that is passed in the
  ' 'name' POST parameter in a sentence
  */

  response.setContentType("text/plain");
%>
Hello <%= request.getParameter("name") %> , welcome to the world of Dojo!

Using a Perl Server

#!/usr/bin/perl
#
#  ' HelloWorldResponsePOST.pl
#  ' --------
#  '
#  ' Print the name that is passed in the
#  ' 'name' POST parameter in a sentence
#
use strict;
use CGI;
my $cgi = CGI::new();
print $cgi->header(-type => "text/html; charset=utf-8");
print "Hello " . $cgi->param('name') . ", welcome to the world of Dojo!\n";

Finding more resources

I hope you've enjoyed this tutorial and found it informative. No doubt though, you will need more information on Dojo and how it and it's widgets work. Below is a list of links that will point you in the right direction.

Contacting the Author

Thinking of making modifications to this document? Want to make suggestions / constructive criticism?

If so, please contact me (Lance Duivenbode) at dojo AT duivenbode DOT id DOT au. Feedback is always welcome since it helps me improve my documentation - both now and in the future. Thanks!

Changelog

Debugging Tutorial

Dojo is very lean and speedy, and uses some very clever tricks to save memory and time. The downside: Dojo does not contain much error trapping, which would bloat and slow down the code. If your code is not behaving, some of the resulting errors can look mighty puzzling at first.

No worries! Here are a few tips to make you a confident, successful bug finder. Knowing these ahead of time will make your Dojo learning curve less steep.

Use Firebug or Firebug Lite

We can't stress this enough. Firebug, an open source debugging extension for Firefox, is essential for JavaScript, HTML and CSS debugging. You can download it from the Firebug web site.

Not a Firefox user? You may want to consider switching, at least for development work. One of Dojo's virtues is its hiding of cross-platform differences. So the more you rely on it, the more your code will be portable, and the less difference your development browser choice matters.

Still not convinced? That's OK too. If you use Internet Explorer or Safari, you can use the Firebug Lite library, bundled with Dojo. This gives you some of the logging and command line features of Firebug. It's not a full emulation, but it's a fairly good alternative and is fully API-compatible.

To use Firebug Lite, you must include the isDebug config parameter 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;}
<script type="text/javascript" src="http://o.aolcdn.com/dojo/1.0.0//dojo/dojo.js"
        djConfig="parseOnLoad: true, isDebug: true">
</script>

This parameter has no effect on Firefox browsers with Firebug already present. So including this parameter makes your debugging code usable in IE, Safari, and Firefox with no changes.

Faulty dojo.require's and the Firebug Console

The following code has a subtle bug:

/* 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>Fix me!</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.js"
                djConfig="parseOnLoad: true, isDebug: true">
</script>
        <script type="text/javascript">
                dojo.require("dojo.parser");
                dojo.require("dijit.form.Textbox");
    </script>
</head>
<body class="tundra">
<form>
What's the 411? <input type="text" size="20" name="info"  dojoType="dijit.form.TextBox"
           trim="true" propercase="true" />
<br>
</body>
</html>

Without Firebug, this code may pop up an unhelpful dialog box (IE) or display nothing (Firefox). In either case the textbox doesn't look right. You check this by entering a lowercase name and tabbing out of the box ... the propercase attribute should capitalize the first letter. It doesn't.

With Firebug the error is easier to spot. First, there's an indicator at the bottom right hand corner.

[inline:debugging1.png]

You click on the "1 Error" message and the Firebug console pops up:

[inline:debugging2.png]

Looking back at your code, you notice the capitalization on "Textbox" in the dojo.require is wrong. That's a common mistake, but easily fixed. It's also common to forget the dojo.require, or to misspell the class in the dojoType attribute. In each case, Firebug will set you straight.

Firebug Lite will give you a similar console, but it will appear naturally at the bottom of your browser window. To turn it off, you must set the isDebug flag to false and run the page again.

Errors In Dojo/Method and Dojo/Event Code

The following code has an error:




Fix me!
    
    
    


Click to break!

But when you look at the console, there's no apparent error. If you were programming in other languages, you might use a debugger and set a breakpoint on the "this.domNode" line. Firebug's debugger let's you do that, but not on this particular code. Scripts of type "dojo/event" and "dojo/method" are compiled and interpreted differently than "text/javascript" ones. But there are a couple of alternate debugging methods.

Method 1: Logging

The first method is logging, and if you've used modern logging tools like log4j you'll find it familiar. The idea is to write trace messages to a log which you can then use to find variable values or the last executed bit of code.

Why not just use alert() ? The trusty JavaScript alert() is a favorite debugging tool, but it suffers from the following problems:

Clearly alert's just not powerful enough. In Dojo logging, you can associate messages with severity, just like in log4j. The following code illustrates the five severity levels:

/* 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;}
console.log("Nothing happening");
console.debug("Checking to make sure nothing happened");
console.info("Something might happen.");
console.warn("Something happened, but it's no big deal.");
console.error("Cough cough!");

In the Firebug console, the messages will appear like this:

[inline:firebug_logging.png]

In IE, they will appear like this:

[inline:firebug_ie_capture.png]

Another useful method, console.dir() dumps variable contents to the screen. While console.log works fine for strings and integers, console.dir prints more complex variables - objects, arrays, arrays of objects, or whatever. For example:

/* 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;}
console.dir([
   {attribute: "last_name", sortDescending: true},
   {attribute: "last_name", sortDescending: true}
]);

produces:

[inline:firebug_logging2.png]

So in our example above, we write:

console.debug("dojo.newWidth is" + dojo.newWidth);
this.domNode.style.width = dojo.newWidth;

Running this, we quickly find that dojo.newWidth is undefined. Maybe we spelled it wrong? To quickly find out, we change the debugging statement to:

console.dir("dojo is" + dojo);
this.domNode.style.width = dojo.newWidth;

Nope, there's no property in dojo that looks like newWidth. Finally, we spot our error and change the right hand side to "newWidth." Case closed.

Method 2: The "debugger" Statement

Alternatively you can set a "poor person's breakpoint" in the code. Just insert the debugger; statement, which is a legal JavaScript reserved word.

debugger;
this.domNode.style.width = dojo.newWidth;

This statement stops the code and brings you to a Firebug command prompt. It appears the code has stopped at ... huh?

[inline:debugging4.png]

That's a side effect of running dojo/event code. The breakpoints don't seem correct at all.

But just click the Console tab and now you can examine variables or execute just about any JavaScript you want. In this case, we look at the dojo.newWidth property, which has nothing in it. But "dojo" does and we examine it by console.dir(dojo). Basically all the logging features of method 1 are available to type here.

[inline:debugging3.png]

To Follow The dojo.require Trail, Use Dojo Locally

Since that code is now running, we try a minor variant which sets the button to blue:

/* 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>Fix me!</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");
        dojo.require("dijit.form.Button");
    </script>
</head>
<body class="tundra">
<div dojoType="dijit.form.Button">
   Click to break!
   <script type="dojo/event" event="onClick">
      this.domNode.style.backgroundColor = dojo.Color.named.aliceblue;
   </script>
</div>
</html>

You check the console ... no errors there. But that dojo.Color.named.aliceblue is a little questionable. You know that dojo.colors needs to be included, but you thought dijit.form.Button already did that.

You can find out for sure by using a local copy of Dojo. CDN Dojo is very quiet about the modules it loads. Local Dojo is very noisy. So, assuming our local copy of Dojo is installed on the web server underneath /dojoroot, the following change:

/* 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;}
<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>

Yields the following on the console

[inline:debugging5.png]

You see every Dojo Core and Dijit Component loaded. Sure enough, dojo.colors is not in the list, so we add a dojo.require statement

That Doesn't Look Right ... DOM Inspection

Unfortunately, that doesn't fix the problem either. When styling errors occur, it's a good time to use Firebug's DOM Inspector. You can think of it as View Source on steroids.

So we click Inspect and point at the screen button

[inline:debugging6.png]

The right-hand side of the console tells what styles and style rules are applied to this class. Crossed-off lines are styles that have been overriden. Very nice!

Debugging External Classes With debugAtAllCosts

dojo/method and dojo/event scripts are good for short, non-reusable snippets of code. But when you start building reusable components, you'll be storing your code into Dojo-declared classes instead. The good news is the more you make this switch, the easier your debugging task will be.

So here's a piece of HTML code and a reusable Dojo-based widget:

/* 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>Goolica Tax Form</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");
        dojo.require("dojobook.online-book.debugging.BuggyWidget");
    </script>
</head>
<body class="tundra">
    <div dojoType="dojobook.online-book.debugging.BuggyWidget"></div>
</body>
</html>
dojo.provide("dojobook.online-book.debugging.BuggyWidget");

dojo.require("dijit._Widget");

dojo.declare(
"dojobook.online-book.debugging.BuggyWidget",
[dijit._Widget],
{ 
   postCreate: function() {
      dojo.nonExistentMethod();
   }
});

Running this code, you will see an error appear, but it's nowhere near the right location:

[inline:debugging8.png]

But by simply setting the debugAtAllCosts flag to true:

/* 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="/dojoroot/dojo/dojo.js"
            djConfig="parseOnLoad: true, debugAtAllCosts: true">
</script>

the displayed error location will now be correct:

[inline:debugging7.png]

Important! you should always remove debugAtAllCosts from production code. It slows down the client unnecessarily. Rather than manually inserting and removing them, I like to delegate that job to a server side language like PHP:

/* 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 .co1 {color: #808080; font-style: italic;} .geshifilter .co2 {color: #808080; font-style: italic;} .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 .me1 {color: #006600;} .geshifilter .me2 {color: #006600;} .geshifilter .re0 {color: #0000ff;} .geshifilter .re1 {color: #ff0000}
<?php
$djConfig     = $inProduction ? "parseOnLoad: true" : "parseOnLoad: true, debugAtAllCosts: true";
$loadLocation = $inProduction ? "http://o.aolcdn.com/dojo/1.0.0" : "/dojoroot";
$useXd        = $inProduction ? ".xd" : "";
?>
    <style type="text/css">
        @import "<?= $loadLocation ?>/dijit/themes/tundra/tundra.css";
        @import "<?= $loadLocation ?>/dojo/resources/dojo.css"
    </style>
    <script type="text/javascript" src="<?= $loadLocation ?>/dojo/dojo<?= $useXd ?>.js"
            djConfig="<?= $djConfig ?>"></script>

Introduction

Dojo provides a lot of power and attempts to make it digestable in three major layers: Dojo Core, Dijit, and DojoX. This book serves as a guide to these layers, introducing concepts as you need them and working downward from high-level usage to getting your hands dirty in building your own widgets, custom namespaces, and unit tests.

As you'll see, Dijit and DojoX build on the solid foundation that Dojo Core provides for all Dojo applications. With Core and Dijit locked into stable APIs and a heavy QA, i18n, and accessibility process many edge-of-the-web features for which Dojo is known are developed in the looser, more organic DojoX project. Throughout the book we will show you how these parts build on each other and use the infrastructure each provides to elegantly solve user experience problems which until now have been difficult for browser-based UIs to address.

Thanks for checking out Dojo and the Dojo Book. If things aren't clear in the book, just comment on the book page in question and we'll try to improve it. It's your applications that have inspired us to build Dojo and the stories of people improving user experiences with the toolkit keep us going, and it's with your help that we are evolving this book.

Licensing

You may use Dojo in commercial software without obtaining a separate license or incurring other obligations.

The Dojo Toolkit is dual-licensed. The preferred license is the Academic Free License v2.1. It is extremely liberal, allows for commercial use, and provides for sub-licensing. All Dojo Foundation projects release their code under the terms of this license. It is almost never necessary to exercise the "dual" portion of the dual-licensing terms since the AFL is extremely permissive.

For users who face the problem of artificial ambiguity the FSF has created regarding the compatibility of the AFL and the (L)GPL, The Dojo Toolkit may alternately be used under the terms of the BSD License. Both the AFL and the BSD licenses meet the licensing goals of the Dojo Foundation.

Dojo's "dual licensing" is different than that of many Open Source projects in that the terms of both licenses are Open Source and extremely permissive. There are no royalties or commercial use clauses to complicate the matter. In almost every case, you will not need to choose anything other than the AFL and in the common case you need not do anything to denote this choice of license. If you have questions regarding Dojo licensing, please do not hesitate to contact Alex Russell, current President of the Dojo Foundation.

The Role of the Dojo Foundation

Dojo is Open Source software, distributed by a non-profit foundation which has been set up for the purpose of providing a vendor-neutral owner of Dojo intellectual property. In order to ensure to users of Foundation projects that there is no ambiguity or hidden liability regarding the use of Foundation code, all contributors are required to provide signed Contributor License Agreements.

All committers on Dojo Foundation projects have a vote in Foundation matters. The Foundation is run by contributors, operates in a transparent way, and is funded exclusively by donations. The licensing goals of the Foundations are briefly covered on the Foundation page.

Third-Party Licenses

Dojo uses code from other open source projects, subject to the terms of their licenses. Those licenses and software copyright notices are listed below:

History

In early 2004 Alex Russell (original creator of netWindows) began looking to hire a collaborator on DHTML projects at Informatica. In the process many members of the DHTML community were contacted, culminating in the April 25, 2004 email titled "Selling the future of DHTML". David Schontzler (Stilleye) spent a summer working at Informatica, and Dylan Schiemann also joined Informatica at that time. The first lines of code contributed to Dojo were done by Alex and Dylan with the support of Informatica. There were many other community members that were active participants in shaping the direction of Dojo, including Joyce Park, Tom Trenka, Mark Anderson, Leonard Lin (who suggested the name Dojo), Aaron Boodman, Simon Willison, Cal Henderson, and Dan Pupius.

After several months of discussions on the ng-dhtml (now dojo-developer) mailing list about licensing, choosing a name, coding conventions, build tools, server configuration, and requirements, work began and the Dojo Foundation was formed. The foundation is a 501(c)6 entity designed to house the code and IP rights and today hosts several other projects as well. By March 2005 contributions from the community began to outweigh those of the core development team and today 8 major releases have been made with over 1 million downloads of Dojo to date. Contributions and code have come from more than 60 developers and companies and major users such as IBM, AOL, Sun, SitePen, Bloglines, Google, Nexaweb and others continue to keep Dojo's quality high and the community vibrant.

Why Dojo?

Today there are several high-quality JavaScript toolkits available and several hundred other toolkits of varying quality and completeness. Why, out of that sea of possible options, should you chose Dojo?

  • Breadth and Depth: Dojo is the "full stack". Instead of cobbling together components from several different sources, Dojo allows each component to build on a trusted set of high-quality building blocks by providing integrated infrastructure and a wide variety of optional modules. These components provide good solutions to common user experience problems and can be easily tweaked to meet your needs. From pane-based layouts to client-side charting and graphing to data binding to a time-tested module system, Dojo is solid infrastructure for delivering great experiences.
  • Quality: Infrastructure for internationalization and accessibility is woven through the entire fabric of Dojo. Keystrokes are hinted correctly. The components all fit together as a cohesive whole. Everything is customizable easily with CSS, but very little needs to be tweaked to get a great looking UI nearly everywhere. These are the hallmarks of a system which has been designed and tested to deliver great experiences, not only to users, but also to designers and developers.
  • Performance: Dojo is used on high-profile, high-traffic sites every day and Dojo's build tools are a key reason why. Dojo's package system makes it easy to manage large-scale UI development projects and the build system layers on top to make your applications scream; all without code changes. Dojo also packs high-performance implementations of common utilties into its core, and the rebuild of Dojo for 0.9 focuses heavily on performance and reduced code footprint. The result is a small, tight toolkit that is blazing fast. Dojo's performance alone makes it an ideal platform to extend and build on.
  • Community: Dojo is an open community. As a result many individuals and companies have been able to come together on a level playing field to build tools that benefit everyone. The licensing of the toolkit is designed to be as apolitical as possible and we work hard to ensure that getting your itches scratched is easy if you are willing to get involved. All development happens in the open and the barriers to entry are intentionally very low. We don't care where you work or how "qualified" you are, all we care is that you want to build products that makes user experiences better. Designer or developer or doc writer, the community Dojo values contributions of every kind and position in the community is commensurate with the quality of the work you do, not political wrangling. We're working to change the notions of who can be contributors in Open Source and we invite you to join us in charting a new path. If you want to build a great product and think you can help us, we want to hear from you.

Dojo vs. Other Toolkits

Several other toolkits are often compared to Dojo. What follows is not a comprehensive comparison, but rather a high-level comparison of the features and design goals of these alternatives and how they compare to Dojo's features, development process, and philosophy.

  • MochiKit: MochiKit is a high-quality JavaScript toolkit which makes writing JavaScript as pythonic as is reasonably possible while still achieving good performance. It follows many of the same conservative principles about packaging, naming, and global namespace usage as Dojo. Authored primarily by Bob Ippolito, it has great docs and tests but unlike Dojo does not ship with a widget system or extensive component set. Some code is shared between Mochi and Dojo (under CLA, of course). Mochi is not backed by a foundation and code lineage is not verified but it is very liberally licensed.
  • Prototype+Scriptaculous: These pervasive libraries provide much of the same functionality as Dojo Core but do not share the same conservative philosophy regarding global namespace contention, favoring shorter naming of commonly used functions over other concerns. They feature good documentation and broad community support as well as tight integration with Ruby On Rails (among other frameworks). Scriptaculous provides some controls (auto-complete, sliders, etc.) but is not a widget toolkit and does not have support for building widgets easily. Many add-on libraries are available for Prototype+Scriptaculous but they are not distributed with the library as such. They do not feature a package or build system (aside from their own distributables).  Prototype and Scriptaculous are not backed by a foundation and code lineage is not verified but they are very liberally licensed.
  • YUI: YUI is developed in-house at Yahoo and features extensive, high-quality documentation and examples. Designed for speed and targeted at a population of professional PHP developers, YUI is designed with the needs of Yahoo-scale applications in mind. A growing list of controls is available with the toolkit as are useful CSS normalizing and layout style sheets. No package system is available, but "roll up" files of common functionality are distributed and documentation makes clear what order to load files in. No CSS query or markup-driven widget construction system is available in YUI. YUI has an active community and liberal licensing but external committers are not allowed on the project and Yahoo has not clarified the code lineage and other IP rights around the toolkit. No source control access of any sort is available. YUI is edge-cached on Yahoo's CDN for use by all.
  • JQuery: A minimalist system focused primarily on operating on existing DOM structures, JQuery features a hybrid XPath/CSS query language (Dojo uses standard CSS 3 queries) and provides a rich set of options and operations on the results of these queries. JQuery packs Ajax, effects, and other utilities into a small core (beating all but MooTools). While there is no widget or package system in JQuery per sae, 3rd party component libraries are available which build on top of JQuery. The JQuery community is highly active, usually helpful, and external contributions and patches are accepted. Good docs for the system are readily available. JQuery is dual-licensed MIT and GPL with all copyrights resting with John Resig. It is not clear how IP rights are assigned to John by other contributors and under what terms. Several frameworks (notably Drupal) integrate JQuery.
  • EXT: Like Dojo's Dijit system, EXT is a component library. It features a large number of consistent, good looking widgets with an emphasis on pixel-perfect layout and desktop-like UIs across browsers. Originally developed to run on top of YUI and later JQuery, EXT now has it's own low-level library, removing the need for 3rd party dependencies. The EXT community is very active and good documentation is available for the library. It is licensed under the terms of the LGPL and commercial licenses of various forms are available. It is not clear whether or not external contributions are accepted (and under what terms) and anonymous subversion access is limited to those who financially support the project in some way.
  • GWT: Directly integrating server-side development and client-side development, GWT takes the perspective that JavaScript is a bug to be solved and uses advanced compiler technology to allow developers to write in Java and generate dynamic, JavaScript based UIs in the Google style. The default widget set is a strict subset of those provided by Dijit, but GWT takes great pains to optimize all generated code. A growing trove of add-on libraries are available to enhance the default components. Unlike YUI and EXT, GWT is being run as a real Open Source project, allowing external committers, and doing development in the open while managing IP issues in a very sophisticated manner (CLAs, code review, etc. Much like Apache or Dojo).  GWT applications can only be written in Java and are most often deployed on Java containers. Good documentation is available and a thriving community is helpful.

For the sake of comparison, Dojo:

  • Allows external committers and uses CLAs (like GWT or Apache) to ensure that there are no IP issues
  • Is very liberally licensed and provides anonymous SVN access to everyone. Committer privs are earned
  • Provides a relatively rich client-side component set but does not require tight binding to any server-side language ("protocols, not APIs")
  • Attempts to provide a balance between on-the-wire size and common-case functionality. Dojo Base is similar in size to Prototype.
  • Is very conservative about not stepping on the toes of other code in your pages and preserving the global namespace
  • Is edge-cached on AOLs CDN for use by all
  • Provides a package system which makes knowing which order to load things in a moot problem
  • Allows for incremental enhancement via markup and provides a very easy-to-use widget system for building your own reusable components which can then be easily instantiated via markup.

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" dojoType="dijit.form.CheckBox">
             </td>
             <td><label for="fooCB2">Remember Password</label></td>
        </tr>
        <tr>
            <td colspan="2" style="text-align:center;">
                <button dojoType="dijit.form.Button" type="submit"
                              iconClass="mailIconOk">
OK</button>
                <button dojoType="dijit.form.Button" type="submit"
                              iconClass="mailIconCancel">
Abort</button>
             </td>
        </tr>
        </table>
</div>

You can look at dijit.Dialog as a mini-form, with Dijit form elements sprinkled into it. Note, we don't do anything with the data here, but a finished app would save the settings and communicate them back to the server.

Andy places the dojo.require's in the header

dojo.require("dijit.form.Button"); 
dojo.require("dijit.Menu");
dojo.require("dijit.Tooltip");
dojo.require("dijit.Dialog");
dojo.require("dijit.form.ComboBox"); 
dojo.require("dijit.form.CheckBox"); 
dojo.require("dijit.form.FilteringSelect");
dojo.require("dijit.form.Textarea");

And step 3 is done. "Wow," Andy thought, "that's a lot less JavaScript than I thought." To tell the truth, there is a lot of JavaScript behind it, but you didn't have to write it. Dojo has it all!

A Compose Mail Widget

Next, Andy needs a Compose Mail window that looks like this:

[inline:mail_step4.png]

Hmmmm, composing HTML email looks like it might be a problem. There's that huge toolbar and all those display stuff. Well, to be honest, with Dojo that's the easiest part of this step. Simply write a template 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;}
<textarea dojoType="dijit.Editor" style="overflow:auto"
        extraPlugins="[{name:'dijit._editor.plugins.LinkDialog'}]"
>

<i> This is just a sample message. There is email-address auto-complete in the to: field.
<br><br> give it a whirl.
</i>
</textarea>

And save it in the file mail/newMail.html on your server. The dijit.Editor widget sets up the word-processor like environment for you, complete with toolbar, accelerator keys, and a fairly full array of HTML tools. The LinkDialog plugin adds an "Add Link..." button to the editor. Wow! It's a 5 line word processor. Andy mentally files this away - it could come in handy for composing his heartfelt speech later!

Using Dijit components is fun in a geeky sorta way. Still, Andy was one of those kids who got bored with Lego's ... until he made new ones with a band saw and a soldering iron.

If there were only one Compose Mail screen at a time, he could model it as a single tab and show it when necessary. But we need more than one. Andy thinks ... if one could just make a Compose Mail widget, then you can create as many instances as you want.

You can do that! And just like you can create widget instances declaratively or programmatically, so you can create new widget classes declratively or programmatically. As you might guess, the declarative way is easier. You simply pull out the dijit.Declaration widget.

Dijit.Declaration defines a widget class using a familiar macro-like template. Let's take a look:

/* 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.Declaration" widgetClass="mail.NewMessage">
    <div dojoType="dijit.layout.LayoutContainer" dojoAttachPoint="container"
            title="Composing..." closeable="true">

        <div dojoType="dijit.layout.LayoutContainer" layoutAlign="top"
                style="overflow: visible; z-index: 10; color:#666; ">

            <table width=100%>
                <tr style="padding-top:5px;">
                    <td style="padding-left:20px; padding-right: 8px; text-align:right;">
                         <label for="${id}_to">To:</label>
                    </td>
                    <td width=100%>
                        <select dojoType="dijit.form.ComboBox"
                                     id="${id}_to" hasDownArrow="false">

                            <option></option>
                            <option>adam@yahoo.com</option>
                            <option>barry@yahoo.com</option>
                            <option>bob@yahoo.com</option>
                            <option>cal@yahoo.com</option>
                            <option>chris@yahoo.com</option>
                            <option>courtney@yahoo.com</option>
                       </select>
                    </td>
                </tr>
                <tr>
                    <td style="padding-left: 20px; padding-right:8px; text-align:right;">
                          <label for="${id}_subject">Subject:</label>
                    </td>
                    <td width=100%>
                        <select dojoType="dijit.form.ComboBox"
                                     id="${id}_subject" hasDownArrow="false">

                            <option></option>
                            <option>progress meeting</option>
                            <option>reports</option>
                            <option>lunch</option>
                            <option>vacation</option>
                            <option>status meeting</option>
                        </select>
                   </td>
                </tr>
            </table>
        <hr noshade size="1">
        </div>
                       
        <div dojoType="dijit.layout.LayoutContainer"
                layoutAlign="bottom" align=center>

            <button dojoType="dijit.form.Button"
                          iconClass="mailIconOk">
Send</button>
             <button dojoType="dijit.form.Button"
                           iconClass="mailIconCancel">
Cancel</button>
      </div>
        <!-- new messase part -->
        <div dojoType="dijit.layout.LayoutContainer" layoutAlign="client">
            <div dojoType="dijit.layout.ContentPane" href="mail/newMail.html"></div>
        </div>   
    </div>
</div>

The HTML defined inside the dijit.Declaration tagis dropped in wherever the widget class "mail.newMessage" is requested. You could, in fact, drop one in 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;}
<div dojoType="mail.newMessage" id="myNewMessage"></div>

You pass parameters to your new widget by using:

  • An attribute on the calling side: in our case, we set id="myNewMessage" to pass the parameter "id".
  • A ${___} on the declaration side: in our case, we have ${id} marked several places in the definition. These are substituted with myNewMessage in this call.

This declarative usage is neat, but not helpful in our case since we don't know how many newMessage components to create. Instead, we create instances programmatically using the Compose Mail button:

/* 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">
                /* make a new tab for composing the message */
                var newTab = new mail.NewMessage({id: "new"+paneId  }).container;
                dojo.mixin(newTab,
                        {
                                title: "New Message #" + paneId++,
                                closable: true,
                                onClose: testClose
                        }
                );
                tabs.addChild(newTab);
                tabs.selectChild(newTab);
        </script>
</button>

This new code creates a mail.NewMessage programmatically, then creates a Tab out of it and adds it to the TabContainer (specified by the JavaScript variable newTabs)

Add some declarations and a few supplemental variables to the header:

dojo.require("dijit.Declaration");
dojo.require("dijit.Editor");
dojo.require("dijit._editor.plugins.LinkDialog");

// Global var for new mail pane
var paneId=1;
        
// for "new message" tab closing
function testClose(pane,tab){
  return confirm("Are you sure you want to leave your changes?");
}

And now we can compose mail to people.

Mail Progress Bar

The people who run clickdammit.com are cheapskates. Their servers are Pentium Pros bought off Ebay in exchange for some Beanie babies. So Andy knows that users will click "Get Mail" and wonder what's taking so long.

This is a common problem in Web 2.0 applications. Hopefully your servers are faster than Pentium Pros, but even then you can't always be sure when response times will be slow. Since you're not submitting the form, you can't rely on the browser's spinning logo to tell the user something's happening.

Fortunately, Dijit has a progress bar for those situations. Andy doesn't have access to clickdammit's mail server yet, but he decides to sketch out the progress bar and write some Wait loops behind them to simulate delays.

First he places the progress bar where the placeholder text was:

/* 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 id="fetchMail" style="opacity:0;visibility:hidden">
        <div annotate="true" id="fakeFetch" dojoType="dijit.ProgressBar"
                style="height:15px; width:275px;" indeterminate="true"
                report="fakeReport">
</div>
</div>

We make this progress bar hidden so it can be called up when needed. At first the progress will be incalculable since we don't know the number of messages to download. That's what "indeterminate=true" shows.

We have already stubbed out fakeDownload() and connected it to the Get Mail button. Now we can actually add some code to it.

var fakeDownload = function(){
	dojo.byId('fetchMail').style.visibility='visible';
	numMails = Math.floor(Math.random()*10)+1;
	dijit.byId('fakeFetch').update({ maximum: numMails, progress:0 });
	dojo.fadeIn({ node: 'fetchMail', duration:300 }).play();
	for (var i=0; i<=numMails; i++){
	    setTimeout(
                      "updateFetchStatus("+i+")",
                      ((i+1)*(Math.floor(Math.random()*100)+400))
            );
	}
}

First we turn on the progress bar, then pick a random number (1 to 10) of emails to download. Now to update the progress bar ... Andy looks up the dijit.ProgressBar widget in the Dijit catalog and finds under Methods:

Methods
update update progress information

Update looks good, and he consults the example before writing the update statement.

dojo.fadeIn does what you think. It fades-in an object but turning up the opacity from 0% to 100%. The "300" here is the number of milliseconds fadeIn should take to complete its job. 0.3 seconds sounds small, but it's enough to make the animation noticeable without stopping the proceedings.

Any adds the updateFetchStatus function, which will fire once for every email.

var numMails;
var updateFetchStatus = function(x){
	if (x == 0) {
		dijit.byId('fakeFetch').update({ indeterminate: false });
		return;
	}
	dijit.byId('fakeFetch').update({ progress: x });
	if (x == numMails){
		dojo.fadeOut({ node: 'fetchMail', duration:800,
			// set progress back to indeterminate. we're cheating, because this
			// doesn't actually have any data to "progress"
			onEnd: function(){ 
				dijit.byId('fakeFetch').update({ indeterminate: true });
                                // remove progress bar from tab order
				dojo.byId('fetchMail').style.visibility='hidden'; 
			}
		}).play();
	}
}

dojo.fadeOut looks a lot like fadeIn, but the onEnd property is new. It expects a function as its property. Here we add a function literal that removes the progress bar from the screen. But why put this in onEnd? Why not just put it after play()? Andy tried that first, and found the progress bar would blink out before it had completely faded. That's because fadeOut, like all Dojo animations, runs asynchronously. In other words, it kicks off the fadeOut and starts executing the next statement. FadeOut continues executing. So setting the duration to 800000000 would make the animation slow, but the browser would still be immediately usable.

Finally, we connect a function to ProgressBar's "report" extension point. Remember how we specified report="fakeReport" in the Progress Bar tag? Now we can fill it in. Report gets passed in a percent and expects a string to place on the progress bar.

var fakeReport = function(percent){
    return "Fetching: "+(percent*this.maximum) + " of " + this.maximum + " messages.";
}

Andy clicks on the Get Mail and sits back. It looks darn good! He clicks it a few more times just to make sure. Very nice. We're getting pretty close to a working demo to show off. Just a few more things need to go in place.

Mail Folders and dojo.Data

JSON and dojo.data stores worked pretty well for address book data. Andy wonders whether they would work to send mail and folders too.

[inline:mail_step6.png]

Not only is it possible ... you can pass all the data back in one fell swoop. This is possible because ItemFileReadStore and JSON handle heirarchical data with ease. In mail land, you have folders which contain other folders or mail items. In JSON notation, you can model folders like this:

{ 
identifier: 'id',
label: 'label',
items: [
	// Hierarchy of folders

	{ type: 'folder', id: 'mailbox', label:'Folders', folders: [
		{ type: 'folder', id: 'inbox', label:'Inbox', icon:'mailIconFolderInbox' },
		{ type: 'folder', id: 'deleted', label:'Trash', icon:'mailIconTrashcanFull' },
		{ type: 'folder', id: 'save', label:'Save',  folders:[
			{ id: 'work', label:'stuff for work'},
			{ id: 'fun', label:'stuff for fun'}
		]}
	]},

You can see how we nest objects within objects, like the "work" and "fun" folders underneath the "Save" folder. We can model mail like this:

// Flat list of messages (each message lists its folder)

	{ type: 'message', id: 'node1.1',
                  folder: 'inbox', label: "today's meeting", 
                  sender: "Adam Arlen", sent: "2005-12-19",
	          text: "Today's meeting is cancelled.
Let's do it tomorrow instead.

Adam" }, { type: 'message', id: 'node1.2', folder: 'inbox', label: "remaining work", sender: "Bob Baxter", sent: "2005-12-18", text: "Hey, we need to talk about who's gonna do all the left over work. Pick a day you want to meet:
" },

Now you have three types of objects, delineated by type: address, folder and message. Andy decides to stick them all in one JSON data message. That means only one trip ito the server grabs everything about the user's mail store. Gotta love that!

The actual folder listing looks a lot like the address book code. The getIconClass extension point draws the folder icons:

/* 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.AccordionPane" title="Folders">
        <div dojoType="dijit.Tree" id="mytree" store="mailStore"
                labelAttr="label" childrenAttr="folders"
                query="{type:'folder'}" label="Folders">

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

The "childrenAttr" in the Tree tag does all the sub-folder magic.

One more thing and he'd be ready to show it to Ms. Opulence: connect the folders to messages and the messages to a preview pane. Andy looks at his watch. He's got about 15 minutes before the el stops by his office. If he could catch it and get home early, that'd give him some serious writing and reflecting time. So let's make short work of this, shall we?

The Message List - dojox.Grid

Andy has been studying up on Dojo 1.0, and found one of the great new features - the Dijit grid. A grid is like a mini spreadsheet, or a maxi-HTML-table. You can sort the columns, edit cells, and enable all kinds of cool stuff. It sounds like the perfect container for a message list.

[inline:mail_step7.png]

Feeding the Grid

Once you've worked with Dojo awhile, you start to see Grand Unifying Themes. One of them is dojo.data, feeding server-pulled data to different kinds of widgets. Grid is no different. Fortunately, Andy already has the data store defined. And since that data store has messages in it, hierarchically tied to folders, it's a short step to display them. First he adds the separate Grid style sheet:

/* 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;}
<style type="text/css">
    @import "http://o.aolcdn.com/dojo/1.0.0/dojox/grid/_grid/tundraGrid.css";
    @import "http://o.aolcdn.com/dojo/1.0.0/resources/dojo.css";
    ...

A grid has two main elements: the model and the structure. The model is the data behind the grid, while the structure is a set of display instructions.

Andy does the model first. We already havy the data in a dojo.data store called mailModel. To make the data source mailStore work with Grid, he declares a dojox.grid.data.DojoData adapter. You can think of it as a pipe where the data flows in, and the appropriately filtered data comes out for use in a grid. When the app initially starts, there is no folder selected. Hence there are no messages to display. Andy decides to initialize the adapter, but issues a query that will return no items. This has performance benefits, as we'll see in a minute.

/* 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="dojox.grid.data.DojoData" jsId="mailModel"
     rowsPerPage="20" store="mailStore" query="{ type: 'NONE' }"
     clientSort="true">

</div>

The structure is a description of the Grid layout. Structures are composed from views, which are little independently scrollable subgrids called views. Our grid is simple, containing only one view. A cell is Grid's name for a column.

var mailView = {
    cells: [[
        {name: 'From', field:'sender',width:"25%"}, 
        {name: 'Subject', field:'label',width:"65%"},
        {name: 'Date', field:'sent',width:"10%"}
    ]]
};

var layout = [ mailView ];

The cells property is a two dimensional JavaScript array, hence the double square brackets. In the Grid section, you'll see how to split large amounts of data into subrows, all selectable as one unit. Here we have just one subrow with a small amount of data.

Wiring up the actual Grid is a snap. The Grid widget does all the work for you:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<div id="listPane" dojoType="dijit.layout.ContentPane" sizeMin="20" sizeShare="20">
    <div id="grid" dojoType="dojox.Grid" jsId="mailGrid" structure="layout" model="mailModel"
         onRowClick="displayMailMessage">

    </div>
</div>

Clicking a row in this grid will display the appropriate message in the bottom area. So Andy stubs out the call to the onRowClick event, which he'll fill in later.

In the folder tree, Andy connects some Dojo code to the onClick event. Once you know the folder name, you simply build a new adapter, set that as the Grid model, and the grid will automatically populate. The nice thing here is the Mail messages are already loaded in the dojo.data store. Building a new Grid model on top of it re-uses the data with a new query without a server round-trip.

/* 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="mytree" store="mailStore"
    labelAttr="label" childrenAttr="folders" query="{type:'folder'}" label="Folders">

    <script type="dojo/method" event="onClick" args="item">
        if(!item){
            return; // top level node in tree doesn't correspond to any item
        }
        /* filter the message list to messages in this folder */
        var newMailModel = new dojox.grid.data.DojoData();
        newMailModel.rowsPerPage = 20;
        newMailModel.store = mailStore;
        newMailModel.query = {
            type: "message",
            folder: mailStore.getValue(item, "id")
        };
        newMailModel.clientSort = true;
        mailGrid.setModel(newMailModel);
    </script>

So now clicking a folder displays the message list. It's time to go back and fill in onRowClick for the message. This turns out to be fairly trivial:

function displayMailMessage(evt) {
     var msg = mailGrid.model.getDatum(evt.rowIndex,6);
     dijit.byId("message").setContent(msg);
}

So now he has a message list and a message. Andy looks at his watch. 5 minutes to spare. He can't resist adding one more item - displaying a widget inside a mail message. So he quickly adds a test case to the sample data:

{ type: 'message', id: 'node4.4', folder: 'fun', 
   label: "paint", sender: "Jack Jackson", sent: "2005-12-16", 
   text: "what color is good for the new office?
Let me know soon" },

And dojo.require's dijit.ColorPalette. Sure enough, displaying the message automagically displays the palette:

[inline:mail_step8.png]

Summary

Wow! That's a lot of functionality. In a short amount of code, about 1/2 JavaScript and 1/2 HTML, we have a working email client user interface with:

  • Command buttons with icons
  • An Options dialog box
  • A tabbed interface where you can compose multiple emails at once and quickly switch between them.
  • A full HTML-based email format. You can create, edit and read email messages with full, rich formatting.
  • An address book and folder list, heirachically arranged with expansion and contraction buttons.
  • A message list tied to the folders with a click
  • Lots of little user cues - tooltips and a progress meter for downloads

Sure, the server part needs writing. But that can wait until tomorrow, and it shouldn't be too difficult. Find the right words to get a woman to marry you ... now that's difficult. Andy heads out the door for the train

Example 3: Chatting With Tech Support

Laura Allen has been an AIM user for years, and has been reading about the business uses of chat software. One use that intrigues her is "Live Support". Her company sells very sophisticated equipment which generates many sales questions. It be very nice to have a support person on the opposite end of a chat client. Maybe if they would all just use AIM ... but this is unrealistic.

She considers writing it in PHP. The trouble is the stateless nature of the web. It's easy to send a message to the server. But that generates a page refresh that loses most of the state. Plus the refresh makes it look primitive and ugly compared to AIM.

Enter Dojo. With it, she can write a mini chat client that polls the server via XHR and no page refreshes. The ASP server portion is easy - just a simple storage mechanism and a way to transmit the data via JSON. The event model of dojo is familiar like Visual Basic or Java Swing, and passes the chat messages easily. Not to mention (and this is wickedly cool), the rich text editor of dojo makes it easy to do italics, bold, sizes, etc. Finally, wrapping this all up in a collapsible title pane tucks it out of the way so a customer can pull it in when needed.

On the operator end, dojo helps as well. Operators can have several conversations at once, with each conversation in a separate tab. The tab will light up when there's activity, so the operator can keep chats on hold without losing them.



* The source code for this demo, written by Peter Higgins, is included in the dijit/demos folder of the Dojo distribution. For now, to make this demo work you will need your own Cometd server. We hope to have a public sandbox server available shortly for your demoing pleasure.

Architecture

Laura, being the agile programmer she is, wants to first build the smallest solution that solves the problem. She sighs, longing for the days of C and network sockets. But JavaScript's security model prohibits peer-to-peer networking, so there's nothing like that.

She looks on the Dojo site and finds the cometd (pronounced comet-d) server. Interesting! It's a lightweight, HTTP-based server with a small footprint. And it is supposed to have good Dojo integration through publish-subscribe events.

Publish-Subscribe

Publish-subscribe is an easy-to-understand model for cross-process communication, and Dojo has it baked in. It effectively promotes loose coupling between components. Suppose you have a tax form similar to Example 1. You have ten dijit.form.CurrencyTextBox's whose contents get added up to a Gross Income, another dijit.form.CurrencyTextBox. How do you keep Gross Income updated? One way is for the component CurrencyTextBox's to call a central procedure for updating:

/* 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">
function reAdd() {
   newSubtotal = dijit.byId("wages").getValue() + ...;
   dojo.byId("grossIncome") = newSubtotal;
}
</script>
<div dojoType="dijit.form.CurrencyTextBox" id="wages">
   <script type="dojo/connect" event="onChange">
      reAdd();
   </script>
</div>
<div dojoType="dijit.form.CurrencyTextBox" id="tips">
   <script type="dojo/connect" event="onChange">
      reAdd();
   </script>
</div>
...

Because this system is tightly coupled, problems arise:

  • If you add an 11th line to total up, you must change the reAdd() procedure.
  • If half of the boxes need to send extra onChange events to computeDeductions(), those half need to be changed to call this method.

Granted, this example is contrived because the methods are so small. But the larger your system, the more nightmarish maintenance becomes. It would be nice to have a whiteboard where controls could sign up for events that interest them. This whiteboard could be maintained by a third party to keep the controls from worrying about communication details.

That's the essence of publish-subscribe. Dojo is the whiteboard maintainer. The GrossIncome box then says "I'm interested whenever a box changes." This is called a topic, and we say the GrossIncome box subscribes to that topic. The component boxes agree to publish information on this topic when they change. Here's how it looks like in code:

/* 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">
function reAdd(args) {
   dojo.byId("grossIncome").setValue(
        dojo.byId("grossIncome").getValue()
        + args.newValue
        - args.oldValue
   );
}
</script>
<div dojoType="dijit.form.CurrencyTextBox" id="GrossIncome">
   <script type="dojo/method">
      dojo.subscribe("componentChange", reAdd);
   </script>
</div>
<div dojoType="dijit.form.CurrencyTextBox" id="wages">
   <script type="dojo/connect" event="onChange" args="e">
      dojo.publish("componentChange", { oldValue: e.oldValue, newValue: e.targetNode.value });
   </script>
</div>
...

So the topic "componentChange" is published when any of the component boxes change. Sent along with the topic are message-specific data elements that help the subscriber - oldValue and newValue in this case. Need to find which elements in the event object they are.. The lone subscriber, the GrossIncome box, uses these to adjust the current sum. Now to add a component box, you just add it to the form with the same dojo.publish statement. Very nice.

Publish-Subscribe With Cometd

Cometd effectively extends Dojo publish-subscribe past the browser. Cometd speaks the Bayeaux protocol which itself runs over HTTP. Laura immediately sees the usefulness of this for online support. The client can publish a topic to Cometd containing the message. The operator subscribes to that topic and displays messages on the console. It replies on that same topic. Both operator and client send the userid with the message, so they can ignore the message they themselves send.

The protocol works like this. Assume the client's username is "alex.russell" and operator's username is "operator".

  • Operator:
  • (Time Marches On...)
  • Client: Subscribe to "/chat/demo/alex.russell".
  • Client: Publish a message on "/chat/demo/alex.russell": { user: "alex.russell", join: true, chat : "alex.russell has joined the room."}
  • Client: Publish a message on "/chat/demo": { user: "alex.russell", joined: "alex.russell"}
  • Operator: (Sees message on /chat/demo) Subscribe to "/chat/demo/alex.russell"
  • Client: Publish a message on "/chat/demo/alex.russell": { user: "alex.russell", chat : "I have a question about Dojo."}
  • Operator: (Sees message on /chat/demo/alex.russell) Publish a message on "/chat/demo/alex.russell": { user: "operator", chat : "Shoot."}
  • Client: (Sees message on /chat/demo/alex.russell) Publish a message on "/chat/demo/alex.russell": { user: "alex.russell", chat : "Oh wait, I invented Dojo. Never mind."}
  • Client: Unsubscribe to "/chat/demo/alex.russell"
  • Client: Publish a message on "/chat/demo/alex.russell": { user: "alex.russell", leave: true, chat : "alex.russell has left the room."}

Although the protocol looks a little "chatty", Laura has designed it with public chat rooms in mind. When those come to pass, users will be able to join arbitrary public chat rooms using this protocol.

This upfront design will payoff - coding will go quite rapidly.

Details for the Impatient

The Room Widget

In previous examples, we have built a few widgets with the dojo.Declaration tag. This is a fast way to add functionality, but the problem is sharing it with other pages. Although Laura knows the Operator and Client pages will be different, she senses common elements in the way they chat. So she will define a widget class through JavaScript instead. That way, both pages can use it.

The Widget Skeleton

A widget is a way to tie Dojo API calls into one displayable element. The way Laura designs it, the display is pretty much the same on both sides. The only difference is the container. Here's a cocktail napkin view of the client side:

[inline:chat1.png]

And the operator side:

To be added once we get the public cometd server up.

The message display area, the input box for messages and the send button are exactly the same. So we can package those up as a widget, which we will call dijit.demos.chat.room. Using a little object oriented analysis, Laura stubs out the object code:

dojo.provide("dijit.demos.chat.room"); 

dojo.require("dojox.cometd");
dojo.require("dijit._Widget");
dojo.require("dijit._Templated");

dojo.declare("dijit.demos.chat.Room",
    // All widgets inherit from _Widget, and all templated widgets mix in _Templated
    [dijit._Widget,dijit._Templated],
    {
    
        // Username of client
    _username: null,
    
        // Default room id.  Will become the client's username for our tech support line
    roomId: "public",
    
        // For future expansion into public chat rooms
    isPrivate: false,
    
        // Constant
    prompt: "Name:",
    
    join: function(name){
            // Join a room
    },
    
    _join: function(/* Event */e){
            // Respond to someone joining a room (only operator does this)
    },
    
    leave: function(){ 
            // leave a room
    },
    
    chat: function(text){
            // Send a message
    },
    
    _chat: function(message){
            // Receive a message
    },
    
    startup: function(){ 
            // Required function for a widget.  Called on startup
    },

});

The Widget Template

As we did in Example 1 for i18n, we build a template for the widget. Essentially this template will replace the tag with dojoType="dijit.demos.chat.room"

/* 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 id="${id}" class="chatroom">
    <div dojoAttachPoint="chatNode" class="chat"></div>
    <div dojoAttachPoint="input" class="input">
        <div dojoAttachPoint="joining">
            <span>${prompt}</span>
            <input class="username" dojoAttachPoint="username" type="text" dojoAttachEvent="onkeyup: _join">
            <input dojoAttachPoint="joinB" class="button" type="submit" name="join"
                   value="Contact" dojoAttachEvent="onclick: _join"/>

        </div>
        <div dojoAttachPoint="joined" class="hidden">
            <input type="text" class="phrase" dojoAttachPoint="phrase" dojoAttachEvent="onkeyup: _cleanInput" />
            <input type="submit" class="button" value="Send" dojoAttachPoint="sendB"
                   dojoAttachEvent="onclick: _sendPhrase"/>

        </div>
    </div>
</div>

The placeholders ${id} and ${prompt} look familiar. These are replaced with the properties id and prompt from the widget instance. A few of the properties look unfamiliar, but they all start with the prefix "dojo".

  • dojoAttachPoint creates a property in the widget containing the DOM node. For example, the DIV tag with dojoattachpoint="joining" creates a joining property, which you can pick up with "this.joining" in your widget code. You can do with anything you can do with a DOM node, for instance set its styles or CSS class.
  • dojoAttachEvent connects an event and DOM node with a function. So dojoAttachEvent="onkeyup: _cleanInput" means "when the onkeyup event happens here, call _cleanInput."

Only one of the "joining" node:

[inline:chat2.png]

and the "joined" node:

[inline:chat3.png]

is visible at any one time. That gives the illusion that the "joined" and "joining" nodes toggle back and forth, occupying the same screen real estate. This is a fairly common trick in widget templates.

Starting the Widget

Every widget has extension points that get called during widget creation. The widget class designer can hook into these by providing methods, as we do here with startup():

startup: function(){ 
    this.joining.className='';
    this.joined.className='hidden';
    //this.username.focus();
    this.username.setAttribute("autocomplete","OFF");
    if (this.registeredAs) { this.join(this.registeredAs); } 
    this.inherited("startup",arguments); 
},

this.inherited() acts like Java's super() operator, but more generally. Here it calls dijit._Widget.startup(). This is a good practice to get into for the widget extension points.

Joining and Leaving a Room

Dojo calls the join method upon clicking the Submit button. Just as we outlined on the previous page, it establishes the chat connection with the operator (only the client calls this particular method). "_join" does the same thing in response to keystrokes.

Note how the dojoAttachPoints come in handy here: they make flipping the nodes on and off straightforward.

join: function(name){
    if(name == null || name.length==0){
        alert('Please enter a username!');
    }else{
        if(this.isPrivate){ this.roomId = name; } 
        this._username=name;
        this.joining.className='hidden';
        this.joined.className='';
        this.phrase.focus();
        dojox.cometd.subscribe("/chat/demo/" + this.roomId, this, "_chat");
        dojox.cometd.publish("/chat/demo/" + this.roomId, 
           { user: this._username, join: true, 
             chat : this._username+" has joined the room."}
        );
        dojox.cometd.publish("/chat/demo", { user: this._username, joined: this.roomId });
    }
},

_join: function(/* Event */e){
    var key = (e.charCode == dojo.keys.SPACE ? dojo.keys.SPACE : e.keyCode);
    if (key == dojo.keys.ENTER || e.type=="click"){
        this.join(this.username.value); 
    }
},

Leaving, for the most part, just reverses the join sequence:

leave: function(){ 
    dojox.cometd.unsubscribe("/chat/demo/" + this.roomId, this, "_chat");
    dojox.cometd.publish("/chat/demo/" + this.roomId, 
       { user: this._username, leave: true, 
         chat : this._username+" has left the chat."}
    );

    // switch the input form back to login mode
    this.joining.className='';
    this.joined.className='hidden';
    this.username.focus();
    this._username=null;
},

Sending and Receiving a Message

Finally, the meaty part of the Room widget. Sending is a fairly simple matter over our protocol:

chat: function(text){
    // summary: publish a text message to the room
    if(text != null && text.length>0){
        // lame attempt to prevent markup
        text=text.replace(//g,'>');
        dojox.cometd.publish("/chat/demo/" + this.roomId, { user: this._username, chat: text});
    }
},

Receive is handled by the topic subscriptions, which we connected in join(). The event mediator sends receive() a message, which becomes the parameter "message" here. Then message.data contains the object that the publisher sent over.

_chat: function(message){
    // summary: process an incoming message
    if (!message.data){
        console.warn("bad message format "+message);
        return;
    }
    var from=message.data.user;
    var special=message.data.join || message.data.leave;
    var text=message.data.chat;
    if(text!=null){
        if(!special && from == this._last ){ from="...";
        }else{
            this._last=from;
            from+=":";
        }

        if(special){
            this.chatNode.innerHTML += 
                ""+from+
                " "+text+"
"; this._last=""; }else{ this.chatNode.innerHTML += ""+from+" "+text+ "
"; this.chatNode.scrollTop = this.chatNode.scrollHeight - this.chatNode.clientHeight; } } },

Entering and leaving messages use the alert CSS class to distinguish them from regular chat messages.

There's our bouncing baby widget! Now let's make use of it...

Details for the Impatient

The Client Page

The client page uses some eye candy from Dojo. (Clients love eye candy!)

Animation

The client chat window can be open and closed at will. Laura decides that the window should fade in and fade out - an easy thing to do with Dojo.

An animation in Dojo terms is the graduated movement of a DOM node from one state to another. A fade-in, for example, is the movement of opacity (the opposite of transparency) from 0% to 100%. Animations cover a set span of time, so you can fade in over the course of milliseconds, seconds, or minutes. In Dojo Animation, the most general form of an animation is a function. Some animations are so popular, like fades and slides, that Dojo packages those functions for you.

The animation for fading in, triggered by the Show button, looks 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;}
<button dojoType="dijit.form.Button">
Show / Hide Tech Support Chat
<script type="dojo/method" event="onClick">
   var anim = dojo.fadeIn({ node: helpNode.domNode, duration: 400 });
   dojo.connect(anim,"beforeBegin",function(){
        dojo.style(helpNode.domNode,"display","block"); 
        helpNode.toggle();
        _positionIt();
   });
   anim.play();
</script>
</button>

The fade-in lasts for 400 ms. We hook a snippet of code in front of the animation to set the panel to display:block mode first, turn it on and position it to the top of the screen. This is necessary because opacity only works on an element that's actually displayed ... if it's display:node, as our node starts out as, the fade in won't work at all.

Because the corresponding fade-out for hiding is very similar, we rewrite the extension point to handle both jobs. helpNode.open is true if the chat window is currently open.

/* 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 dojoType="dijit.form.Button">
Show / Hide Tech Support Chat
<script type="dojo/method" event="onClick">
    var anim = dojo[(helpNode.open ? "fadeOut" : "fadeIn")]({ node: helpNode.domNode, duration: 400 });
    dojo.connect(anim,(helpNode.open ? "onEnd" : "beforeBegin"),function(){
       dojo.style(helpNode.domNode,"display",(helpNode.open ? "none" : "block"));       
       helpNode.toggle();
       _positionIt();
    });
    anim.play();
</script>
</button>

Keeping the Window Visible

Of course we don't want the chat window to scroll up as the user scrolls up. So we hook some code into the onScroll event of the window. onScroll is one of those handy Dojo Events. It is actually a front for the DOM Level 2 event of the same name, which Firefox implements in a standard way, and IE in a non-standard way. Well, we don't have to worry about that. That gives Laura more time to be creative.

// this puts our help box in the top/right corner on scroll and show
function _positionIt(evt){
    if (helpNode.domNode.style.display == "block"){
        dojo.style(helpNode.domNode,"top",(dijit.getViewport().t + 4) + "px");
    }
}

var helpNode; 
dojo.addOnLoad(function(){ 
    dojo.parser.parse(dojo.body()); 
    helpNode = dijit.byId('helpPane');
    dojo.connect(window,"onscroll","_positionIt");

    // this is a placeholder for the cometd server, once we get a public one.
    dojox.cometd.init("http://comet.yours.com:9000/cometd"); 
});

_positionIt repositions the window to be 4 pixels from the top of the viewport, which Dijit provides through its handy dijit.viewport function. The four viewport coordinates are kept in properties t, b, l and w (top, bottom, length, width).

dojo.addOnLoad specifies a code snippet to be run after all the DOM nodes have loaded and widgets have been drawn. It's a good place for connecting events, as we've done here with onscroll. Finally, we connect to the cometd server for the chat.

Lastly, Laura needs the operator page...

The Operator Page

The operator, unlike the client, can carry on multiple conversations. So they can subscribe to many chat topics, all called /chat/demo/username for uniqueness. Each conversation will have a different Room widget in a different ContentPane

When a message arrives, it may be a new chat request or an existing conversation. We keep track of this in objects that correspond to conversations. First, the addOnLoad starts subscriptions running:

dojo.addOnLoad(function(){ 
	dojo.parser.parse(dojo.body());

	dojox.cometd.init("http://comet.sitepen.com:9000/cometd"); 
	dojox.cometd.subscribe("/chat/demo",control,"_getAlert");

});

The control object is an overall controller for operator conversations. Here's a skeleton:

var control = {
    _chats: {},
    _getAlert: function(e){
    },

    _privateChat: function(e){
    }
};

Coversation records are kept in the _chats object, which will be a hash of username keys with current conversations. Through our subscribe() above, _getAlert will get any messages over the public topic /chat/demo. It will then either create a new conversation and tabbed pane or simply return:

_getAlert: function(e){
    // Ignore all control messages where we already have a conversation registered, 
    // or messages bound for someone else.
    if (!this._chats[(e.data.user)] && (operator != e.data.user)){
        dojox.cometd.subscribe("/chat/demo/"+e.data.joined,this,"_privateChat");

        // Create a tab called chatWithUsername and insert it into the tabbed container
        var tabNode = document.createElement('div');
        tabNode.id = "chatWith" + e.data.user; 
        var chatNode = document.createElement('div');
        chatNode.id = e.data.user + "Widget";
        tabNode.appendChild(chatNode);
        var newTab = new dijit.layout.ContentPane({
            title: e.data.user,
            closable: true
        },tabNode);
        dijit.byId('tabView').addChild(newTab);

        // Create an associated Room object
        var chat = new dijit.demos.chat.Room({
            roomId: e.data.joined,
            registeredAs: operator
        },chatNode);
        chat.startup();

        // And record this conversation
        this._chats[(e.data.user)]=true;
    }
},

The subscription to /chat/demo/username passes received messages to the proper ContentPane:

_privateChat: function(e){
    var thisChat = dijit.byId(e.data.user+"Widget") || false;
    if (thisChat) { thisChat._chat(e); }
}

Laura calls over one of her team members and shows him the chat function. On separate PC's they play with it for about 15 minutes or so, trading war stories and jokes. It feels pretty nice to have a working system built this quickly and with so little code. Laura no longer misses C.

Part 2: Dijit - The Dojo Widget Library

[inline:themeTester.png]

Dijit is a widget system layered on top of dojo.  If you are new to the whole Dojo experience, Dijit is a good place to start.  You can build amazing Web 2.0 GUI's using very little, or no, JavaScript. 

You can use Dijit in one of two ways: declaratively by using special attributes inside of regular HTML tags, and programmatically through JavaScript. You have the same options either way. As we did in Part 1, we'll use declarative Dijit for all the examples in this part. Part 3 will show how to make the same calls programmatically.

Dijit comes bundled with its own theme, tundra, which brings a common design and color scheme to all the widgets.  You can override the theme by container or by element to add nuance and flair. 

Everything in Dijit is designed to be globally accessible -- to accommodate users with different languages and cultures as well as those with different abilities. Language translations, bi-directional text, and cultural representation of things like numbers and dates are all encapsulated within the widgets. Server interactions are done in a way that makes no assumptions about local conventions. All widgets are keyboard accessible and using the standard Dijit theme, usable in high-contrast mode as well as by screen readers. These features are baked in so that, as much as possible, all users are treated equally.

Dijit at a Glance

Form, Validation, Specialized Input

CheckBox [inline:checkbox.png]
ComboBox [inline:combo_box.png]
CurrencyTextBox [inline:currency_textbox.png]
DateTextBox [inline:date_textbox.png]
FilteringSelect [inline:filtering_select.png]
InlineEditBox 0.9 - 1.0 [inline:inline_edit.png]
NumberSpinner [inline:number_spinner.png]
NumberTextBox [inline:number_textbox.png]
Slider [inline:slider.png]
Textarea Textarea expands to fit content [inline:textarea.png]
TextBox [inline:textbox.png]
TimeTextBox [inline:time_textbox.png]
ValidationTextBox [inline:validating_textbox.png]

Layout

AccordionContainer [inline:accordion_pane.png]
Border Container (new in 1.1) [inline:border_container.png]
ContentPane [inline:content_pane.png]
LayoutContainer (deprecated in 1.1) [inline:layout_container.png]
SplitContainer (deprecated in 1.1) [inline:split_container.png]
StackContainer [inline:stack_container.png]
TabContainer [inline:tab_container.png]

Command Control

Button, ComboButton, DropDownButton [inline:button.png]
Menu [inline:menu.png]
Toolbar [inline:toolbar.png]

User Assistance and Feedback

Dialog [inline:dialog_box.png]
TooltipDialog [inline:tooltipDialog.png]
ProgressBar [inline:progress_bar.png]
TitlePane Click arrow to expand/contract [inline:title_pane.png]
Tooltip [inline:tooltip.png]

Advanced Editing and Display

ColorPalette [inline:color_picker.png]
Editor [inline:editor.png]
Grid (1.0) [inline:grid_terms.gif]
InlineEditBox 0.9 - 1.0 [inline:inline_edit.png]
Tree [inline:tree.png]

Common Features

Attributes, Methods, Extension Points

Each Dijit component has interaction points, so you can customize them for your app. In order of complexity:

  • An attribute is a data element used for controlling display or behavior. For example, the label attribute of a ContentPane displays when that pane is part of a TabbedContainer.

    Attributes are settable only at creation time. That means you can set them in the declarative tag, or in the new dijit.___() call. After that, you may not set them directly. Some attributes can be changed by a method call, usually with the name setAttributeName(). We'll note these in the guide where applicable.

  • A method is a function you call to control something. For example, in a NumberSpinner the user can click the Up and Down buttons to adjust a number. But you can call the method adjust() to do the same thing.
  • An extension point is a function you provide to override behavior. For example, a ValidationTextBox has an isValid() extension point. If your validation involves several fields or needs more power than regular expressions provide, you can create a new subclass of ValidationTextBox and provide your own isValid function.

Common Attributes

These attributes may be specified in any widget, in addition to all the normal HTML attributes (which are simply passed through unchanged to the finished widget).

Attribute Type or values Default Description
domNode Node None this is our visible representation of the widget! Other DOM Nodes may by assigned to other properties, usually through the template system's dojoAttachPoint syntax, but the domNode property is the canonical "top level" node in widget UI. In non-templated widgets, this will equal the srcNodeRef.
id String None a unique, opaque ID string that can be assigned by users or by the system. If the developer passes an ID which is known not to be unique, the specified ID is ignored and the system-generated ID is used instead.
lang String djConfig.locale or default provided by navigator object overrides the page-wide setting to use a different language for this widget only. Must choose from bootstrapped languages in djConfig.extraLocale. It's usually best to default to the page settings so that the page can be localized with a single setting or the default. Use of this attribute is typically limited to demonstrations or pages with multiple languages besides the user's.
layoutAlign String Depends onorder within LayoutContainer Only applies to Dijit components in dijit.layout.LayoutContainer. Possible values: "left", "right", "bottom", "top", and "client". The LayoutContainer takes its children marked as left/top/bottom/right, and lays them out along the edges of the box, and then it takes the child marked "client" and puts it into the remaining space in the middle.
srcNodeRef Node DOM Node which has the dojoType attribute. Consider this attribute Read Only. In templated widgets, usually this node will disappear, replaced by a filled-in template.

Extension Points

Both methods and extension points are JavaScript functions, and the difference between them is a semantic one. We use the term method for "functions programmers normally call" and extension point for "functions the Dijit component normally calls". There's no technical reason you couldn't call an extension point yourself, or override a method yourself, but it makes little sense to do so.

You can easily provide an extension point function for declarative elements. As an example, here's how to extend a ValidationTextBox:

  1. First, find the extension point on the appropriate Dijit page. In our case, it's dijit.form.ValidationTextBox.isValid
  2. Copy the signature from the API documentation. For example, isValid() for ValidatingTextBox has signature function(/* Boolean*/ isFocused) and returns a boolean.
  3. Then, use a dojo/method call directly in the markup, 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;}
    <div dojoType="dijit.form.ValidatingTextBox" isValid="my.form.isValid"  ...>
            <script type="dojo/method" event="isValid">
                // Flip a coin to determine validity.  *evil laughter!* 
                return Math.round(Math.random() * 100) % 2 == 0;
            </script>       
       </div>

Of course, if your isValid function is the same for many widgets, you don't want to define it over and over. So alternatively:

  1. Follow first 2 steps above
  2. Write your 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;}
    my.form.isValid = function(isFocused) {
       // Flip a coin to determine validity.  *evil laughter!* 
       return Math.round(Math.random() * 100) % 2 == 0;
    }
  3. Connect the function to the extension point through an HTML 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;}
    <input dojoType="dijit.form.ValidatingTextBox" isValid="my.form.isValid" .../>

Other Things You Can Do With Dijit

  • You can change the style individual components, a Dijit class, or even create an entire theme spanning all Dijit components. The Themes and Design section tells you how.
  • You can create all Dijit components programmatically as well as declaratively (through markup). All attributes, methods and extension points are available through JavaScript.
  • You can create subclasses of existing Dijit classes, or create your own from scratch, described in the Writing Your Own Widget section.

Form, Validation, Specialized Input

The following widgets can be used in a FORM tag, in a dijit.form.Form widget, or outside of a form.

All form widgets implement the following attributes and methods. Many of them act as "shadow" attributes for their HTML counterparts, as in "name" or "id".

dijit.form._FormWidget
Base class for all form widgets - that is, all widgets beginning with "dijit.form"
Attributes
disabled Boolean Should this widget respond to user input? In markup, this is specified as "disabled='disabled'", or just "disabled". Use setAttribute("disabled", true/false) to change after creation time.
intermediateChanges Boolean If true, trigger an onChange event every time setValue is called. If false, trigger onChange only when asked by the callee. For example, on Slider a true value would fire onChange events at each point of the mouse drag. False would trigger onChange only on mouseUp.
tabIndex Integer Order fields are traversed when user hits the tab key
Methods
focus Set the focus on this widget
getValue get the value of the widget.
setValue set the value of the widget.
reset resets the widget to it's initial value
undo restore the value to the last value passed to onChange

Changed in 1.1

setAttribute Controls all sorts of attributes for widgets like disabled, readonly, etc. The exception is value, which is generally still controlled via setValue()/getValue(). Methods like setDisabled() have been deprecated.
Extension Points
onChange callback when value is changed

CheckBox, RadioButton [inline:checkbox.png]
ComboBox [inline:combo_box.png]
CurrencyTextBox [inline:currency_textbox.png]
DateTextBox [inline:date_textbox.png]
FilteringSelect [inline:filtering_select.png]
InlineEditBox (0.9) For 1.0, see dijit.InlineEditBox [inline:inline_edit.png]
NumberSpinner [inline:number_spinner.png]
NumberTextBox [inline:number_textbox.png]
Slider [inline:slider.png]
Textarea Textarea expands to fit content [inline:textarea.png]
TextBox [inline:textbox.png]
TimeTextBox [inline:time_textbox.png]
ValidationTextBox [inline:validating_textbox.png]

Form Widget

Although you're not required to place Dijit form elements in a dijit.form.Form, doing so gets you some nice methods and extension points to use.

dijit.form.Form
Adds conveniences to regular HTML form.
Methods
getValues generate JSON structure from form values get widget values
isValid Return true if every widget's isValid method returns true.
setValues fill in form values from a JSON structure generate map from name --> [list of widgets with that name]
submit programatically submit form
Extension Points
execute User defined function to do stuff when the user hits the submit button

CheckBox, RadioButton, ToggleButton

dijit.form.CheckBox, dijit.form.RadioButton, and dijit.form.ToggleButton capture binary user-choices. Unlike command buttons, which do some action on being pressed, these buttons are more for form data. ToggleButtons can be used on Toolbars - the Bold button being the classic example - so they act a little like a data button, a little like a command button.

Examples

CheckBoxes in dijit are very intuitive and easy to use. Markup constructs for check boxes is the same as html but dojo provides more control and styling options than a conventional check box. The following example creates a CheckBox:

/* 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>Checkbox Example</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");
        dojo.require("dijit.form.CheckBox");
     </script>
</head>
<body class="tundra">
    <input id="cb" dojotype="dijit.form.CheckBox"
           name="developer" checked="checked" value="on"
           type="checkbox" />

    <label for="cb"> Are you a Developer </label>
</body></html>

dijit.form.ToggleButton works very similarly to a checkbox, but requires including the "dijit.form.Button" module.

RadioButtons in dijit are also easy to create and use as the following example shows:

/* 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>Radio Button Example</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");
        dojo.require("dijit.form.CheckBox");
    </script>
</head>
<body class="tundra">
    <input dojoType="dijit.form.RadioButton" id="sum1" name="album"
           checked="checked" value="metallica" type="radio" />

           <label for="sum1"> Metallica </label>
    <input dojotype="dijit.form.RadioButton" id="sum2"  name="album"
           value="Extreme" type="radio" />

           <label for="sum2"> Extreme </label>
    <input dojotype="dijit.form.RadioButton" id="sum3"  name="album"
           value="Slayer" type="radio" />

           <label for="sum3"> Slayer </label>
</body></html>

The RadioButton class is declared in the CheckBox.js file, hence you need to dojo.require() only dijit.form.CheckBox for RadioButtons to work.

dijit.form.CheckBox, dijit.form.RadioButton, dijit.form.ToggleButton
Checkbox, RadioButton and ToggleButton capture binary user choices. Checkbox and RadioButton are like their HTML counterparts, while ToggleButton can be pushed in or out (like the Bold button in word processors).
Attributes
checked String Corresponds to the native HTML input element's attribute. In markup, specified as "checked='checked'" or just "checked". True if the button is depressed, or the checkbox is checked, or the radio button is selected, etc. Use setChecked() to change after creation time.
Methods
setValue(/* Boolean */ checked) If true, turn button or box on.
Extension Points
onClick(/*Event*/ e) Called when widget is clicked.

Accessibility

Keyboard

Tabbing moves through each form element, however, for radio buttons, tabbing will only go to the currently selected item in the radio group.

WidgetKeyboard Interaction
ToggleButtonSpacebar
CheckboxSpacebar
RadioButtonArrow keys: up or left arrow selects the previous radio button, down or right arrow selects the next.

ComboBox

The ComboBox is a hybrid between a SELECT combo box and a text field. Like a SELECT, you provide a list of acceptable values. Unlike SELECT, and like a text box, the user can ignore all the choices and type whatever they want. This is especially good for open-ended multiple choice questions.  Rather than having two fields - a combo box and an Other text box - you can use just one field.

Note that SELECT's always have value/description pairs, e.g. the OPTION's value attribute and the OPTION's body text. ComboBoxes do not. They only pass their displayed text - just like a text box.

Examples

Using inlined data with the ComboBox is very much like using a native <select> 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;}
<!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>Simple ComboBox</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");
       dojo.require("dijit.form.ComboBox");
       function setVal1(value) {
           console.debug("Selected "+value);
       }
   </script>
</head>
<body class="tundra">
        <select name="state1"
                dojoType="dijit.form.ComboBox"
                autocomplete="false"
                value="California"
                onChange="setVal1">

                <option selected="selected">California</option>
                <option >Illinois</option>
                <option >New York</option>
                <option >Texas</option>
        </select>
</body></html>

Name and autocomplete are passed through to the HTML. The value attribute enables you to set the default value. The option tags do not have hidden submit values; to use a hidden value, change your ComboBoxes to FilteringSelects.

Important: IE7 only uses the selected attribute of an option tag and ignores the value attribute on the select tag. For best results, set both the selected attribute on the default option and the value attribute on the select.

dojo.Data-Enabled ComboBox

ComboBox, FilteringSelect, Tree and Grid are dojo.data-Enabled widgets. This means rather than specifying all the data in the page - the OPTION's or tree nodes - you can have dojo.data fetch them from a server-based store. The unified dojo.data architecture, can get its data from various places - databases, web services, etc.. See the dojo.data manual section for complete details.

The code:

/* 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.ItemFileReadStore" jsId="stateStore"
              url="states.txt">
</div>

... looks like a widget, but it's not. (Only dojoTypes from the module dijit.___ are widgets). This tag takes advantage of dojo.parser, which creates JavaScript objects by using standard HTML. You can read about Dojo.parser at Understanding the Parser in Part 3.

For this example, we'll use a fixed JSON data store. You can download it from states.txt below. Here's an excerpt:

/* 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;}
{
identifier:"abbreviation",
items: [
        {name:"Alabama", label:"Alabama",abbreviation:"AL"},
        {name:"Alaska", label:"Alaska",abbreviation:"AK"},
        {name:"American Samoa", label:"American Samoa",abbreviation:"AS"},
        {name:"Arizona", label:"Arizona",abbreviation:"AZ"},

The following example makes use of our data store:

/* 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>Simple ComboBox</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");
       dojo.require("dijit.form.ComboBox");
       dojo.require("dojo.data.ItemFileReadStore");
       function setVal2(value) {
           console.debug("Selected "+value);
       }
   </script>
</head>
<body class="tundra">
        <div dojoType="dojo.data.ItemFileReadStore" jsId="stateStore"
              url="states.txt">
</div>
              
     <input dojoType="dijit.form.ComboBox"
                store="stateStore"
                value="California"
                searchAttr="name"
                name="state2"
                onChange="setVal2" />

       
</body></html>

jsId is the name of the global variable that the store is assigned to. url can be used to suck data out of particular URL, perhaps a web service. Instead of using the url attribute, you can embed your data directly (as is common in traditional server-side programming) using the data attribute.

dijit.form.ComboBox
Auto-completing text box The drop down box's values are populated from an class called a data provider, which returns a list of values based on the characters that the user has typed into the input box. Some of the options to the ComboBox are actually arguments to the data provider.
Attributes
autoComplete Boolean
true
If you type in a partial string, and then tab out of the <input> box, automatically copy the first entry displayed in the drop down list to the <input> field
hasDownArrow Boolean
true
Set this textbox to have a down arrow button
ignoreCase Boolean
true
True if the ComboBox should ignore case when matching possible items.
pageSize Integer
Infinity
Argument to data provider. Specifies number of search results per page (before hitting "next" button)
query Object
{}
A query that can be passed to 'store' to initially filter the items, before doing further filtering based on searchAttr and the key.
searchAttr String
name
Searches pattern match against this field
searchDelay Integer
100
Delay in milliseconds between when user types something and we start searching based on that value
store Object Reference to data provider object used by this ComboBox.

Accessibility

Keyboard

ActionKey
Open the menu of options (filtered by current input)Down arrow
Navigate through the optionsUp and down arrows
Pick an optionEnter
Close the menu of options without picking oneEsc

Known Issues (updated for 1.1)

JAWS 8 and Window-Eyes 6 may fail to read an option when it becomes highlighted. In Dojo 1.1 the Combobox was updated so that JAWS 9 will speak "editable combo" when the Combobox gets focus. However, there are some issues reading the highlighted choice. Generally JAWS 9 with Firefox 2 will only speak the part of the word that is currently selected in the textbox. For example, if you are working with a ComboBox containing the US state names and you type in an "I" to filter the list of states. If the user arrows down and highlights "Iowa" in the drop down list, "Iowa" will be displayed in the textbox with the "owa" portiion selected. JAWS 9 will speak, "owa" rather than "Iowa". This is not an issue with Firefox 3 and JAWS 9.

FilteringSelect

FilteringSelect is like an HTML SELECT tag, but is populated dynamically. It works very nicely with very large data sets because it can load and page data as needed. It also resembles ComboBox, but does not allow values outside of the provided ones.

When the user tries to submit invalid input (say if they choose an option, and the legal options change) the user gets a warning message, but the Select keeps text box input as is and also keeps the last valid submit value. If the user selects the text box and presses Escape on the keyboard, the text box reverts to the last valid value, corresponding to the hidden value. This change guarantees that you will always get a valid submit value.

Examples

First, here is a FilteringSelect with inlined data:

/* 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>Filter Select Example 1</title>
    <style type="text/css">
        @import "http://o.aolcdn.com/dojo/1.0/dijit/themes/tundra/tundra.css";
        @import "http://o.aolcdn.com/dojo/1.0/dojo/resources/dojo.css"
    </style>
    <script type="text/javascript" src="http://o.aolcdn.com/dojo/1.0/dojo/dojo.xd.js"
        djConfig="parseOnLoad: true">
</script>
    <script type="text/javascript">
       dojo.require("dojo.parser");
       dojo.require("dijit.form.FilteringSelect");
     </script>
</head>
<body class="tundra">
        <select dojoType="dijit.form.FilteringSelect"
        name="state3"
        autocomplete="false"
        value="CA">

                <option value="CA" selected="selected">California</option>
                <option value="IL" >Illinois</option>
                <option value="NY" >New York</option>
                <option value="TX" >Texas</option>
        </select>
</body></html>

As with ComboBox, has a value attribute. Unlike ComboBox, this value refers to the value attribute of the <option> tag. For example, if you set the value to AL, the text "Alabama" will appear in the text box on load. If you want to change the value attribute programmatically, use

/* 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;}
dijit.byId("yourwidgetid").setValue("yourhiddenvalue")

Like ComboBox FilteringSelect is dojo.data-enabled. As with all dojo.data stores, you can add an identifier field to the top level of your data. The value of the identifier field tells the store which field in your data contains the submit value. Here's an example from states.txt:

/* 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;}
{
identifier:"abbreviation",
items: [
        {name:"Alabama", label:"Alabama",abbreviation:"AL"},
        ...

This code shows an identifier set to abbreviation. The identifier instructs the dojo.data store to set the submit value of the items in this store to the value of the attribute named abbreviation. In this example, the first item has a field named abbreviation with a value of AL. If one of your users selected that item in your FilteringSelect, the form would submit AL to your server.

Here's the corresponding FilteringSelect

/* 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>Data-Enabled FilteringSelect</title>
    <style type="text/css">
        @import "http://o.aolcdn.com/dojo/1.0/dijit/themes/tundra/tundra.css";
        @import "http://o.aolcdn.com/dojo/1.0/dojo/resources/dojo.css"
    </style>
    <script type="text/javascript" src="http://o.aolcdn.com/dojo/1.0/dojo/dojo.xd.js"
        djConfig="parseOnLoad: true">
</script>
        <script type="text/javascript">
       dojo.require("dojo.parser");
       dojo.require("dijit.form.FilteringSelect");
       dojo.require("dojo.data.ItemFileReadStore");
   </script>
</head>
<body class="tundra">
        <div dojoType="dojo.data.ItemFileReadStore" jsId="stateStore"
              url="states.txt">
</div>
        <form method="post">
                <input dojoType="dijit.form.FilteringSelect"
                    store="stateStore"
                        searchAttr="name"
                        name="state1"
                        autocomplete="true"
                        />

            <input type="submit" value="Go!" />
        </form>
</body></html>

The net result of the identifier is that you can easily set the submit attribute of any number of Selects using the same data without actually adding extra attributes to the Selects.

dijit.form.FilteringSelect
Attributes
autoComplete Boolean
true
If you type in a partial string, and then tab out of the <input> box, automatically copy the first entry displayed in the drop down list to the <input> field
hasDownArrow Boolean
true
Set this textbox to have a down arrow button/td>
ignoreCase Boolean
true
Does the ComboBox menu ignore case?
labelAttr String
searchAttr
String Searches pattern match against this field String Optional. The text that actually appears in the drop down. If not specified, the searchAttr text is used instead.
labelType String
text
"html" or "text"
pageSize Integer
Infinity
Argument to data provider. Specifies number of search results per page (before hitting "next" button)
query Object
{}
A query that can be passed to 'store' to initially filter the items, before doing further filtering based on searchAttr and the key.
searchAttr String
name
Searches pattern match against this field
searchDelay Integer
100
Delay in milliseconds between when user types something and we start searching based on that value
store String Reference to data provider object used by this ComboBox.
Methods
setDisplayedValue(/*String*/ label) Set label (and corresponding value) to "label"
setValue(/*String*/ value) Set value (and corresponding label) to "value"

Accessibility

Keyboard

ActionKey
Open the menu of options (filtered by current input)Down arrow
Navigate through the optionsUp and down arrows
Pick an optionEnter
Close the menu of options without picking oneEsc

Known Issues

JAWS 8 and Window-Eyes 6 may fail to read an option when it becomes highlighted.

InlineEditBox (0.9)

In 1.0, dijit.form.InlineEditBox has been deprecated in favor of dijit.InlineEditBox

InlineEditBox is better described as a widget wrapper, which takes a child widget declared in markup and makes it inline-editable. This widget acts like a <div> tag when not in edit mode. When the contained rendered text is clicked, the widget enters an edit mode. In this mode, the previously displayed text is hidden, and another widget capable of editing text is made visible in its place.

Examples

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
            "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>InlineEdit Demo</title>
    <style type="text/css">
        @import "http://o.aolcdn.com/dojo/0.9.0/dijit/themes/tundra/tundra.css";
        @import "http://o.aolcdn.com/dojo/0.9.0/dojo/resources/dojo.css"
    </style>
    <script type="text/javascript" src="http://o.aolcdn.com/dojo/0.9.0/dojo/dojo.xd.js"
                djConfig="parseOnLoad: true">
</script>
    <script type="text/javascript">
       dojo.require("dojo.parser");
       dojo.require("dijit.form.InlineEditBox");
       dojo.require("dijit.form.TextBox");
       function myHandler(idOfBox, value) {
           console.debug("Edited value from "+idOfBox+" is now "+value);
       }
     </script>
</head>
<body class="tundra">
        <span id="editable" style="font-size:larger;"
                dojoType="dijit.form.InlineEditBox"
                onChange="myHandler(this.id,arguments[0])">

        <input dojoType="dijit.form.TextBox" value="Edit me - I trigger the onChange callback">
    </span>
</body></html>

The outermost span is the InlineEditBox. The inner input tag is the TextBox widget. When a user loads the page, they see the text "Edit me - I trigger the onChange callback". If the user clicks the text, a TextBox widget containing the text "Edit me - I trigger the onChange callback" appears. When the user changes the value and clicks away, the TextBox disappears and the TextBox's contents appear inline.

InlineEditBox supports the textarea mode through the Textarea widget. By simply adding a Textarea inside the InlineEditBox HTML tag, you add inline-editing to the Textarea widget. Furthermore, by adding renderAsHtml=true, users can enter HTML into the Textarea and have it appear inline as rich text. :

/* 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>InlineEdit Demo</title>
    <style type="text/css">
        @import "http://o.aolcdn.com/dojo/0.9.0/dijit/themes/tundra/tundra.css";
        @import "http://o.aolcdn.com/dojo/0.9.0/dojo/dojo.css"
    </style>
    <script type="text/javascript" src="http://o.aolcdn.com/dojo/0.9.0/dojo/dojo.xd.js"
                djConfig="parseOnLoad: true">
</script>
    <script type="text/javascript">
       dojo.require("dojo.parser");
       dojo.require("dijit.form.InlineEditBox");
       dojo.require("dijit.form.Textarea");
     </script>
</head>
<body class="tundra">
    <span id="areaEditable" dojoType="dijit.form.InlineEditBox"
        renderAsHtml="true" autoSave="false">

                <textarea dojoType="dijit.form.Textarea">
                        I'm one big paragraph.  Go ahead and <i>edit</i> me.  <b>I dare you.</b>
                        The quick brown fox jumped over the lazy dog.  Blah blah blah blah blah blah blah ...
                </textarea>
        </span>   
</body></html>

The outermost span in this code is the InlineEditBox. The inner textarea tag is the Textarea widget. When a user loads the page, they see the paragraph of rich text. If the user clicks the text, a Textarea widget containing the paragraph in plain text form appears. When the user changes the value and clicks away, the Textarea disappears and the Textarea's contents appear inline.

InlineEditBox can make any arbitrary widget that has a text value, or has the methods get/setDisplayedValue, inline. DateTextBox is an example of such a widget. This code shows a DateTextBox made inline in 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;}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
            "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>InlineEdit Demo</title>
    <style type="text/css">
        @import "http://o.aolcdn.com/dojo/0.9.0/dijit/themes/tundra/tundra.css";
        @import "http://o.aolcdn.com/dojo/0.9.0/dojo/dojo.css"
    </style>
    <script type="text/javascript" src="http://o.aolcdn.com/dojo/0.9.0/dojo/dojo.xd.js"
                djConfig="parseOnLoad: true">
</script>
    <script type="text/javascript">
       dojo.require("dojo.parser");
       dojo.require("dijit.form.InlineEditBox");
       dojo.require("dijit.form.DateTextBox");
     </script>
</head>
<body class="tundra">
    <p id="backgroundArea" dojoType="dijit.form.InlineEditBox" >
                <input name="date" value="2005-12-30"
                        dojoType="dijit.form.DateTextBox"
                        constraints={datePattern:'MM/dd/yy'}
                        lang="en-us"
                        required="true"
                        promptMessage="mm/dd/yy"
                        invalidMessage="Invalid date. Use mm/dd/yy format.">

   
</body></html>

The InlineEditBox can wrap around any widget that implements the following interface:

/* 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;}
/* void */ setTextValue(/*String*/ value) { ... }
String value = getTextValue() {... }
/* void */ focus() { ... }

The contained widget's setTextValue() method is called with the previously displayed text. When the Save button is pressed, the editing widget's getTextValue() method is called to retrieve the new text. After which, the editing widget is hidden, and the returned text is displayed. The focus method allows the editing widget to intelligently set focus to an appropriate node.


dijit.form.InlineEditBox
Wrapper widget for holding click-to-edit text.
Methods
addChild Process the given child widget, inserting it's dom node as a child of our dom node
getChildren returns array of children widgets
hasChildren returns true if widget has children
removeChild removes the passed widget instance from this widget but does not destroy it

Accessibility


General Behavior

InlineEditBoxes are wrappers around the various types of dojo TextBoxes and Textareas. When they are activated the underlying dojo widget is activated. When they are "closed" they appear as text but are tab stops in the keyboard focus ring and have an accessible role of button. They can have autoSave or non-autoSave behavior. When an non-autoSave InlineEditBox is open it has associated Save and Cancel buttons. An autoSave InlineEditBox does not have these buttons and they act like miniature forms or dialogs, i.e pressing the Esc key will close the widget and pressing the Enter key will close the widget, saving and displaying the text.

Note that since InlineEditBoxes may be used on the page without a traditional label element, the developer should add a title attribute in order to provide a description that is available to screen reader users. The title will also be displayed by the browser when the user places the mouse over the element.



Keyboard


If the widget is closed.


ActionKey
Navigate to the next widget in the tab order.Tab
Navigate to the prior widget in the tab order.Shift+Tab
Open the widget.Enter or spacebar
Note: The Esc key is ignored.

TextBox with autoSave specified and the TextBox is open:


ActionKeyComments
Navigate to the next widget in the tab order.TabThe data is saved and the widget closes.
Navigate to the prior widget in the tab order.Shift+TabThe data is saved and the widget closes.
Close the TextBox, saving changes.EnterKeyboard focus is on the closed InlineEditBox.
Revert the last entry.EscIf the user has not entered data, the TextBox is closed.
Close the Textarea, discarding changes.EscIf the user has entered data, the Esc must be pressed two times; the first time the data will be reverted; the second time the TextBox will close.

Textarea with autoSave specified and the Textarea is open:


ActionKeyComments
Navigate to the next widget in the tab order.Tab (press twice in Firefox - see the Known Issues below)The data is saved and the widget closes.
Navigate to the prior widget in the tab order.Shift+TabThe data is saved and the widget closes.
Enter a newline into the text.EnterThere is no equivalent to the Enter key behavior of TextBoxes. The user would have to use something like Tab and Shift + Tab.
Revert the last entry.EscIf the user has not entered data, the Textarea is closed.
Close the Textarea, discarding changes.EscIf the user has entered data, the Esc must be pressed two times; the first time the data will be reverted; the second time the Textarea will close.

TextBox without autoSave specified, the TextBox is open, keyboard focus is in the edit field:


ActionKeyComments
Navigate to the Save or Cancel button.TabFocus changes to the Save button if the data has been changed, otherwise it moves to the Cancel button.
Navigate to the prior widget in the tab order.Shift+TabThe TextBox remains open.
Close the TextBox, saving changes.Tab to the Save button, then press the Enter keyKeyboard focus is on the closed InlineEditBox.
Revert the last entry.EscIf the user has not entered data, the Esc key is ignored.
Close the Text Box, discarding changes.Tab to the Cancel button, then press the Enter key.Keyboard focus is on the closed InlineEditBox.
Note: The Enter key is ignored when focus is in the edit field.

Textarea without autoSave specified, the Textarea is open, keyboard focus is in the edit field:


ActionKeyComments
Navigate to the Save or Cancel button.Tab (press twice in Firefox - see the Known Issues below)Focus changes to the Save button if the data has been changed, otherwise it moves to the Cancel button.
Navigate to the prior widget in the tab order.Shift+TabThe Textarea remains open.
Close the Textarea, saving changes.Tab to the Save button, then press the Enter keyKeyboard focus is on the closed InlineEditBox.
Revert the last entry.EscIf the user has not entered data, the Esc key is ignored.
Close the Textarea, discarding changes.Tab to the Cancel button, then press the Enter key.Keyboard focus is on the closed InlineEditBox.
Note: Pressing the Enter key results in a newline being inserted into the edit field.

Known Issues

  • See the Comment for the Enter key in the information for autoSaving Text Areas above.
  • Ticket 3910: When Inline Text Boxes are opened, all the text should be selected.
  • On Firefox 2, the user must press the Tab key twice with focus in an textarea before keyboard focus moves to the next widget. This is a permanent restriction on Firefox 2. This is because the Dojo text area is implemented using the Firefox editor component in an iframe. This editor component implements usage of the tab key within the editor to indent text and shift-tab to outdent text. There is no keyboard mechanism in Firefox to move focus out of the editor. So, the dijit editor traps the tab key in the editor and sets focus to the editor iframe. From there pressing tab again will move to the next focusable item after the editor.

Screen Reader Issues

The InlineEditBox is implemented as a button. Since these are intended to be used "in-line" within text there is often no label element associated with the underlying control. For this reason, developers are encouraged to add a title attribute to InlineEditBoxes. The Window-Eyes screen reader will speak the title as part of the button description. JAWS has an option to speak different attributes on an button. A JAWS user may need to use the insert-v command to modify the behavior to speak the button title when working with Dojo InlineEditBoxes.

NumberSpinner

The Number Spinner, a familiar widget in GUI interfaces, makes integer entry easier when small adjustments are required. The down and up arrow buttons "spin" the number up and down. Furthermore, when you hold down the buttons, the spinning accelerates to make coarser adjustments easier.

Example

/* 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>Number Spinner Demo</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");
       dojo.require("dijit.form.NumberSpinner");
    </script>
</head>
<body class="tundra">
        <input dojoType="dijit.form.NumberSpinner"
                value="1000"
                smallDelta="10"
                constraints="{min:9,max:1550,places:0}"
                maxlength="20"
                id="integerspinner2">

</body></html>
dijit.form.NumberSpinner
Number Spinner
Attributes
constraints Object
{}
min and max properties are used for bounds. See ValidationTextBox for details.
defaultTimeout Integer
500
number of milliseconds before a held key or button becomes typematic
invalidMessage String
locale dep.
The message to display if value is invalid. constraints: Object user-defined object needed to pass parameters to the validator functions
intermediateChanges Boolean If true, trigger an onChange event every time setValue is called. If false, trigger onChange only when asked by the callee. For example, on Slider a true value would fire onChange events at each point of the mouse drag. False would trigger onChange only on mouseUp.
largeDelta Number
10
adjust the value by this much when spinning using the PgUp/Dn keys
promptMessage String
Hint string
rangeMessage String
The message to display if value is out-of-range
required Boolean
true
Defaults to true in NumberSpinner because the arrows won't work otherwise.
smallDelta Number
1
adjust the value by this much when spinning using the arrow keys/buttons
timeoutChangeRate Number
0.90
fraction of time used to change the typematic timer between events 1.0 means that each typematic event fires at defaultTimeout intervals < 1.0 means that each typematic event fires at an increasing faster rate
trim Boolean
false
Removes leading and trailing whitespace if true. Default is false.

Accessibility (added for 1.1 but true for 1.0 as well)

Keyboard

ActionKey
Interact with the number spinnerThe textbox for the number spinner is in the tab order of the page. Press tab key to set focus into the number spinner textbox.
Increase the number spinner value by single incrementWith focus in the number spinner textbox press the up arrow
Decrease number spinner value by single incrementWith focus in the number spinner textbox press the down arrow
In the future pageup, pagedown, home and end may be implemented.

Slider

A slider is a scale with a handle you can drag up/down or left/right to select a value. Calling dojo.require("dijit.form.Slider") provides dijit.form.HorizontalSlider, dijit.form.VerticalSlider and all the rule and label classes.

Examples

One way you could show the user the value of your Slider is to create a textbox that the Slider fills when the user moves the Slider. The following code fills in a simple textbox called horizontalSliderValue.

/* 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>Slider Example 1</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");
       dojo.require("dijit.form.Slider");
    </script>
</head>
<body class="tundra">
<div id="horizontalSlider" dojoType="dijit.form.HorizontalSlider"
   value="5" minimum="-10" maximum="10" discreteValues="11"
   intermediateChanges="true"
   onChange="dojo.byId('horizontalSlider').value = arguments[0];"
   handleSrc="http://o.aolcdn.com/dojo/1.0.0/dijit/themes/tundra/images/preciseSliderThumb.png"
>
</div>
</body></html>

You can point the handleSrc image to wherever you want. If you want to use the default handle image, just remove the handleSrc.

For a horizontal slider, you can use the HorizintalRule and HorizontalRuleLabels to create your ruler marks programmatically, reducing the amount of data being transferred over the wire:

  1.  
  2. 20%
  3. 40%
  4. 60%
  5. 80%
  6.  
  1. 0%
  2. 50%
  3. 100%
/* 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>Slider Example 2</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");
       dojo.require("dijit.form.Slider");
    </script>
</head>
<body class="tundra">
<div id="horizontalSlider" dojoType="dijit.form.HorizontalSlider"
        value="5" minimum="-10" maximum="10" discreteValues="11"
        intermediateChanges="true"
        showButtons="true">

                <div dojoType="dijit.form.HorizontalRuleLabels" container="topDecoration"
             style="height:1.2em;font-size:75%;color:gray;">
</div>
                <ol dojoType="dijit.form.HorizontalRuleLabels" container="topDecoration"
            style="height:1em;font-size:75%;color:gray;">

            <li> </li>
            <li>20%</li>
            <li>40%</li>
            <li>60%</li>
            <li>80%</li>
            <li> </li>
        </ol>
                <div dojoType="dijit.form.HorizontalRule" container="bottomDecoration"
             count=5 style="height:5px;">
</div>
                <ol dojoType="dijit.form.HorizontalRuleLabels" container="bottomDecoration"
            style="height:1em;font-size:75%;color:gray;">

            <li>0%</li>
            <li>50%</li>
            <li>100%</li>
        </ol>
</div>
</div>
</body></html>

The VerticalSlider API is identical to the HorizontalSlider API. You can use the VerticalRule class to create vertical ruler marks.

dijit.form.HorizontalSlider, dijit.form.VerticalSlider
A form widget that allows one to select a value with a horizontally (or vertically) draggable image
Attributes
clickSelect boolean
true
If clicking the progress bar changes the value or not
discreteValues integer
Infinity
The maximum allowed values dispersed evenly between minimum and maximum (inclusive).
intermediateChanges Boolean
false
If true, trigger an onChange event every time setValue is called. If false, trigger onChange only when asked by the callee. For example, on Slider a true value would fire onChange events at each point of the mouse drag. False would trigger onChange only on mouseUp.
maximum integer
100
The maximum allowed value.
minimum integer
0
The minimum value allowed.
pageIncrement integer
2
The amount of change with pageup/pagedown shift+arrow
showButtons boolean
true
Show increment/decrement buttons at the ends of the slider?
Methods
setValue((/*Number*/ value, /*Boolean, optional*/ priorityChange) Regular setValue, but if priorityChange is true, then it is more likely to be animated.

dijit.form.HorizontalRule, dijit.form.VerticalRule
Create hash marks for the Horizontal/Vertical slider
Attributes
container Node
containerNode
If this is a child widget, connect it to this parent node
count Integer
3
Number of hash marks to generate
ruleStyle String CSS style to apply to individual hash marks

dijit.form.HorizontalRuleLabels, dijit.form.VerticalRuleLabels
Create labels for the Slider
Attributes
labels Array
[]
Array of text labels to render - evenly spaced from left-to-right or bottom-to-top
labelStyle String CSS style to apply to individual text labels

Accessibility (added for 1.1 but true for 1.0 as well)

Keyboard

ActionKey
Interact with the sliderThe slider handle is in the tab order of the page. Press tab key to set focus to the slider handle.
Increase slider value by single incrementWith focus on slider handle press right or up arrow
Decrease slider value by single incrementWith focus on slider handle press left or down arrow
Increase slider value by multiple incrementsWith focus on slider handle press pageup. The amount of increment is determined by the pageIncrement parameter
Decrease slider value by multiple incrementsWith focus on slider handle press pagedown. The amount of increment is determined by the pageIncrement parameter
Set slider to minimum valueWith focus on slider handle press home.
Set slider to maximum valueWith focus on slider handle press end.

Known Issues

Currently the text labels of a slider are not spoken by screen readers. Firefox 3 will support this type of labeling so the plan is to add support for it in the Dojo 1.2 release. Developers should take care when using text labels since a screen reader user will hear only a numeric interpretation.

Textarea

A Textarea widget is like a regular HTML textarea, but it dynamically resizes to fit the content of the text inside. It takes nearly all the parameters (name, value, etc.) that a vanilla textarea takes. Cols is not supported and the width should be specified with style width. Rows is not supported since this widget adjusts the height. It is especially useful in an InlineEditBox. Note that when declaring a Textarea in markup you should use a <textarea> node to preserve the newline formatting.

Example

/* 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>Textarea Demo</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");
       dojo.require("dijit.form.Textarea");
    </script>
</head>
<body class="tundra">
        <textarea dojoType="dijit.form.Textarea" style="width:300px">
            Click at the end of this paragraph and start typing. 
            You will be amazed!
        </textarea>
</body>
</html>

Accessibility

Keyboard

ActionKeyComments
Move focus to the next widget in the tab order.Tab
Move focus to the prior widget in the tab order.Shift+Tab
Enter a newline into the text.Enter
Revert the last entry.EscIf the user has not entered data, the Esc key is ignored.

Known Issues

  • Ticket 3902: When the Text Area widget is first opened the caret is at the start; it should be at the end.
  • On Firefox 2, the user must press the Tab key twice before keyboard focus moves to the next widget. (There is no problem when using Shift+Tab.) This is a permanent restriction on Firefox 2.

TextBox family: Validation, Currency, Number, Date, Time

These widgets augment the functionality of the /* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .javascript .imp {font-weight: bold; color: red;} .javascript .kw1 {color: #000066; font-weight: bold;} .javascript .kw2 {color: #003366; font-weight: bold;} .javascript .kw3 {color: #000066;} .javascript .co1 {color: #009900; font-style: italic;} .javascript .coMULTI {color: #009900; font-style: italic;} .javascript .es0 {color: #000099; font-weight: bold;} .javascript .br0 {color: #66cc66;} .javascript .st0 {color: #3366CC;} .javascript .nu0 {color: #CC0000;} .javascript .me1 {color: #006600;} .javascript .re0 {color: #0066FF;} <INPUT type="text"> tag. The base widget dijit.form.Textbox by itself can trim, change case, and require input. dijit.form.ValidationTextbox extends this by validating the input when the box loses focus. The other widgets further extend the validation function with range and format checking. Internal to the MappedTextBox widget subclass are two INPUT elements. One interacts with the user obeying local customs, the other is hidden and represents the named form element to submit data to the server using a normalized serialization. By default, the widget will discover the appropriate locale and behavior as specified by Dojo.

For example, when using a NumberTextBox in the United States, an optional comma is used for the thousands separator and a period for a decimal separator when interacting with the user. For German users, a period is used for the thousands separator and a comma for the decimal separator. Other locales may have different conventions. When sending data to the server or interpreting the "value" attribute, numbers are represented simply as JavaScript formats them with a period for decimal and no thousands separators. This representation is unambiguous, so other applications may interact with this data without assuming any locale-specific behavior. With DateTextBox, a subset of the ISO-8601 format (e.g. 12-31-2006) is used for the value attribute. For CurrencyTextBox, a number is transmitted, and it is the responsibility of the developer to associate the ISO-4217 country code with the amount to qualify what type of currency is indicated. All of these behaviors are considered Dojo and JSON best practices, but may be customized as described below.

Constraints

To override the defaults, you can use the "constraints" attribute. "constraints" is an object passed to functions responsible for validating, parsing, and formatting the data in the box, and various properties may be provided to override system or locale-specific defaults. Constraints are handled in Dojo low-level routines in dojo.date, dojo.currency and dojo.number, and you can refer to the API documentation for complete details. We summarize them here for convenience:

For CurrencyTextBox and NumberTextBox:

  • currency: (currency only) the ISO-4217 currency code, a three letter sequence like "USD" See http://en.wikipedia.org/wiki/ISO_4217
  • fractional: (currency only) where places are implied by pattern or explicit 'places' parameter, whether to include the fractional portion.
  • locale: override the locale on this widget only, choosing from djConfig.extraLocale
  • pattern: override localized convention with this pattern. As a result, all users will see the same behavior, regardless of locale, and your application may not be globalized. See http://www.unicode.org/reports/tr35/#Number_Format_Patterns.
  • places: number of decimal places to accept.
  • strict: strict parsing, false by default. When strict mode is false, certain allowances are made to be more tolerant of user input, such as 'am' instead of 'a.m.', some white space may be optional, etc.
  • symbol: (currency only) override currency symbol. Normally, will be looked up in localized table of supported currencies (dojo.cldr) 3-letter ISO 4217 currency code will be used if not found.
  • type: choose a format type based on the locale from the following: decimal, scientific (not yet supported), percent, currency. decimal by default.

For DateTextBox and TimeTextBox:

  • am,pm: override strings for am/pm in times
  • clickableIncrement (TimeTextBox): ISO-8601 string representing the amount by which every clickable element in the time picker increases. Set in non-Zulu time, without a time zone. Example: "T00:15:00" creates 15 minute increments. Must divide visibleIncrement evenly.
  • datePattern,timePattern: override localized convention with this pattern. As a result, all users will see the same behavior, regardless of locale, and your application may not be globalized. See http://www.unicode.org/reports/tr35/#Date_Format_Patterns
  • formatLength: choose from formats appropriate to the locale -- long, short, medium or full (plus any custom additions). Defaults to 'short'
  • locale: override the locale on this widget only, choosing from djConfig.extraLocale
  • selector: choice of 'time', 'date' (default: date and time)
  • strict: false by default. If true, parsing matches exactly by regular expression. If false, more tolerant matching is used for abbreviations and some white space.
  • visibleIncrement (TimeTextBox): ISO-8601-style string representing the amount by which every element with a visible time in the time picker increases. Set in non Zulu time, without a time zone or date. Example: "T01:00:00" creates text in every 1 hour increment.
  • visibleRange (TimeTextBox): ISO-8601 string representing the range of this time picker. The time picker will only display times in this range. Example: "T05:00:00" displays 5 hours of options

Examples

dijit.form.TextBox

/* 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>TextBox Demo</title>
    <style type="text/css">
        @import "http://o.aolcdn.com/dojo/1.0/dijit/themes/tundra/tundra.css";
        @import "http://o.aolcdn.com/dojo/1.0/dojo/resources/dojo.css"
    </style>
    <script type="text/javascript" src="http://o.aolcdn.com/dojo/1.0/dojo/dojo.xd.js"
        djConfig="parseOnLoad: true">
</script>
    <script type="text/javascript">
       dojo.require("dojo.parser");
       dojo.require("dijit.form.TextBox");
    </script>
</head>
<body class="tundra">
        <input type="text" name="firstname" value="testing testing"
                dojoType="dijit.form.TextBox"
                trim="true"
                propercase="true" />

</body></html>

dijit.form.DateTextBox

/* 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>Textarea Demo</title>
    <style type="text/css">
        @import "http://o.aolcdn.com/dojo/1.0/dijit/themes/tundra/tundra.css";
        @import "http://o.aolcdn.com/dojo/1.0/dojo/resources/dojo.css"
    </style>
    <script type="text/javascript" src="http://o.aolcdn.com/dojo/1.0/dojo/dojo.xd.js"
        djConfig="parseOnLoad: true">
</script>
    <script type="text/javascript">
        dojo.require("dojo.parser");
        dojo.require("dijit.form.DateTextBox");
    </script>
</head>
<body class="tundra">
        <input type="text" name="date1" value="2005-12-30"
                dojoType="dijit.form.DateTextBox"
                required="true" />

</body></html>

dojo.form.CurrencyTextBox

/* 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>CurrencyTextBox Demo</title>
    <style type="text/css">
        @import "http://o.aolcdn.com/dojo/1.0/dijit/themes/tundra/tundra.css";
        @import "http://o.aolcdn.com/dojo/1.0/dojo/resources/dojo.css"
    </style>
    <script type="text/javascript" src="http://o.aolcdn.com/dojo/1.0/dojo/dojo.xd.js"
        djConfig="parseOnLoad: true">
</script>
    <script type="text/javascript">
       dojo.require("dojo.parser");
       dojo.require("dijit.form.CurrencyTextBox");
    </script>
</head>
<body class="tundra">
    <form method="post">
        <input type="text" name="income1" value="54775.53"
                dojoType="dijit.form.CurrencyTextBox"
                required="true"
                constraints="{fractional:true}"
                currency="USD"
                invalidMessage="Invalid amount.  Include dollar sign, commas, and cents." />

    <input type="submit" value="Go!"/>
    </form>
</body></html>

The value attribute is a floating point number. This means that you can easily build CurrencyTextBoxes for a wide range of currencies without having to set a different value for each currency format. fractional is still set to true, but it is set inside the constraints object instead of on the widget.

/* 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>NumberTextBox Demo</title>
    <style type="text/css">
        @import "http://o.aolcdn.com/dojo/1.0/dijit/themes/tundra/tundra.css";
        @import "http://o.aolcdn.com/dojo/1.0/dojo/resources/dojo.css"
    </style>
    <script type="text/javascript" src="http://o.aolcdn.com/dojo/1.0/dojo/dojo.xd.js"
        djConfig="parseOnLoad: true">
</script>
    <script type="text/javascript">
       dojo.require("dojo.parser");
       dojo.require("dijit.form.NumberTextBox");
    </script>
</head>
<body class="tundra">
        <input id="q05" type="text"
                dojoType="dijit.form.NumberTextBox"
                name= "elevation"
                value="3000"
                constraints="{min:-20000,max:20000,places:0}"
                promptMessage= "Enter a value between -20000 and +20000"
                required= "true"
                invalidMessage= "Invalid elevation."
                />

</body></html>

dijit.form.ValidationTextBox

ValidationTextBoxes usually use Regular Expression validation, as in the following example:

/* 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>ValidationTextBox Demo</title>
    <style type="text/css">
        @import "http://o.aolcdn.com/dojo/1.0/dijit/themes/tundra/tundra.css";
        @import "http://o.aolcdn.com/dojo/1.0/dojo/resources/dojo.css"
    </style>
    <script type="text/javascript" src="http://o.aolcdn.com/dojo/1.0/dojo/dojo.xd.js"
        djConfig="parseOnLoad: true">
</script>
    <script type="text/javascript">
       dojo.require("dojo.parser");
       dojo.require("dijit.form.ValidationTextBox");
    </script>
</head>
<body class="tundra">
        <input type="text" name="phone" class="medium" value="someTestString"
                dojoType="dijit.form.ValidationTextBox"
                regExp="[\w]+"
                required="true"
                invalidMessage="Invalid Non-Space Text.">

</body></html>

The regular expression syntax comes directly from JavaScript. The start and ending qualifiers of the regular expression, ^ and $, are implicit - you do not need to include them. This code demonstrates a ValidationTextBox that only accepts a 5 digit zip code.

/* 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>