define("dijit/tree/TreeStoreModel", [ "dojo/_base/array", // array.filter array.forEach array.indexOf array.some "dojo/aspect", // aspect.after "dojo/_base/declare", // declare "dojo/_base/lang" // lang.hitch ], function(array, aspect, declare, lang){ // module: // dijit/tree/TreeStoreModel return declare("dijit.tree.TreeStoreModel", null, { // summary: // Implements dijit/Tree/model connecting to a dojo.data store with a single // root item. Any methods passed into the constructor will override // the ones defined here. // store: dojo/data/api/Read // 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 lang.mixin(this, args); this.connects = []; var store = this.store; if(!store.getFeatures()['dojo.data.api.Identity']){ throw new Error("dijit.tree.TreeStoreModel: 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([ aspect.after(store, "onNew", lang.hitch(this, "onNewItem"), true), aspect.after(store, "onDelete", lang.hitch(this, "onDeleteItem"), true), aspect.after(store, "onSet", lang.hitch(this, "onSetItem"), true) ]); } }, destroy: function(){ var h; while(h = this.connects.pop()){ h.remove(); } // 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: lang.hitch(this, function(items){ if(items.length != 1){ throw new Error("dijit.tree.TreeStoreModel: root query returned " + items.length + " items, but must return exactly one"); } 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 array.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 = lang.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