281 lines
8.7 KiB
JavaScript
281 lines
8.7 KiB
JavaScript
|
dojo.provide("dijit.tree.TreeStoreModel");
|
||
|
|
||
|
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"],
|
||
|
|
||
|
// 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,
|
||
|
|
||
|
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;
|
||
|
|
||
|
// 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
|
||
|
if(this.labelAttr){
|
||
|
return this.store.getValue(item,this.labelAttr); // String
|
||
|
}else{
|
||
|
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, /*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 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){
|
||
|
if(typeof insertIndex == "number"){
|
||
|
var childItems = store.getValues(newParentItem, parentAttr);
|
||
|
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.
|
||
|
|
||
|
// 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
|
||
|
this.onDelete(item);
|
||
|
},
|
||
|
|
||
|
_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);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
|