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.