Tree is bundled in dijit, it allows programmer to represent data in a tree view.
With dojo 1.2, creating an tree is quite easy. The 3 simple steps to create a simple tree are:
1) Create a data store
2) Create a model from this store
3) Create a tree, and assign the model to pick up the data from.
Using the default tree, user can expand/collapse a node, click on the node, and with just a little change in code have the ability to drag and drop nodes. But one thing that the default tree lacks is the ability to select multiple nodes in the tree. In this article, i'll show the simple way of customizing the dojo tree to have the ability to multi select nodes.
The best way to customize an existing widget is to extend the widget & create a new widget from it.
In this article, we'll create a new widget "dijit.TreeCustom", which extends from dijit.Tree.
In the custom widget, following four methods of dijit.Tree should be overridden
1) postCreate
2) _onClick
3) focusNode
4) blurNode
Custom Tree Widget:
Here is the skeleton code for custom tree widget, extending from default dojo tree.
dojo.provide("dijit.TreeCustom");
dojo.require("dijit.Tree");
dojo.require("dojo.dnd.Manager");
dojo.declare("dijit.TreeCustom", dijit.Tree, {
});
(If you need more details on creating an widget, refer my previous article "Creating custom widgets in DOJO")
Adding new properties for Custom tree widget
lastFocusedNode: null, // To store the currently focused node in the tree. allFocusedNodes: [], // array of currently focused nodes. eg., user holds cntrl key and selects multiple nodes. ctrlKeyPressed: false, // Flag to find out if ctrl key was pressed, when click happened.The properties are self-explanatory.
Overriding postCreate:
When dojo engine has finished creating & parsing the TreeCustom widget on browser, postCreate method is called, where in the initilization stuffs can take place. Here is the postCreate method for TreeCustom widget, initializing the properties to proper values.
postCreate: function(){
this.inherited(arguments); // Call parent's postCreate
this.onClick = this.onClickDummy;
this.allFocusedNodes = [];
this.lastFocusedNode = null;
},
Notice that, default onClick is dummied out, since the new tree widget will have to send out array of selected nodes for onClick event, rather than just one selected node. So, a new customOnClick event will be added for this purpose. (See below)
Dummy onclick method (Does nothing)
onClickDummy: function(item, node) {
},
Overriding _onClick:
_onClick is the method called by default tree whenever user clicks on the tree node.
_onClick: function(/*Event*/ e){
if(e.ctrlKey) {
this.ctrlKeyPressed = true;
} else {
this.ctrlKeyPressed = false;
}
this.inherited(arguments);
dojo.stopEvent(e);
},
This method, stores the state of ctrl key pressed, and passes on to default tree's _onClick, which inturn calls focusNode method, which is also one of the method that will be overridden for custom tree widget.
Overriding focusNode:
focusNode: function(/* _tree.Node */ node){
this.inherited(arguments);
this.lastFocusedNode = node;
if(this.ctrlKeyPressed) {
// Ctrl key was pressed
} else {
// Ctrl key was not pressed, blur the previously selected nodes except the clicked node.
for(i=0; i < this.allFocusedNodes.length; i++) {
var temp = this.allFocusedNodes[i];
if(temp != node)
this._customBlurNode(this.allFocusedNodes[i]);
}
this.allFocusedNodes = [];
}
var isExists = false; // Flag to find out if this node already been selected
for(i=0;i < this.allFocusedNodes.length; i++) {
var temp = this.allFocusedNodes[i];
if(temp.item.id == node.item.id) isExists = true;
}
if( ! isExists)
this.allFocusedNodes.push(node);
this.customOnClick (node.item, node, this.getSelectedItems() );
this.ctrlKeyPressed = false;
},
_customBlurNode method:
_customBlurNode: function(node) {
var labelNode = node.labelNode;
dojo.removeClass(labelNode, "dijitTreeLabelFocused");
labelNode.setAttribute("tabIndex", "-1");
dijit.setWaiState(labelNode, "selected", false);
},
customOnClick method:
An event handler, that must be overriden by page using this widget. This method is called when user clicks on any node in the tree.Passes node, item & array of all selected items to the method.
Here is the default implementation. (to avoid runtime exceptions)
customOnClick: function (item, node, allSelectedItems) {
//User overridable method.
},
Overriding blurNode:
Since a custom blur node method was used, the default blurNode is no more required. Overriding to cover the default behavior.
blurNode: function(){
// Not using, we've our own custom made blur method. See _customBlurNode
},
Extra method to get all selected items An utility method for the custom tree, which can be used to get list of all the selected items in the tree.
// Returns array of currently selected items.
getSelectedItems: function() {
var selectedItems = [];
for(i=0;i < this.allFocusedNodes.length; i++) {
var iNode = this.allFocusedNodes[i];
selectedItems.push(iNode.item);
}
return selectedItems ;
},
Final dijit.TreeCustom JS:
Put this in TreeCustom.js file under dijit folder. (DOJO_HOME/dijit).
dojo.provide("dijit.TreeCustom");
dojo.require("dijit.Tree");
dojo.require("dojo.dnd.Manager");
dojo.declare("dijit.TreeCustom", dijit.Tree, {
lastFocusedNode: null, // To store the currently focused node in the tree.
allFocusedNodes: [], // array of currently focused nodes. eg., user holds cntrl key and selects multiple nodes.
ctrlKeyPressed: false, // Flag to find out if ctrl key was pressed, when click happened.
postCreate: function(){
this.inherited(arguments);
this.onClick = this.onClickDummy;
this.allFocusedNodes = [];
this.lastFocusedNode = null;
},
focusNode: function(/* _tree.Node */ node){
this.inherited(arguments);
this.lastFocusedNode = node;
if(this.ctrlKeyPressed) {
// Ctrl key was pressed
} else {
// Ctrl key was not pressed, blur the previously selected nodes except the clicked node.
for(i=0;i < this.allFocusedNodes.length; i++) {
var temp = this.allFocusedNodes[i];
if(temp != node)
this._customBlurNode(this.allFocusedNodes[i]);
}
this.allFocusedNodes = [];
}
var isExists = false; // Flag to find out if this node already been selected
for(i=0;i < this.allFocusedNodes.length; i++) {
var temp = this.allFocusedNodes[i];
if(temp.item.id == node.item.id) isExists = true;
}
if( ! isExists)
this.allFocusedNodes.push(node);
this.customOnClick (node.item, node, this.getSelectedItems() );
this.ctrlKeyPressed = false;
},
blurNode: function(){
// Not using, we've our own custom made blur method. See _customBlurNode
},
_onBlur:function () {
},
_onClick: function(/*Event*/ e){
if(e.ctrlKey) {
this.ctrlKeyPressed = true;
} else {
this.ctrlKeyPressed = false;
}
this.inherited(arguments);
dojo.stopEvent(e);
},
_customBlurNode: function(node) {
var labelNode = node.labelNode;
dojo.removeClass(labelNode, "dijitTreeLabelFocused");
labelNode.setAttribute("tabIndex", "-1");
dijit.setWaiState(labelNode, "selected", false);
},
// Returns array of currently selected items.
getSelectedItems: function() {
var selectedItems = [];
for(i=0;i < this.allFocusedNodes.length; i++) {
var iNode = this.allFocusedNodes[i];
selectedItems.push(iNode.item);
}
return selectedItems ;
},
onClickDummy: function(item, node) {
},
customOnClick: function (item, node, allSelectedItems) {
//User overridable method.
}
});
Example of using the Custom Tree
Put this html file under dijit/tests/tree.
Dijit Tree Test
Let me know of any suggestion/changes for this custom tree code.
In next article, I'll show how to lazy load data in tree using DWR (Ajax).