This documentation refers to new TreeV3, not to stable Tree widget. Many classes have V3 on the end for TreeV3, but that suffix can be omitted, because it will be removed in dojo 0.5.
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, quite incomplete, 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.
Currently, multiple drag'n'drop does not work with multiple selection because of dojo bugs. Hopefully will be fixed.
There are many classes of events, published with dojo.event.publish mechanism. Every event has a name and message object, containing more precise information about what happened. You may use events to update your data while tree changes, and to perform additional processing of involved objects.
There is a default naming scheme for an event class. E.g for a tree with widgetId='mytree', event of class afterTreeCreate will be named "mytree/afterTreeCreate". You may provide other names in eventNames property of the tree.
Event occurs after tree creation is complete. There is an alternative to hook on this action by putting your objects in "listeners" property of the tree. The difference is that listeners are guaranteed to hook before nodes get added, and afterTreeCreate is published after Tree widget is created.
Published right before actual Tree#destroy method is called. Useful for cleanups
Right before TreeNode#destroy is called. Node is detached after this event fired.
This event is tightly created with node creation process. It is fired when
Fires when a node obtains "folder" state. That may happen when a first child is added to a leaf, or if a node was initially created with isFolder=true
Fires when a node obtains looses "folder" state. That may happen when a last child leaves the node, and Tree.unsetFolderOnEmptyis set, or when unsetFolder is called explicitly.
These events share same arguments and fire when a node is moved. Move process is considered something special. When you move a node, no detach/addChild events get thrown. That allows to tell situations when a node leaves a tree for some time (detached then attached) from situations when a node is simply moved to another location
Published when a node is attached to parent. This may happen at the end of creation process, or when a node is lazily instantiated from data object.
Occurs when a node is detached. This may happen in the process of node destruction.
Fire when a node is expanded/collapsed. Some togglers do nice animation hiding/showing node. This event fires when animation finishes.
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
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>
Every data object you send is created with Tree#createNode call.
You want to pass a special object, specify widgetName in widgetName property of your object.
Another option is to change defaultChildWidget
property of the tree.
Helpful in case when you have your own widgets and use them elsewhere instead of TreeNode
Extensions are also called plugins, they can be hooked onto widgets in various combinations and provide wanted options.
Currently there is a couple of extensions
There are few code paths that lead to same purpose: to create a tree node. They differ in effeciency and use patterns
You specity a tree and its nodes in HTML, relying upon dojo to parse it and turn into widgets. That is a slowest way, but nice for small trees or if only tree top is specified and the rest is created later.
dojo widget parser walks DOM and creates a special structure. The next pass creates widgets from the structure.
The generic widget creation routine. It basically runs the operations in order:
buildRendering
to make fill template and create domNodeinitialize
postInitialize
. registers widget as a child of its parent
and after it creates all subwidgetspostCreate
Note that initialize
is called in pre-order: parent is initialized before children, postInitialize
is called
in post-order: a child is postCreated before its parent.
If you create nodes with javascript, then you run create calls manyally. So parents are naturally created (and postCreated) before children
There seem to be no good way to distinguish betwen markup creation and manual creation. From the one hand its seems good, because allows reuse of generic creation code. From the other hand code paths going through this code are subtly different.
The reliable thing is that initialize
will process widget after
its domNode is built, BUT it should not assume anything about children.
nodeCreate event is fired on initialization also. If you want to know anything about children and do something at this point - check addChild, but not node creation.
children
array may be
isFolder
comes into play only when there are no children. It allows creation
of empty folders, with UNCHECKED state that can be filled later.
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.
Make a single root node with actionsDisabled="DETACH;MOVE". User will be unable to remove it, so interface will stay sane.
There are 2 ways. The first one is to attach TreeSelector and hook on "select" event. So when a user clicks, event handler will change url to node.object.href. Of course, you should fill hrefs.
A probably more convinient path would be to employ TreeLinkExtension, which will turn your labelNodes into real links, and apply attrbutes from node object to them.
Dojo performs actions not only when a node is created, but also cleanup when a node is destroyed. Lazy features allow node creation be distributed in time, but when you navigate away from a large tree, cleanup need some time. I don't know a way to evade that.