The trees we see in User Interfaces help sort out long, heirarchical lists. A file system is the classic example, with Windows using it in Explorer and Macintoshes with its folder windows. The Dijit tree widget is like that. The Tree widget itself is simple, but the real power comes in the data you pass - this represents the heirarchical structure of the tree. This data is fed by the powerful dojo.data API.
Dojo makes easy trees easy, and hard trees possible. In particular, you can:
To start, here's a one level tree. We split the tree data off into a separate file, poptarts.txt, in JSON format. You will need to download this and save it in the same directory as the example HTML below:
{ label: 'name',
identifier: 'name',
items: [
{ name:'Fruit', type:'category'},
{ name:'Cinammon', type: 'category'},
{ name:'Chocolate', type: 'category'}
]
}
Then here's the HTML that draws the actual tree:
This is a good start, but there's a lot more you can do with Tree. Read on!
To desconstruct the previous example, we need some dojo.data terminology:
dijit.Tree, conforming to the dojo.data spec, expects the following in its data store:
So now let's take a step further. There are two methods to nesting a tree, corresponding to the two heirarchical methods in dojo.data.
The easiest method for fixed text is to nest the items in the data store. To add a subtree, you must add a children attribute to the parent item, then add the child items to the children attribute. So for our previous example, we can add nodes under the Cinammon node:
By downloading this file into poptarts.txt, you can use the same HTML as our previous example. And voila!
Direct Nesting is a little inconvenient for relational table data. So Tree supports references, where all the data nodes are at the same level, but nesting occurs with psuedo pointers to child nodes. So our example above is written: /* 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 our nested children example, the parent node requires a children attribute. But instead of actual items, you place reference objects linking to the identifier of another object. This requires a small change to the Tree 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 query is necessary to choose only the top level items from the store. The menu produced is exactly the same:
The problem is our tree does nothing but stand around looking beautiful. Nothing wrong with that. Normally, though, you'd want some kind of actions.
At the very least, you probably want to do something when a user clicks or press [ENTER] on a node. To do this, you can use the onClick event.
Alternatively, you can use Dojo's publish/subscribe event system. When a node is clicked, the tree id is sent as the topic along with the message:
Dojo trees are great, and so is Dojo Drag And Drop (DnD). But together, they're unstoppable! Not being satisfied with our selection of Pop Tarts, we'll create a pool of Drag and Drop sources to add:
/* 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 user should only be allowed to drop a Pop Tart on its home category - for example, Blueberry should only go under Fruit. To handle this, we use JavaScript regular expressions to make an intelligent guess. We add these to the data store:
{ label: 'name',
identifier: 'name',
items: [
{ name:'Fruit', type:'category', regexp:'.*erry$'},
{ name:'Cinammon', type: 'category', regexp:'[Cc]innamon',
children: [
{ name:'Cinammon Roll', type:'poptart' },
{ name:'Brown Sugar Cinnamon', type:'poptart' },
{ name:'French Toast', type:'poptart' }
]
},
{ name:'Chocolate', type: 'category', regexp:'([Cc]hocolate|Fudge)'}
]
}
You can download this file below. Next we wire in DnD to the Tree. This is as simple as specifying two attributes: the controller and the acceptance checker extension point, which we'll write shortly.
/* 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 checkItemAcceptance extension point function is called each time a drop target is entered. In Tree's case, every node is a drop target. How do we know it's valid? The function must check the dragged node to the regular expression of the drop node:
function poptartCheckItemAcceptance(node,source) {
// Get the associated dojo.data item for the target
item = dijit.getEnclosingWidget(node).item;
// Need to check for item because when dropping on a root node,
// item === null
if (! item) return false;
ptType = ptTree.store.getValue(item,"type");
if (ptType == 'category') {
// We make intelligent guesses about the correct folder
re = new RegExp(ptTree.store.getValue(item,"regexp"));
okToMove = true;
for (var itemId in source.selection) {
console.debug(itemId+" tested against "+re.toString());
okToMove &= re.test(itemId);
}
return okToMove;
}
else
return false;
}
So now all the pieces are in place, yielding:
And here's the full source code, which is downloadable 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;}In Actions we saw how to hook a piece of code into onClick. That's not all. With JavaScript and a Tree, you can style and manipulate trees in all sorts of combinations.
OnClick is an extension point, which we'll cover in detail in Part 3. Three other extension points are used in drawing the tree nodes:
Here is an example of coloring our Pop Tarts labels. Notice the loose coupling here. If another category of Pop Tarts are introduced in the data source, you only need to add a corresponding class to poptarts.css: /* 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;}
And the getLabelClass is straightforward:
|
dijit.Tree, dijit._TreeNode
dijit.Tree is a container for a hierarchical list with expandable and collapsible items. dijit._TreeNode's are the items themselves. _TreeNodes are almost never created with markup, and in general you don't deal with them.
|
||
|
Attributes
|
||
| childrenAttr | String | name of attribute that holds children of a tree node consider this "root node" to be always expanded |
| label | String | New in 1.0 label for the top node of the tree, if desired. Note there is no actual item associated with this node. |
| query | String | get top level node(s) of tree (ex: {type:'continent'}) |
| store | dojo.data.Store | The store to get data to display in the tree |
|
Methods
|
||
| isExpanded | if expandible, returns true if children are displayed | |
| isExpandible | returns true if node can be expanded (has an expando icon next to it) | |
|
Extension Points
|
||
| getIconClass | user overridable class to return CSS class name to display icon | |
| getItemChildren | User overridable function that return array of child items of given parent item, or if parentItem==null then return top items in tree | |
| getItemParentIdentity | User overridable function, to return id of parent (or null if top level). It's called with args from dojo.store.onNew | |
| getLabel | user overridable function to get the label for a tree node (given the item) | |
| mayHaveChildren | New in 1.0 User overridable function to tell if an item has or may have children. Controls whether or not +/- expando icon is shown. (For efficiency reasons we may not want to check if an element has children until user clicks the expando node) | |
| onClick(item, node) | Called when someone clicks a tree item | |
| Action | Key |
|---|---|
| Navigate to first tree item* | Tab |
| Navigate to the next sibling | Down arrow |
| Navigate to the previous sibling | Up arrow |
| Open a subtree | Right arrow |
| Close a subtree | Left arrow |
| Navigate to open subtree | Right arrow |
| Navigate to parent | Left arrow |
| Activate a tree item | Enter |
* Note: The last tree item focused will be in the Tab order.