dojo.provide("dijit.Tree"); dojo.require("dojo.fx"); dojo.require("dijit._Widget"); dojo.require("dijit._Templated"); dojo.require("dijit._Container"); dojo.require("dijit._Contained"); dojo.require("dojo.cookie"); dojo.declare( "dijit._TreeNode", [dijit._Widget, dijit._Templated, dijit._Container, dijit._Contained], { // summary: // Single node within a tree. This class is used internally // by Tree and should not be accessed directly. // tags: // private // item: dojo.data.Item // the dojo.data entry this tree represents item: null, // isTreeNode: [protected] Boolean // Indicates that this is a TreeNode. Used by `dijit.Tree` only, // should not be accessed directly. isTreeNode: true, // label: String // Text of this tree node label: "", // isExpandable: [private] Boolean // This node has children, so show the expando node (+ sign) isExpandable: null, // isExpanded: [readonly] Boolean // This node is currently expanded (ie, opened) isExpanded: false, // state: [private] 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", "templates/TreeNode.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); if(this == this.tree.rootNode){ dijit.setWaitState(this.tree.domNode, "expanded", this.isExpanded); } } }, _setIndentAttr: function(indent){ // summary: // Tell this node how many levels it should be indented // description: // 0 for top level nodes, 1 for their children, 2 for their // grandchildren, etc. this.indent = indent; // Math.max() is to prevent negative padding on hidden root node (when indent == -1) // 19 is the width of the expandoIcon (TODO: get this from CSS instead of hardcoding) var pixels = (Math.max(indent, 0) * 19) + "px"; dojo.style(this.domNode, "backgroundPosition", pixels + " 0px"); dojo.style(this.rowNode, dojo._isBodyLtr() ? "paddingLeft" : "paddingRight", pixels); dojo.forEach(this.getChildren(), function(child){ child.attr("indent", indent+1); }); }, markProcessing: function(){ // summary: // Visually denote that tree is loading data, etc. // tags: // private this.state = "LOADING"; this._setExpando(true); }, unmarkProcessing: function(){ // summary: // Clear markup from markProcessing() call // tags: // private 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) // tags: // private 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; } if(this._iconClass){ dojo.removeClass(this.iconNode, this._iconClass); } this._iconClass = tree.getIconClass(item, this.isExpanded); if(this._iconClass){ dojo.addClass(this.iconNode, this._iconClass); } dojo.style(this.iconNode, tree.getIconStyle(item, this.isExpanded) || {}); if(this._labelClass){ dojo.removeClass(this.labelNode, this._labelClass); } this._labelClass = tree.getLabelClass(item, this.isExpanded); if(this._labelClass){ dojo.addClass(this.labelNode, this._labelClass); } dojo.style(this.labelNode, tree.getLabelStyle(item, this.isExpanded) || {}); }, _updateLayout: function(){ // summary: // Set appropriate CSS classes for this.domNode // tags: // private 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 // tags: // private // 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"); dojo.addClass(this.contentNode,'dijitTreeContentExpanded'); this._setExpando(); this._updateItemClasses(this.item); if(this == this.tree.rootNode){ dijit.setWaiState(this.tree.domNode, "expanded", "true"); } if(!this._wipeIn){ this._wipeIn = dojo.fx.wipeIn({ node: this.containerNode, duration: dijit.defaultDuration }); } this._wipeIn.play(); }, collapse: function(){ // summary: // Collapse this node (if it's expanded) if(!this.isExpanded){ return; } // cancel in progress expand operation this._wipeIn && this._wipeIn.stop(); this.isExpanded = false; dijit.setWaiState(this.labelNode, "expanded", "false"); if(this == this.tree.rootNode){ dijit.setWaiState(this.tree.domNode, "expanded", "false"); } dojo.removeClass(this.contentNode,'dijitTreeContentExpanded'); 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){ // summary: // Sets the label this.labelNode.innerHTML = ""; this.labelNode.appendChild(dojo.doc.createTextNode(label)); }, // indent: Integer // Levels from this node to the root node indent: 0, 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), indent: this.indent + 1 }); if(existingNode){ existingNode.attr('indent', this.indent+1); } 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._state(item)){ 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, make the selected TreeNode as 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]; if(fc){ fc.setSelected(true); tree.lastFocused = fc; }else{ // fallback: no nodes in tree so focus on Tree