define("dijit/tree/ForestStoreModel", [ "dojo/_base/array", // array.indexOf array.some "dojo/_base/declare", // declare "dojo/_base/kernel", // global "dojo/_base/lang", // lang.hitch "./TreeStoreModel" ], function(array, declare, kernel, lang, TreeStoreModel){ // module: // dijit/tree/ForestStoreModel return declare("dijit.tree.ForestStoreModel", TreeStoreModel, { // summary: // Interface between a dijit.Tree and a dojo.data store that doesn't have a root item, // a.k.a. a store that has multiple "top level" items. // // description: // Use this class to wrap a dojo.data 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. // This class allows dijit.Tree to assume a single root item, even if the store doesn't have one. // // When using this class the developer must override a number of methods according to their app and // data, including: // // - onNewRootItem // - onAddToRoot // - onLeaveRoot // - onNewItem // - onSetItem // 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){ // summary: // Sets up variables, etc. // tags: // private // 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) // tags: // extension 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: lang.hitch(this, function(items){ this.root.children = items; callback(items); }), onError: onError }); } }else{ this.inherited(arguments); } }, // ======================================================================= // Inspecting items isItem: function(/* anything */ something){ return (something === this.root) ? true : this.inherited(arguments); }, fetchItemByIdentity: function(/* object */ keywordArgs){ if(keywordArgs.identity == this.root.id){ var scope = keywordArgs.scope || kernel.global; if(keywordArgs.onItem){ keywordArgs.onItem.call(scope, this.root); } }else{ this.inherited(arguments); } }, 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(/* dijit/tree/dndSource.__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. if(parent === this.root){ this.onNewRootItem(args); return this.store.newItem(args); }else{ return this.inherited(arguments); } }, onNewRootItem: function(/* dijit/tree/dndSource.__Item */ /*===== 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, /*int?*/ insertIndex){ // 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); } } this.inherited(arguments, [childItem, oldParentItem === this.root ? null : oldParentItem, newParentItem === this.root ? null : newParentItem, bCopy, insertIndex ]); 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); } }, // ======================================================================= // Handling for top level children onAddToRoot: function(/* item */ item){ // summary: // Called when item added to root of tree; user must override this method // to modify the item so that it matches the query for top level items // example: // | store.setValue(item, "root", true); // tags: // extension console.log(this, ": item ", item, " added to root"); }, onLeaveRoot: function(/* item */ item){ // summary: // Called when item removed from root of tree; user must override this method // to modify the item so it doesn't match the query for top level items // example: // | store.unsetAttribute(item, "root"); // tags: // extension 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 oldChildren = this.root.children || []; this.store.fetch({ query: this.query, onComplete: lang.hitch(this, function(newChildren){ this.root.children = newChildren; // If the list of children or the order of children has changed... if(oldChildren.length != newChildren.length || array.some(oldChildren, function(item, idx){ return newChildren[idx] != item;})){ this.onChildrenChange(this.root, newChildren); } }) }); }, onNewItem: function(/* dojo/data/api/Item */ item, /* Object */ parentInfo){ // summary: // Handler for when new items appear in the store. Developers should override this // method to be more efficient based on their app/data. // description: // Note that the default implementation requeries the top level items every time // a new item is created, since any new item could be a top level item (even in // addition to being a child of another item, since items can have multiple parents). // // If developers can detect which items are possible top level items (based on the item and the // parentInfo parameters), they should override this method to only call _requeryTop() for top // level items. Often all top level items have parentInfo==null, but // that will depend on which store you use and what your data is like. // tags: // extension 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(array.indexOf(this.root.children, item) != -1){ this._requeryTop(); } this.inherited(arguments); }, onSetItem: function(/* item */ item, /* attribute-name-string */ attribute, /* Object|Array */ oldValue, /* Object|Array */ newValue){ // summary: // Updates the tree view according to changes to an item in the data store. // Developers should override this method to be more efficient based on their app/data. // description: // Handles updates to an item's children by calling onChildrenChange(), and // other updates to an item by calling onChange(). // // Also, any change to any item re-executes the query for the tree's top-level items, // since this modified item may have started/stopped matching the query for top level items. // // If possible, developers should override this function to only call _requeryTop() when // the change to the item has caused it to stop/start being a top level item in the tree. // tags: // extension this._requeryTop(); this.inherited(arguments); } }); });