Navigation

Need help updating this page. Describe the navigation widgets in Dojo including attributes that are common to all these widgets.



Menu2

PopupMenu

ProgressBar

ToolBar

Tree2

FishEye

Tree widget

Introduction

This documentation refers to 3rd major version of the tree widget, sometimes refered to as TreeV3.

Many mentioned classes (e.g TreeLoadingController) have V3 on the end, but that suffix is sometimes omitted, because it will be removed in dojo 0.5.

If there exist 2 same classes, but one with V3 at the end - it's the one you need.

Examples are given in tests (dojo/tests/widget/treeV3), so you might want to check them first and copy-paste exactly the things you need.



Please browse the Book,

then ask questions in dojo-interest list

If you feel the question private

or want to contribute

IRC: Freenode, #dojo by nick [algo]

ICQ: 820317

"Ilia Kantor" ilia @ dojotoolkit.org

Extensions



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

TreeDisableWrapExtension

Tree extension, disables wrapping for tree nodes. Also it fixes IE bug when an 'unwrappable' node (e.g single word) will move to next line if no space left.

TreeDocIconExtension

Tree extension, places icon to the left of a node, depening on nodeType property

TreeEmphaseOnSelect

Selector extension, highlights currently selected nodes

TreeDeselectOnDblselect

Selector extension, deselects a selected node when it is clicked. Usually, one should ctrl-click, or click another node.

TreeLinkExtension

Tree extension, turns labels into links, merges object property into tag

Faq



How to make tree unselectable?

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.

How to bind an object to tree node?

There is an "objectId" property and "object" property ready to be filled in from markup or program-way.

How to walk all node descendants ?

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.

How to evade a situation where all nodes are (re)moved and tree is empty without a way to add new child (no nodes) ?

Make a single root node with actionsDisabled="DETACH;MOVE". User will be unable to remove it, so interface will stay sane.

Also, you may want to set actionsDisabled="ADDCHILD" to tree itself, so now children can be added besides the root.

How to create a custom tree node ?

First, of course, you may explicitly use createSimple for your widget and declare your widgetType in markup.



But sometimes, tree has to create a node from data object or just from "nothing", e.g in case of createAndEdit.



Then it checks for widgetName property of data object (can be namespaced), and if no widgetName, then tree.defaultChildWidget property should contain node class, e.g mycustom.tree.Node.



Usually, when you override a node, all you need is to adjust defaultChildWidget,

because widgetName uses generic create and hence works slower right now.



How to make pages open when a user clicks on node?

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.

I open very large tree. But navigation away to another page from the tree takes time. What's up?

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, large cleanup causes visible delay. I don't know a way to evade that.

How to add icons to nodes ?

TreeDocIconExtension handles that. You should declare nodeType for your nodes, so they'll get nodeIcon[Your type] CSS class. Default type is Document for leaves and Folder for folders.

There is also setNodeTypeClass method to update node CSS when its nodeType changes e.g programmatically.



Introduction

Introduction

This documentation refers to 3rd major version of the tree widget, sometimes refered to as TreeV3.

Many mentioned classes (e.g TreeLoadingController) have V3 on the end, but that suffix is sometimes omitted, because it will be removed in dojo 0.5.

If there exist 2 same classes, but one with V3 at the end - it's the one you need.

Examples are given in tests (dojo/tests/widget/treeV3), so you might want to check them first and copy-paste exactly the things you need.



Please browse the Book,

then ask questions in dojo-interest list



If you feel the question private

IRC: Freenode, #dojo by nick [algo]

ICQ: 820317

"Ilia Kantor" ilia @ dojotoolkit.org

Features

Features

Flexible styling

  • All design in CSS through classes and class combinations
  • Different trees be styled with different CSS class families
  • Multiline and rich content support

Full set of node operations

  • expand/collapse
  • create with JS or markup
  • destroy/move/clone
  • addChild/detach/(de)folderize
  • inline editing
  • multiple selection and drag'n'drop
  • keyboard controls

Performance

  • batch operations
  • special features
  • profiled and optimized

Dynamic node loading & RPC features

  • rich API

  • callbacks & errbacks
  • suited to be in-sync with data
  • locking

Event system

  • publish

  • hook on any tree change

Customization

  • change everything through inheritance, events and css
  • out-of-the box extensions
  • coded with it in mind

Tests and demos

Tree overall structure

Note: most classes here omitt 'V3' suffix

Model + View

The tree itself is a TreeV3 class instance. Hierarchy is maintained in a standard widgety way: through children[] array. Children are usually TreeNodeV3 instances, but you could use your own(overriding?) implementation of course.



TreeV3 instance also represents an 'invisible root' node, so it shares common methods with TreeNodeV3. These methods reside in TreeWithNode mixin.



Model contains data and manipulation methods like "addChild", "detach" .. etc. It also publishes events when modified.

DOM-structure and view is also merged into model.



Various functionality can be hooked on model's events: controller, menu, drag'n'drop etc.



Model events should help you to integrate tree with application on data-level, so you hook on actual data changes, not the cause (program call, user click etc).

Controller

Main controllers are TreeBasicController -> TreeLoadingController -> TreeRpcController

Basically, they are responsible for operating on model and performing most logic, besides model's action. It also makes checks / remote calls.

Usually, one should work with controller only and let it process model.



TreeLoadingController and TreeRpcController are known to perform remote calls to server. They use dojo.Deferred and dojo.DeferredList for that purpose.



Most customizations are also about controller.



And, by the way, model has no idea about its controller... It throws events and delivers API to call, that's all.

Extensions

The stuff is loosely coupled, so a bunch of extensions can be hooked on events too









What's new in TreeV3

New HTML/CSS structure

Nested divs

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 design in CSS through classes and class combinations

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

Different trees be styled with different CSS class families

Want to put 2 differently styled trees on a page? Give them different classPrefix.

Multiline content support

Rich content support was incomplete, because list-of-divs model could not handle arbitrary-sized nodes. Now you may have <br>, <p> and any other width/height

modifiers.

Event system modified

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.

Lazy widget creation

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

Tree extensions

  • Many features were moved from core into extensions
  • Added TreeDocIconExtension instead of builtin childIcon support
  • Selector now only throws events, not doing anything with nodes
  • Out-of-the box extensions introduced to be examples and handle well-known requirements

Implicit helpers removed

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.

RPC has both sync/async modes

Old callbacks code was removed in favor to dojo.Deferred. Now all operations may be async and run your callbacks at the end.

Drag'n'drop changes

Multiple selection and multiple drag'n'drop (incomplete)

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.

Drop of any source, not just tree node

If treeNode property is empty, tree will create a new node from the data returned by source.getTreeNode, then source.onDrop will be called to remove old node.

Inline node editing

It became possible to edit nodes inline, using TreeEditor. Base variant uses RichText widget, you can make another wrapper though. Remote calls can be made on save only, or on start/cancel too e.g for locking purposes.

Author: Ilya

Node creation

There are few code paths that lead to same purpose: to create a tree node. They differ in effeciency and use patterns

Markup creation

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.

Widget#create

The generic widget creation routine. It basically runs the operations in order:

  • Mix in widget properties from parameters/markup
  • Register widget in widget.Manager
  • Call buildRendering to make fill template and create domNode
  • Call initialize
  • Call postInitialize. registers widget as a child of its parent and after it creates all subwidgetsCall postCreate

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.

Manual creation

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.

afterChangeTree 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.

Input parameters

children array may be

  • empty
  • contain widgets, e.g if created from markup, or someone created them before parent and pushed in
  • contain data objects, that will be turned into widgets when parent expands.
  • isFolder comes into play only when there are no children. It allows creation of empty folders, with UNCHECKED state that can be filled later.

Performance

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 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.

Creation from markup or with standard create/addChild routines is 2-3 times slower, because these routines are generic.

Comparison

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.

Important

The results described here refer to operations without any lazy features involved. Most of time you will use lazy creation or lazy loading, or both, and operate with thousands of "virtual" nodes with ease.

Performance Tricks

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.

Lazy loading

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"

Lazy creation

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.

Comparison between lazy creation and lazy loading

  • You need web-service for lazy loading, not for lazy creation
  • No network waits for lazy creation
  • Lazy creation gives you the tree right here. You can search data objects and modify them without spending time and memory on graphical widgets

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 image-reloading fixup (!!!)

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>
Author: Ilia

Server communication



To talk with server, one should use TreeLoadingControllerV3 or TreeRpcControllerV3. They inherit from TreeBasicControllerV3 and override its methods to deliver remote calls possibility.

TreeLoadingControllerV3 contains main methods for server calls, and allows dynamic node loading. TreeRpcControllerV3 adds server calls to tree manupulations like "createChild/move/edit...".

Url settings

All requests go through dojo.io.bind, usually via XMLHttpRequest transport.

contains basic Url for all requests, e.g "http://site.com/remoteTreeService.do". You can have query string in it also.

every call adds special action parameter to query string to distinguish between call types. Actions are move, createChild..

For children loading, the action is getChildren.

An example url for such action would be "http://site.com/remoteTreeService.do?action=getChildren".



Most actions imply additional data parameter with information about node/tree and other action details server may want to know.



This way of composing an url is described in getRpcUrl, feel free to override if need.



Request format

data parameter is JSON-serialized. It usually sends some information about involved nodes and position. If you want to extend it somehow,

  1. override method of controller that corresponds your action, your changes will affect this action only,
  2. override getInfo method of node/tree to affect parameters globally
  3. override getInfo method of controller if that's the right place =)



Response format

All data is JSON-serialized. There are libraries for JSON in most of programming languages.



Server response is evaluated as javascript. That means you can embed any javascript, that will be evaluated on client-side. But it should return object. Use object error property to signalize about server-side error.



Good answer:

dojo.debug('I can also put javascript in server answer');

([{title:"test",isFolder:true,objectId:"myobj"},{title:"test2",children:[ {title:"test2.1"} ]}])




Good answer:

({})



Good answer, will return dojo.RpcError

({error: "Permission denied"})





Bad answer format (string), will return dojo.FormatError

Exception: blabla at line 50



Transport error (e.g 404) will also return dojo.CommunicationError



If you don't know what to return, return ({}). That means just "ok". Note outer brackets, they are needed to make sure it evaluates to javascript Object.



Callbacks and Error handling

Any request may be performed in synchroneous and asynchronous manner.

Both of them return dojo.Deferred object, but for synchroneous call, it will be called until next script line.



You can call deferred.addCallback / deferred.addErrback to add your actions.



An example of usage would be



var deferred = loadingController.expandAll(tree);



// add action when operation finishes successfully

deferred.addCallback(function() { alert('expanded all nodes!'); });



// process error

deferred.addErrback(function(err) { dojo.debugShallow(err); });





More information about Deferred class and asynchronous programming can be found at http://mochikit.com/doc/html/MochiKit/Async.html (dojo implementation is Mochikit port), http://twistedmatrix.com/projects/core/documentation/howto/async.html (python implementation and a nice state-of-art intro).











Tree Events

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.

afterTreeCreate

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.

source

references to tree

beforeTreeDestroy

Published right before actual Tree#destroy method is called. Useful for cleanups

source

references to tree

beforeNodeDestroy

Right before TreeNode#destroy is called. Node is detached after this event fired.

source

references to node

afterChangeTree

This event is tightly created with node creation process. It is fired when

  • a node is created

    • no parent at this stage
    • fires in initialize(), so children may be not added yet

  • a node was moved to another tree widget

oldTree

references previous tree, null if node has been just created

newTree

new(current) tree

node

target node

afterSetFolder

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

source

references to node

afterUnsetFolder

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.

source

references to node

(before|after)Move(From|To)

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

oldParent

previous parent

oldTree

previous tree

oldIndex

previous index among siblings

newParent

new parent

newTree

new tree

newIndex

new index among siblings

child

target node

afterAddChild

Published when a node is attached to parent. This may occur at the end of creation process, or when a node is lazily instantiated from data object.

Also it occurs when a detached node gets attached.

child

references to node

index

index among siblings

parent

current parent who adopted a child

childWidgetCreated

flag is set if child was laziliy instantiated. That is: it resided as data object in children array, but user expanded its parent, so node widget came to life.

afterDetach

Occurs when a node is detached. This may happen in the process of node destruction. Keep in mind, that detaching a node sets its parent to null, but

tree remains same.

child

references to node

parent

references to old parent

index

references to index among children of old parent

after(Expand|Collapse)

Fire when a node is expanded/collapsed. Some togglers do nice animation hiding/showing node. This event fires when animation finishes.

source

target node

afterSetTitle

When a node is edited, or explicit setTitle method is called, this event helps to inform interested parts about changes.

source

target node

oldTitle

replaced node title

Tree HTML/CSS model

There are few major approaches to building dynamic trees.

1. list of idented divs

Each tree node is a div with indentation. Indentation is e.g 20px * node depth, so everything looks fine. Usually indentation is made of many quadrantic images, each of them represents empty space or grid lines, which visibly link nodes together.nested divs.

Of course, 'div' can be changed to any tag, e.g 'li'.

2. nested divs

Divs are nested same way tree nodes are nested. Can use ul/li instead of divs, there's only symantic difference, of course, if styles are same.

Each div can be idented relatively to its parent with padding/margin property, or with images.

If we use images here, then there will be lots of extra tags, so padding/margin seems better.

Dojo tree adapts the 2nd approach, of course, with padding/margin identation.

Let's consider a simple tree

* Node1

* Node 1.1

* Node 1.2

* Node 2















(Page is unfinished, and content will probably be merged into the Trees intro page -- CAR)