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", templatePath: dojo.moduleUrl("dijit", "_tree/Node.html"), 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 icon and label dom node (used to allow for item updates to change respective CSS) var tree = this.tree, model = tree.model; if(tree._v10Compat && item === model.root){ // For back-compat with 1.0, need to use null to specify root item (TODO: remove in 2.0) item = null; } this.iconNode.className = "dijitTreeIcon " + tree.getIconClass(item, this.isExpanded); this.labelNode.className = "dijitTreeLabel " + tree.getLabelClass(item, this.isExpanded); }, _updateLayout: function(){ // summary: set appropriate CSS classes for this.domNode var parent = this.getParent(); if(!parent || parent.rowNode.style.display == "none"){ /* 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 _a11yStates = ["*","-","+","*"]; 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 = _a11yStates[idx]; }, expand: function(){ // summary: show my children if(this.isExpanded){ return; } // cancel in progress collapse operation this._wipeOut && this._wipeOut.stop(); this.isExpanded = true; dijit.setWaiState(this.labelNode, "expanded", "true"); dijit.setWaiRole(this.containerNode, "group"); this.contentNode.className = "dijitTreeContent dijitTreeContentExpanded"; this._setExpando(); this._updateItemClasses(this.item); if(!this._wipeIn){ this._wipeIn = dojo.fx.wipeIn({ node: this.containerNode, duration: dijit.defaultDuration }); } this._wipeIn.play(); }, collapse: function(){ if(!this.isExpanded){ return; } // cancel in progress expand operation this._wipeIn && this._wipeIn.stop(); this.isExpanded = false; dijit.setWaiState(this.labelNode, "expanded", "false"); this.contentNode.className = "dijitTreeContent"; this._setExpando(); this._updateItemClasses(this.item); if(!this._wipeOut){ this._wipeOut = dojo.fx.wipeOut({ node: this.containerNode, duration: dijit.defaultDuration }); } this._wipeOut.play(); }, setLabelNode: function(label){ this.labelNode.innerHTML = ""; this.labelNode.appendChild(dojo.doc.createTextNode(label)); }, setChildItems: function(/* Object[] */ items){ // summary: // Sets the child items of this node, removing/adding nodes // from current children to match specified items[] array. var tree = this.tree, model = tree.model; // Orphan all my existing children. // If items contains some of the same items as before then we will reattach them. // Don't call this.removeChild() because that will collapse the tree etc. this.getChildren().forEach(function(child){ dijit._Container.prototype.removeChild.call(this, child); }, this); this.state = "LOADED"; if(items && items.length > 0){ this.isExpandable = true; // Create _TreeNode widget for each specified tree node, unless one already // exists and isn't being used (presumably it's from a DnD move and was recently // released dojo.forEach(items, function(item){ var id = model.getIdentity(item), existingNode = tree._itemNodeMap[id], node = ( existingNode && !existingNode.getParent() ) ? existingNode : this.tree._createTreeNode({ item: item, tree: tree, isExpandable: model.mayHaveChildren(item), label: tree.getLabel(item) }); this.addChild(node); // note: this won't work if there are two nodes for one item (multi-parented items); will be fixed later tree._itemNodeMap[id] = node; if(this.tree.persist){ if(tree._openedItemIds[id]){ tree._expandNode(node); } } }, 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/from dot or + icon, as appropriate this._setExpando(false); } // On initial tree show, put focus on either the root node of the tree, // or the first child, if the root node is hidden if(this == tree.rootNode){ var fc = this.tree.showRoot ? this : this.getChildren()[0], tabnode = fc ? fc.labelNode : this.domNode; tabnode.setAttribute("tabIndex", "0"); tree.lastFocused = fc; } }, removeChild: function(/* treeNode */ node){ this.inherited(arguments); 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); }, _onNodeFocus: function(evt){ var node = dijit.getEnclosingWidget(evt.target); this.tree._onTreeFocus(node); }, _onMouseEnter: function(evt){ dojo.addClass(this.contentNode, "dijitTreeNodeHover"); }, _onMouseLeave: function(evt){ dojo.removeClass(this.contentNode, "dijitTreeNodeHover"); } }); dojo.declare( "dijit.Tree", [dijit._Widget, dijit._Templated], { // 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. // May remove for 2.0 in favor of "model". store: null, // model: dijit.Tree.model // Alternate interface from store to access data (and changes to data) in the tree model: null, // query: anything // Specifies datastore query to return the root item for the tree. // // Deprecated functionality: if the query returns multiple items, the tree is given // a fake root node (not corresponding to any item in the data store), // whose children are the items that match this query. // // The root node is shown or hidden based on whether a label is specified. // // Having a query return multiple items is deprecated. // If your store doesn't have a root item, wrap the store with // dijit.tree.ForestStoreModel, and specify model=myModel // // example: // {type:'continent'} query: null, // label: String // Deprecated. Use dijit.tree.ForestStoreModel directly instead. // Used in conjunction with query parameter. // If a query is specified (rather than a root node id), and a label is also specified, // then a fake root node is created and displayed, with this label. label: "", // showRoot: Boolean // Should the root node be displayed, or hidden? showRoot: true, // childrenAttr: String[] // one ore more attributes that holds children of a tree node childrenAttr: ["children"], // openOnClick: Boolean // If true, clicking a folder node's label will open it, rather than calling onClick() openOnClick: false, templatePath: dojo.moduleUrl("dijit", "_tree/Tree.html"), 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", "dragThreshold"], //declare the above items so they can be pulled from the tree's markup onDndDrop:null, itemCreator:null, onDndCancel:null, checkAcceptance:null, checkItemAcceptance:null, dragThreshold:0, _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._itemNodeMap={}; if(!this.cookieName){ this.cookieName = this.id + "SaveStateCookie"; } }, 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); } } // Create glue between store and Tree, if not specified directly by user if(!this.model){ this._store2model(); } // monitor changes to items this.connect(this.model, "onChange", "_onItemChange"); this.connect(this.model, "onChildrenChange", "_onItemChildrenChange"); this.connect(this.model, "onDelete", "_onItemDelete"); this._load(); this.inherited(arguments); if(this.dndController){ if(dojo.isString(this.dndController)){ this.dndController = dojo.getObject(this.dndController); } var params={}; for (var i=0; i