diff options
Diffstat (limited to 'includes/js/dijit/Tree.js')
-rw-r--r-- | includes/js/dijit/Tree.js | 1336 |
1 files changed, 0 insertions, 1336 deletions
diff --git a/includes/js/dijit/Tree.js b/includes/js/dijit/Tree.js deleted file mode 100644 index fc9be8b..0000000 --- a/includes/js/dijit/Tree.js +++ /dev/null @@ -1,1336 +0,0 @@ -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\" waiRole=\"presentation\"\n\t><div dojoAttachPoint=\"rowNode\" waiRole=\"presentation\"\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><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=\"-1\" waiState=\"selected-false\" dojoAttachEvent=\"onfocus:_onNodeFocus\"></span>\n\t\t</div\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 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 = "dijitInline 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 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.contentNode.className = "dijitTreeContent dijitTreeContentExpanded"; - this._setExpando(); - this._updateItemClasses(this.item); - - 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.contentNode.className = "dijitTreeContent"; - this._setExpando(); - this._updateItemClasses(this.item); - - 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; - 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, 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 : - new dijit._TreeNode({ - 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.parent){ - var fc = this.tree.showRoot ? this : this.getChildren()[0], - 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}); - } - }, - - 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); - } -}); - -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, - - templateString:"<div class=\"dijitTreeContainer\" waiRole=\"tree\"\n\tdojoAttachEvent=\"onclick:_onClick,onkeypress:_onKeyPress\">\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._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); - } - } - - // make template for container node (we will clone this and insert it into - // any nodes that have children) - var div = dojo.doc.createElement('div'); - div.style.display = 'none'; - div.className = "dijitTreeContainer"; - dijit.setWaiRole(div, "presentation"); - this.containerNodeTemplate = div; - - // 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"); - // TODO: monitor item deletes so we don't end up w/orphaned nodes? - - this._load(); - - this.inherited("postCreate", arguments); - - 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); - } - }, - - _store2model: function(){ - // summary: user specified a store&query rather than model, so create model from store/query - this._v10Compat = true; - dojo.deprecated("Tree: from version 2.0, should specify a model object rather than a store/query"); - - var modelParams = { - id: this.id + "_ForestStoreModel", - store: this.store, - query: this.query, - childrenAttrs: this.childrenAttr - }; - - // Only override the model's mayHaveChildren() method if the user has specified an override - if(this.params.mayHaveChildren){ - modelParams.mayHaveChildren = dojo.hitch(this, "mayHaveChildren"); - } - - if(this.params.getItemChildren){ - modelParams.getChildren = dojo.hitch(this, function(item, onComplete, onError){ - this.getItemChildren((this._v10Compat && item === this.model.root) ? null : item, onComplete, onError); - }); - } - this.model = new dijit.tree.ForestStoreModel(modelParams); - - // For backwards compatibility, the visibility of the root node is controlled by - // whether or not the user has specified a label - this.showRoot = Boolean(this.label); - }, - - _load: function(){ - // summary: initial load of the tree - // load root node (possibly hidden) and it's children - this.model.getRoot( - dojo.hitch(this, function(item){ - var rn = this.rootNode = new dijit._TreeNode({ - item: item, - tree: this, - isExpandable: true, - label: this.label || this.getLabel(item) - }); - if(!this.showRoot){ - rn.rowNode.style.display="none"; - } - this.domNode.appendChild(rn.domNode); - this._itemNodeMap[this.model.getIdentity(item)] = rn; - - rn._updateLayout(); // sets "dijitTreeIsRoot" CSS classname - - // load top level children - this._expandNode(rn); - }), - function(err){ - console.error(this, ": error loading root: ", err); - } - ); - }, - - ////////////// Data store related functions ////////////////////// - // These just get passed to the model; they are here for back-compat - - 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 actually - // has children until user clicks the expando node) - }, - - 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 - }, - - /////////////////////////////////////////////////////// - // Functions for converting an item to a TreeNode - getLabel: function(/*dojo.data.Item*/ item){ - // summary: user overridable function to get the label for a tree node (given the item) - return this.model.getLabel(item); // String - }, - - getIconClass: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){ - // summary: user overridable function to return CSS class name to display icon - return (!item || this.model.mayHaveChildren(item)) ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "dijitLeaf" - }, - - getLabelClass: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){ - // summary: user overridable function to return CSS class name to display label - }, - - /////////// 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[this.isLeftToRight() ? dojo.keys.LEFT_ARROW : dojo.keys.RIGHT_ARROW]="_onLeftArrow"; - map[this.isLeftToRight() ? dojo.keys.RIGHT_ARROW : dojo.keys.LEFT_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 node = this._getNextNode(message.node); - if(node && node.isTreeNode){ - this.focusNode(node); - } - }, - - _onUpArrow: function(/*Object*/ message){ - // summary: up arrow pressed; move to previous visible node - - var node = message.node; - - // if younger siblings - var previousSibling = node.getPreviousSibling(); - if(previousSibling){ - node = previousSibling; - // if the previous node is expanded, dive in deep - while(node.isExpandable && node.isExpanded && node.hasChildren()){ - // move to the last child - var children = node.getChildren(); - node = 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 = node.getParent(); - if(!(!this.showRoot && parent === this.rootNode)){ - node = parent; - } - } - - if(node && node.isTreeNode){ - this.focusNode(node); - } - }, - - _onRightArrow: function(/*Object*/ message){ - // summary: right arrow pressed; go to child node - var node = message.node; - - // if not expanded, expand, else move to 1st child - if(node.isExpandable && !node.isExpanded){ - this._expandNode(node); - }else if(node.hasChildren()){ - node = node.getChildren()[0]; - if(node && node.isTreeNode){ - this.focusNode(node); - } - } - }, - - _onLeftArrow: function(/*Object*/ message){ - // summary: - // Left arrow pressed. - // If not collapsed, collapse, else move to parent. - - var node = message.node; - - if(node.isExpandable && node.isExpanded){ - this._collapseNode(node); - }else{ - node = node.getParent(); - if(node && node.isTreeNode){ - this.focusNode(node); - } - } - }, - - _onHomeKey: function(){ - // summary: home pressed; get first visible node, set focus there - var node = this._getRootOrFirstNode(); - if(node){ - this.focusNode(node); - } - }, - - _onEndKey: function(/*Object*/ message){ - // summary: end pressed; go to last visible node - - var node = this; - while(node.isExpanded){ - var c = node.getChildren(); - node = c[c.length - 1]; - } - - if(node && node.isTreeNode){ - this.focusNode(node); - } - }, - - _onLetterKeyNav: function(message){ - // summary: letter key pressed; search for node starting with first char = key - var node = startNode = message.node, - key = message.key; - do{ - node = this._getNextNode(node); - //check for last node, jump to first node if necessary - if(!node){ - node = this._getRootOrFirstNode(); - } - }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){ - this.focusNode(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( (this.openOnClick && nodeWidget.isExpandable) || - (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); - } - dojo.stopEvent(e); - }, - - _onExpandoClick: function(/*Object*/ message){ - // summary: user clicked the +/- icon; expand or collapse my children. - var node = message.node; - - // If we are collapsing, we might be hiding the currently focused node. - // Also, clicking the expando node might have erased focus from the current node. - // For simplicity's sake just focus on the node with the expando. - this.focusNode(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 - }, - - _getNextNode: function(node){ - // summary: get next visible node - - if(node.isExpandable && node.isExpanded && node.hasChildren()){ - // if this is an expanded node, get the first child - return node.getChildren()[0]; // _TreeNode - }else{ - // find a parent node with a sibling - while(node && node.isTreeNode){ - var returnNode = node.getNextSibling(); - if(returnNode){ - return returnNode; // _TreeNode - } - node = node.getParent(); - } - return null; - } - }, - - _getRootOrFirstNode: function(){ - // summary: get first visible node - return this.showRoot ? this.rootNode : this.rootNode.getChildren()[0]; - }, - - _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; - } - - node.collapse(); - if(this.persist && node.item){ - delete this._openedItemIds[this.model.getIdentity(node.item)]; - this._saveState(); - } - } - }, - - _expandNode: function(/*_TreeNode*/ node){ - // summary: called when the user has requested to expand the node - - if(!node.isExpandable){ - return; - } - - var model = this.model, - item = node.item; - - 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; - model.getChildren(item, function(items){ - node.unmarkProcessing(); - node.setChildItems(items); - _this._expandNode(node); - }, - function(err){ - console.error(_this, ": error loading root children: ", err); - }); - break; - - default: - // data is already loaded; just proceed - node.expand(); - if(this.persist && item){ - this._openedItemIds[model.getIdentity(item)] = true; - this._saveState(); - } - } - }, - - ////////////////// 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"); - dijit.setWaiState(labelNode, "selected", false); - 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 - - this.inherited(arguments); - if(this.lastFocused){ - var labelNode = this.lastFocused.labelNode; - dojo.removeClass(labelNode, "dijitTreeLabelFocused"); - } - }, - - _onTreeFocus: function(/*Widget*/ node){ - // summary: - // called from onFocus handler of treeitem labelNode to set styles, wai state and tabindex - // for currently focused treeitem. - - if (node){ - 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"); - dijit.setWaiState(labelNode, "selected", true); - dojo.addClass(labelNode, "dijitTreeLabelFocused"); - this.lastFocused = node; - } - }, - - //////////////// Events from the model ////////////////////////// - - _onItemDelete: function(/*Object*/ item){ - //summary: delete event from the store - // TODO: currently this isn't called, and technically doesn't need to be, - // but it would help with garbage collection - - var identity = this.model.getIdentity(item); - var node = this._itemNodeMap[identity]; - - if(node){ - var parent = node.getParent(); - if(parent){ - // if node has not already been orphaned from a _onSetItem(parent, "children", ..) call... - parent.removeChild(node); - } - delete this._itemNodeMap[identity]; - node.destroyRecursive(); - } - }, - - _onItemChange: function(/*Item*/ item){ - //summary: set data event on an item in the store - var model = this.model, - identity = model.getIdentity(item), - node = this._itemNodeMap[identity]; - - if(node){ - node.setLabelNode(this.getLabel(item)); - node._updateItemClasses(item); - } - }, - - _onItemChildrenChange: function(/*dojo.data.Item*/ parent, /*dojo.data.Item[]*/ newChildrenList){ - //summary: set data event on an item in the store - var model = this.model, - identity = model.getIdentity(parent), - parentNode = this._itemNodeMap[identity]; - - if(parentNode){ - parentNode.setChildItems(newChildrenList); - } - }, - - /////////////// Miscellaneous funcs - - _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(",")); - }, - - destroy: function(){ - if(this.rootNode){ - this.rootNode.destroyRecursive(); - } - this.rootNode = null; - this.inherited(arguments); - }, - - destroyRecursive: function(){ - // A tree is treated as a leaf, not as a node with children (like a grid), - // but defining destroyRecursive for back-compat. - this.destroy(); - } -}); - - -dojo.declare( - "dijit.tree.TreeStoreModel", - null, -{ - // summary - // Implements dijit.Tree.model connecting to a store with a single - // root item. Any methods passed into the constructor will override - // the ones defined here. - - // store: dojo.data.Store - // Underlying store - store: null, - - // childrenAttrs: String[] - // one ore more attributes that holds children of a tree node - childrenAttrs: ["children"], - - // root: dojo.data.Item - // Pointer to the root item (read only, not a parameter) - root: null, - - // query: anything - // Specifies datastore query to return the root item for the tree. - // Must only return a single item. Alternately can just pass in pointer - // to root item. - // example: - // {id:'ROOT'} - query: null, - - constructor: function(/* Object */ args){ - // summary: passed the arguments listed above (store, etc) - dojo.mixin(this, args); - - this.connects = []; - - var store = this.store; - if(!store.getFeatures()['dojo.data.api.Identity']){ - throw new Error("dijit.Tree: store must support dojo.data.Identity"); - } - - // if the store supports Notification, subscribe to the notification events - if(store.getFeatures()['dojo.data.api.Notification']){ - this.connects = this.connects.concat([ - dojo.connect(store, "onNew", this, "_onNewItem"), - dojo.connect(store, "onDelete", this, "_onDeleteItem"), - dojo.connect(store, "onSet", this, "_onSetItem") - ]); - } - }, - - destroy: function(){ - dojo.forEach(this.connects, dojo.disconnect); - }, - - // ======================================================================= - // Methods for traversing hierarchy - - getRoot: function(onItem, onError){ - // summary: - // Calls onItem with the root item for the tree, possibly a fabricated item. - // Calls onError on error. - if(this.root){ - onItem(this.root); - }else{ - this.store.fetch({ - query: this.query, - onComplete: dojo.hitch(this, function(items){ - if(items.length != 1){ - throw new Error(this.declaredClass + ": query " + query + " returned " + items.length + - " items, but must return exactly one item"); - } - this.root = items[0]; - onItem(this.root); - }), - onError: onError - }); - } - }, - - mayHaveChildren: function(/*dojo.data.Item*/ item){ - // summary - // Tells if an item has or may have children. Implementing logic here - // avoids showing +/- expando icon for nodes that we know don't have children. - // (For efficiency reasons we may not want to check if an element actually - // has children until user clicks the expando node) - return dojo.some(this.childrenAttrs, function(attr){ - return this.store.hasAttribute(item, attr); - }, this); - }, - - getChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ onComplete, /*function*/ onError){ - // summary - // Calls onComplete() with array of child items of given parent item, all loaded. - - var store = this.store; - - // get children of specified item - var childItems = []; - for (var i=0; i<this.childrenAttrs.length; i++){ - var vals = store.getValues(parentItem, this.childrenAttrs[i]); - childItems = childItems.concat(vals); - } - - // 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 - var onItem = 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, - onError: onError - }); - } - }); - } - }, - - // ======================================================================= - // Inspecting items - - getIdentity: function(/* item */ item){ - return this.store.getIdentity(item); // Object - }, - - getLabel: function(/*dojo.data.Item*/ item){ - // summary: get the label for an item - return this.store.getLabel(item); // String - }, - - // ======================================================================= - // Write interface - - newItem: function(/* Object? */ args, /*Item*/ parent){ - // summary - // Creates a new item. See dojo.data.api.Write for details on args. - // Used in drag & drop when item from external source dropped onto tree. - var pInfo = {parent: parent, attribute: this.childrenAttrs[0]}; - return this.store.newItem(args, pInfo); - }, - - pasteItem: function(/*Item*/ childItem, /*Item*/ oldParentItem, /*Item*/ newParentItem, /*Boolean*/ bCopy){ - // summary - // Move or copy an item from one parent item to another. - // Used in drag & drop - var store = this.store, - parentAttr = this.childrenAttrs[0]; // name of "children" attr in parent item - - // remove child from source item, and record the attributee that child occurred in - if(oldParentItem){ - dojo.forEach(this.childrenAttrs, function(attr){ - if(store.containsValue(oldParentItem, attr, childItem)){ - if(!bCopy){ - var values = dojo.filter(store.getValues(oldParentItem, attr), function(x){ - return x != childItem; - }); - store.setValues(oldParentItem, attr, values); - } - parentAttr = attr; - } - }); - } - - // modify target item's children attribute to include this item - if(newParentItem){ - store.setValues(newParentItem, parentAttr, - store.getValues(newParentItem, parentAttr).concat(childItem)); - } - }, - - // ======================================================================= - // Callbacks - - onChange: function(/*dojo.data.Item*/ item){ - // summary - // Callback whenever an item has changed, so that Tree - // can update the label, icon, etc. Note that changes - // to an item's children or parent(s) will trigger an - // onChildrenChange() so you can ignore those changes here. - }, - - onChildrenChange: function(/*dojo.data.Item*/ parent, /*dojo.data.Item[]*/ newChildrenList){ - // summary - // Callback to do notifications about new, updated, or deleted items. - }, - - // ======================================================================= - ///Events from data store - - _onNewItem: function(/* dojo.data.Item */ item, /* Object */ parentInfo){ - // summary: handler for when new items appear in the store. - - // In this case there's no correspond onSet() call on the parent of this - // item, so need to get the new children list of the parent manually somehow. - if(!parentInfo){ - return; - } - this.getChildren(parentInfo.item, dojo.hitch(this, function(children){ - // NOTE: maybe can be optimized since parentInfo contains the new and old attribute value - this.onChildrenChange(parentInfo.item, children); - })); - }, - - _onDeleteItem: function(/*Object*/ item){ - // summary: handler for delete notifications from underlying store - }, - - _onSetItem: function(/* item */ item, - /* attribute-name-string */ attribute, - /* object | array */ oldValue, - /* object | array */ newValue){ - //summary: set data event on an item in the store - - if(dojo.indexOf(this.childrenAttrs, attribute) != -1){ - // item's children list changed - this.getChildren(item, dojo.hitch(this, function(children){ - // NOTE: maybe can be optimized since parentInfo contains the new and old attribute value - this.onChildrenChange(item, children); - })); - }else{ - // item's label/icon/etc. changed. - this.onChange(item); - } - } -}); - -dojo.declare("dijit.tree.ForestStoreModel", dijit.tree.TreeStoreModel, { - // summary - // Interface between Tree and a dojo.store that doesn't have a root item, ie, - // has multiple "top level" items. - // - // description - // Use this class to wrap a dojo.store, making all the items matching the specified query - // appear as children of a fabricated "root item". If no query is specified then all the - // items returned by fetch() on the underlying store become children of the root item. - // It allows dijit.Tree to assume a single root item, even if the store doesn't have one. - - // Parameters to constructor - - // rootId: String - // ID of fabricated root item - rootId: "$root$", - - // rootLabel: String - // Label of fabricated root item - rootLabel: "ROOT", - - // query: String - // Specifies the set of children of the root item. - // example: - // {type:'continent'} - query: null, - - // End of parameters to constructor - - constructor: function(params){ - // Make dummy root item - this.root = { - store: this, - root: true, - id: params.rootId, - label: params.rootLabel, - children: params.rootChildren // optional param - }; - }, - - // ======================================================================= - // Methods for traversing hierarchy - - mayHaveChildren: function(/*dojo.data.Item*/ item){ - // summary - // Tells if an item has or may have children. Implementing logic here - // avoids showing +/- expando icon for nodes that we know don't have children. - // (For efficiency reasons we may not want to check if an element actually - // has children until user clicks the expando node) - return item === this.root || this.inherited(arguments); - }, - - getChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ callback, /*function*/ onError){ - // summary - // Calls onComplete() with array of child items of given parent item, all loaded. - if(parentItem === this.root){ - if(this.root.children){ - // already loaded, just return - callback(this.root.children); - }else{ - this.store.fetch({ - query: this.query, - onComplete: dojo.hitch(this, function(items){ - this.root.children = items; - callback(items); - }), - onError: onError - }); - } - }else{ - this.inherited(arguments); - } - }, - - // ======================================================================= - // Inspecting items - - getIdentity: function(/* item */ item){ - return (item === this.root) ? this.root.id : this.inherited(arguments); - }, - - getLabel: function(/* item */ item){ - return (item === this.root) ? this.root.label : this.inherited(arguments); - }, - - // ======================================================================= - // Write interface - - newItem: function(/* Object? */ args, /*Item*/ parent){ - // summary - // Creates a new item. See dojo.data.api.Write for details on args. - // Used in drag & drop when item from external source dropped onto tree. - if(parent===this.root){ - this.onNewRootItem(args); - return this.store.newItem(args); - }else{ - return this.inherited(arguments); - } - }, - - onNewRootItem: function(args){ - // summary: - // User can override this method to modify a new element that's being - // added to the root of the tree, for example to add a flag like root=true - }, - - pasteItem: function(/*Item*/ childItem, /*Item*/ oldParentItem, /*Item*/ newParentItem, /*Boolean*/ bCopy){ - // summary - // Move or copy an item from one parent item to another. - // Used in drag & drop - if(oldParentItem === this.root){ - if(!bCopy){ - // It's onLeaveRoot()'s responsibility to modify the item so it no longer matches - // this.query... thus triggering an onChildrenChange() event to notify the Tree - // that this element is no longer a child of the root node - this.onLeaveRoot(childItem); - } - } - dijit.tree.TreeStoreModel.prototype.pasteItem.call(this, childItem, - oldParentItem === this.root ? null : oldParentItem, - newParentItem === this.root ? null : newParentItem - ); - if(newParentItem === this.root){ - // It's onAddToRoot()'s responsibility to modify the item so it matches - // this.query... thus triggering an onChildrenChange() event to notify the Tree - // that this element is now a child of the root node - this.onAddToRoot(childItem); - } - }, - - // ======================================================================= - // Callbacks - - onAddToRoot: function(/* item */ item){ - // summary - // Called when item added to root of tree; user must override - // to modify the item so that it matches the query for top level items - // example - // | store.setValue(item, "root", true); - console.log(this, ": item ", item, " added to root"); - }, - - onLeaveRoot: function(/* item */ item){ - // summary - // Called when item removed from root of tree; user must override - // to modify the item so it doesn't match the query for top level items - // example - // | store.unsetAttribute(item, "root"); - console.log(this, ": item ", item, " removed from root"); - }, - - // ======================================================================= - // Events from data store - - _requeryTop: function(){ - // reruns the query for the children of the root node, - // sending out an onSet notification if those children have changed - var _this = this, - oldChildren = this.root.children; - this.store.fetch({ - query: this.query, - onComplete: function(newChildren){ - _this.root.children = newChildren; - - // If the list of children or the order of children has changed... - if(oldChildren.length != newChildren.length || - dojo.some(oldChildren, function(item, idx){ return newChildren[idx] != item;})){ - _this.onChildrenChange(_this.root, newChildren); - } - } - }); - }, - - _onNewItem: function(/* dojo.data.Item */ item, /* Object */ parentInfo){ - // summary: handler for when new items appear in the store. - - // In theory, any new item could be a top level item. - // Do the safe but inefficient thing by requerying the top - // level items. User can override this function to do something - // more efficient. - this._requeryTop(); - - this.inherited(arguments); - }, - - _onDeleteItem: function(/*Object*/ item){ - // summary: handler for delete notifications from underlying store - - // check if this was a child of root, and if so send notification that root's children - // have changed - if(dojo.indexOf(this.root.children, item) != -1){ - this._requeryTop(); - } - - this.inherited(arguments); - } -}); - -} |