diff options
author | Andrew Dolgov <[email protected]> | 2011-11-08 20:40:44 +0400 |
---|---|---|
committer | Andrew Dolgov <[email protected]> | 2011-11-08 20:40:44 +0400 |
commit | 81bea17aefb26859f825b9293c7c99192874806e (patch) | |
tree | fb244408ca271affa2899adb634788802c9a89d8 /lib/dijit/tree/TreeStoreModel.js | |
parent | 870a70e109ac9e80a88047044530de53d0404ec7 (diff) |
upgrade Dojo to 1.6.1
Diffstat (limited to 'lib/dijit/tree/TreeStoreModel.js')
-rw-r--r-- | lib/dijit/tree/TreeStoreModel.js | 510 |
1 files changed, 373 insertions, 137 deletions
diff --git a/lib/dijit/tree/TreeStoreModel.js b/lib/dijit/tree/TreeStoreModel.js index c03bef526..5164176b7 100644 --- a/lib/dijit/tree/TreeStoreModel.js +++ b/lib/dijit/tree/TreeStoreModel.js @@ -1,145 +1,381 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.tree.TreeStoreModel"]){ -dojo._hasResource["dijit.tree.TreeStoreModel"]=true; +if(!dojo._hasResource["dijit.tree.TreeStoreModel"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.tree.TreeStoreModel"] = true; dojo.provide("dijit.tree.TreeStoreModel"); -dojo.declare("dijit.tree.TreeStoreModel",null,{store:null,childrenAttrs:["children"],newItemIdAttr:"id",labelAttr:"",root:null,query:null,deferItemLoadingUntilExpand:false,constructor:function(_1){ -dojo.mixin(this,_1); -this.connects=[]; -var _2=this.store; -if(!_2.getFeatures()["dojo.data.api.Identity"]){ -throw new Error("dijit.Tree: store must support dojo.data.Identity"); -} -if(_2.getFeatures()["dojo.data.api.Notification"]){ -this.connects=this.connects.concat([dojo.connect(_2,"onNew",this,"onNewItem"),dojo.connect(_2,"onDelete",this,"onDeleteItem"),dojo.connect(_2,"onSet",this,"onSetItem")]); -} -},destroy:function(){ -dojo.forEach(this.connects,dojo.disconnect); -},getRoot:function(_3,_4){ -if(this.root){ -_3(this.root); -}else{ -this.store.fetch({query:this.query,onComplete:dojo.hitch(this,function(_5){ -if(_5.length!=1){ -throw new Error(this.declaredClass+": query "+dojo.toJson(this.query)+" returned "+_5.length+" items, but must return exactly one item"); -} -this.root=_5[0]; -_3(this.root); -}),onError:_4}); -} -},mayHaveChildren:function(_6){ -return dojo.some(this.childrenAttrs,function(_7){ -return this.store.hasAttribute(_6,_7); -},this); -},getChildren:function(_8,_9,_a){ -var _b=this.store; -if(!_b.isItemLoaded(_8)){ -var _c=dojo.hitch(this,arguments.callee); -_b.loadItem({item:_8,onItem:function(_d){ -_c(_d,_9,_a); -},onError:_a}); -return; -} -var _e=[]; -for(var i=0;i<this.childrenAttrs.length;i++){ -var _f=_b.getValues(_8,this.childrenAttrs[i]); -_e=_e.concat(_f); -} -var _10=0; -if(!this.deferItemLoadingUntilExpand){ -dojo.forEach(_e,function(_11){ -if(!_b.isItemLoaded(_11)){ -_10++; -} -}); -} -if(_10==0){ -_9(_e); -}else{ -dojo.forEach(_e,function(_12,idx){ -if(!_b.isItemLoaded(_12)){ -_b.loadItem({item:_12,onItem:function(_13){ -_e[idx]=_13; -if(--_10==0){ -_9(_e); -} -},onError:_a}); -} -}); -} -},isItem:function(_14){ -return this.store.isItem(_14); -},fetchItemByIdentity:function(_15){ -this.store.fetchItemByIdentity(_15); -},getIdentity:function(_16){ -return this.store.getIdentity(_16); -},getLabel:function(_17){ -if(this.labelAttr){ -return this.store.getValue(_17,this.labelAttr); -}else{ -return this.store.getLabel(_17); -} -},newItem:function(_18,_19,_1a){ -var _1b={parent:_19,attribute:this.childrenAttrs[0],insertIndex:_1a}; -if(this.newItemIdAttr&&_18[this.newItemIdAttr]){ -this.fetchItemByIdentity({identity:_18[this.newItemIdAttr],scope:this,onItem:function(_1c){ -if(_1c){ -this.pasteItem(_1c,null,_19,true,_1a); -}else{ -this.store.newItem(_18,_1b); -} -}}); -}else{ -this.store.newItem(_18,_1b); -} -},pasteItem:function(_1d,_1e,_1f,_20,_21){ -var _22=this.store,_23=this.childrenAttrs[0]; -if(_1e){ -dojo.forEach(this.childrenAttrs,function(_24){ -if(_22.containsValue(_1e,_24,_1d)){ -if(!_20){ -var _25=dojo.filter(_22.getValues(_1e,_24),function(x){ -return x!=_1d; -}); -_22.setValues(_1e,_24,_25); -} -_23=_24; -} -}); -} -if(_1f){ -if(typeof _21=="number"){ -var _26=_22.getValues(_1f,_23).slice(); -_26.splice(_21,0,_1d); -_22.setValues(_1f,_23,_26); -}else{ -_22.setValues(_1f,_23,_22.getValues(_1f,_23).concat(_1d)); -} -} -},onChange:function(_27){ -},onChildrenChange:function(_28,_29){ -},onDelete:function(_2a,_2b){ -},onNewItem:function(_2c,_2d){ -if(!_2d){ -return; -} -this.getChildren(_2d.item,dojo.hitch(this,function(_2e){ -this.onChildrenChange(_2d.item,_2e); -})); -},onDeleteItem:function(_2f){ -this.onDelete(_2f); -},onSetItem:function(_30,_31,_32,_33){ -if(dojo.indexOf(this.childrenAttrs,_31)!=-1){ -this.getChildren(_30,dojo.hitch(this,function(_34){ -this.onChildrenChange(_30,_34); -})); -}else{ -this.onChange(_30); -} -}}); + + +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 or more attribute names (attributes in the dojo.data item) that specify that item's children + childrenAttrs: ["children"], + + // newItemIdAttr: String + // Name of attribute in the Object passed to newItem() that specifies the id. + // + // If newItemIdAttr is set then it's used when newItem() is called to see if an + // item with the same id already exists, and if so just links to the old item + // (so that the old item ends up with two parents). + // + // Setting this to null or "" will make every drop create a new item. + newItemIdAttr: "id", + + // labelAttr: String + // If specified, get label for tree node from this attribute, rather + // than by calling store.getLabel() + labelAttr: "", + + // root: [readonly] 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, + + // deferItemLoadingUntilExpand: Boolean + // Setting this to true will cause the TreeStoreModel to defer calling loadItem on nodes + // until they are expanded. This allows for lazying loading where only one + // loadItem (and generally one network call, consequently) per expansion + // (rather than one for each child). + // This relies on partial loading of the children items; each children item of a + // fully loaded item should contain the label and info about having children. + deferItemLoadingUntilExpand: false, + + constructor: function(/* Object */ args){ + // summary: + // Passed the arguments listed above (store, etc) + // tags: + // private + + 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); + // TODO: should cancel any in-progress processing of getRoot(), getChildren() + }, + + // ======================================================================= + // 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 " + dojo.toJson(this.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; + if(!store.isItemLoaded(parentItem)){ + // The parent is not loaded yet, we must be in deferItemLoadingUntilExpand + // mode, so we will load it and just return the children (without loading each + // child item) + var getChildren = dojo.hitch(this, arguments.callee); + store.loadItem({ + item: parentItem, + onItem: function(parentItem){ + getChildren(parentItem, onComplete, onError); + }, + onError: onError + }); + return; + } + // 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; + if(!this.deferItemLoadingUntilExpand){ + dojo.forEach(childItems, function(item){ if(!store.isItemLoaded(item)){ _waitCount++; } }); + } + + if(_waitCount == 0){ + // all items are already loaded (or we aren't loading them). proceed... + onComplete(childItems); + }else{ + // still waiting for some or all of the items to load + dojo.forEach(childItems, function(item, idx){ + if(!store.isItemLoaded(item)){ + store.loadItem({ + item: item, + onItem: function(item){ + childItems[idx] = item; + if(--_waitCount == 0){ + // all nodes have been loaded, send them to the tree + onComplete(childItems); + } + }, + onError: onError + }); + } + }); + } + }, + + // ======================================================================= + // Inspecting items + + isItem: function(/* anything */ something){ + return this.store.isItem(something); // Boolean + }, + + fetchItemByIdentity: function(/* object */ keywordArgs){ + this.store.fetchItemByIdentity(keywordArgs); + }, + + getIdentity: function(/* item */ item){ + return this.store.getIdentity(item); // Object + }, + + getLabel: function(/*dojo.data.Item*/ item){ + // summary: + // Get the label for an item + if(this.labelAttr){ + return this.store.getValue(item,this.labelAttr); // String + }else{ + return this.store.getLabel(item); // String + } + }, + + // ======================================================================= + // Write interface + + newItem: function(/* dojo.dnd.Item */ args, /*Item*/ parent, /*int?*/ insertIndex){ + // 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. + // description: + // Developers will need to override this method if new items get added + // to parents with multiple children attributes, in order to define which + // children attribute points to the new item. + + var pInfo = {parent: parent, attribute: this.childrenAttrs[0]}, LnewItem; + + if(this.newItemIdAttr && args[this.newItemIdAttr]){ + // Maybe there's already a corresponding item in the store; if so, reuse it. + this.fetchItemByIdentity({identity: args[this.newItemIdAttr], scope: this, onItem: function(item){ + if(item){ + // There's already a matching item in store, use it + this.pasteItem(item, null, parent, true, insertIndex); + }else{ + // Create new item in the tree, based on the drag source. + LnewItem=this.store.newItem(args, pInfo); + if (LnewItem && (insertIndex!=undefined)){ + // Move new item to desired position + this.pasteItem(LnewItem, parent, parent, false, insertIndex); + } + } + }}); + }else{ + // [as far as we know] there is no id so we must assume this is a new item + LnewItem=this.store.newItem(args, pInfo); + if (LnewItem && (insertIndex!=undefined)){ + // Move new item to desired position + this.pasteItem(LnewItem, parent, parent, false, insertIndex); + } + } + }, + + pasteItem: function(/*Item*/ childItem, /*Item*/ oldParentItem, /*Item*/ newParentItem, /*Boolean*/ bCopy, /*int?*/ insertIndex){ + // 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 attribute 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){ + if(typeof insertIndex == "number"){ + // call slice() to avoid modifying the original array, confusing the data store + var childItems = store.getValues(newParentItem, parentAttr).slice(); + childItems.splice(insertIndex, 0, childItem); + store.setValues(newParentItem, parentAttr, childItems); + }else{ + 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. + // tags: + // callback + }, + + onChildrenChange: function(/*dojo.data.Item*/ parent, /*dojo.data.Item[]*/ newChildrenList){ + // summary: + // Callback to do notifications about new, updated, or deleted items. + // tags: + // callback + }, + + onDelete: function(/*dojo.data.Item*/ parent, /*dojo.data.Item[]*/ newChildrenList){ + // summary: + // Callback when an item has been deleted. + // description: + // Note that there will also be an onChildrenChange() callback for the parent + // of this item. + // tags: + // callback + }, + + // ======================================================================= + // Events from data store + + onNewItem: function(/* dojo.data.Item */ item, /* Object */ parentInfo){ + // summary: + // Handler for when new items appear in the store, either from a drop operation + // or some other way. Updates the tree view (if necessary). + // description: + // If the new item is a child of an existing item, + // calls onChildrenChange() with the new list of children + // for that existing item. + // + // tags: + // extension + + // We only care about the new item if it has a parent that corresponds to a TreeNode + // we are currently displaying + if(!parentInfo){ + return; + } + + // Call onChildrenChange() on parent (ie, existing) item with new list of children + // In the common case, the new list of children is simply parentInfo.newValue or + // [ parentInfo.newValue ], although if items in the store has multiple + // child attributes (see `childrenAttr`), then it's a superset of parentInfo.newValue, + // so call getChildren() to be sure to get right answer. + this.getChildren(parentInfo.item, dojo.hitch(this, function(children){ + this.onChildrenChange(parentInfo.item, children); + })); + }, + + onDeleteItem: function(/*Object*/ item){ + // summary: + // Handler for delete notifications from underlying store + this.onDelete(item); + }, + + onSetItem: function(/* item */ item, + /* attribute-name-string */ attribute, + /* object | array */ oldValue, + /* object | array */ newValue){ + // summary: + // Updates the tree view according to changes in the data store. + // description: + // Handles updates to an item's children by calling onChildrenChange(), and + // other updates to an item by calling onChange(). + // + // See `onNewItem` for more details on handling updates to an item's children. + // tags: + // extension + + if(dojo.indexOf(this.childrenAttrs, attribute) != -1){ + // item's children list changed + this.getChildren(item, dojo.hitch(this, function(children){ + // See comments in onNewItem() about calling getChildren() + this.onChildrenChange(item, children); + })); + }else{ + // item's label/icon/etc. changed. + this.onChange(item); + } + } + }); + } |