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.
| 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 |
There are three main ways to install Dojo:
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.
For you traditionalists out there, you can download, install and use Dojo the old fashioned way.
You've got a working Dojo!
Finally, for those of you who live on the edge ... you can get the latest, greatest code directly from the Subversion code repository.
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).
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.
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]
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;}
As it can be seen above, the page is a just a standard HTML skeleton with three things:
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;}
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;}
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.
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;}
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!
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;}
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;}
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;}
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.
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;}
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;}
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).
/* 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}
/* 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;}
Hello, #url.name#, welcome to the world of Dojo!
<%
/*
' 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!#!/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";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;}
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;}
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;}
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;}
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.
/* 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}
/* 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;}
Hello, #form.name#, welcome to the world of Dojo!
<%
/*
' 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!#!/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";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.
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!
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.
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;}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.
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;}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.
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.
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;}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;}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.
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]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;}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;}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
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!
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;}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;}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}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.
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.
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.
Dojo uses code from other open source projects, subject to the terms of their licenses. Those licenses and software copyright notices are listed below:
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.
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.
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:
The Sovreign Nation of Googolica, In Search We Trust
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:
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;}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.
You must add two snippets of code for every page using dijits:
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;}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;}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;}But if you don't do it, your widgets will look very strange on screen. They rely almost entirely on CSS.
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;}
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;}The form is looking better already:
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...
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.
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;}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.
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;}And because we haven't added a require for CurrencyTextBox, we add one to the top:
dojo.require("dijit.form.CurrencyText");
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;}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;}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:
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");
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;}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:
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.
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.
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;}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;}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;}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;}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;}Well, that's helpful but we still need our translated text. That's where we'll call Dojo.
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;}And we're ready to go!
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;}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.
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!
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:
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;}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 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;}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.
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;}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;}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.
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;}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;}
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...
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?
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;}He has decided to use the ItemFileWriteStore to write entries back later (maybe not in the demo, but later on.)
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;}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.
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...
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:
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;}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;}
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.
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;}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;}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!
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;}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;}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;}You pass parameters to your new widget by using:
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;}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.
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;}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.
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;}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?
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]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;}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;}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;}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;}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]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:
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
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.
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 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;}Because this system is tightly coupled, problems arise:
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;}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.
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".
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.
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.
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
},
});
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;}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".
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.
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.
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;
},
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...
The client page uses some eye candy from Dojo. (Clients love eye candy!)
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;}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;}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, 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.
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.
| 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] |
| 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] |
| Button, ComboButton, DropDownButton | [inline:button.png] |
| Menu | [inline:menu.png] |
| Toolbar | [inline:toolbar.png] |
| 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] |
| 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] |
Each Dijit component has interaction points, so you can customize them for your app. In order of complexity:
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.
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. |
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:
Of course, if your isValid function is the same for many widgets, you don't want to define it over and over. So alternatively:
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] |
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 | |
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.
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:
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:
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. | |
Tabbing moves through each form element, however, for radio buttons, tabbing will only go to the currently selected item in the radio group.
| Widget | Keyboard Interaction |
|---|---|
| ToggleButton | Spacebar |
| Checkbox | Spacebar |
| RadioButton | Arrow keys: up or left arrow selects the previous radio button, down or right arrow selects the next. |
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.
Using inlined data with the ComboBox is very much like using a native <select> tag:
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.
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;}... 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;}The following example makes use of our data store:
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. |
| Action | Key |
|---|---|
| Open the menu of options (filtered by current input) | Down arrow |
| Navigate through the options | Up and down arrows |
| Pick an option | Enter |
| Close the menu of options without picking one | Esc |
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 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.
First, here is a FilteringSelect with inlined data:
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;}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;}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
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" | |
| Action | Key |
|---|---|
| Open the menu of options (filtered by current input) | Down arrow |
| Navigate through the options | Up and down arrows |
| Pick an option | Enter |
| Close the menu of options without picking one | Esc |
JAWS 8 and Window-Eyes 6 may fail to read an option when it becomes highlighted.
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.
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. :
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:
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;}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 | |
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.
If the widget is closed.
| Action | Key |
|---|---|
| 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 |
TextBox with autoSave specified and the TextBox is open:
| Action | Key | Comments |
|---|---|---|
| Navigate to the next widget in the tab order. | Tab | The data is saved and the widget closes. |
| Navigate to the prior widget in the tab order. | Shift+Tab | The data is saved and the widget closes. |
| Close the TextBox, saving changes. | Enter | Keyboard focus is on the closed InlineEditBox. |
| Revert the last entry. | Esc | If the user has not entered data, the TextBox is closed. |
| Close the Textarea, discarding changes. | Esc | If 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:
| Action | Key | Comments |
|---|---|---|
| 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+Tab | The data is saved and the widget closes. |
| Enter a newline into the text. | Enter | There 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. | Esc | If the user has not entered data, the Textarea is closed. |
| Close the Textarea, discarding changes. | Esc | If 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:
| Action | Key | Comments |
|---|---|---|
| Navigate to the Save or Cancel button. | Tab | 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+Tab | The TextBox remains open. |
| Close the TextBox, saving changes. | Tab to the Save button, then press the Enter key | Keyboard focus is on the closed InlineEditBox. |
| Revert the last entry. | Esc | If 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. |
Textarea without autoSave specified, the Textarea is open, keyboard focus is in the edit field:
| Action | Key | Comments |
|---|---|---|
| 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+Tab | The Textarea remains open. |
| Close the Textarea, saving changes. | Tab to the Save button, then press the Enter key | Keyboard focus is on the closed InlineEditBox. |
| Revert the last entry. | Esc | If 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. |
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.
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.
|
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. |
| Action | Key |
|---|---|
| Interact with the number spinner | The 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 increment | With focus in the number spinner textbox press the up arrow |
| Decrease number spinner value by single increment | With focus in the number spinner textbox press the down arrow |
| In the future pageup, pagedown, home and end may be implemented. |
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.
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.
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:
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 |
| 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 |
| Action | Key |
|---|---|
| Interact with the slider | The 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 increment | With focus on slider handle press right or up arrow |
| Decrease slider value by single increment | With focus on slider handle press left or down arrow |
| Increase slider value by multiple increments | With focus on slider handle press pageup. The amount of increment is determined by the pageIncrement parameter |
| Decrease slider value by multiple increments | With focus on slider handle press pagedown. The amount of increment is determined by the pageIncrement parameter |
| Set slider to minimum value | With focus on slider handle press home. |
| Set slider to maximum value | With focus on slider handle press end. |
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.
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.
| Action | Key | Comments |
|---|---|---|
| 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. | Esc | If the user has not entered data, the Esc key is ignored. |
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.
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:
For DateTextBox and TimeTextBox:
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.
ValidationTextBoxes usually use Regular Expression validation, as in the following example:
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.