Index: dojo-release-1.3.2-src/dijit/Tree.js =================================================================== --- dojo-release-1.3.2-src.orig/dijit/Tree.js 2009-09-16 13:19:31.000000000 -0700 +++ dojo-release-1.3.2-src/dijit/Tree.js 2009-11-10 01:38:19.000000000 -0800 @@ -39,6 +39,11 @@ // This node is currently expanded (ie, opened) isExpanded: false, + // isSelectable: Boolean + // If false, node will only toggle expanded when clicked and the onClick + // will not fire. + isSelectable: true, + // state: [private] String // Dynamic loading-related stuff. // When an empty folder node appears, it is "UNCHECKED" first, @@ -267,6 +272,7 @@ item: item, tree: tree, isExpandable: model.mayHaveChildren(item), + isSelectable: tree.getSelectable(item), label: tree.getLabel(item), indent: this.indent + 1 }); @@ -294,19 +300,6 @@ // 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
itself - tree.domNode.setAttribute("tabIndex", "0"); - } - } }, removeChild: function(/* treeNode */ node){ @@ -340,7 +333,6 @@ // tags: // private dojo.addClass(this.labelNode, "dijitTreeLabelFocused"); - this.tree._onNodeFocus(this); }, _onLabelBlur: function(evt){ @@ -355,6 +347,11 @@ }, setSelected: function(/*Boolean*/ selected){ + dojo.deprecated("setSelected"+selected+"() is deprecated. Use attr('selected', "+selected+" instead.", "", "2.0"); + this.attr('selected', selected); + }, + + _setSelectedAttr: function(/*Boolean*/ selected){ // summary: // A Tree has a (single) currently selected node. // Mark that this node is/isn't that currently selected node. @@ -362,9 +359,12 @@ // In particular, setting a node as selected involves setting tabIndex // so that when user tabs to the tree, focus will go to that node (only). var labelNode = this.labelNode; + labelNode.setAttribute("tabIndex", selected ? "0" : "-1"); dijit.setWaiState(labelNode, "selected", selected); dojo.toggleClass(this.rowNode, "dijitTreeNodeSelected", selected); + + this.selected = selected; }, _onMouseEnter: function(evt){ @@ -432,6 +432,10 @@ // If true, double-clicking a folder node's label will open it, rather than calling onDblClick() openOnDblClick: false, + // selectedItem: dojo.data.Item + // Optional initially selected item + selectedItem: null, + templatePath: dojo.moduleUrl("dijit", "templates/Tree.html"), // isExpandable: [private deprecated] Boolean @@ -540,17 +544,17 @@ this.cookieName = this.id + "SaveStateCookie"; } + // Create glue between store and Tree, if not specified directly by user + if(!this.model){ + this._store2model(); + } + // TODO: this.inherited(arguments) }, postCreate: function(){ this._initState(); - // 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"); @@ -614,6 +618,7 @@ item: item, tree: this, isExpandable: true, + isSelectable: this.getSelectable(item), label: this.label || this.getLabel(item), indent: this.showRoot ? 0 : -1 }); @@ -702,6 +707,87 @@ // extension }, + getSelectable: function(/*dojo.data.Item*/ item){ + // summary: + // Overridable function to determine if the item's node is selectable. + // returns: + // Boolean + // tags: + // extension + return this.model.getSelectable(item); + }, + + _itemsEqual: function(/*dojo.data.Item[]*/){ + // summary: + // Compares all argument's model identity against each other + // description: + // Returns true if all items are null or have the same identity + if(arguments.length < 2){ + return false; + } + var items = Array.prototype.slice.call(arguments); + if(dojo.every(items, function(item){return item === null})){ + return true; + } + if(dojo.some(items, function(item){return item === null})){ + return false; + } + var firstIdentity = this.model.getIdentity(items.shift()); + return dojo.every(items, function(item){ + return this.model.getIdentity(item) == firstIdentity; + }, this); + }, + + _setSelectedItemAttr: function(/*dojo.data.Item*/ item){ + // summary: + // Set the selected item. + // description: + // If the item's node widget is not instantiated yet it will do nothing. + // If item is null the current selection will be cleared. + if(this.attr("selectedItem") !== null + && this._itemsEqual(this.attr("selectedItem"), item)){ + return; + } + if(this.attr("selectedNode") !== undefined + && !this._itemsEqual(this.attr("selectedItem"), item)){ + this.attr("selectedNode").attr("selected", false); + } + if(item === null){ + // deselect current selection + this.selectedItem = null; + return; + } + var node = this._item2node(item); + if(node === undefined){ + // node not created yet, cannot select item. Will be selected when the + // parent node is selected. + } + else{ + node.attr("selected", true); + this.focusNode(node); + this.selectedItem = item; + } + }, + + _getSelectedItemAttr: function(){ + // summary: + // Get the selected item. + return this.selectedItem; + }, + + _setSelectedNodeAttr: function(node){ + // summary: + // Set the selected item via a node. + this.attr("selectedItem", node.item); + }, + + _getSelectedNodeAttr: function(){ + // summary: + // Get the selected node. + return this._item2node(this.attr("selectedItem")); + }, + + /////////// Keyboard and Mouse handlers //////////////////// _onKeyPress: function(/*Event*/ e){ @@ -741,8 +827,9 @@ }, _onEnterKey: function(/*Object*/ message){ - this._publish("execute", { item: message.item, node: message.node} ); - this.onClick(message.item, message.node); + if(this._execute(message.item)){ + this.onClick(message.item, message.node); + } }, _onDownArrow: function(/*Object*/ message){ @@ -871,17 +958,16 @@ return; } - if( (this.openOnClick && nodeWidget.isExpandable) || + if( (this.openOnClick && nodeWidget.isExpandable) || !nodeWidget.isSelectable || (domElement == nodeWidget.expandoNode || domElement == nodeWidget.expandoNodeText) ){ // expando node was clicked, or label of a folder node was clicked; open it if(nodeWidget.isExpandable){ this._onExpandoClick({node:nodeWidget}); } - }else{ - this._publish("execute", { item: nodeWidget.item, node: nodeWidget} ); - this.onClick(nodeWidget.item, nodeWidget); - this.focusNode(nodeWidget); + }else if(this._execute(nodeWidget.item)){ + this.onClick(nodeWidget.item, nodeWidget.node); } + this.focusNode(nodeWidget); dojo.stopEvent(e); }, _onDblClick: function(/*Event*/ e){ @@ -901,11 +987,10 @@ if(nodeWidget.isExpandable){ this._onExpandoClick({node:nodeWidget}); } - }else{ - this._publish("execute", { item: nodeWidget.item, node: nodeWidget} ); - this.onDblClick(nodeWidget.item, nodeWidget); - this.focusNode(nodeWidget); + }else if(this._execute(nodeWidget.item)){ + this.onDblClick(nodeWidget.item, nodeWidget.node); } + this.focusNode(nodeWidget); dojo.stopEvent(e); }, @@ -1016,15 +1101,48 @@ case "UNCHECKED": // need to load all the children, and then expand node.markProcessing(); - var _this = this; - model.getChildren(item, function(items){ + model.getChildren( + item, + dojo.hitch(this, function(items){ node.unmarkProcessing(); node.setChildItems(items); - _this._expandNode(node); - }, - function(err){ - console.error(_this, ": error loading root children: ", err); - }); + this._expandNode(node); + if(node !== this.rootNode){ + // Check to see if the selected item is a child of this newly expanded node. + if(dojo.some(items, function(i){return i === this.attr("selectedItem")}, this)){ + this.attr("selectedNode").attr("selected", true); + } + } + else{ + // On initial tree show, make the selected TreeNode as either: + // the node that matches this.selectedItem, + // or the root node of the tree, + // or the first child, if the root node is hidden + if(this.attr("selectedItem") !== null){ + if(this.attr("selectedNode") === undefined){ + // Default selection is set but cannot select it because the + // node is not created yet, most likely due to not being a + // direct child of the root. + return; + } + else{ + this.attr("selectedNode").attr("selected", true); + return; + } + } + var fc = this.tree.showRoot ? node : node.getChildren()[0]; + if(fc){ + this.attr('selectedNode', fc); + return; + } + // fallback: no nodes in tree so focus on Tree
itself + this.domNode.setAttribute("tabIndex", "0"); + } + }), + dojo.hitch(this, function(err){ + console.error(this, ": error loading root children: ", err); + }) + ); break; default: @@ -1051,26 +1169,6 @@ node.labelNode.focus(); }, - _onNodeFocus: function(/*Widget*/ node){ - // summary: - // Called when a TreeNode gets focus, either by user clicking - // it, or programatically by arrow key handling code. - // description: - // It marks that the current node is the selected one, and the previously - // selected node no longer is. - - if (node){ - if(node != this.lastFocused){ - // mark that the previously selected node is no longer the selected one - this.lastFocused.setSelected(false); - } - - // mark that the new node is the currently selected one - node.setSelected(true); - this.lastFocused = node; - } - }, - _onNodeMouseEnter: function(/*Widget*/ node){ // summary: // Called when mouse is over a node (onmouseenter event) @@ -1086,9 +1184,7 @@ _onItemChange: function(/*Item*/ item){ // summary: // Processes notification of a change to an item's scalar values like label - var model = this.model, - identity = model.getIdentity(item), - node = this._itemNodeMap[identity]; + var node = this._item2node(item); if(node){ node.setLabelNode(this.getLabel(item)); @@ -1099,9 +1195,7 @@ _onItemChildrenChange: function(/*dojo.data.Item*/ parent, /*dojo.data.Item[]*/ newChildrenList){ // summary: // Processes notification of a change to an item's children - var model = this.model, - identity = model.getIdentity(parent), - parentNode = this._itemNodeMap[identity]; + var parentNode = this._item2node(parent); if(parentNode){ parentNode.setChildItems(newChildrenList); @@ -1196,6 +1290,26 @@ // of just specifying a widget for the label, rather than one that contains // the children too. return new dijit._TreeNode(args); + }, + + _item2node: function(item){ + // summary: + // Get the node that represents the item in the tree. Returns undefined + // if the item does not have a node created for it. + return item === null ? undefined : this._itemNodeMap[this.model.getIdentity(item)]; + }, + + _execute: function(/*dojo.data.Item*/item){ + // summary: + // This is what gets called when the 'execute' happens. Usually from + // clicking or pressing enter on a selectable item. + var node = this._item2node(item); + if(!node.isSelectable){ + return false; + } + this.attr("selectedItem", item); + this._publish("execute", { item: item, node: node} ); + return true; } }); Index: dojo-release-1.3.2-src/dijit/tree/TreeStoreModel.js =================================================================== --- dojo-release-1.3.2-src.orig/dijit/tree/TreeStoreModel.js 2009-09-16 13:19:31.000000000 -0700 +++ dojo-release-1.3.2-src/dijit/tree/TreeStoreModel.js 2009-10-02 17:27:31.000000000 -0700 @@ -21,6 +21,10 @@ // If specified, get label for tree node from this attribute, rather // than by calling store.getLabel() labelAttr: "", + + // selectableAttr: String + // Default attribute used to determine if items are selectable + selectableAttr: "selectable", // root: [readonly] dojo.data.Item // Pointer to the root item (read only, not a parameter) @@ -157,6 +161,13 @@ } }, + getSelectable: function(/*dojo.data.Item*/ item){ + // summary: + // Get the selectability of an item + var selectable = this.store.getValue(item,this.selectableAttr); + return selectable === undefined ? true : selectable; // Boolean + }, + // ======================================================================= // Write interface Index: dojo-release-1.3.2-src/dijit/tree/model.js =================================================================== --- dojo-release-1.3.2-src.orig/dijit/tree/model.js 2009-09-16 13:19:31.000000000 -0700 +++ dojo-release-1.3.2-src/dijit/tree/model.js 2009-11-10 01:12:20.000000000 -0800 @@ -64,6 +64,13 @@ // tags: // extension }, + + getSelectable: function(/*dojo.data.Item*/ item){ + // summary: + // Get the selectability of an item + // tags: + // extension + }, // ======================================================================= // Write interface