This documentation refers to new TreeV3, not to stable Tree widget. Feel free to suggest features and test/critisize existing solutions.
Basic things are shown in tests (dojo/tests/widget/treeV3), so you might want to check them first and copy-paste exactly the things you need. The tutorial is meant to provide deeper understanding for those who want to extend/modify tree behavior.
This is just a draft, but I'd be grateful for your replies. The contacts are
Previous tree used a list of divs, each of them was indented with grid and spacers to right level. The new tree uses natural nested divs structure (children' divs inside parent's div). Grid is contigous and structure is displayed correctly for any node/font size
All image and size information was removed from JS code. There is a bunch of classes applied to nodes, that may denote node folder state, node type, show if there are children, etc. CSS moves this logical classes into style
Want to put 2 differently styled trees on a page? Give them different classPrefix.
nodeDOMCreated
event was removed. That's because listeners are bound to tree and may want to modify the new node,
but that's only possible when the node is being bound to the tree, not when it was created and hanging around.
afterTreeChange
was introduced to help listeners to (un)bind nodes the right moment
All events were renamed to better reflect the moment of their publishing
afterExpand
, afterCollapse
events now fire
when the animation (e.g fading in or out) finishes, not when the actual expand/collapse is called.
Before TreeV3, all nodes must be widgets. A node is added - hence graphical widget is created. For performance reasons that behavior was altered. Now when you add a node, you may actually add a "data object", containing node data, e.g {title:"new node"}. You may want to add a large nested branch of such data objects, like {title:"new", children:[...data objects..]}.
Data objects will become real members of children array (you may recursively search them, modify etc), but graphical widgets will be created only when visitor expands them.
The compatibility drawback of such behavior is that old code may erroneously call *widget* methods on *data objects* while recursively traversing a tree, e.g with Widget#getDescendants. You should change such code to use TreeCommon#processDescendants, or handle data objects in special way.
There are no special mechanisms to add laziliy instantiated "data objects".
You may manipulate them simply modifying children
array, but no events are thrown until a
real widget appears on the scene. In most cases that is fine, but you are free to "disable" lazy widget creation - do not modify
children
directly and enable tree.eagerWidgetInstantiation
Many features were moved from core into extensions
The Tree is actually a pack of loosely coupled components, connected through events. To keep things simple and also for compatibility reasons, such components(controller,selector...) were created implicitly, if not declared. But actually this proved to be a source of questions and misunderstandings. So now nothing is created implicitly, read how-to and declare things.
Old callbacks code was removed in favor to dojo.Deferred. Now all operations may be async and run your callbacks at the end.
Sounds simple enough.. Select multiple nodes with ctrl and get them with selector.selectedNodes
.
instead of removed selectorNode
call.
Tree was coded with performance in mind. Although, JavaScript itself is a slow language. Flexible model requires some code that slows it down. It's not DOM manipulations, but actually javascript that I couldn't make lighter. Being a part of dojo/widget structure also implies some overhead, but also power.
Almost all operations require small constant time when single node is involved. Depending on your application you may notice slowdown when (most common) creating lots of nodes or performing other batch operations.
In my tests 1000 nodes required 0.7-0.8 sec, growth is linear, depth does not matter, children
are created with createSimple
and added to parent all at once with setChildren
.
Creation from markup or with standard create/addChild routines is 2-3 times slower, because these routines are generic.
Memory footprint (IE,FF) is about 1M per 100 nodes, that were not postponed by lazy creation feature
Fast node creation with dojo tree is 2-3 times slower than xtree 1.7, another tree widget, not so featured, but nicely optimized for performance.
When talking about performance, one should understand, that there are single-node operations that operate on single node... These ones are fast. The examples are: create a node, delete a node, move a node along the tree.
... And there are batch operations that touch a lot of nodes. The examples are: initial tree creation, moving a node from one tree to another which has different listeners, etc.
That performance issues become noticeable at 100-300 tree nodes depending on your trees. All algorithms are linear in worst case, but JS is slow language, DOM is also not that fast.
There is a number of features one could use to get a speedup.
A node can be created with isFolder=true
flag, but without children.
Any node has a state
, initially UNCHECKED for empty folder, and used
by TreeLoadingController.
When a user presses expand, tree controller (supporting lazy loading) will send a request to server asking for nodes, and parse the answer creating children.
The benefit is obvious: you don't have to load/process whole tree at once. You can only load a single node and user will load the rest clicking "expand"
Node/tree keeps array of its children in children
property.
Lazy creation is somewhat a half-way approach to lazy loading. It allows you to put data objects
into this array and tree will create widgets of them later, when they are expanded.
For instance, one can call node.children = [{title:'node1'},{title:'node2'}]
.
The objects will be set, but no widgets are created. You can also set children to nested array:
node.children = [{title:'node1', children:[{title:'node2'}] }]
.
You can create tree on server, JSON-serialize it and put to HTML, that is gzip-compressed. Compression will be 6 times or more, so it is not that space hungry.
The benefit comes from postponing almost all real job: widget creation and attaching it to tree will happen in expansion-time.
Sometimes, lazy creation and loading may work together nicely, providing seamless increase in speed and decrease in memory footprint. For instance, server may pass a whole tree branch in JSON to lazy loading controller. Top nodes will be created right along, because user needs them, but the rest of the branch will be postponed relying on lazy creation feature.
There are operations, like "expandAll" where such lazy tricks don't help, because all graphical widgets must be processed. That is why
widget creation process is well-optimized itself. createSimple
is a hacky program-only way
to create TreeNodes fast. setChildren
is a method to assign (and create if needed)
all children at once. It helps to evade some extra work happening when children are added one by one.
IE has a well-known bug. If an image was loaded dynamically - with a new Image()
, or img.src=
assignment, or even as a background of a new node, it will not be cached. So every time when you create a node, all needed icons get loaded
from server (or requested at least). A possible solution is to put a special div into HTML (adjust src to your path):
<div style="display:none">
<!-- IE has a bug: it reloads all dynamically resolved images, no matter, is it
new Image() or CSS background. If you don't specify images like that,
it will reload them every time a new node is created -->
<img src="../../../src/widget/templates/images/TreeV3/i.gif"/>
<img src="../../../src/widget/templates/images/TreeV3/i_half.gif"/>
<img src="../../../src/widget/templates/images/TreeV3/expand_minus.gif"/>
<img src="../../../src/widget/templates/images/TreeV3/expand_plus.gif"/>
<img src="../../../src/widget/templates/images/TreeV3/expand_leaf.gif"/>
<img src="../../../src/widget/templates/images/TreeV3/i_long.gif"/>
<img src="../../../src/widget/templates/images/TreeV3/document.gif"/>
<img src="../../../src/widget/templates/images/TreeV3/open.gif"/>
<img src="../../../src/widget/templates/images/TreeV3/closed.gif"/>
</div>
You can pass objects as element of children
array, but you can also pass arrays.
If you pass objects, they are treated as data objects for TreeNodes, but arrays are treated as arguments for createWidget call.
E.g if you want to createWidget("MyWidgetNodeType", {title:'somedata'})
, then you pass
["MyWidgetNodeType, {title:'somedata'}] instead of just {title:'somedata'} from server (or in setChildren call).
Another option is to change defaultChildWidgetType
property of the tree.
Helpful in case when you have your own widgets and use them elsewhere instead of TreeNode
Two classes handle the model: TreeV3
is "tree" itself and somewhat a root node, while
TreeNodeV3
is any other node, linked by parent
and children[]
properties. There is a common mixin TreeWithNode
.
Extensions provide additional functionality by either adding new methods or hooking on events
Additional aim of writing them is to demonstrate how one can do certain things.
You can declare extensions in markup by listing them in extension
property.
An extension is declared just like any other widget and it has special loadExtension
method that applies it.
TODO: write extension Sometimes people want tree nodes to be unselectable, sometimes selection is a good way to copy (part of) content from the node.
To make tree (or its elements) unselectable use dojo.html.disableSelection in nodeCreate and treeCreate hooks. Apply disableSelection to every node you want to make unselectable.
There is an "objectId" property and "object" property ready to be filled in from markup or program-way.
You may use dojo.lang.forEach(nodeOrTree.getDescendants(),function(elem) { ... })
to process all descendants,
it will walk children
property recursively.
The safer way would be to call TreeCommon.prototype.processDescendants(nodeOrTree, filter, func)
, it will process all children
with func
, but will not descend into nodes if filter(node)
returns false. E.g see collapseAll
controller method uses it to collapse all widgets, but skip non-folders and data objects.