Blame | Last modification | View Log | RSS feed
if(!dojo._hasResource["dijit.Tree"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dijit.Tree"] = true;
dojo.provide("dijit.Tree");
dojo.require("dojo.fx");
dojo.require("dijit._Widget");
dojo.require("dijit._Templated");
dojo.require("dijit._Container");
dojo.require("dojo.cookie");
dojo.declare(
"dijit._TreeNode",
[dijit._Widget, dijit._Templated, dijit._Container, dijit._Contained],
{
// summary
// Single node within a tree
// item: dojo.data.Item
// the dojo.data entry this tree represents
item: null,
isTreeNode: true,
// label: String
// Text of this tree node
label: "",
isExpandable: null, // show expando node
isExpanded: false,
// state: String
// dynamic loading-related stuff.
// When an empty folder node appears, it is "UNCHECKED" first,
// then after dojo.data query it becomes "LOADING" and, finally "LOADED"
state: "UNCHECKED",
templateString:"<div class=\"dijitTreeNode dijitTreeExpandLeaf dijitTreeChildrenNo\" waiRole=\"presentation\"\n\t><span dojoAttachPoint=\"expandoNode\" class=\"dijitTreeExpando\" waiRole=\"presentation\"\n\t></span\n\t><span dojoAttachPoint=\"expandoNodeText\" class=\"dijitExpandoText\" waiRole=\"presentation\"\n\t></span\n\t>\n\t<div dojoAttachPoint=\"contentNode\" class=\"dijitTreeContent\" waiRole=\"presentation\">\n\t\t<div dojoAttachPoint=\"iconNode\" class=\"dijitInline dijitTreeIcon\" waiRole=\"presentation\"></div>\n\t\t<span dojoAttachPoint=\"labelNode\" class=\"dijitTreeLabel\" wairole=\"treeitem\" tabindex=\"-1\"></span>\n\t</div>\n</div>\n",
postCreate: function(){
// set label, escaping special characters
this.setLabelNode(this.label);
// set expand icon for leaf
this._setExpando();
// set icon and label class based on item
this._updateItemClasses(this.item);
if(this.isExpandable){
dijit.setWaiState(this.labelNode, "expanded", this.isExpanded);
}
},
markProcessing: function(){
// summary: visually denote that tree is loading data, etc.
this.state = "LOADING";
this._setExpando(true);
},
unmarkProcessing: function(){
// summary: clear markup from markProcessing() call
this._setExpando(false);
},
_updateItemClasses: function(item){
// summary: set appropriate CSS classes for item (used to allow for item updates to change respective CSS)
this.iconNode.className = "dijitInline dijitTreeIcon " + this.tree.getIconClass(item);
this.labelNode.className = "dijitTreeLabel " + this.tree.getLabelClass(item);
},
_updateLayout: function(){
// summary: set appropriate CSS classes for this.domNode
var parent = this.getParent();
if(parent && parent.isTree && parent._hideRoot){
/* if we are hiding the root node then make every first level child look like a root node */
dojo.addClass(this.domNode, "dijitTreeIsRoot");
}else{
dojo.toggleClass(this.domNode, "dijitTreeIsLast", !this.getNextSibling());
}
},
_setExpando: function(/*Boolean*/ processing){
// summary: set the right image for the expando node
// apply the appropriate class to the expando node
var styles = ["dijitTreeExpandoLoading", "dijitTreeExpandoOpened",
"dijitTreeExpandoClosed", "dijitTreeExpandoLeaf"];
var idx = processing ? 0 : (this.isExpandable ? (this.isExpanded ? 1 : 2) : 3);
dojo.forEach(styles,
function(s){
dojo.removeClass(this.expandoNode, s);
}, this
);
dojo.addClass(this.expandoNode, styles[idx]);
// provide a non-image based indicator for images-off mode
this.expandoNodeText.innerHTML =
processing ? "*" :
(this.isExpandable ?
(this.isExpanded ? "-" : "+") : "*");
},
expand: function(){
// summary: show my children
if(this.isExpanded){ return; }
// cancel in progress collapse operation
if(this._wipeOut.status() == "playing"){
this._wipeOut.stop();
}
this.isExpanded = true;
dijit.setWaiState(this.labelNode, "expanded", "true");
dijit.setWaiRole(this.containerNode, "group");
this._setExpando();
this._wipeIn.play();
},
collapse: function(){
if(!this.isExpanded){ return; }
// cancel in progress expand operation
if(this._wipeIn.status() == "playing"){
this._wipeIn.stop();
}
this.isExpanded = false;
dijit.setWaiState(this.labelNode, "expanded", "false");
this._setExpando();
this._wipeOut.play();
},
setLabelNode: function(label){
this.labelNode.innerHTML="";
this.labelNode.appendChild(document.createTextNode(label));
},
_setChildren: function(/* Object[] */ childrenArray){
// summary:
// Sets the children of this node.
// Sets this.isExpandable based on whether or not there are children
// Takes array of objects like: {label: ...} (_TreeNode options basically)
// See parameters of _TreeNode for details.
this.destroyDescendants();
this.state = "LOADED";
var nodeMap= {};
if(childrenArray && childrenArray.length > 0){
this.isExpandable = true;
if(!this.containerNode){ // maybe this node was unfolderized and still has container
this.containerNode = this.tree.containerNodeTemplate.cloneNode(true);
this.domNode.appendChild(this.containerNode);
}
// Create _TreeNode widget for each specified tree node
dojo.forEach(childrenArray, function(childParams){
var child = new dijit._TreeNode(dojo.mixin({
tree: this.tree,
label: this.tree.getLabel(childParams.item)
}, childParams));
this.addChild(child);
var identity = this.tree.store.getIdentity(childParams.item);
nodeMap[identity] = child;
if(this.tree.persist){
if(this.tree._openedItemIds[identity]){
this.tree._expandNode(child);
}
}
}, this);
// note that updateLayout() needs to be called on each child after
// _all_ the children exist
dojo.forEach(this.getChildren(), function(child, idx){
child._updateLayout();
});
}else{
this.isExpandable=false;
}
if(this._setExpando){
// change expando to/form dot or + icon, as appropriate
this._setExpando(false);
}
if(this.isTree && this._hideRoot){
// put first child in tab index if one exists.
var fc = this.getChildren()[0];
var tabnode = fc ? fc.labelNode : this.domNode;
tabnode.setAttribute("tabIndex", "0");
}
// create animations for showing/hiding the children (if children exist)
if(this.containerNode && !this._wipeIn){
this._wipeIn = dojo.fx.wipeIn({node: this.containerNode, duration: 150});
this._wipeOut = dojo.fx.wipeOut({node: this.containerNode, duration: 150});
}
return nodeMap;
},
_addChildren: function(/* object[] */ childrenArray){
// summary:
// adds the children to this node.
// Takes array of objects like: {label: ...} (_TreeNode options basically)
// See parameters of _TreeNode for details.
var nodeMap = {};
if(childrenArray && childrenArray.length > 0){
dojo.forEach(childrenArray, function(childParams){
var child = new dijit._TreeNode(
dojo.mixin({
tree: this.tree,
label: this.tree.getLabel(childParams.item)
}, childParams)
);
this.addChild(child);
nodeMap[this.tree.store.getIdentity(childParams.item)] = child;
}, this);
dojo.forEach(this.getChildren(), function(child, idx){
child._updateLayout();
});
}
return nodeMap;
},
deleteNode: function(/* treeNode */ node){
node.destroy();
var children = this.getChildren();
if(children.length == 0){
this.isExpandable = false;
this.collapse();
}
dojo.forEach(children, function(child){
child._updateLayout();
});
},
makeExpandable: function(){
//summary
// if this node wasn't already showing the expando node,
// turn it into one and call _setExpando()
this.isExpandable = true;
this._setExpando(false);
}
});
dojo.declare(
"dijit.Tree",
dijit._TreeNode,
{
// summary
// This widget displays hierarchical data from a store. A query is specified
// to get the "top level children" from a data store, and then those items are
// queried for their children and so on (but lazily, as the user clicks the expand node).
//
// Thus in the default mode of operation this widget is technically a forest, not a tree,
// in that there can be multiple "top level children". However, if you specify label,
// then a special top level node (not corresponding to any item in the datastore) is
// created, to father all the top level children.
// store: String||dojo.data.Store
// The store to get data to display in the tree
store: null,
// query: String
// query to get top level node(s) of tree (ex: {type:'continent'})
query: null,
// childrenAttr: String
// one ore more attributes that holds children of a tree node
childrenAttr: ["children"],
templateString:"<div class=\"dijitTreeContainer\" style=\"\" waiRole=\"tree\"\n\tdojoAttachEvent=\"onclick:_onClick,onkeypress:_onKeyPress\">\n\t<div class=\"dijitTreeNode dijitTreeIsRoot dijitTreeExpandLeaf dijitTreeChildrenNo\" waiRole=\"presentation\"\n\t\tdojoAttachPoint=\"rowNode\"\n\t\t><span dojoAttachPoint=\"expandoNode\" class=\"dijitTreeExpando\" waiRole=\"presentation\"\n\t\t></span\n\t\t><span dojoAttachPoint=\"expandoNodeText\" class=\"dijitExpandoText\" waiRole=\"presentation\"\n\t\t></span\n\t\t>\n\t\t<div dojoAttachPoint=\"contentNode\" class=\"dijitTreeContent\" waiRole=\"presentation\">\n\t\t\t<div dojoAttachPoint=\"iconNode\" class=\"dijitInline dijitTreeIcon\" waiRole=\"presentation\"></div>\n\t\t\t<span dojoAttachPoint=\"labelNode\" class=\"dijitTreeLabel\" wairole=\"treeitem\" tabindex=\"0\"></span>\n\t\t</div>\n\t</div>\n</div>\n",
isExpandable: true,
isTree: true,
// persist: Boolean
// enables/disables use of cookies for state saving.
persist: true,
// dndController: String
// class name to use as as the dnd controller
dndController: null,
//parameters to pull off of the tree and pass on to the dndController as its params
dndParams: ["onDndDrop","itemCreator","onDndCancel","checkAcceptance", "checkItemAcceptance"],
//declare the above items so they can be pulled from the tree's markup
onDndDrop:null,
itemCreator:null,
onDndCancel:null,
checkAcceptance:null,
checkItemAcceptance:null,
_publish: function(/*String*/ topicName, /*Object*/ message){
// summary:
// Publish a message for this widget/topic
dojo.publish(this.id, [dojo.mixin({tree: this, event: topicName}, message||{})]);
},
postMixInProperties: function(){
this.tree = this;
this.lastFocused = this.labelNode;
this._itemNodeMap={};
this._hideRoot = !this.label;
if(!this.store.getFeatures()['dojo.data.api.Identity']){
throw new Error("dijit.tree requires access to a store supporting the dojo.data Identity api");
}
if(!this.cookieName){
this.cookieName = this.id + "SaveStateCookie";
}
// if the store supports Notification, subscribe to the notification events
if(this.store.getFeatures()['dojo.data.api.Notification']){
this.connect(this.store, "onNew", "_onNewItem");
this.connect(this.store, "onDelete", "_onDeleteItem");
this.connect(this.store, "onSet", "_onSetItem");
}
},
postCreate: function(){
// load in which nodes should be opened automatically
if(this.persist){
var cookie = dojo.cookie(this.cookieName);
this._openedItemIds = {};
if(cookie){
dojo.forEach(cookie.split(','), function(item){
this._openedItemIds[item] = true;
}, this);
}
}
// make template for container node (we will clone this and insert it into
// any nodes that have children)
var div = document.createElement('div');
div.style.display = 'none';
div.className = "dijitTreeContainer";
dijit.setWaiRole(div, "presentation");
this.containerNodeTemplate = div;
if(this._hideRoot){
this.rowNode.style.display="none";
}
this.inherited("postCreate", arguments);
// load top level children
this._expandNode(this);
if(this.dndController){
if(dojo.isString(this.dndController)){
this.dndController= dojo.getObject(this.dndController);
}
var params={};
for (var i=0; i<this.dndParams.length;i++){
if(this[this.dndParams[i]]){
params[this.dndParams[i]]=this[this.dndParams[i]];
}
}
this.dndController= new this.dndController(this, params);
}
this.connect(this.domNode,
dojo.isIE ? "onactivate" : "onfocus",
"_onTreeFocus");
},
////////////// Data store related functions //////////////////////
mayHaveChildren: function(/*dojo.data.Item*/ item){
// summary
// User overridable function to tell if an item has or may have children.
// Controls whether or not +/- expando icon is shown.
// (For efficiency reasons we may not want to check if an element has
// children until user clicks the expando node)
return dojo.some(this.childrenAttr, function(attr){
return this.store.hasAttribute(item, attr);
}, this);
},
getItemChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ onComplete){
// summary
// User overridable function that return array of child items of given parent item,
// or if parentItem==null then return top items in tree
var store = this.store;
if(parentItem == null){
// get top level nodes
store.fetch({ query: this.query, onComplete: onComplete});
}else{
// get children of specified node
var childItems = [];
for (var i=0; i<this.childrenAttr.length; i++){
childItems= childItems.concat(store.getValues(parentItem, this.childrenAttr[i]));
}
// count how many items need to be loaded
var _waitCount = 0;
dojo.forEach(childItems, function(item){ if(!store.isItemLoaded(item)){ _waitCount++; } });
if(_waitCount == 0){
// all items are already loaded. proceed..
onComplete(childItems);
}else{
// still waiting for some or all of the items to load
function onItem(item){
if(--_waitCount == 0){
// all nodes have been loaded, send them to the tree
onComplete(childItems);
}
}
dojo.forEach(childItems, function(item){
if(!store.isItemLoaded(item)){
store.loadItem({item: item, onItem: onItem});
}
});
}
}
},
getItemParentIdentity: function(/*dojo.data.Item*/ item, /*Object*/ parentInfo){
// summary
// User overridable function, to return id of parent (or null if top level).
// It's called with args from dojo.store.onNew
return this.store.getIdentity(parentInfo.item); // String
},
getLabel: function(/*dojo.data.Item*/ item){
// summary: user overridable function to get the label for a tree node (given the item)
return this.store.getLabel(item); // String
},
getIconClass: function(/*dojo.data.Item*/ item){
// summary: user overridable function to return CSS class name to display icon
},
getLabelClass: function(/*dojo.data.Item*/ item){
// summary: user overridable function to return CSS class name to display label
},
_onLoadAllItems: function(/*_TreeNode*/ node, /*dojo.data.Item[]*/ items){
// sumary: callback when all the children of a given node have been loaded
var childParams=dojo.map(items, function(item){
return {
item: item,
isExpandable: this.mayHaveChildren(item)
};
}, this);
dojo.mixin(this._itemNodeMap,node._setChildren(childParams));
this._expandNode(node);
},
/////////// Keyboard and Mouse handlers ////////////////////
_onKeyPress: function(/*Event*/ e){
// summary: translates keypress events into commands for the controller
if(e.altKey){ return; }
var treeNode = dijit.getEnclosingWidget(e.target);
if(!treeNode){ return; }
// Note: On IE e.keyCode is not 0 for printables so check e.charCode.
// In dojo charCode is universally 0 for non-printables.
if(e.charCode){ // handle printables (letter navigation)
// Check for key navigation.
var navKey = e.charCode;
if(!e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey){
navKey = (String.fromCharCode(navKey)).toLowerCase();
this._onLetterKeyNav( { node: treeNode, key: navKey } );
dojo.stopEvent(e);
}
}else{ // handle non-printables (arrow keys)
var map = this._keyHandlerMap;
if(!map){
// setup table mapping keys to events
map = {};
map[dojo.keys.ENTER]="_onEnterKey";
map[dojo.keys.LEFT_ARROW]="_onLeftArrow";
map[dojo.keys.RIGHT_ARROW]="_onRightArrow";
map[dojo.keys.UP_ARROW]="_onUpArrow";
map[dojo.keys.DOWN_ARROW]="_onDownArrow";
map[dojo.keys.HOME]="_onHomeKey";
map[dojo.keys.END]="_onEndKey";
this._keyHandlerMap = map;
}
if(this._keyHandlerMap[e.keyCode]){
this[this._keyHandlerMap[e.keyCode]]( { node: treeNode, item: treeNode.item } );
dojo.stopEvent(e);
}
}
},
_onEnterKey: function(/*Object*/ message){
this._publish("execute", { item: message.item, node: message.node} );
this.onClick(message.item, message.node);
},
_onDownArrow: function(/*Object*/ message){
// summary: down arrow pressed; get next visible node, set focus there
var returnNode = this._navToNextNode(message.node);
if(returnNode && returnNode.isTreeNode){
returnNode.tree.focusNode(returnNode);
return returnNode;
}
},
_onUpArrow: function(/*Object*/ message){
// summary: up arrow pressed; move to previous visible node
var nodeWidget = message.node;
var returnWidget = nodeWidget;
// if younger siblings
var previousSibling = nodeWidget.getPreviousSibling();
if(previousSibling){
nodeWidget = previousSibling;
// if the previous nodeWidget is expanded, dive in deep
while(nodeWidget.isExpandable && nodeWidget.isExpanded && nodeWidget.hasChildren()){
returnWidget = nodeWidget;
// move to the last child
var children = nodeWidget.getChildren();
nodeWidget = children[children.length-1];
}
}else{
// if this is the first child, return the parent
// unless the parent is the root of a tree with a hidden root
var parent = nodeWidget.getParent();
if(!(this._hideRoot && parent === this)){
nodeWidget = parent;
}
}
if(nodeWidget && nodeWidget.isTreeNode){
returnWidget = nodeWidget;
}
if(returnWidget && returnWidget.isTreeNode){
returnWidget.tree.focusNode(returnWidget);
return returnWidget;
}
},
_onRightArrow: function(/*Object*/ message){
// summary: right arrow pressed; go to child node
var nodeWidget = message.node;
var returnWidget = nodeWidget;
// if not expanded, expand, else move to 1st child
if(nodeWidget.isExpandable && !nodeWidget.isExpanded){
this._expandNode(nodeWidget);
}else if(nodeWidget.hasChildren()){
nodeWidget = nodeWidget.getChildren()[0];
}
if(nodeWidget && nodeWidget.isTreeNode){
returnWidget = nodeWidget;
}
if(returnWidget && returnWidget.isTreeNode){
returnWidget.tree.focusNode(returnWidget);
return returnWidget;
}
},
_onLeftArrow: function(/*Object*/ message){
// summary: left arrow pressed; go to parent
var node = message.node;
var returnWidget = node;
// if not collapsed, collapse, else move to parent
if(node.isExpandable && node.isExpanded){
this._collapseNode(node);
}else{
node = node.getParent();
}
if(node && node.isTreeNode){
returnWidget = node;
}
if(returnWidget && returnWidget.isTreeNode){
returnWidget.tree.focusNode(returnWidget);
return returnWidget;
}
},
_onHomeKey: function(){
// summary: home pressed; get first visible node, set focus there
var returnNode = this._navToRootOrFirstNode();
if(returnNode){
returnNode.tree.focusNode(returnNode);
return returnNode;
}
},
_onEndKey: function(/*Object*/ message){
// summary: end pressed; go to last visible node
var returnWidget = message.node.tree;
var lastChild = returnWidget;
while(lastChild.isExpanded){
var c = lastChild.getChildren();
lastChild = c[c.length - 1];
if(lastChild.isTreeNode){
returnWidget = lastChild;
}
}
if(returnWidget && returnWidget.isTreeNode){
returnWidget.tree.focusNode(returnWidget);
return returnWidget;
}
},
_onLetterKeyNav: function(message){
// summary: letter key pressed; search for node starting with first char = key
var node = startNode = message.node;
var key = message.key;
do{
node = this._navToNextNode(node);
//check for last node, jump to first node if necessary
if(!node){
node = this._navToRootOrFirstNode();
}
}while(node !== startNode && (node.label.charAt(0).toLowerCase() != key));
if(node && node.isTreeNode){
// no need to set focus if back where we started
if(node !== startNode){
node.tree.focusNode(node);
}
return node;
}
},
_onClick: function(/*Event*/ e){
// summary: translates click events into commands for the controller to process
var domElement = e.target;
// find node
var nodeWidget = dijit.getEnclosingWidget(domElement);
if(!nodeWidget || !nodeWidget.isTreeNode){
return;
}
if(domElement == nodeWidget.expandoNode ||
domElement == nodeWidget.expandoNodeText){
// expando node was clicked
if(nodeWidget.isExpandable){
this._onExpandoClick({node:nodeWidget});
}
}else{
this._publish("execute", { item: nodeWidget.item, node: nodeWidget} );
this.onClick(nodeWidget.item, nodeWidget);
this.focusNode(nodeWidget);
}
dojo.stopEvent(e);
},
_onExpandoClick: function(/*Object*/ message){
// summary: user clicked the +/- icon; expand or collapse my children.
var node = message.node;
if(node.isExpanded){
this._collapseNode(node);
}else{
this._expandNode(node);
}
},
onClick: function(/* dojo.data */ item, /*TreeNode*/ node){
// summary: user overridable function for executing a tree item
},
_navToNextNode: function(node){
// summary: get next visible node
var returnNode;
// if this is an expanded node, get the first child
if(node.isExpandable && node.isExpanded && node.hasChildren()){
returnNode = node.getChildren()[0];
}else{
// find a parent node with a sibling
while(node && node.isTreeNode){
returnNode = node.getNextSibling();
if(returnNode){
break;
}
node = node.getParent();
}
}
return returnNode;
},
_navToRootOrFirstNode: function(){
// summary: get first visible node
if(!this._hideRoot){
return this;
}else{
var returnNode = this.getChildren()[0];
if(returnNode && returnNode.isTreeNode){
return returnNode;
}
}
},
_collapseNode: function(/*_TreeNode*/ node){
// summary: called when the user has requested to collapse the node
if(node.isExpandable){
if(node.state == "LOADING"){
// ignore clicks while we are in the process of loading data
return;
}
if(this.lastFocused){
// are we collapsing a descendant with focus?
if(dojo.isDescendant(this.lastFocused.domNode, node.domNode)){
this.focusNode(node);
}else{
// clicking the expando node might have erased focus from
// the current item; restore it
this.focusNode(this.lastFocused);
}
}
node.collapse();
if(this.persist && node.item){
delete this._openedItemIds[this.store.getIdentity(node.item)];
this._saveState();
}
}
},
_expandNode: function(/*_TreeNode*/ node){
// summary: called when the user has requested to expand the node
// clicking the expando node might have erased focus from the current item; restore it
var t = node.tree;
if(t.lastFocused){ t.focusNode(t.lastFocused); }
if(!node.isExpandable){
return;
}
var store = this.store;
var getValue = this.store.getValue;
switch(node.state){
case "LOADING":
// ignore clicks while we are in the process of loading data
return;
case "UNCHECKED":
// need to load all the children, and then expand
node.markProcessing();
var _this = this;
var onComplete = function(childItems){
node.unmarkProcessing();
_this._onLoadAllItems(node, childItems);
};
this.getItemChildren(node.item, onComplete);
break;
default:
// data is already loaded; just proceed
if(node.expand){ // top level Tree doesn't have expand() method
node.expand();
if(this.persist && node.item){
this._openedItemIds[this.store.getIdentity(node.item)] = true;
this._saveState();
}
}
break;
}
},
////////////////// Miscellaneous functions ////////////////
blurNode: function(){
// summary
// Removes focus from the currently focused node (which must be visible).
// Usually not called directly (just call focusNode() on another node instead)
var node = this.lastFocused;
if(!node){ return; }
var labelNode = node.labelNode;
dojo.removeClass(labelNode, "dijitTreeLabelFocused");
labelNode.setAttribute("tabIndex", "-1");
this.lastFocused = null;
},
focusNode: function(/* _tree.Node */ node){
// summary
// Focus on the specified node (which must be visible)
// set focus so that the label will be voiced using screen readers
node.labelNode.focus();
},
_onBlur: function(){
// summary:
// We've moved away from the whole tree. The currently "focused" node
// (see focusNode above) should remain as the lastFocused node so we can
// tab back into the tree. Just change CSS to get rid of the dotted border
// until that time
if(this.lastFocused){
var labelNode = this.lastFocused.labelNode;
dojo.removeClass(labelNode, "dijitTreeLabelFocused");
}
},
_onTreeFocus: function(evt){
var node = dijit.getEnclosingWidget(evt.target);
if(node != this.lastFocused){
this.blurNode();
}
var labelNode = node.labelNode;
// set tabIndex so that the tab key can find this node
labelNode.setAttribute("tabIndex", "0");
dojo.addClass(labelNode, "dijitTreeLabelFocused");
this.lastFocused = node;
},
//////////////// Events from data store //////////////////////////
_onNewItem: function(/*Object*/ item, parentInfo){
//summary: callback when new item has been added to the store.
var loadNewItem; // should new item be displayed in tree?
if(parentInfo){
var parent = this._itemNodeMap[this.getItemParentIdentity(item, parentInfo)];
// If new item's parent item not in tree view yet, can safely ignore.
// Also, if a query of specified parent wouldn't return this item, then ignore.
if(!parent ||
dojo.indexOf(this.childrenAttr, parentInfo.attribute) == -1){
return;
}
}
var childParams = {
item: item,
isExpandable: this.mayHaveChildren(item)
};
if(parent){
if(!parent.isExpandable){
parent.makeExpandable();
}
if(parent.state=="LOADED" || parent.isExpanded){
var childrenMap=parent._addChildren([childParams]);
}
}else{
// top level node
var childrenMap=this._addChildren([childParams]);
}
if(childrenMap){
dojo.mixin(this._itemNodeMap, childrenMap);
//this._itemNodeMap[this.store.getIdentity(item)]=child;
}
},
_onDeleteItem: function(/*Object*/ item){
//summary: delete event from the store
//since the object has just been deleted, we need to
//use the name directly
var identity = this.store.getIdentity(item);
var node = this._itemNodeMap[identity];
if(node){
var parent = node.getParent();
parent.deleteNode(node);
this._itemNodeMap[identity]=null;
}
},
_onSetItem: function(/*Object*/ item){
//summary: set data event on an item in the store
var identity = this.store.getIdentity(item);
node = this._itemNodeMap[identity];
if(node){
node.setLabelNode(this.getLabel(item));
node._updateItemClasses(item);
}
},
_saveState: function(){
//summary: create and save a cookie with the currently expanded nodes identifiers
if(!this.persist){
return;
}
var ary = [];
for(var id in this._openedItemIds){
ary.push(id);
}
dojo.cookie(this.cookieName, ary.join(","));
}
});
}