Writing Your Own Widget Class

It's hard for you to leave well-enough alone. We give you widgets, and now you want to change them. Or you want to make your own.

No problem! Dijit components are extendible, so you can make changes without touching the source code. In a way, you already do this by specifying your own attributes - e.g. sliders that go from 0-100 look different than those going from 0-200. But sometimes you need to go further. Maybe you need to create different behavior for onClick, or substitute a custom validation routine. This kind of modification uses extension points described in Common Attributes. You can add your own code to extension points through markup or through pure JavaScript calls to dojo.declare.

You can also create Dijit classes from scratch. Again, you can do this either through markup - using the dijit.Declaration dojoType attribute - or through dojo.declare.

The Template

Often, you'll find a widget that does almost exactly what you want ... but needs just a bit of help. The last thing you'd want to do is hack the source code. Good thing you don't have to! The same widget construction techniques apply to both creating and extending widgets. So first, let's extending an existing stable widget: the AccordionContainer and AccordionPane. Right now the pane titles can only be text, but suppose you want images there as well.

Most Dijit components revolve around a template. A template looks like a macro, and can perform simple substitutions of ${...} variables and substitutions of DOM nodes.

To see how the template language works, let's look at a plain ol' Accordion Container instance:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<div dojoType="dijit.layout.AccordionContainer">
    <div dojoType="dijit.layout.AccordionPane" title="pane 1">
        Text of Pane 1
    </div>
    <div dojoType="dijit.layout.AccordionPane" title="pane 2">
        Text of Pane 2
    </div>
</div>

The DOM that ends of in memory and on the screen is quite different. Though [View Source] in your browser shows the HTML above, Firebug shows the actual DOM used by the AccordionPane:

[inline:simple_accordion_firebug.png]

Dojo replaces the simple nodes of our example with groups of HTML elements. That's one of the jobs of the Dojo parser. The parser consults the widget's template to construct this group. Where's the template kept? For AccordionPane, you can find it in the source version of Dojo at dijit/layout/templates/AccordionPane.html:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<div class='dijitAccordionPane'
        >
<div dojoAttachPoint='titleNode,focusNode' dojoAttachEvent='ondijitclick:_onTitleClick,onkeypress:_onKeyPress'
                class='dijitAccordionTitle' wairole="tab"
                >
<div class='dijitAccordionArrow'></div
                >
<div class='arrowTextUp' waiRole="presentation"></div
                >
<div class='arrowTextDown' waiRole="presentation"></div
                >
<span dojoAttachPoint='titleTextNode'>${title}</span></div
        >
<div><div dojoAttachPoint='containerNode' style='overflow: hidden; height: 1px; display: none'
                dojoAttachEvent='onkeypress:_onKeyPress'
                class='dijitAccordionBody' waiRole="tabpanel"
        >
</div></div>
</div>

That's a lot of HTML to replace the one DIV tag. Mostly this looks like garden variety HTML with lots of CSS class markers to fill in the styling. That makes the theme system work well. But a few attributes and constructs are alien to HTML - these are Dojo's template language elements. In particular:

${title}
is replaced with the title="..." attribute sent to the widget
dojoAttachPoint='containerNode'
An attachPoint is a tag merged with user-provided HTML. It's like a substitution variable for HTML.
dojoAttachEvent='onkeypress:_onKeyPress'
Connects an event to an event handler at that node.

Attach Points

The AttachPoint is a little bit complex, so let's look at that a bit more closely. Each widget has a special variable named containerNode which represents the unchanged HTML. The act of attaching involves three steps.

  1. Start with containerNode: /* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
    <div dojoType="dijit.layout.AccordionPane" title="pane 1">
        Text of Pane 1
    </div>
  2. mix in any attributes in the same tag as the attachPoint section (except the attachPoint attribute itself): /* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
    <div dojoType="dijit.layout.AccordionPane"
            title="pane 1" style='overflow: hidden; height: 1px; display: none'
            class='dijitAccordionBody' waiRole="tabpanel">

            Text of Pane 1
    </div>
  3. Pop the result into the template

This is a recursive procedure so the containerNode itself can contain widgets.

A Template for ImageAccordion

ImageAccordion only needs a few small changes to the AccordionPane template. All of the rest of the code for AccordionPane can be reused. So here is our new template: /* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}

<div class='dijitAccordionPane'
        >
<div dojoAttachPoint='titleNode,focusNode'
                  dojoAttachEvent='ondijitclick:_onTitleClick,onkeypress:_onKeyPress'
                class='dojocAccordionTitle' wairole="tab"
                >
<div class='dijitAccordionArrow'></div
                >
<div class='arrowTextUp' waiRole="presentation"></div
                >
<div class='arrowTextDown' waiRole="presentation"></div
                >
<span dojoAttachPoint='titleTextNode'><img alt="${title}" src="${src}"
        >
</span></div
        >
<div><div dojoAttachPoint='containerNode'
                           style='overflow: hidden; height: 1px; display: none'
                dojoAttachEvent='onkeypress:_onKeyPress'
                class='dojocImageAccordionBody' waiRole="tabpanel"
        >
</div></div>
</div>

What's with the > signs on different lines? That ensures extra whitespace is not included in the actual generated HTML. It makes the template slightly less readable, but pays big dividends in performance.

There are only a few changes from the original AccordionPane:

  1. The most important change is the replacement of ${text} with ${title}. This means the ImageAccordion must be sent an additional attribute - src - with the URL for the image.
  2. The titleNode now has the CSS class "dojocAccordionTitle"
  3. The containerNode now has the CSS class "dojocImageAccordionBody"

So now we have the heart of our new widget. Let's pop the template in and make things happen.

Widgets inside the Template

So what if we want the widget to have a widget inside of it, as in ...:

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

When using this template in a directly extended widget class, you will need to set the property widgetsInTemplate: true. Why? Because a widget inside a template requires some recursive parsing, which may be slow if you're drawing thousands of widgets ... especially if there is nothing extra to parse. Therefore, it is false by default.

dijit.Declaration-based widget classes automatically set widgetsInTemplate to true.

Dijit.Declaration

Just as there are two ways to create a widget instance - declarative and programmatic - so there are two ways to declare a Dijit class. As you might guess, the declarative way is slightly easier so we'll start there.

To declare your class declaratively, use dijit.Declaration. Uhhhh, OK, too many "declare"s in that sentence. It's easier to show than to tell. Here's the entire ImageAccordion spec. The code doesn't display anything itself, so it's best to place it right after the BODY tag before any other displayable code, or at the end.

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<div dojoType="dijit.Declaration"
       widgetClass="dojoc.widget.ImageAccordion"
       mixins="dijit.layout.AccordionPane"
>
<div class='dijitAccordionPane'
      >
<div dojoAttachPoint='titleNode,focusNode'
                dojoAttachEvent='ondijitclick:_onTitleClick,onkeypress:_onKeyPress'
                class='dojocAccordionTitle' wairole="tab"
                >
<div class='dijitAccordionArrow'></div
                >
<div class='arrowTextUp' waiRole="presentation"></div
                >
<div class='arrowTextDown' waiRole="presentation"></div
                >
<span dojoAttachPoint='titleTextNode'><img alt="${title}" src="${src}"
        >
</span></div
        >
<div><div dojoAttachPoint='containerNode'
                style='overflow: hidden; height: 1px; display: none'
                dojoAttachEvent='onkeypress:_onKeyPress'
                class='dojocImageAccordionBody' waiRole="tabpanel"
        >
</div></div>
  </div>
</div>

Dijit.Declaration turns this markup into a templated widget class. The mixins attribute tells which classes ImageAccordion will be based on. You can include more than one class here - each separated by commas. The first class is not technically a mixin - it's the parent class of this new widget. So all of the methods in AccordionPane are inherited in ImageAccordion. Only the template changes.

Declaring extension point implementations, or connecting to events inside a dijit.Declaration is exactly the same as for a declaratively-defined individual widget. So:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<div dojoType="dijit.Declaration" widgetClass="simpleConnectedWidget" >
   Just a plain ol' piece of text
   <script type="dojo/connect" event="dblclick">
      console.debug("Ouch!  I've been double-clicked");
   </script>
</div>

Every widget declared with class simpleConnectedWidget will have the handler connected to it.

Notes

  • widgetsInTemplate is automatically set to true, so any widgets you place in the template will be automatically filled in.
  • If you do not specify mixin, the widget class will be a subclass of dijit._Widget and mix in dijit._Templated. If you specify mixin, the first class listed must be a subclass of dijit._Widget. At least one of the mixins should itself mixin dijit._Templated, or you should supply dijit._Templated yourself as a mixin.
  • Only one extension point implementation of preamble.

Direct Extension

Every widget class is a Dojo class. So you should be able to extend a widget class just like you would any other Dojo class, right? Like with dojo.declare? Absolutely!

What do you gain by using dojo.declare instead of dijit.Declaration? Mostly you gain flexibility. For example, say you wanted to declare a widget class with the same functionality, but two completely different versions of a template based on a user preferences. No problem. You construct the template itself with JavaScript code, then pass it to dojo.declare in the template property.

But let's get back to ImageAccordion. Following the examples in Modules, we'll place this class dojoc.layout.ImageAccordion in a file named dojoc/layout/ImageAccordion.js under the Dojo root. Here's how to write it 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: #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;}
// all packages need to dojo.provide() _something_, and only one thing
dojo.provide("dojoc.widget.ImageAccordion");
// AccordionContainer is the module with dijit.layout.AccordionPane
dojo.require("dijit.layout.AccordionContainer");
// our declared class
dojo.declare("dojoc.widget.ImageAccordion",
        // we inherit from this class, which in turn mixes
        // in _Templated and _Layout
        [ dijit.layout.AccordionPane ],
        // class properties:
        {
        templatePath: dojo.moduleUrl("dojoc","layout/templates/ImageAccordion.html"),
        // Necessary to keep Dijit from using templateString in AccordionPane
        templateString: "",
        // src: String
        //      src url for AccordionPaneExtension header
        src: ""
});

The src string does pretty much what we think - unlike with dijit.Declaration, we must declare the new attributes explicitly. The templatePath requires some explanation.

Attaching the Template to a Direct Extension

With dijit.Declaration, the template is just the body of the tag. In dojo.declare land, there are three ways to specify a template:

  • Use the templatePath attribute to point to a URL with a template in it.
  • -OR- Specify the template directly in the templateString attribute
  • -OR- Pass a DOM node with a parsed representation of the template

The first option is preferred because it separates the JavaScript and HTML code cleanly. With templateString, you must remember to escape all the quote marks, required in a JavaScript string. The Custom build system will convert the template in templateUrl to an inline templateString to help performance, so no need to worry there. Note: as in our above example, if you are overriding a widget with a templateString, and you want to use a templateUrl in your subclass, be sure to set templateString to "".

The templates are stored in the module along with all the JavaScript code. We place this template file into dojoc/widgets/template/ImageAccordion.html:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<div class='dojocImageAccordion'
        >
<div dojoAttachPoint='titleNode,focusNode'
dojoAttachEvent='ondijitclick:_onTitleClick,onkeypress:_onTitleKeyPress,onfocus:_handleFocus,onblur:_handleFocus'
                  class='dojocImageAccordionTitle' wairole="tab"
                >
<img alt="${title}" src="${src}"
        >
</div
        >
<div><div dojoAttachPoint='containerNode'
                style='overflow: hidden; height: 1px; display: none'
                class='dojocImageAccordionBody' waiRole="tabpanel"
        >
</div></div>
</div>

The Widget Life-cycle

The lifecycle of a widget decribes the phases of its creation and destruction which you can hook into. Its useful to understand exactly what happens when. Whether you are sub-classing an existing widget, using dojo/method script blocks, or passing in method overrides to the constructor, these are your entry points for making a widget do what you want it to do.

Note: some aspects of the widget lifecycle have changed post the 0.9 release; please consult the 0.9 to 1.0 porting guide.

Widgets are classes, created with dojo.declare. All widgets inherit from dijit._Widget, and most get the _Templated mixin. That provides you the following extension points (methods) you can override and provide implementation for:

constructor
Your constructor method will be called before the parameters are mixed into the widget, and can be used to initialize arrays, etc.
parameters are mixed into the widget instance
This is when attributes in the markup (ex: <button iconClass=...>) are mixed in or, if you are instantiating directly, the properties object you passed into the constructor (ex: new dijit.form.Button({label: "hi"})). This step itself is not overridable, but you can play with the result in...
postMixInProperties
If you provide a postMixInProperties method for your widget, it will be invoked before rendering occurs, and before any dom nodes are created. If you need to add or change the instance's properties before the widget is rendered - this is the place to do it.
buildRendering
_Templated provides an implementation of buildRendering that most times will do what you need. The template is fetched/read, nodes created and events hooked up during buildRendering. If you dont mixin _Templated (and most OOTB dijits do) and want to handle rendering yourself (e.g. to really streamline a simple widget, or even use a different templating system) this is where you'd do it.
postCreate
This is typically the workhorse of a custom widget. The widget has been rendered (but note that sub-widgets in the containerNode have not!)
startup
If you need to be sure parsing and creation of any child widgets is complete, use startup.
destroy
Implement destroy if you have special tear-down work to do (the superclasses will take care of most of it for you; be sure to call this.inherited(arguments);)

In all cases its good practice to assume that you are overriding a method that may do something important in a class up the inheritance chain. So, call the superclass' method before or after your own code. E.g.

/* 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;}
postCreate: function() {
   // do my stuff, then...
   this.inherited("postCreate", arguments);
}

Testing the Widget

Does this thing actually work? Here's a unit test page to find out. We simply added some src="image.png" attribs to the AccordionContainer children in Dijit's AccordionContainer test page. You will need to add your own image.png to dojoc/widgets/images, or change the path. Here there are three 3 simple ImageAccordion's all 200px x 26px. Add in some basic CSS for the classes we changed, and voila:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
        "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
        <title>dojoc.widget.ImageAccordion Test</title>
        <style type="text/css">
                /* several CSS files you probably want: */           
                @import "dojo/dojo/resources/dojo.css";
                @import "dojo/dijit/themes/tundra/tundra.css";
                @import "dojo/dijit/themes/dijit.css";
                /* this one is just for Dojo tests, but we'll use it in
                    hopes they will accept our code */
                @import "dojo/dijit/tests/css/dijitTests.css";
                .dojocImageAccordion .dojocImageAccordionBody {
                     background: #fff; margin:0; padding:0; 
                }
                .dojocImageAccordion { border-right:1px solid #ededed; }
                .dojocImageAccordionTitle { cursor:pointer; }
                p { padding:2px; margin-top:2px; }
        </style>
        <script type="text/javascript" src="dojo/dojo/dojo.js"
                    djConfig="isDebug:true, parseOnLoad: true">
</script>
        <script type="text/javascript">
                // Comment this out to use the Declarative Version
                dojo.require("dojoc.widget.ImageAccordion");
                dojo.require("dijit.layout.AccordionContainer");
                dojo.require("dojo.parser");    
        </script>
</head>
<body class="tundra">
        <div><!-- The Declarative version goes here.  --></div>
        <h1>dojoc.widget.ImageAccordion</h1>
       
        It's poorly named, I know. An "ImageAccordion" would be an
        AccordionContainer of "ImagePane"'s (but one does not exist).
        This is an example of an AccordionContainer, with Images
        exclusively as their Titles, named "dojoc.widget.ImageAccordion".
       
        <div dojoType="dijit.layout.AccordionContainer" duration="300"
                style="width:200px; height:400px; background:#fff; ">

                <div dojoType="dojoc.widget.ImageAccordion" title="Node 1"
                        selected="selected" src="images/title1.png">

                        Hello World
                </div>
                <div dojoType="dojoc.widget.ImageAccordion" title="Node 2"
                        src="images/title2.png">

                       
                        Nunc consequat nisi vitae quam. Suspendisse sed nunc. Proin
                        suscipit porta magna. Duis accumsan nunc in velit. Nam et nibh.
                        Nulla facilisi. Cras venenatis urna et magna. Aenean magna mauris,
                        bibendum sit amet, semper quis, aliquet nec, sapien.  Aliquam
                        aliquam odio quis erat. Etiam est nisi, condimentum non, lacinia
                        ac, vehicula laoreet, elit. Sed interdum augue sit amet quam
                        dapibus semper. Nulla facilisi. Pellentesque lobortis erat nec
                        quam.
                       
                       
                        Sed arcu magna, molestie at, fringilla in, sodales eu, elit.
                        Curabitur mattis lorem et est. Quisque et tortor. Integer bibendum
                        vulputate odio. Nam nec ipsum. Vestibulum mollis eros feugiat
                        augue. Integer fermentum odio lobortis odio. Nullam mollis nisl non
                        metus. Maecenas nec nunc eget pede ultrices blandit. Ut non purus
                        ut elit convallis eleifend. Fusce tincidunt, justo quis tempus
                        euismod, magna nulla viverra libero, sit amet lacinia odio diam id
                        risus. Ut varius viverra turpis. Morbi urna elit, imperdiet eu,
                        porta ac, pharetra sed, nisi. Etiam ante libero, ultrices ac,
                        faucibus ac, cursus sodales, nisl. Praesent nisl sem, fermentum eu,
                        consequat quis, varius interdum, nulla. Donec neque tortor,
                        sollicitudin sed, consequat nec, facilisis sit amet, orci. Aenean
                        ut eros sit amet ante pharetra interdum.
                       
                </div>
                <div dojoType="dojoc.widget.ImageAccordion" title="Third Pane"
                        src="images/title3.png">

                       
                        The quick brown fox jumps over the lazy dog.  The quick brown
                        fox jumps over the lazy dog.  The quick brown fox jumps over the lazy dog.
                       
                </div>
        </div>
        <!-- filler text to show HTML-after -->
       
                Sed arcu magna, molestie at, fringilla in, sodales eu, elit.
                Curabitur mattis lorem et est. Quisque et tortor. Integer bibendum
                consequat quis, varius interdum, nulla. Donec neque tortor,
                sollicitudin sed, consequat nec, facilisis sit amet, orci. Aenean
                ut eros sit amet ante pharetra interdum.
       
</body>

Example: File Upload Dialog Box

So now, we'll build a widget to solve a practical problem. Your site is all neatly designed and feng-shui'ed ... except this one /* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .html4strict .imp {font-weight: bold; color: red;} .html4strict .kw1 {color: #b1b100;} .html4strict .kw2 {color: #000000; font-weight: bold;} .html4strict .kw3 {color: #000066;} .html4strict .coMULTI {color: #808080; font-style: italic;} .html4strict .es0 {color: #000099; font-weight: bold;} .html4strict .br0 {color: #66cc66;} .html4strict .st0 {color: #ff0000;} .html4strict .nu0 {color: #cc66cc;} .html4strict .sc0 {color: #00bbdd;} .html4strict .sc1 {color: #ddbb00;} .html4strict .sc2 {color: #009900;} <input type="file"> tag. The user clicks on the button, and all of a sudden gets an operating system-styled box that doesn't look right, and is completely out of your control. Ugggh.

This is a perennial thorn-in-the-side for web developers. A Google search finds the ever-useful web site quirksmode, and they have a pretty simple solution: hide the file input, put a real input directly beneath where it would be, and make your own button off to the side. Logical enough. How hard would that be to make a widget?

Not hard at all, it turns out. Here's the plan:

  1. Hide the input boxes and draw our own
  2. Use Dojo to handle all my connections and fancy stuff
  3. Wrap it all up using dijit.form._FormWidget and diijt._Templated to take care of everything else

Hiding the Built-In File Upload Box

We'll use the programmatic method here. First we'll need a location on-disk for our widget. Since others have expressed interest in this widget, and we hope to contribute it to dojox, we'll place the widget in dojox/widget/FileInput.js. This means we'll be able to require() and provide() the module "dojox.widget.FileInput" as per the package system conventions. To get additional behavior we'll need to require() mixin classes, in case we don't already have them. So here's a stub for the class:

/* 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;}
dojo.provide("dojox.widget.FileInput");
dojo.require("dijit.form._FormWidget");
dojo.require("dijit._Templated");
dojo.declare("dojox.widget.FileInput",
        [dijit.form._FormWidget,dijit._Templated],
        {
        // summary: A styled input type="file"
        //
        // description: A input type="file" form widget, with a button for uploading to be styled via css,
        //      a cancel button to clear selection, and FormWidget mixin to provide standard    
        //      dijit.form.Form
        //     
});

Next we'll need a template, a label for our submit button, a label for our cancel button, and the name of the input:

/* 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;}
// label: String
        //      the title text of the "Browse" button
        label: "Browse ...",
        // cancelText: String
        //      the title of the "Cancel" button
        cancelText: "Cancel",
        // name: String
        //      ugh, this should be pulled from this.domNode
        name: "uploadFile",
        templatePath: dojo.moduleUrl("dojox.widget","FileInput/FileInput.html"),

Also note that whenever we use template variables , it's good practice to supply a default, e.g. "Browse ..." for the label. Otherwise, if your widget user omits the label attribute, the parser will complain.

As we said earlier in Direct Extension, it's preferrable to separate the template out into a different file. Ours will look like the following, placed in dojox/widget/FileInput/FileInput.html: /* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}

<div class="dijitFileInput">
        <input id="${id}" class="dijitFileInputReal" type="file"
                   dojoAttachPoint="fileInput" name="${name}" />

        <div class="dijitFakeInput">
                <input class="dijitFileInputVisible" type="text"
                           dojoAttachPoint="focusNode, inputNode" />

                <span class="dijitFileInputText"
                           dojoAttachPoint="titleNode">
${label}</span>
                <span class="dijitFileInputButton" dojoAttachPoint="cancelNode"
                        dojoAttachEvent="onclick:_onClick">

                        ${cancelText}
                </span>
        </div>
</div>

Note how we assign classes to each major part, so we can apply design as CSS styles. We're also using dojoAttachPoint="aString" so the parser makes these nodes available to us in code at the location this.fileInput.

dojoAttachEvent="onclick: _onClick" connects the onclick event of this.cancelNode to this._onClick, the method we are about to define (otherwise dojo.hitch will throw an error mentioning something about _onClick not having properties). We'll get to the onClick handler for the cancel button, and the reasons for having to do it later.

What is the following for?

/* 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;}
// nonRequired: String
//    this way, the build system knows about me
nonRequired: "",

for now, lets make sure our widget starts up, and looks right. lets make some simple css rules using the class names we set in our template, based on the relative / absolute, opacity:0 stuff we learned earlier:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000000; font-weight: bold;} .geshifilter .kw2 {color: #993333;} .geshifilter .co1 {color: #a1a100;} .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: #933;} .geshifilter .re0 {color: #cc00cc;} .geshifilter .re1 {color: #6666ff;} .geshifilter .re2 {color: #3333ff;} .geshifilter .re3 {color: #933;} .geshifilter .re4 {color: #933;}
.dijitFileInput {
        position:relative;
        height:1.3em;
        padding:2px
}
.dijitFileInputReal {
        position:absolute;
        z-index:2;
        opacity:0;
        filter:alpha(opacity:0);
}
.dijitFileInputButton,
.dijitFileInputText {
        border:1px solid #333;
        padding:2px 12px 2px 12px;
        cursor:pointer;
}
.dijitFileInputButton {
        opacity:0;
        filter:alpha(opacity:0);
        z-index:3;
        visibility:hidden;
       
}
.dijitFakeInput { position:absolute; top:0; left:0; z-index:1; }

looks good!

Connecting the Elements with Events

dojoAttachEvent="onclick: _onClick" connects the onclick event of this.cancelNode to this._onClick, the method we are about to define (otherwise dojo.hitch will throw an error mentioning something about _onClick not having properties). We'll get to the onClick handler for the cancel button, and the reasons for having to do it later.

We also need to implement a simple onchange listener, like the article hints, so that when our onchange is detected in on our real file input (this.fileInput), we will call this._matchValue() to steal the value from it, and populate our visible input:

/* 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;}
startup: function(){
                // summary: listen for changes on our real file input
                this.inherited("startup",arguments);
                this._listener = dojo.connect(this.fileInput,"onchange",this,"_matchValue");
                this._keyListener = dojo.connect(this.fileInput,"onkeyup",this,"_matchValue");
        },
        _matchValue: function(){
                // summary: set the content of the upper input based on the semi-hidden file input
                this.inputNode.value = this.fileInput.value;
                if(this.inputNode.value){
                        this.cancelNode.style.visibility = "visible";
                        dojo.fadeIn({ node: this.cancelNode, duration:275 }).play();
                }
        }

You've probably also noticed we added an onkeyup connection, running the same code. This way, if we type in the input, our changes will be reflected after each key press. Fortunately for this example, we'll ignore little nitpicks like "holding backspace doesn't fire onkeyup".

The _matchValue() function simply steals the file input value, sets it to the visible input value and fades in the cancel button (which we set earlier to visbility:hidden in FileInput.css).

So next, we need a reset button. Unfortunately because we're faking HTML out a bit, a plain old Reset button won't work. Since we aren't allowed write access to the file input, we can't just null the data. So our _onClick method actually destroys the /* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .html4strict .imp {font-weight: bold; color: red;} .html4strict .kw1 {color: #b1b100;} .html4strict .kw2 {color: #000000; font-weight: bold;} .html4strict .kw3 {color: #000066;} .html4strict .coMULTI {color: #808080; font-style: italic;} .html4strict .es0 {color: #000099; font-weight: bold;} .html4strict .br0 {color: #66cc66;} .html4strict .st0 {color: #ff0000;} .html4strict .nu0 {color: #cc66cc;} .html4strict .sc0 {color: #00bbdd;} .html4strict .sc1 {color: #ddbb00;} .html4strict .sc2 {color: #009900;} <input type="file"> and reads it.

/* 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;}
_onClick: function(/* Event */e){
        // summary: on click of cancel button, since we can't clear the input because of
        //      security reasons, we destroy it, and add a new one in it's place.
        // Disconnect the listeners so they're not orphaned, and cleanly remove the tag
        dojo.disconnect(this._listener);
        dojo.disconnect(this._keyListener);
        this.domNode.removeChild(this.fileInput);
        // Fade our the cancel button so we no longer can press it
        dojo.fadeOut({ node: this.cancelNode, duration:275 }).play();
        // Create an identical input tag
        this.fileInput = document.createElement('input');
        this.fileInput.setAttribute("type","file");
        this.fileInput.setAttribute("id",this.id);
        this.fileInput.setAttribute("name",this.name);
        dojo.addClass(this.fileInput,"dijitFileInputReal");
        // this.domNode is the root DOM node of the widget
        this.domNode.appendChild(this.fileInput);
        // Finally, connect the listeners to this new node.
        this._keyListener = dojo.connect(this.fileInput,"onkeyup",this,"_matchValue");
        this._listener = dojo.connect(this.fileInput,"onchange",this,"_matchValue");
        this.inputNode.value = "";
}

Wiring It All Together

So now we have our widget and our basic styles. We include this widget in our page declaratively by:

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

The real file input tag in the DOM goes away our templated input gets put in it's place. But if JavaScript is not present, it stays a regular HTML input tag, so it degrades nicely. Here is a sample test page to work 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;}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
        "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
        <title>dojox.widget.FileInput | The Dojo Toolkit</title>
        <style type="text/css">
                @import "../../../dojo/resources/dojo.css";
                @import "../../../dijit/themes/dijit.css";
                @import "../FileInput/FileInput.css";
        </style>
        <script type="text/javascript" src="../../../dojo/dojo.js"
                   djConfig="isDebug:true, parseOnLoad: true">
</script>
        <script type="text/javascript">
                dojo.require("dojox.widget.FileInput");
                dojo.require("dojo.parser");    // scan page for widgets and instantiate them
        </script>
</head>
<body>
        <h3>A standard file input:</h3>
        <input type="file" id="normal" name="inputFile" />
        <h3>The default dojox.widget.FileInput:</h3>
       
        <input dojoType="dojox.widget.FileInput" id="default" name="uploadFile" />

In theory, it will work inside of a

tag just as the original element did.

Because this is dojox, and we're bigs fan of re-using code, we can steal some CSS stuff from tundra.css and soria.css to provide theme-specific styles, so our input nodes look like they would in with all the other dijit.form Widgets (like ComboBox, FilteringSelect, ValidationTextBox, etc) ...

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000000; font-weight: bold;} .geshifilter .kw2 {color: #993333;} .geshifilter .co1 {color: #a1a100;} .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: #933;} .geshifilter .re0 {color: #cc00cc;} .geshifilter .re1 {color: #6666ff;} .geshifilter .re2 {color: #3333ff;} .geshifilter .re3 {color: #933;} .geshifilter .re4 {color: #933;}
/* tundra */
.tundra .dijitProgressOverlay {
        border:1px solid #84a3d1;
        background-color:#cad2de;
}
.tundra .dijitFakeInput input {
        font-size: inherit;
        background:#fff
            url("../../../dijit/themes/tundra/images/validationInputBg.png")
            repeat-x top left;
        border:1px solid #9b9b9b;
        line-height: normal;
        padding: 0.2em 0.3em;
}
.tundra .dijitFileInputButton,
.tundra .dijitFileInputText {
        border:1px solid #9b9b9b;
        padding:2px 12px 2px 12px; /* .3em .4em .2em .4em; */
        background:#e9e9e9
            url("../../../dijit/themes/tundra/images/buttonEnabled.png")
            repeat-x top;
}
/* Soria */
.soria .dijitProgressOverlay {
        border:1px solid #333;
        background-color:#cad2de;
}
.soria .dijitFakeInput input {
        border:1px solid #333;
        background:#fff
            url("../../../dijit/themes/soria/images/gradientInverseTopBg.png")
            repeat-x top left;
        line-height:normal;
        background-position:0 -30px;
        padding:0.2em 0.3em;
}
.soria .dijitFileInputButton,
.soria .dijitFileInputText {
        border:1px solid #333;
        padding:2px 12px 2px 12px;
        background:#b7cdee
            url('../../../dijit/themes/soria/images/gradientTopBg.png') repeat-x;
}

Future Plans

If you've been paying attention all this way, you probably noticed a class up there that we didn't define. .dijitProgressOverlay ... it doesn't exist anywhere in the dojox.widget.FileInput template or code.

The Dojo folks are building an extension to this widget called dojox.widget.FileInputAuto. It works like FileInput except it submits itself after a delay following a blur() on the element. For details (as yet undocumented), get the latest nightly build of Dojo and look for it in dojox.widget.

Creating Accessible Widgets

Device Independent behavior means more than just supporting the keyboard. Where ever possible use the most generic event handler available. For example, consider a widget where the down arrow key selects an element in the widget.  The selection needs to be distinguished with a specific style.  Rather than modifying the style of the element when processing the down arrow key event, focus the item from the down arrow key event handler and change the style via a focus event handler. This way, if focus is set from a means other than the keyboard such as a voice input system, the styling is properly set and does not depend solely on keyboard actions. 

Determining Keyboard Behavior

When implementing keyboard navigation, the ideal solution is to mimic the behavior of the operating system.  For example, the right and left arrow keys are used to expand and collapse nodes in a Windows tree control and the up and down arrow keys move between nodes in the control.   Unfortunately it is not always possible to mimic the operating system or browser behavior because the widgets may not be able to capture the necessary keys. A group of industry representatives are working to create a style guide to describe the navigation and behaviors of Web widgets. When completed, this Style Guide will be provided to open source and dijit plans to implement the recommendations.  Eventually, the Style Guide will attempt to normalize the differences between operating systems and provide a generalized solution for Web components 

Within all widgets interaction with both the keyboard and the mouse is important – users may switch between using the mouse and using the keyboard at any time. A widget author can not assume only keyboard or only mouse interaction. Thus, the widget component will generally need to store information about the current item with focus.  This can also be useful when the keyboard event handler is placed on an owning object in the component hierarchy rather than the actual element generating the event – for example on the table element rather than on each td element.   Even though the event handler provides information on exactly what element generated the event, it is often necessary or easier to use the stored point of reference In order to support both the mouse and standard enter key and space key press a dijit widget will implement an onDijitClick event hander.  This handler must include steps to update the point of regard so that any additional keyboard actions after the onDijitClick will continue to work.  In addition, the point of regard is often needed in order to update the style on the element losing focus before updating the new item irregardless or whether the mouse or the keyboard generated the event that results in a focus change.

Trapping Key Events

When implementing keyboard navigation, first determine where in the hierarchy to trap the key events. It is generally best to trap the key events at as high a level as possible and use the event object to determine that actual source of the event and perform the necessary action.  This method prevents having to add a key handler to each individual element thus conserving the amount of markup to be generated.  However, there may be cases where the event needs to be trapped at the level of each individual element. The actual source of the event is needed in order to determine how to process the keystroke received.

Once the component handles an event, it will usually stop that event from being propagated to other elements.   For example, if the down arrow key is captured and moves focus to the next item in a tree control, the event should not propagate up to the browser where it might be interpreted as a command to scroll the page.  Use the dojo.stopEvent(event) method to stop the event.

In order to assist with key event handling, an onkeypress event has been added to Dojo to normalize key events. The appropriate key event, either onkeydown or onkeypress, will be used depending upon the browser.  The key codes have been normalized as well. See dojo.keys class in dojo._base.event.js. Add the dojo onkeypress event into the widget template or via scripting using one of the dojo event connection apis.

Example using dojo.connect to connect the object represented by the node variable to the onKey handler function in the current object:
dojo.connect(node, "onkeypress", this, "onKey");

Example from tab container template to demonstrate adding the onkey handler and waiRole within the template:
/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<div class="dijitTabContainer">
        <div dojoAttachPoint="tablistNode"></div>
        <div class="dijitTabPaneWrapper" dojoAttachPoint="containerNode" dojoAttachEvent="onkeypress:_onKeyPress" waiRole="tabpanel"></div>
</div>

Tabindex and Focus

When navigating via the keyboard is it essential that the element that is navigated to receives focus.   The focus should NOT be simulated via CSS - call the focus() method on the element.  Styling can be used to enhance the visual focus or selection but should not replace actually setting focus on an element.  A screen reader will only speak information about the element when it receives focus. Screen magnifiers rely on focus to move the zoomed viewport on the screen.

Use the tabindex value to provide direct or programmatic keyboard focus to an element. See the tabindex chart in the Dojo Accessibility Strategy Device Independence section.

When adding support for keyboard navigation, consider the widget as a component.  The tab key can be used to navigate from component to component on a page and then the arrow and other keys should be used to navigate within the component.   Only one element in a given component should have a tabindex equal to zero at any one time.  This allows the user to navigate into and set focus within in the component using the tab key.  Then, trap the onkey events and use the arrow keys to navigate within the elements of the component. All of the elements within the component which can receive focus must have a tabindex equal to -1. When an element is programmatically given focus, its tabindex value is changed from -1 to 0 and the tabindex of the previous element with focus will be changed from 0 to -1. This will insure that only one element within the component is in the tab order of the page and that the element with tabindex = 0 is the most recently focused element in the component.

For example, when creating a tree control, each tree item is represented by an element.  The first tree item in the control will be given a tabindex of 0.   All of the other elements which represent tree items and can receive focus programmatically will have a tabindex value of -1. An onkeypress handler will trap the keyboard events for the tree control.  When a tree item element is given focus via element.focus(), the element’s tabindex will be changed from -1 to 0. and it will be put into the tab order. Now if the user moves focus out of the tree control (either via a mouse click or by tabbing to the next component on the page), when the user sets focus back into the tree control using the tab key, the last focused tree item, which was given a tabindex of 0, will receive focus.  

Add ARIA information

The Accessible Rich Internet Applications Roadmap is being developed by the W3C Web Accessibility Initiative (WAI) Protocols and Formats working group. The group is creating specifications for role and state information which can be added to markup to provide semantic information about user interface components. The browsers will translate this role and state information into the accessibility api for the platform in use. Currently Firefox 1.5 and later supports this additional semantic information on the Windows platform where it converts the information into the Microsoft Active Accessibility (MSAA) api. When recent versions of the Window-Eyes and JAWS screen readers are used with Firefox, this additional information is spoken to the user.

Some HTML elements such as links and form elements have well defined roles and behaviors. Interactive controls created from generic elements can now also be identified with roles and states. When an element receives focus the role and state information provided by the developer will be made available to assistive technologies. For example, as a screen reader traverses through a dijit tree control using the arrow keys, as each tree item receives focus the title of the tree item will be spoken as well as its expanded or collapsed state if it has children. Likewise, a dijit checkbox created using <div> and <span> tags can be identified as a checkbox and its checked or unchecked state can be reported. When creating a new Dojo widget, the role of the widget must be identified and the state of the widget must be set and updated as it changes.

As of August, 2007, the ARIA specifications are still under development; however a significant portion of the specification has been implemented in Firefox 1.5 and 2.0. The public drafts of the specifications can be found at http://www.w3.org/wai/pf

Accessible DHML in the Mozilla Developer Center provides additional information about using the ARIA specifications and includes a table of roles and states supported in current versions of Firefox (http://developer.mozilla.org/en/docs/Accessible_DHTML#Supported_roles_and_properties)

Assigning Roles

Use the tabindex to provide keyboard focus or to allow programmatic focus to an object. By adding a tabindex to an element, the element will now be included in the accessibility hierarchy of the Firefox browser. Information about elements in the accessibility hierarchy will be provided to assistive technologies. If you use a tabindex attribute on a <DIV>, <SPAN>, <IMG> or any element which has no natural role of its own then you need to provide a role. Any element that can receive focus must have a role, either implied, such as input elements and anchors, or specified via a role attribute. For things with an implied role such as input fields and anchors, you can use tabindex=”-1” to remove them from the tab order. You can also specify a different role for elements which already have an implied role.

The role and states are added to Dojo widgets within the widget template or via the dijit.wai APIs as described in the Accessibility Resources section.

Providing Hierarchical Information

In order for Firefox to determine the correct parent child relationships between objects, and to communicate this via an accessibility API to assistive technologies, it is best to create components in a hierarchical fashion. For example, when creating a menubar it is best to have the components that make up the menus and menuitems of the menubar be children of the menubar. Likewise, menuitems should be children of the owning menus. This hierarchy allows Firefox to provide menu information to the assistive technologies, and for a screen reader to speak more information about the menu such as, “menu open, File, item 1 of 5” when the user opens a menu. Here is a simple pseudo code example demonstrating a hierarchical layout of elements for a menu control. This exmaple only shows the addition of role attributes and does not represent a complete menu widget. (Note: Attributes are not quoted in pseudo-code examples to help improve the readability):

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

<div role="menubar">
<div role="menuitem">A</div>
      <div role="menu">
            <div role="menuitem">A.1</div>
            <div role="menuitem">A.2</div>
      </div>
      <div role="menuitem">B</div>
      <div role="menu">
            <div role="menuitem">B.1</div>
            <div role="menuitem">B.2</div>
            <div role="menu">
                  <div role="menuitem">B.2.1</div>
            </div>
      </div>
</div>

It may not always be practical to create items via HTML in a hierarchical fashion. In that case the group role can help to associate the items properly. This is illustrated in the following simple pseudo code example of a tree hierarchy.

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<div role="tree">
  <div role="treeitem">Top </div>
  <div role="group">
     <div role="treeitem">1</div>
     <div role="group">
       <div role="treeitem">1.1 </div>
       <div role="treeitem">1.2</div>
       <div role="treeitem">1.3</div>
<div role="group">
<div role="treeitem">1.3.1</div>
         <div role="treeitem">1.3.2</div>
         <div role="treeitem">1.3.3</div>
         <div role="treeitem">1.3.4</div>
       </div>
       <div role="treeitem">1.4</div>
     </div>
     <div role="treeitem">2</div>
     <div role="treeitem">3</div>
     <div role="group">
       <div role="treeitem">3.1</div>
       <div role="treeitem">">3.2</div>
     </div>
  </div>
</div>

The tree items at the same level in the hierarchy are grouped together within a

element identified with role=group. With this organization, the assistive technologies can be provided with the information about what level and item number a particular treeitem represents. For example, in the above tree example, with focus on item 1.3.3 a screen reader might speak, “one dot three dot three item three of four, level four” or something similar.

Other items included in the hierarchy may not be essential to the component. These items can be marked with a role of presentation to eliminate them from consideration when determining information about the component.

Using the Presentation Role

While it is preferable to use CSS for layout, tables are still used to quickly and easily arrange elements on a page. This is especially true of existing widgets which were originally created to work in older browsers. Putting information in tables can easily confuse the hierarchy of the component. If tables must be used, they can be marked with a role of presentation to eliminate them from the hierarchy. Here is a pseudo code example where the presentation role was used on tables within a tree component:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<div role="tree">
      <table role="presentation">
            <tr><td><div role="treeitem">Top</div></td></tr>
      </table>
      <div role="group">
            <table role="presentation">
                  <tr><td><span role="treeitem">1</span></td></tr>
            </table>
            <div role="group">
                  <table role="presentation">
                        <tr><td><span role="treeitem">1.1</span></td></tr>
                  </table>
                  <table role="presentation">
                        <tr><td><span role="treeitem">1.2</span></td></tr>
                  </table>
            </div>
            <table role="presentation">
                  <tr><td><span role="treeitem">2</span></td></tr>
            </table>