- Components Involved In a Tree
- A Simple Tree Example
- Hiding a Tree's root node
- Updating a Tree
- Lazy Loading a Tree
- Drag and Drop
- Context Menu
- More examples
The trees we see in User Interfaces help sort out long, hierarchical lists. A file system is the classic example, with Windows using it in Explorer. The Dijit tree widget is like that.
Dojo makes simple trees easy, and complicated trees possible. In particular, you can:
- Connect your tree to any dojo.store, with or without a single root item, and with various ways to express parent/child relationships
- Nest items to an arbitrary depth ... each branch is independently expandable
- Apply different icons to different leaf or branch items
- Setup a global handler for when a user clicks or double clicks a particular nodes.
- Tree will automatically reflect changes made to the underlying data store (when connected to the data store through the :ref:`ObjectStoreModel <dijit/tree/ObjectStoreModel>`, or legacy :ref:`TreeStoreModel <dijit/tree/TreeStoreModel>` or :ref:`ForestStoreModel <dijit/tree/ForestStoreModel>`)
- Allow nodes to be dragged and dropped through the familiar Dojo DnD API.
- Drag and drop onto the tree, which updates the data store indirectly
To understand how to use a Tree, you need to be aware of three components that feed each other:
The Tree widget itself is merely a view of the data. It's in charge of displaying the data and handling user events only.
The Tree is a black-box in the sense that the developer generally won't be dealing with individual nodes of the Tree. Rather, there are just onClick() etc. notifications, which refer to the item that was clicked. Item is usually an item in a dojo.store that the tree is connected to.
Note also that a Tree has an idea of a currently selected item, such as the currently opened folder in a mail program.
The real power comes in the :ref:`tree model <dijit/tree/Model>`, which represents the hierarchical data that the tree will display. Tree can interface to any class implementing the model API, but typically interfaces through the :ref:`ObjectStoreModel <dijit/tree/ObjectStoreModel>`, which itself interfaces with the powerful :ref:`dojo.store <dojo/store>` API.
It's important to note that the tree is merely a '''view''' onto the model. The model is in charge of tasks like connecting to the data source (often on the server), lazy loading, and notifying the tree of changes to the data. It's also in charge of handle drop operations, when someone drags and drops an item onto the tree.
To put it another way, you cannot "delete data from the tree" or "insert data into the tree" directly, but rather you must update the model.
Note also that each item in your Tree needs a different identifier (the value of the identifier has to be unique). It's the same concept as a primary key in a database.
Although not required, usually the model interfaces with a :ref:`dojo.store <dojo/store>`.
There can be many different types of stores, such as stores that work from XML vs. stores that work from JSON, stores that execute on the client vs. stores that pass through to the server, stores that load data as it's needed or stores that load all the data on initialization, etc. All the stores, though, have the same API, so they can be connected to with the :ref:`ObjectStoreModel <dijit/tree/ObjectStoreModel>`.
From the simplest point of view, the information flows like this:
Data Store --> Model --> Tree
That gets more complicated when we think about drag & drop, but we'll address that later.
We can display a Tree on a page by creating a data store, a model, and the Tree widget itself.
Creating a programmatic tree is very simple:
Each node in the tree has an icon. Like other dijits, the icon is expressed as a CSS class (which should load a background-image). You specify the class per item by overriding dijit.Tree's getIconClass().
The default implementation of getIconClass() shows two types of icons: folders and leafs. (Actually, it has separate icons for opened and closed folders, so that's three icons...) It tries to guess if the node is a folder or not by whether or not it has a children attribute:
Note that the !item check refers to the root node in the tree, which may not have any associated item when using the old version of the Tree API, connecting the Tree directly to a store instead of using a model.
That works fairly well, but will fail if mayHaveChildren() returns false for items with no children. The definition of mayHaveChildren() for "empty folders" is actually somewhat vague, so it's best not to depend on it. A better getIconClass() method for a Tree connected (through a model) to a :ref:`dojox.data.FileStore <dojox/data/FileStore>` would determine if the item was a folder or not based on whether or not the item had the "directory" attribute (and it was set to true):
If you want to have different icon types depending on the type of items in the tree (for example, separate icons for songs, movies, and TV shows), then you really need to override the method to return a separate class name based on the type of item:
There's always a single root item for a Tree, returned by the model's getRoot() method. It might be a real item from the store (such as a tree of employees, with the CEO as the root), or it if there's no single root item in the store (like if the store lists continents but the top item, "the world", is implied, the model is responsible for fabricating such a root item (from the perspective of the tree).
Correspondingly, all trees have a root node, corresponding to the root "item" from the model.
Sometimes you don't want that "the world" top level node to be displayed, especially if the Tree is inside a TitlePane/AccordionPane/etc. with the label "The World". In that case you should set showRoot=false. The item still exists in the model but it's hidden on the screen:
Note that you can hide or show the root item regardless of whether that root item is fabricated (see :ref:`dijit.tree.ForestStoreModel <dijit/tree/ForestStoreModel>`) or corresponds to a real item in the store.
People often ask:
You can't update the tree directly, but rather you need to update the model. Usually the model is connected to a data store and in that case you need to update the data store. Thus, you need to use a data store that allows updates (through its official API), like :ref:`dojo.store.Memory <dojo/store/Memory>`.
When using :ref:`dijit.tree.ObjectStoreModel <dijit/tree/ObjectStoreModel>`, the store needs to be wrapped in a dojo.store.Observable <dojo/store/Observable>, as below:
This isn't supported. The store needs to notify the tree of any changes to the data. Currently this is really only supported (out of the box) by a :ref:`dojo.store <dojo/store>` wrapped in a dojo.store.Observable <dojo/store/Observable>, or by :ref:`dojo.data.ItemFileWriteStore <dojo/data/ItemFileWriteStore>`.
Setting up a client-server dojo.store source where the server notifies the client whenever the data has changed is quite complicated, and beyond the scope of dojo, which is a client-only solution.
People often ask how to lazy-load a tree, but this question is really unrelated to the Tree itself. If you use a data store that is lazy loading, such as :ref:`dojo.store.JsonRest <dojo/store/JsonRest>` then the data will be loaded lazily.
Tree's support drag and drop, meaning that a user can:
- drop an item onto the tree
- drag an item from the tree
- move items within the tree
In the first and last case (ie, when an item is dropped onto the tree), the drop is processed by the model, which in turn sends it to the data store (updating the underlying data). Thus:
- the model must implement the pasteItem() method
- the store must implement put(), and Observable.
In addition, to enable DnD on the Tree you must require
and set the Tree's dndController to
You can also specify custom checkAcceptance() and checkItemAcceptance() to accept/reject items to the tree. (The former function operates at the Tree level, and the latter operates per Tree node, allowing things like rejecting dropping items onto leaf nodes.)
If you are interested in further examples, please make sure you have glanced at the unit tests. You can find a good example in test_Tree_Dnd.html.
If between threshold is set to a positive integer value like 5 (which represents 5 pixels), then dragging within 5px of the top or bottom of a tree node, is interpreted as trying to make the drag source the previous or next sibling of the drop target rather than the child of the drop target. This is useful for when a user can control the order of the children of the child nodes:
What happens when a user moves an item from one position in a tree to another? It's actually quite complicated...
- The Tree widget does not change its display at all. Rather, it notifies the model of the paste operation.
- The model updates the store.
- The store notifies the model that the data has been changed.
- The model notifies the tree of the change (presumably the children list of nodeA is one shorter, and the children list of nodeB has a new entry)
- The Tree updates its display.
In this way, the Tree, Model, and data store are always in sync.
Tree has no built-in support for context menus, but you can use the Menu widget in conjunction with the Tree:
Note that it's including dojo/query in order to use Menu.selector.
If you don't want to display the grid lines for a Tree then simply write CSS rules to override the theme and hide the relevant background images. The pertinent lines from tundra are:
Due to implementation details, on the tundra, soria, and nihilo themes the hover effect for tree nodes is done with a near-transparent image:
So in order to change the hover effect you would need to create a new image (with for example 95% transparency), and write a CSS rule to override the one above.
You can also remove the hover effect altogether by just writing a CSS rule that sets background-image to none, overriding the above rule.
On the claro theme, the hover effect is done via a background-color (combined with a white gradient background image), so changing the hover effect just involves changing that background color.
By default, a Tree will remember which branches were opened/closed. To use this feature you must specify an id for the Tree. To disable the feature, set the "persist" parameter to false.
There are :ref:`more extensive examples <dijit/Tree-examples>` of using the tree.
|Navigate into tree*||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|
|Navigate to first tree node||Home|
|Navigate to last visible tree node||End|
- Note: The most recently focused tree item will be in the Tab order.
Tree items can also be accessed by typing alphanumeric characters. For example, typing "A" will navigate from the currently focused node to the next node that begins with the letter A (case insensitive). Typing "Al" will navigate to the next node that starts with "Al". Only the nodes that are visible are searched, not nodes that are hidden inside a closed node. The nodes are searched in the order that they appear on the screen, from the focused node downwards and then looping back up to the top of the tree.
Using JAWS 10 in Firefox 3 the properties of each tree item are spoken including the open/close state and the level information. Using JAWS 10 with IE 8, the open/close state of each item is spoken but the level information is not spoken. In both Firefox 3 and IE 8 the JAWS user should be in App mode or virtual pc cursor off mode for best performance (toggle the mode via the insert+z key).