You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
279 lines
9.0 KiB
279 lines
9.0 KiB
define([ |
|
"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); |
|
} |
|
|
|
}); |
|
|
|
});
|
|
|