-
Notifications
You must be signed in to change notification settings - Fork 34
CheckBox Tree Usage
Tree nodes are template driven widgets which are automatically generated when the tree is
created. The cbtree node template used is located at cbtree/templates/cbtreeNode.html
.
Each generated HTML element with the class dijitTreeRow also gets the branch
attribute whose value is the value of the tree node widget property isExpandable.
Whenever a tree node widget is passed as an argument to a function, typically an event handler, one can simple test the isExpandable property to determine if the tree node is a branch or not.
function onCheckBoxClick(item, nodeWidget, event) {
if (nodeWidget.isExpandable) {
// Node is a branch
...
} else {
// Node is a leaf
...
}
}
Although not very efficient, compared to the approach above, you can also access the DOM branch attribute in your application like:
require(["dojo/dom-attr", ... ], function (domAttr, ... ) {
...
function onCheckBoxClick(item, nodeWidget, event) {
var isBranch = domAttr.get(nodeWidget.rowNode, "branch");
}
}
The following example collects all tree node branches in the DOM and iterates over them. For each DOM node the dijit registry is called to get the associated tree node widget.
require(["dojo/query", "dijit/registry", ... ], function (query, regsitry, ...) {
...
var allTreeBranches = query(".dijitTreeRow[branch=\"true\"]");
allTreeBranches.forEach(function (domNode) {
var nodeWidget = registry.getEnclosingWidget(domNode);
...
console.log(nodeWidget.label);
});
}
Next, lets assume you want to give the label of tree nodes a different background color depending on whether or not a tree node is a branch.
<style type="text/css">
.dijitTreeRow[branch="true"] .dijitTreeLabel {
background-color: yellow;
...
}
</style>
[1]The branch attribute is available since cbtree release cbtree-v0.9.3-4
With dojo 1.7 a new event handling module was introduced called dojo/on
.
The intend of this module is/was to make event handling with dojo easier and
mimic some of the DOM4 Event system. However, dojo/on
is not a self contained
DOM-4 Event system implementation.
In addition, dijit widgets inherit a modified version of dojo/on
to make it
backward compatible with callback methods and non-DOM compliant events.
The dijit/_WidgetBase.on()
method tries to map event types, case insensitive
that is, to callback functions whose name starts with "on" like onClick() or
onDblClick().
The next sections discuss the difference in implementation of event listeners for callbacks and events.
Before registering an event listener with a dijit widget we must first check if
the widget has a method that could be auto linked with the event type.
For example, if we want to listen for an event named "myEvent", does the widget
have a method called onMyEvent()
or onmyevent()
?
If so, we need to define our event listener as a callback function with the
same set of argument as the widget's onMyEvent() method.
Let's assume our widget has a method (callback) called onMyEvent()
with a
signature like: onMyEvent( item, node, event )
in which case we can establish
our event listener in two ways:
- Using
dojo/aspect
or, - Call the widget's
on()
method.
Using dojo/aspect
we attach after advice to the widget method so our event
listener gets called each time the widget calls onMyEvent()
like:
require(["dojo/aspect", "cbtree/Tree", ... ], function (aspect, Tree, ... ) {
// Event listener
function clickEvent( item, node, event ) {
console.log( "A Tree Node was clicked" );
}
...
var myTree = new Tree( {model:someModel, ... } );
aspect.after( myTree, "onMyEvent", clickEvent, true );
...
})
Make sure the fourth
argument is set to true otherwise clickEvent()
is not called with the
original arguments of onMyEvent()
but with the result instead. Also, notice
that when calling aspect.after()
we specify the callback function name
"onMyEvent" and not an event type.
Using the widget's on()
method we specify the event type "myEvent" and not the
callback function name, like:
require(["cbtree/Tree", ... ], function (Tree, ... ) {
// Event listener
function clickEvent( item, node, event ) {
console.log( "A Tree Node was clicked" );
}
...
var myTree = new Tree( {model:someModel, ... } );
myTree.on( "myEvent", clickEvent );
...
})
The above example accomplishes exactly the same thing as dojo/aspect did in the
first example simply because the underlaying _WidgetBase.on()
method is able
to map the event type "myEvent" to the callback onMyEvent()
and as a result
calls dojo/aspect on our behalf.
This time, let's assume our widget does NOT have a callback that could be auto linked to the event type "myEvent". Instead the widget actually emits events of type "myEvent". In this case both dojo/aspect and the widget's on() method will automatically create the callback, which in our case will be named "onmyEvent".
The big difference between the widget calling callback functions programmatically and emitting events is the way the event listener will be called. In the latter case the event handlers are called with just one argument, the event.
require(["cbtree/Tree", ... ], function (Tree, ... ) {
// Event listener
function clickEvent( event ) {
console.log( "A Tree Node was clicked" );
}
...
var myTree = new Tree( {model:someModel, ... } );
myTree.on( "myEvent", clickEvent );
...
})
In the above example notice that the function clickEvent()
now is called with
just a single argument event. The event is a JavaScript key:value pairs
object.
In case of true events that is, the event name can not be mapped to a callback function, the event name is handled case sensitive. Therefore, as a good practise, always use the correct case for event names regardless if you are registering for an event or callback. Note that DOM-3/4 always handles event types case sensitive.
The CheckBox Tree modules use both callbacks and events. Callbacks are used
whenever dojo or dijit API's require them, for example the dijit model API
dijit/tree/model
, otherwise events are used.
The tables below list the event names and associated callback by CheckBox Tree module. If for any given event type a callback is specified, the arguments column specifies the list of arguments passed to the event listener.
The arguments passed to event handles are defined as follows:
Argument | Description |
---|---|
event | Event object, the object is context specific |
item | Store object |
node | Tree node widget |
Event Type | Callback | Arguments | Description |
---|---|---|---|
click | onClick | (item, node, event) | A tree node is clicked. |
checkBoxClick | onCheckBoxClick | (item, node, event) | A checkbox is clicked. |
close | onClose | (item, node) | Node closed, a tree node collapsed. |
dblClick | onDblClick | (item, node, event) | Double click |
open | onOpen | (item, node) | Node opened. a tree node is expanded. |
event | onEvent | (item, event, value) | User event successful. |
load | onLoad | (void) | Tree finished loading. |
The CheckBox Tree models, although not widgets, offer the same on()
functionality as widgets do. Therefore you can simply register a model event
listener like model.on( "dataValidated", myEventHandler );
Event Type | Callback | Arguments | Description |
---|---|---|---|
change | onChange | (item, propertyName, newValue, oldValue) | Property of a store item changed. |
childrenChange | onChildrenChange | (parent, newChildrenList) | The children of a node changed. |
dataValidated | onDataValidated | (void) | The store data has been validated. |
delete | onDelete | (item) | An item was removed from the store. |
pasteItem | onPasteItem | (item, insertIndex, before) | Item was pasted at a new location. |
rootChange | onRootChange | (item, action) | The children of the tree root changed. |
reset | onReset | (void) | The model is being reset. |
Store events are available if, and only if, the store is made Eventable or the store is a cbtree/store/Object store.
Event Type | Callback | Argument | Description |
---|---|---|---|
change | (event) | Property of a store object changed. | |
close1 | onClose | (count, cleared) | The store was closed. |
new | (event) | A new store object was added. | |
remove | (event) | A store object was removed/deleted. |
The following example demonstrate the use of events and callbacks:
require( ["cbtree/Tree",
"cbtree/model/ForestStoreModel",
"cbtree/store/Memory",
"cbtree/store/Eventable"], function (Tree, ForestStoreModel, Memory, Eventable) {
...
// Create an eventable store and listen for events of type "new".
var myStore = Eventable( new Memory( {data: myData, idProperty:"name"} ));
myStore.on( "new", function (event) {
console.log( "An event of type: " + event.type + "was recieved" );
console.log( "Object with id: " + this.getIdentity( event.item ) + " was added");
});
var myModel = new ForestStoreModel( {store:myStore, query:{type:"parent"}} );
myModel.on( "dataValidated", function () {
console.log( "Store data has been validated" );
});
var myTree = new Tree( {model:myModel}, "CheckboxTree" };
myTree.on( "load", function () {
console.log( "The Tree has been loaded." );
}
myTree.startup();
...
myStore.put( {name:"Lisa", lastName:"Simpson", hair:"blond"} );
}
If you want to learn more about the DOM-4 Events checkout DOM Events. If you're interested in a dojo style implementation of a fully DOM-4 compliant Event system checkout the event system as part of my IndexedDB project.
In contrast to the standard dijit Tree, the CheckBox Tree offers the options to delete tree nodes using the keyboard. However, the functionality is disabled by default. There are two tree properties associated with this feature:
- enableDelete
- deleteRecursive
The enableDelete enables or disables the keyboard delete feature whereas
deleteRecursive determines if the descendants of the to be deleted item(s)
are to be removed from the store as well, the default is false
.
To delete an item using the keyboard simply select the tree node(s) and press the CTRL+DELETE keys.
The order in which tree nodes are displayed depends on the model query and the order of the store records. Lets assume we have the following set or records in our store:
{ name:"Root", parent:[] },
{ name:"Lisa", parent:["Homer","Marge"], hair:"blond", age: 7 },
{ name:"Bart", parent:["Homer","Marge"], hair:"blond", age: 8 },
{ name:"Maggie", parent:["Homer","Marge"], hair:"blond", age: 3 },
{ name:"Patty", parent:["Jacqueline"], hair:"blond", age: 40 },
{ name:"Selma", parent:["Jacqueline"], hair:"blond", age: 40 },
{ name:"Rod", parent:["Ned"], hair:"blond", age: 9 },
{ name:"Todd", parent:["Ned"], hair:"blond", age: 10 },
{ name:"Abe", parent:["Root"], hair:"none", age: 75 },
{ name:"Mona", parent:["Root"], hair:"none", age: 72 },
{ name:"Jacqueline", parent:["Root"], hair:"none", age: 70 },
{ name:"Homer", parent:["Abe","Mona"], hair:"none", age: 40 },
{ name:"Marge", parent:["Jacqueline"], hair:"none", age: 38 },
{ name:"Ned", parent:["Root"], hair:"none", age: 38 },
{ name:"Apu", parent:["Root"], hair:"black", age: 50 },
{ name:"Manjula", parent:[Apu], hair:"black", age: 52}
When we create the model using most of the model's default settings like:
var model = new TreeStoreModel( { store: store, query: {name: "Root"} });
The root children will be displayed in the order they appear in the store, that is: Abe, Mona, Jacqueline, Ned and finally Apu. The same goes for any children, for example, expanding tree node 'Homer' displays Lisa, Bart and Maggie.
As of cbtree v0.9.3-3 the Tree models have a new property called options which is a JavaScript key:value pairs object defining the sort order of the tree. The options object is currently defined as follows:
options = "{" "sort" ":" sortOptions "}"
sortOptions = "[" SortInfo ["," SortInfo]* "]" / Function
SortInfo = "{" attribute ["," descending] ["," ignoreCase] "}"
attribute = "attribute" ":" JSidentifier
descending = "descending" ":" boolean
ignoreCase = "ignoreCase" ":" boolean
boolean = "true" / "false"
See also sort directives. For example:
var options = {sort: [
{attribute: "name", descending: true, ignoreCase: true},
{attribute: "hair", ignoreCase: true},
{attribute: "age"}
]};
The value of both the descending and ignoreCase sort info properties defaults
to false
.
The sort operation is performed in the order in which the sort information appears
in the sort options array. Given the example above, tree nodes are first sorted by
name followed by hair color and finally age.
Creating a model with the options property specified would look like:
var mySortOptions = {sort: [
{attribute: "name", descending: true, ignoreCase: true},
{attribute: "hair", ignoreCase: true},
{attribute: "age"}
]};
var model = new TreeStoreModel( { store: store,
query: {name: "Root"},
options: {sort: mySortOptions}
});
Alternatively, instead of specifying the options sort property as an object you can also specify a custom sort function. Consider the following example:
function mySortFunc(itemA, itemB) {
if (itemA.name > itemB.name) {
return 1;
} else if (itemA.name < itemB.name) {
return -1;
}
return 0;
}
var model = new TreeStoreModel( { store: store, query: {name: "Root"}, options: {sort: mySortFunc}});
Here the custom sort function mySortFunc is called with two store items that matched the associated model query. The custom sort function MUST return -1, 0 or 1. See JavaScript Array.prototype.sort for additional information.
Be aware that using the sort feature overrides the store's optional before property when adding records to the store. The store will still insert the record at the desired location but because the model query results are sorted the significance of the actual location is lost to the model and tree.