Subversion Repositories Applications.papyrus

Rev

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(","));
        }
});

}