1
0
mirror of https://git.tt-rss.org/git/tt-rss.git synced 2026-02-10 16:01:33 +00:00

upgrade Dojo to 1.6.1

This commit is contained in:
Andrew Dolgov
2011-11-08 20:40:44 +04:00
parent 870a70e109
commit 81bea17aef
680 changed files with 51915 additions and 74107 deletions
+271 -74
View File
@@ -1,83 +1,280 @@
/*
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.ForestStoreModel"]){
dojo._hasResource["dijit.tree.ForestStoreModel"]=true;
if(!dojo._hasResource["dijit.tree.ForestStoreModel"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dijit.tree.ForestStoreModel"] = true;
dojo.provide("dijit.tree.ForestStoreModel");
dojo.require("dijit.tree.TreeStoreModel");
dojo.declare("dijit.tree.ForestStoreModel",dijit.tree.TreeStoreModel,{rootId:"$root$",rootLabel:"ROOT",query:null,constructor:function(_1){
this.root={store:this,root:true,id:_1.rootId,label:_1.rootLabel,children:_1.rootChildren};
},mayHaveChildren:function(_2){
return _2===this.root||this.inherited(arguments);
},getChildren:function(_3,_4,_5){
if(_3===this.root){
if(this.root.children){
_4(this.root.children);
}else{
this.store.fetch({query:this.query,onComplete:dojo.hitch(this,function(_6){
this.root.children=_6;
_4(_6);
}),onError:_5});
}
}else{
this.inherited(arguments);
}
},isItem:function(_7){
return (_7===this.root)?true:this.inherited(arguments);
},fetchItemByIdentity:function(_8){
if(_8.identity==this.root.id){
var _9=_8.scope?_8.scope:dojo.global;
if(_8.onItem){
_8.onItem.call(_9,this.root);
}
}else{
this.inherited(arguments);
}
},getIdentity:function(_a){
return (_a===this.root)?this.root.id:this.inherited(arguments);
},getLabel:function(_b){
return (_b===this.root)?this.root.label:this.inherited(arguments);
},newItem:function(_c,_d,_e){
if(_d===this.root){
this.onNewRootItem(_c);
return this.store.newItem(_c);
}else{
return this.inherited(arguments);
}
},onNewRootItem:function(_f){
},pasteItem:function(_10,_11,_12,_13,_14){
if(_11===this.root){
if(!_13){
this.onLeaveRoot(_10);
}
}
dijit.tree.TreeStoreModel.prototype.pasteItem.call(this,_10,_11===this.root?null:_11,_12===this.root?null:_12,_13,_14);
if(_12===this.root){
this.onAddToRoot(_10);
}
},onAddToRoot:function(_15){
},onLeaveRoot:function(_16){
},_requeryTop:function(){
var _17=this.root.children||[];
this.store.fetch({query:this.query,onComplete:dojo.hitch(this,function(_18){
this.root.children=_18;
if(_17.length!=_18.length||dojo.some(_17,function(_19,idx){
return _18[idx]!=_19;
})){
this.onChildrenChange(this.root,_18);
}
})});
},onNewItem:function(_1a,_1b){
this._requeryTop();
this.inherited(arguments);
},onDeleteItem:function(_1c){
if(dojo.indexOf(this.root.children,_1c)!=-1){
this._requeryTop();
}
this.inherited(arguments);
}});
dojo.declare("dijit.tree.ForestStoreModel", dijit.tree.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: dojo.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?keywordArgs.scope:dojo.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(/* 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.
if(parent === this.root){
this.onNewRootItem(args);
return this.store.newItem(args);
}else{
return this.inherited(arguments);
}
},
onNewRootItem: function(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);
}
}
dijit.tree.TreeStoreModel.prototype.pasteItem.call(this, 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: dojo.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 ||
dojo.some(oldChildren, function(item, idx){ return newChildren[idx] != item;})){
this.onChildrenChange(this.root, newChildren);
}
})
});
},
onNewItem: function(/* dojo.data.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(dojo.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);
}
});
}
+373 -137
View File
@@ -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);
}
}
});
}
+177 -39
View File
@@ -1,49 +1,187 @@
/*
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._dndContainer"]){
dojo._hasResource["dijit.tree._dndContainer"]=true;
if(!dojo._hasResource["dijit.tree._dndContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dijit.tree._dndContainer"] = true;
dojo.provide("dijit.tree._dndContainer");
dojo.require("dojo.dnd.common");
dojo.require("dojo.dnd.Container");
dojo.declare("dijit.tree._dndContainer",null,{constructor:function(_1,_2){
this.tree=_1;
this.node=_1.domNode;
dojo.mixin(this,_2);
this.map={};
this.current=null;
this.containerState="";
dojo.addClass(this.node,"dojoDndContainer");
this.events=[dojo.connect(this.node,"onmouseenter",this,"onOverEvent"),dojo.connect(this.node,"onmouseleave",this,"onOutEvent"),dojo.connect(this.tree,"_onNodeMouseEnter",this,"onMouseOver"),dojo.connect(this.tree,"_onNodeMouseLeave",this,"onMouseOut"),dojo.connect(this.node,"ondragstart",dojo,"stopEvent"),dojo.connect(this.node,"onselectstart",dojo,"stopEvent")];
},getItem:function(_3){
var _4=this.selection[_3],_5={data:dijit.getEnclosingWidget(_4),type:["treeNode"]};
return _5;
},destroy:function(){
dojo.forEach(this.events,dojo.disconnect);
this.node=this.parent=null;
},onMouseOver:function(_6,_7){
this.current=_6.rowNode;
this.currentWidget=_6;
},onMouseOut:function(_8,_9){
this.current=null;
this.currentWidget=null;
},_changeState:function(_a,_b){
var _c="dojoDnd"+_a;
var _d=_a.toLowerCase()+"State";
dojo.removeClass(this.node,_c+this[_d]);
dojo.addClass(this.node,_c+_b);
this[_d]=_b;
},_addItemClass:function(_e,_f){
dojo.addClass(_e,"dojoDndItem"+_f);
},_removeItemClass:function(_10,_11){
dojo.removeClass(_10,"dojoDndItem"+_11);
},onOverEvent:function(){
this._changeState("Container","Over");
},onOutEvent:function(){
this._changeState("Container","");
}});
dojo.getObject("tree", true, dojo);
dijit.tree._compareNodes = function(n1, n2){
if(n1 === n2){
return 0;
}
if('sourceIndex' in document.documentElement){ //IE
//TODO: does not yet work if n1 and/or n2 is a text node
return n1.sourceIndex - n2.sourceIndex;
}else if('compareDocumentPosition' in document.documentElement){ //FF, Opera
return n1.compareDocumentPosition(n2) & 2 ? 1: -1;
}else if(document.createRange){ //Webkit
var r1 = doc.createRange();
r1.setStartBefore(n1);
var r2 = doc.createRange();
r2.setStartBefore(n2);
return r1.compareBoundaryPoints(r1.END_TO_END, r2);
}else{
throw Error("dijit.tree._compareNodes don't know how to compare two different nodes in this browser");
}
};
dojo.declare("dijit.tree._dndContainer",
null,
{
// summary:
// This is a base class for `dijit.tree._dndSelector`, and isn't meant to be used directly.
// It's modeled after `dojo.dnd.Container`.
// tags:
// protected
/*=====
// current: DomNode
// The currently hovered TreeNode.rowNode (which is the DOM node
// associated w/a given node in the tree, excluding it's descendants)
current: null,
=====*/
constructor: function(tree, params){
// summary:
// A constructor of the Container
// tree: Node
// Node or node's id to build the container on
// params: dijit.tree.__SourceArgs
// A dict of parameters, which gets mixed into the object
// tags:
// private
this.tree = tree;
this.node = tree.domNode; // TODO: rename; it's not a TreeNode but the whole Tree
dojo.mixin(this, params);
// class-specific variables
this.map = {};
this.current = null; // current TreeNode's DOM node
// states
this.containerState = "";
dojo.addClass(this.node, "dojoDndContainer");
// set up events
this.events = [
// container level events
dojo.connect(this.node, "onmouseenter", this, "onOverEvent"),
dojo.connect(this.node, "onmouseleave", this, "onOutEvent"),
// switching between TreeNodes
dojo.connect(this.tree, "_onNodeMouseEnter", this, "onMouseOver"),
dojo.connect(this.tree, "_onNodeMouseLeave", this, "onMouseOut"),
// cancel text selection and text dragging
dojo.connect(this.node, "ondragstart", dojo, "stopEvent"),
dojo.connect(this.node, "onselectstart", dojo, "stopEvent")
];
},
getItem: function(/*String*/ key){
// summary:
// Returns the dojo.dnd.Item (representing a dragged node) by it's key (id).
// Called by dojo.dnd.Source.checkAcceptance().
// tags:
// protected
var widget = this.selection[key],
ret = {
data: widget,
type: ["treeNode"]
};
return ret; // dojo.dnd.Item
},
destroy: function(){
// summary:
// Prepares this object to be garbage-collected
dojo.forEach(this.events, dojo.disconnect);
// this.clearItems();
this.node = this.parent = null;
},
// mouse events
onMouseOver: function(/*TreeNode*/ widget, /*Event*/ evt){
// summary:
// Called when mouse is moved over a TreeNode
// tags:
// protected
this.current = widget;
},
onMouseOut: function(/*TreeNode*/ widget, /*Event*/ evt){
// summary:
// Called when mouse is moved away from a TreeNode
// tags:
// protected
this.current = null;
},
_changeState: function(type, newState){
// summary:
// Changes a named state to new state value
// type: String
// A name of the state to change
// newState: String
// new state
var prefix = "dojoDnd" + type;
var state = type.toLowerCase() + "State";
//dojo.replaceClass(this.node, prefix + newState, prefix + this[state]);
dojo.replaceClass(this.node, prefix + newState, prefix + this[state]);
this[state] = newState;
},
_addItemClass: function(node, type){
// summary:
// Adds a class with prefix "dojoDndItem"
// node: Node
// A node
// type: String
// A variable suffix for a class name
dojo.addClass(node, "dojoDndItem" + type);
},
_removeItemClass: function(node, type){
// summary:
// Removes a class with prefix "dojoDndItem"
// node: Node
// A node
// type: String
// A variable suffix for a class name
dojo.removeClass(node, "dojoDndItem" + type);
},
onOverEvent: function(){
// summary:
// This function is called once, when mouse is over our container
// tags:
// protected
this._changeState("Container", "Over");
},
onOutEvent: function(){
// summary:
// This function is called once, when mouse is out of our container
// tags:
// protected
this._changeState("Container", "");
}
});
}
+277 -115
View File
@@ -1,125 +1,287 @@
/*
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._dndSelector"]){
dojo._hasResource["dijit.tree._dndSelector"]=true;
if(!dojo._hasResource["dijit.tree._dndSelector"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dijit.tree._dndSelector"] = true;
dojo.provide("dijit.tree._dndSelector");
dojo.require("dojo.dnd.common");
dojo.require("dijit.tree._dndContainer");
dojo.declare("dijit.tree._dndSelector",dijit.tree._dndContainer,{constructor:function(_1,_2){
this.selection={};
this.anchor=null;
this.simpleSelection=false;
this.events.push(dojo.connect(this.tree.domNode,"onmousedown",this,"onMouseDown"),dojo.connect(this.tree.domNode,"onmouseup",this,"onMouseUp"),dojo.connect(this.tree.domNode,"onmousemove",this,"onMouseMove"));
},singular:false,getSelectedNodes:function(){
return this.selection;
},selectNone:function(){
return this._removeSelection()._removeAnchor();
},destroy:function(){
this.inherited(arguments);
this.selection=this.anchor=null;
},onMouseDown:function(e){
if(!this.current){
return;
}
if(e.button==dojo.mouseButtons.RIGHT){
return;
}
var _3=dijit.getEnclosingWidget(this.current),id=_3.id+"-dnd";
if(!dojo.hasAttr(this.current,"id")){
dojo.attr(this.current,"id",id);
}
if(!this.singular&&!dojo.isCopyKey(e)&&!e.shiftKey&&(this.current.id in this.selection)){
this.simpleSelection=true;
dojo.stopEvent(e);
return;
}
if(this.singular){
if(this.anchor==this.current){
if(dojo.isCopyKey(e)){
this.selectNone();
}
}else{
this.selectNone();
this.anchor=this.current;
this._addItemClass(this.anchor,"Anchor");
this.selection[this.current.id]=this.current;
}
}else{
if(!this.singular&&e.shiftKey){
if(dojo.isCopyKey(e)){
}else{
}
}else{
if(dojo.isCopyKey(e)){
if(this.anchor==this.current){
delete this.selection[this.anchor.id];
this._removeAnchor();
}else{
if(this.current.id in this.selection){
this._removeItemClass(this.current,"Selected");
delete this.selection[this.current.id];
}else{
if(this.anchor){
this._removeItemClass(this.anchor,"Anchor");
this._addItemClass(this.anchor,"Selected");
}
this.anchor=this.current;
this._addItemClass(this.current,"Anchor");
this.selection[this.current.id]=this.current;
}
}
}else{
if(!(id in this.selection)){
this.selectNone();
this.anchor=this.current;
this._addItemClass(this.current,"Anchor");
this.selection[id]=this.current;
}
}
}
}
dojo.stopEvent(e);
},onMouseUp:function(e){
if(!this.simpleSelection){
return;
}
this.simpleSelection=false;
this.selectNone();
if(this.current){
this.anchor=this.current;
this._addItemClass(this.anchor,"Anchor");
this.selection[this.current.id]=this.current;
}
},onMouseMove:function(e){
this.simpleSelection=false;
},_removeSelection:function(){
var e=dojo.dnd._empty;
for(var i in this.selection){
if(i in e){
continue;
}
var _4=dojo.byId(i);
if(_4){
this._removeItemClass(_4,"Selected");
}
}
this.selection={};
return this;
},_removeAnchor:function(){
if(this.anchor){
this._removeItemClass(this.anchor,"Anchor");
this.anchor=null;
}
return this;
},forInSelectedItems:function(f,o){
o=o||dojo.global;
for(var id in this.selection){
f.call(o,this.getItem(id),id,this);
}
}});
dojo.declare("dijit.tree._dndSelector",
dijit.tree._dndContainer,
{
// summary:
// This is a base class for `dijit.tree.dndSource` , and isn't meant to be used directly.
// It's based on `dojo.dnd.Selector`.
// tags:
// protected
/*=====
// selection: Hash<String, DomNode>
// (id, DomNode) map for every TreeNode that's currently selected.
// The DOMNode is the TreeNode.rowNode.
selection: {},
=====*/
constructor: function(tree, params){
// summary:
// Initialization
// tags:
// private
this.selection={};
this.anchor = null;
dijit.setWaiState(this.tree.domNode, "multiselect", !this.singular);
this.events.push(
dojo.connect(this.tree.domNode, "onmousedown", this,"onMouseDown"),
dojo.connect(this.tree.domNode, "onmouseup", this,"onMouseUp"),
dojo.connect(this.tree.domNode, "onmousemove", this,"onMouseMove")
);
},
// singular: Boolean
// Allows selection of only one element, if true.
// Tree hasn't been tested in singular=true mode, unclear if it works.
singular: false,
// methods
getSelectedTreeNodes: function(){
// summary:
// Returns a list of selected node(s).
// Used by dndSource on the start of a drag.
// tags:
// protected
var nodes=[], sel = this.selection;
for(var i in sel){
nodes.push(sel[i]);
}
return nodes;
},
selectNone: function(){
// summary:
// Unselects all items
// tags:
// private
this.setSelection([]);
return this; // self
},
destroy: function(){
// summary:
// Prepares the object to be garbage-collected
this.inherited(arguments);
this.selection = this.anchor = null;
},
addTreeNode: function(/*dijit._TreeNode*/node, /*Boolean?*/isAnchor){
// summary
// add node to current selection
// node: Node
// node to add
// isAnchor: Boolean
// Whether the node should become anchor.
this.setSelection(this.getSelectedTreeNodes().concat( [node] ));
if(isAnchor){ this.anchor = node; }
return node;
},
removeTreeNode: function(/*dijit._TreeNode*/node){
// summary
// remove node from current selection
// node: Node
// node to remove
this.setSelection(this._setDifference(this.getSelectedTreeNodes(), [node]))
return node;
},
isTreeNodeSelected: function(/*dijit._TreeNode*/node){
// summary
// return true if node is currently selected
// node: Node
// the node to check whether it's in the current selection
return node.id && !!this.selection[node.id];
},
setSelection: function(/*dijit._treeNode[]*/ newSelection){
// summary
// set the list of selected nodes to be exactly newSelection. All changes to the
// selection should be passed through this function, which ensures that derived
// attributes are kept up to date. Anchor will be deleted if it has been removed
// from the selection, but no new anchor will be added by this function.
// newSelection: Node[]
// list of tree nodes to make selected
var oldSelection = this.getSelectedTreeNodes();
dojo.forEach(this._setDifference(oldSelection, newSelection), dojo.hitch(this, function(node){
node.setSelected(false);
if(this.anchor == node){
delete this.anchor;
}
delete this.selection[node.id];
}));
dojo.forEach(this._setDifference(newSelection, oldSelection), dojo.hitch(this, function(node){
node.setSelected(true);
this.selection[node.id] = node;
}));
this._updateSelectionProperties();
},
_setDifference: function(xs,ys){
// summary
// Returns a copy of xs which lacks any objects
// occurring in ys. Checks for membership by
// modifying and then reading the object, so it will
// not properly handle sets of numbers or strings.
dojo.forEach(ys, function(y){ y.__exclude__ = true; });
var ret = dojo.filter(xs, function(x){ return !x.__exclude__; });
// clean up after ourselves.
dojo.forEach(ys, function(y){ delete y['__exclude__'] });
return ret;
},
_updateSelectionProperties: function() {
// summary
// Update the following tree properties from the current selection:
// path[s], selectedItem[s], selectedNode[s]
var selected = this.getSelectedTreeNodes();
var paths = [], nodes = [];
dojo.forEach(selected, function(node) {
nodes.push(node);
paths.push(node.getTreePath());
});
var items = dojo.map(nodes,function(node) { return node.item; });
this.tree._set("paths", paths);
this.tree._set("path", paths[0] || []);
this.tree._set("selectedNodes", nodes);
this.tree._set("selectedNode", nodes[0] || null);
this.tree._set("selectedItems", items);
this.tree._set("selectedItem", items[0] || null);
},
// mouse events
onMouseDown: function(e){
// summary:
// Event processor for onmousedown
// e: Event
// mouse event
// tags:
// protected
// ignore click on expando node
if(!this.current || this.tree.isExpandoNode( e.target, this.current)){ return; }
if(e.button == dojo.mouseButtons.RIGHT){ return; } // ignore right-click
dojo.stopEvent(e);
var treeNode = this.current,
copy = dojo.isCopyKey(e), id = treeNode.id;
// if shift key is not pressed, and the node is already in the selection,
// delay deselection until onmouseup so in the case of DND, deselection
// will be canceled by onmousemove.
if(!this.singular && !e.shiftKey && this.selection[id]){
this._doDeselect = true;
return;
}else{
this._doDeselect = false;
}
this.userSelect(treeNode, copy, e.shiftKey);
},
onMouseUp: function(e){
// summary:
// Event processor for onmouseup
// e: Event
// mouse event
// tags:
// protected
// _doDeselect is the flag to indicate that the user wants to either ctrl+click on
// a already selected item (to deselect the item), or click on a not-yet selected item
// (which should remove all current selection, and add the clicked item). This can not
// be done in onMouseDown, because the user may start a drag after mousedown. By moving
// the deselection logic here, the user can drags an already selected item.
if(!this._doDeselect){ return; }
this._doDeselect = false;
this.userSelect(this.current, dojo.isCopyKey( e ), e.shiftKey);
},
onMouseMove: function(e){
// summary
// event processor for onmousemove
// e: Event
// mouse event
this._doDeselect = false;
},
userSelect: function(node, multi, range){
// summary:
// Add or remove the given node from selection, responding
// to a user action such as a click or keypress.
// multi: Boolean
// Indicates whether this is meant to be a multi-select action (e.g. ctrl-click)
// range: Boolean
// Indicates whether this is meant to be a ranged action (e.g. shift-click)
// tags:
// protected
if(this.singular){
if(this.anchor == node && multi){
this.selectNone();
}else{
this.setSelection([node]);
this.anchor = node;
}
}else{
if(range && this.anchor){
var cr = dijit.tree._compareNodes(this.anchor.rowNode, node.rowNode),
begin, end, anchor = this.anchor;
if(cr < 0){ //current is after anchor
begin = anchor;
end = node;
}else{ //current is before anchor
begin = node;
end = anchor;
}
nodes = [];
//add everything betweeen begin and end inclusively
while(begin != end) {
nodes.push(begin)
begin = this.tree._getNextNode(begin);
}
nodes.push(end)
this.setSelection(nodes);
}else{
if( this.selection[ node.id ] && multi ) {
this.removeTreeNode( node );
} else if(multi) {
this.addTreeNode(node, true);
} else {
this.setSelection([node]);
this.anchor = node;
}
}
}
},
forInSelectedItems: function(/*Function*/ f, /*Object?*/ o){
// summary:
// Iterates over selected items;
// see `dojo.dnd.Container.forInItems()` for details
o = o || dojo.global;
for(var id in this.selection){
// console.log("selected item id: " + id);
f.call(o, this.getItem(id), id, this);
}
}
});
}
+532 -228
View File
@@ -1,240 +1,544 @@
/*
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.dndSource"]){
dojo._hasResource["dijit.tree.dndSource"]=true;
if(!dojo._hasResource["dijit.tree.dndSource"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dijit.tree.dndSource"] = true;
dojo.provide("dijit.tree.dndSource");
dojo.require("dijit.tree._dndSelector");
dojo.require("dojo.dnd.Manager");
dojo.declare("dijit.tree.dndSource",dijit.tree._dndSelector,{isSource:true,accept:["text","treeNode"],copyOnly:false,dragThreshold:5,betweenThreshold:0,constructor:function(_1,_2){
if(!_2){
_2={};
/*=====
dijit.tree.__SourceArgs = function(){
// summary:
// A dict of parameters for Tree source configuration.
// isSource: Boolean?
// Can be used as a DnD source. Defaults to true.
// accept: String[]
// List of accepted types (text strings) for a target; defaults to
// ["text", "treeNode"]
// copyOnly: Boolean?
// Copy items, if true, use a state of Ctrl key otherwise,
// dragThreshold: Number
// The move delay in pixels before detecting a drag; 0 by default
// betweenThreshold: Integer
// Distance from upper/lower edge of node to allow drop to reorder nodes
this.isSource = isSource;
this.accept = accept;
this.autoSync = autoSync;
this.copyOnly = copyOnly;
this.dragThreshold = dragThreshold;
this.betweenThreshold = betweenThreshold;
}
dojo.mixin(this,_2);
this.isSource=typeof _2.isSource=="undefined"?true:_2.isSource;
var _3=_2.accept instanceof Array?_2.accept:["text","treeNode"];
this.accept=null;
if(_3.length){
this.accept={};
for(var i=0;i<_3.length;++i){
this.accept[_3[i]]=1;
}
}
this.isDragging=false;
this.mouseDown=false;
this.targetAnchor=null;
this.targetBox=null;
this.dropPosition="";
this._lastX=0;
this._lastY=0;
this.sourceState="";
if(this.isSource){
dojo.addClass(this.node,"dojoDndSource");
}
this.targetState="";
if(this.accept){
dojo.addClass(this.node,"dojoDndTarget");
}
this.topics=[dojo.subscribe("/dnd/source/over",this,"onDndSourceOver"),dojo.subscribe("/dnd/start",this,"onDndStart"),dojo.subscribe("/dnd/drop",this,"onDndDrop"),dojo.subscribe("/dnd/cancel",this,"onDndCancel")];
},checkAcceptance:function(_4,_5){
return true;
},copyState:function(_6){
return this.copyOnly||_6;
},destroy:function(){
this.inherited("destroy",arguments);
dojo.forEach(this.topics,dojo.unsubscribe);
this.targetAnchor=null;
},_onDragMouse:function(e){
var m=dojo.dnd.manager(),_7=this.targetAnchor,_8=this.current,_9=this.currentWidget,_a=this.dropPosition;
var _b="Over";
if(_8&&this.betweenThreshold>0){
if(!this.targetBox||_7!=_8){
this.targetBox=dojo.position(_8,true);
}
if((e.pageY-this.targetBox.y)<=this.betweenThreshold){
_b="Before";
}else{
if((e.pageY-this.targetBox.y)>=(this.targetBox.h-this.betweenThreshold)){
_b="After";
}
}
}
if(_8!=_7||_b!=_a){
if(_7){
this._removeItemClass(_7,_a);
}
if(_8){
this._addItemClass(_8,_b);
}
if(!_8){
m.canDrop(false);
}else{
if(_9==this.tree.rootNode&&_b!="Over"){
m.canDrop(false);
}else{
if(m.source==this&&(_8.id in this.selection)){
m.canDrop(false);
}else{
if(this.checkItemAcceptance(_8,m.source,_b.toLowerCase())&&!this._isParentChildDrop(m.source,_8)){
m.canDrop(true);
}else{
m.canDrop(false);
}
}
}
}
this.targetAnchor=_8;
this.dropPosition=_b;
}
},onMouseMove:function(e){
if(this.isDragging&&this.targetState=="Disabled"){
return;
}
this.inherited(arguments);
var m=dojo.dnd.manager();
if(this.isDragging){
this._onDragMouse(e);
}else{
if(this.mouseDown&&this.isSource&&(Math.abs(e.pageX-this._lastX)>=this.dragThreshold||Math.abs(e.pageY-this._lastY)>=this.dragThreshold)){
var n=this.getSelectedNodes();
var _c=[];
for(var i in n){
_c.push(n[i]);
}
if(_c.length){
m.startDrag(this,_c,this.copyState(dojo.isCopyKey(e)));
}
}
}
},onMouseDown:function(e){
this.mouseDown=true;
this.mouseButton=e.button;
this._lastX=e.pageX;
this._lastY=e.pageY;
this.inherited("onMouseDown",arguments);
},onMouseUp:function(e){
if(this.mouseDown){
this.mouseDown=false;
this.inherited("onMouseUp",arguments);
}
},onMouseOut:function(){
this.inherited(arguments);
this._unmarkTargetAnchor();
},checkItemAcceptance:function(_d,_e,_f){
return true;
},onDndSourceOver:function(_10){
if(this!=_10){
this.mouseDown=false;
this._unmarkTargetAnchor();
}else{
if(this.isDragging){
var m=dojo.dnd.manager();
m.canDrop(false);
}
}
},onDndStart:function(_11,_12,_13){
if(this.isSource){
this._changeState("Source",this==_11?(_13?"Copied":"Moved"):"");
}
var _14=this.checkAcceptance(_11,_12);
this._changeState("Target",_14?"":"Disabled");
if(this==_11){
dojo.dnd.manager().overSource(this);
}
this.isDragging=true;
},itemCreator:function(_15,_16,_17){
return dojo.map(_15,function(_18){
return {"id":_18.id,"name":_18.textContent||_18.innerText||""};
=====*/
dojo.declare("dijit.tree.dndSource", dijit.tree._dndSelector, {
// summary:
// Handles drag and drop operations (as a source or a target) for `dijit.Tree`
// isSource: [private] Boolean
// Can be used as a DnD source.
isSource: true,
// accept: String[]
// List of accepted types (text strings) for the Tree; defaults to
// ["text"]
accept: ["text", "treeNode"],
// copyOnly: [private] Boolean
// Copy items, if true, use a state of Ctrl key otherwise
copyOnly: false,
// dragThreshold: Number
// The move delay in pixels before detecting a drag; 5 by default
dragThreshold: 5,
// betweenThreshold: Integer
// Distance from upper/lower edge of node to allow drop to reorder nodes
betweenThreshold: 0,
constructor: function(/*dijit.Tree*/ tree, /*dijit.tree.__SourceArgs*/ params){
// summary:
// a constructor of the Tree DnD Source
// tags:
// private
if(!params){ params = {}; }
dojo.mixin(this, params);
this.isSource = typeof params.isSource == "undefined" ? true : params.isSource;
var type = params.accept instanceof Array ? params.accept : ["text", "treeNode"];
this.accept = null;
if(type.length){
this.accept = {};
for(var i = 0; i < type.length; ++i){
this.accept[type[i]] = 1;
}
}
// class-specific variables
this.isDragging = false;
this.mouseDown = false;
this.targetAnchor = null; // DOMNode corresponding to the currently moused over TreeNode
this.targetBox = null; // coordinates of this.targetAnchor
this.dropPosition = ""; // whether mouse is over/after/before this.targetAnchor
this._lastX = 0;
this._lastY = 0;
// states
this.sourceState = "";
if(this.isSource){
dojo.addClass(this.node, "dojoDndSource");
}
this.targetState = "";
if(this.accept){
dojo.addClass(this.node, "dojoDndTarget");
}
// set up events
this.topics = [
dojo.subscribe("/dnd/source/over", this, "onDndSourceOver"),
dojo.subscribe("/dnd/start", this, "onDndStart"),
dojo.subscribe("/dnd/drop", this, "onDndDrop"),
dojo.subscribe("/dnd/cancel", this, "onDndCancel")
];
},
// methods
checkAcceptance: function(source, nodes){
// summary:
// Checks if the target can accept nodes from this source
// source: dijit.tree.dndSource
// The source which provides items
// nodes: DOMNode[]
// Array of DOM nodes corresponding to nodes being dropped, dijitTreeRow nodes if
// source is a dijit.Tree.
// tags:
// extension
return true; // Boolean
},
copyState: function(keyPressed){
// summary:
// Returns true, if we need to copy items, false to move.
// It is separated to be overwritten dynamically, if needed.
// keyPressed: Boolean
// The "copy" control key was pressed
// tags:
// protected
return this.copyOnly || keyPressed; // Boolean
},
destroy: function(){
// summary:
// Prepares the object to be garbage-collected.
this.inherited("destroy",arguments);
dojo.forEach(this.topics, dojo.unsubscribe);
this.targetAnchor = null;
},
_onDragMouse: function(e){
// summary:
// Helper method for processing onmousemove/onmouseover events while drag is in progress.
// Keeps track of current drop target.
var m = dojo.dnd.manager(),
oldTarget = this.targetAnchor, // the TreeNode corresponding to TreeNode mouse was previously over
newTarget = this.current, // TreeNode corresponding to TreeNode mouse is currently over
oldDropPosition = this.dropPosition; // the previous drop position (over/before/after)
// calculate if user is indicating to drop the dragged node before, after, or over
// (i.e., to become a child of) the target node
var newDropPosition = "Over";
if(newTarget && this.betweenThreshold > 0){
// If mouse is over a new TreeNode, then get new TreeNode's position and size
if(!this.targetBox || oldTarget != newTarget){
this.targetBox = dojo.position(newTarget.rowNode, true);
}
if((e.pageY - this.targetBox.y) <= this.betweenThreshold){
newDropPosition = "Before";
}else if((e.pageY - this.targetBox.y) >= (this.targetBox.h - this.betweenThreshold)){
newDropPosition = "After";
}
}
if(newTarget != oldTarget || newDropPosition != oldDropPosition){
if(oldTarget){
this._removeItemClass(oldTarget.rowNode, oldDropPosition);
}
if(newTarget){
this._addItemClass(newTarget.rowNode, newDropPosition);
}
// Check if it's ok to drop the dragged node on/before/after the target node.
if(!newTarget){
m.canDrop(false);
}else if(newTarget == this.tree.rootNode && newDropPosition != "Over"){
// Can't drop before or after tree's root node; the dropped node would just disappear (at least visually)
m.canDrop(false);
}else if(m.source == this && (newTarget.id in this.selection)){
// Guard against dropping onto yourself (TODO: guard against dropping onto your descendant, #7140)
m.canDrop(false);
}else if(this.checkItemAcceptance(newTarget.rowNode, m.source, newDropPosition.toLowerCase())
&& !this._isParentChildDrop(m.source, newTarget.rowNode)){
m.canDrop(true);
}else{
m.canDrop(false);
}
this.targetAnchor = newTarget;
this.dropPosition = newDropPosition;
}
},
onMouseMove: function(e){
// summary:
// Called for any onmousemove events over the Tree
// e: Event
// onmousemouse event
// tags:
// private
if(this.isDragging && this.targetState == "Disabled"){ return; }
this.inherited(arguments);
var m = dojo.dnd.manager();
if(this.isDragging){
this._onDragMouse(e);
}else{
if(this.mouseDown && this.isSource &&
(Math.abs(e.pageX-this._lastX)>=this.dragThreshold || Math.abs(e.pageY-this._lastY)>=this.dragThreshold)){
var nodes = this.getSelectedTreeNodes();
if(nodes.length){
if(nodes.length > 1){
//filter out all selected items which has one of their ancestor selected as well
var seen = this.selection, i = 0, r = [], n, p;
nextitem: while((n = nodes[i++])){
for(p = n.getParent(); p && p !== this.tree; p = p.getParent()){
if(seen[p.id]){ //parent is already selected, skip this node
continue nextitem;
}
}
//this node does not have any ancestors selected, add it
r.push(n);
}
nodes = r;
}
nodes = dojo.map(nodes, function(n){return n.domNode});
m.startDrag(this, nodes, this.copyState(dojo.isCopyKey(e)));
}
}
}
},
onMouseDown: function(e){
// summary:
// Event processor for onmousedown
// e: Event
// onmousedown event
// tags:
// private
this.mouseDown = true;
this.mouseButton = e.button;
this._lastX = e.pageX;
this._lastY = e.pageY;
this.inherited(arguments);
},
onMouseUp: function(e){
// summary:
// Event processor for onmouseup
// e: Event
// onmouseup event
// tags:
// private
if(this.mouseDown){
this.mouseDown = false;
this.inherited(arguments);
}
},
onMouseOut: function(){
// summary:
// Event processor for when mouse is moved away from a TreeNode
// tags:
// private
this.inherited(arguments);
this._unmarkTargetAnchor();
},
checkItemAcceptance: function(target, source, position){
// summary:
// Stub function to be overridden if one wants to check for the ability to drop at the node/item level
// description:
// In the base case, this is called to check if target can become a child of source.
// When betweenThreshold is set, position="before" or "after" means that we
// are asking if the source node can be dropped before/after the target node.
// target: DOMNode
// The dijitTreeRoot DOM node inside of the TreeNode that we are dropping on to
// Use dijit.getEnclosingWidget(target) to get the TreeNode.
// source: dijit.tree.dndSource
// The (set of) nodes we are dropping
// position: String
// "over", "before", or "after"
// tags:
// extension
return true;
},
// topic event processors
onDndSourceOver: function(source){
// summary:
// Topic event processor for /dnd/source/over, called when detected a current source.
// source: Object
// The dijit.tree.dndSource / dojo.dnd.Source which has the mouse over it
// tags:
// private
if(this != source){
this.mouseDown = false;
this._unmarkTargetAnchor();
}else if(this.isDragging){
var m = dojo.dnd.manager();
m.canDrop(false);
}
},
onDndStart: function(source, nodes, copy){
// summary:
// Topic event processor for /dnd/start, called to initiate the DnD operation
// source: Object
// The dijit.tree.dndSource / dojo.dnd.Source which is providing the items
// nodes: DomNode[]
// The list of transferred items, dndTreeNode nodes if dragging from a Tree
// copy: Boolean
// Copy items, if true, move items otherwise
// tags:
// private
if(this.isSource){
this._changeState("Source", this == source ? (copy ? "Copied" : "Moved") : "");
}
var accepted = this.checkAcceptance(source, nodes);
this._changeState("Target", accepted ? "" : "Disabled");
if(this == source){
dojo.dnd.manager().overSource(this);
}
this.isDragging = true;
},
itemCreator: function(/*DomNode[]*/ nodes, target, /*dojo.dnd.Source*/ source){
// summary:
// Returns objects passed to `Tree.model.newItem()` based on DnD nodes
// dropped onto the tree. Developer must override this method to enable
// dropping from external sources onto this Tree, unless the Tree.model's items
// happen to look like {id: 123, name: "Apple" } with no other attributes.
// description:
// For each node in nodes[], which came from source, create a hash of name/value
// pairs to be passed to Tree.model.newItem(). Returns array of those hashes.
// returns: Object[]
// Array of name/value hashes for each new item to be added to the Tree, like:
// | [
// | { id: 123, label: "apple", foo: "bar" },
// | { id: 456, label: "pear", zaz: "bam" }
// | ]
// tags:
// extension
// TODO: for 2.0 refactor so itemCreator() is called once per drag node, and
// make signature itemCreator(sourceItem, node, target) (or similar).
return dojo.map(nodes, function(node){
return {
"id": node.id,
"name": node.textContent || node.innerText || ""
};
}); // Object[]
},
onDndDrop: function(source, nodes, copy){
// summary:
// Topic event processor for /dnd/drop, called to finish the DnD operation.
// description:
// Updates data store items according to where node was dragged from and dropped
// to. The tree will then respond to those data store updates and redraw itself.
// source: Object
// The dijit.tree.dndSource / dojo.dnd.Source which is providing the items
// nodes: DomNode[]
// The list of transferred items, dndTreeNode nodes if dragging from a Tree
// copy: Boolean
// Copy items, if true, move items otherwise
// tags:
// protected
if(this.containerState == "Over"){
var tree = this.tree,
model = tree.model,
target = this.targetAnchor,
requeryRoot = false; // set to true iff top level items change
this.isDragging = false;
// Compute the new parent item
var targetWidget = target;
var newParentItem;
var insertIndex;
newParentItem = (targetWidget && targetWidget.item) || tree.item;
if(this.dropPosition == "Before" || this.dropPosition == "After"){
// TODO: if there is no parent item then disallow the drop.
// Actually this should be checked during onMouseMove too, to make the drag icon red.
newParentItem = (targetWidget.getParent() && targetWidget.getParent().item) || tree.item;
// Compute the insert index for reordering
insertIndex = targetWidget.getIndexInParent();
if(this.dropPosition == "After"){
insertIndex = targetWidget.getIndexInParent() + 1;
}
}else{
newParentItem = (targetWidget && targetWidget.item) || tree.item;
}
// If necessary, use this variable to hold array of hashes to pass to model.newItem()
// (one entry in the array for each dragged node).
var newItemsParams;
dojo.forEach(nodes, function(node, idx){
// dojo.dnd.Item representing the thing being dropped.
// Don't confuse the use of item here (meaning a DnD item) with the
// uses below where item means dojo.data item.
var sourceItem = source.getItem(node.id);
// Information that's available if the source is another Tree
// (possibly but not necessarily this tree, possibly but not
// necessarily the same model as this Tree)
if(dojo.indexOf(sourceItem.type, "treeNode") != -1){
var childTreeNode = sourceItem.data,
childItem = childTreeNode.item,
oldParentItem = childTreeNode.getParent().item;
}
if(source == this){
// This is a node from my own tree, and we are moving it, not copying.
// Remove item from old parent's children attribute.
// TODO: dijit.tree.dndSelector should implement deleteSelectedNodes()
// and this code should go there.
if(typeof insertIndex == "number"){
if(newParentItem == oldParentItem && childTreeNode.getIndexInParent() < insertIndex){
insertIndex -= 1;
}
}
model.pasteItem(childItem, oldParentItem, newParentItem, copy, insertIndex);
}else if(model.isItem(childItem)){
// Item from same model
// (maybe we should only do this branch if the source is a tree?)
model.pasteItem(childItem, oldParentItem, newParentItem, copy, insertIndex);
}else{
// Get the hash to pass to model.newItem(). A single call to
// itemCreator() returns an array of hashes, one for each drag source node.
if(!newItemsParams){
newItemsParams = this.itemCreator(nodes, target.rowNode, source);
}
// Create new item in the tree, based on the drag source.
model.newItem(newItemsParams[idx], newParentItem, insertIndex);
}
}, this);
// Expand the target node (if it's currently collapsed) so the user can see
// where their node was dropped. In particular since that node is still selected.
this.tree._expandNode(targetWidget);
}
this.onDndCancel();
},
onDndCancel: function(){
// summary:
// Topic event processor for /dnd/cancel, called to cancel the DnD operation
// tags:
// private
this._unmarkTargetAnchor();
this.isDragging = false;
this.mouseDown = false;
delete this.mouseButton;
this._changeState("Source", "");
this._changeState("Target", "");
},
// When focus moves in/out of the entire Tree
onOverEvent: function(){
// summary:
// This method is called when mouse is moved over our container (like onmouseenter)
// tags:
// private
this.inherited(arguments);
dojo.dnd.manager().overSource(this);
},
onOutEvent: function(){
// summary:
// This method is called when mouse is moved out of our container (like onmouseleave)
// tags:
// private
this._unmarkTargetAnchor();
var m = dojo.dnd.manager();
if(this.isDragging){
m.canDrop(false);
}
m.outSource(this);
this.inherited(arguments);
},
_isParentChildDrop: function(source, targetRow){
// summary:
// Checks whether the dragged items are parent rows in the tree which are being
// dragged into their own children.
//
// source:
// The DragSource object.
//
// targetRow:
// The tree row onto which the dragged nodes are being dropped.
//
// tags:
// private
// If the dragged object is not coming from the tree this widget belongs to,
// it cannot be invalid.
if(!source.tree || source.tree != this.tree){
return false;
}
var root = source.tree.domNode;
var ids = source.selection;
var node = targetRow.parentNode;
// Iterate up the DOM hierarchy from the target drop row,
// checking of any of the dragged nodes have the same ID.
while(node != root && !ids[node.id]){
node = node.parentNode;
}
return node.id && ids[node.id];
},
_unmarkTargetAnchor: function(){
// summary:
// Removes hover class of the current target anchor
// tags:
// private
if(!this.targetAnchor){ return; }
this._removeItemClass(this.targetAnchor.rowNode, this.dropPosition);
this.targetAnchor = null;
this.targetBox = null;
this.dropPosition = null;
},
_markDndStatus: function(copy){
// summary:
// Changes source's state based on "copy" status
this._changeState("Source", copy ? "Copied" : "Moved");
}
});
},onDndDrop:function(_19,_1a,_1b){
if(this.containerState=="Over"){
var _1c=this.tree,_1d=_1c.model,_1e=this.targetAnchor,_1f=false;
this.isDragging=false;
var _20=dijit.getEnclosingWidget(_1e);
var _21;
var _22;
_21=(_20&&_20.item)||_1c.item;
if(this.dropPosition=="Before"||this.dropPosition=="After"){
_21=(_20.getParent()&&_20.getParent().item)||_1c.item;
_22=_20.getIndexInParent();
if(this.dropPosition=="After"){
_22=_20.getIndexInParent()+1;
}
}else{
_21=(_20&&_20.item)||_1c.item;
}
var _23;
dojo.forEach(_1a,function(_24,idx){
var _25=_19.getItem(_24.id);
if(dojo.indexOf(_25.type,"treeNode")!=-1){
var _26=_25.data,_27=_26.item,_28=_26.getParent().item;
}
if(_19==this){
if(typeof _22=="number"){
if(_21==_28&&_26.getIndexInParent()<_22){
_22-=1;
}
}
_1d.pasteItem(_27,_28,_21,_1b,_22);
}else{
if(_1d.isItem(_27)){
_1d.pasteItem(_27,_28,_21,_1b,_22);
}else{
if(!_23){
_23=this.itemCreator(_1a,_1e,_19);
}
_1d.newItem(_23[idx],_21,_22);
}
}
},this);
this.tree._expandNode(_20);
}
this.onDndCancel();
},onDndCancel:function(){
this._unmarkTargetAnchor();
this.isDragging=false;
this.mouseDown=false;
delete this.mouseButton;
this._changeState("Source","");
this._changeState("Target","");
},onOverEvent:function(){
this.inherited(arguments);
dojo.dnd.manager().overSource(this);
},onOutEvent:function(){
this._unmarkTargetAnchor();
var m=dojo.dnd.manager();
if(this.isDragging){
m.canDrop(false);
}
m.outSource(this);
this.inherited(arguments);
},_isParentChildDrop:function(_29,_2a){
if(!_29.tree||_29.tree!=this.tree){
return false;
}
var _2b=_29.tree.domNode;
var ids={};
for(var x in _29.selection){
ids[_29.selection[x].parentNode.id]=true;
}
var _2c=_2a.parentNode;
while(_2c!=_2b&&(!_2c.id||!ids[_2c.id])){
_2c=_2c.parentNode;
}
return _2c.id&&ids[_2c.id];
},_unmarkTargetAnchor:function(){
if(!this.targetAnchor){
return;
}
this._removeItemClass(this.targetAnchor,this.dropPosition);
this.targetAnchor=null;
this.targetBox=null;
this.dropPosition=null;
},_markDndStatus:function(_2d){
this._changeState("Source",_2d?"Copied":"Moved");
}});
}
+129 -14
View File
@@ -1,20 +1,135 @@
/*
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
*/
dojo.declare("dijit.tree.model",null,{destroy:function(){
},getRoot:function(_1){
},mayHaveChildren:function(_2){
},getChildren:function(_3,_4){
},isItem:function(_5){
},fetchItemByIdentity:function(_6){
},getIdentity:function(_7){
},getLabel:function(_8){
},newItem:function(_9,_a,_b){
},pasteItem:function(_c,_d,_e,_f){
},onChange:function(_10){
},onChildrenChange:function(_11,_12){
}});
dojo.declare(
"dijit.tree.model",
null,
{
// summary:
// Contract for any data provider object for the tree.
// description:
// Tree passes in values to the constructor to specify the callbacks.
// "item" is typically a dojo.data.Item but it's just a black box so
// it could be anything.
//
// This (like `dojo.data.api.Read`) is just documentation, and not meant to be used.
destroy: function(){
// summary:
// Destroys this object, releasing connections to the store
// tags:
// extension
},
// =======================================================================
// Methods for traversing hierarchy
getRoot: function(onItem){
// summary:
// Calls onItem with the root item for the tree, possibly a fabricated item.
// Throws exception on error.
// tags:
// extension
},
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
},
getChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ onComplete){
// summary:
// Calls onComplete() with array of child items of given parent item, all loaded.
// Throws exception on error.
// tags:
// extension
},
// =======================================================================
// Inspecting items
isItem: function(/* anything */ something){
// summary:
// Returns true if *something* is an item and came from this model instance.
// Returns false if *something* is a literal, an item from another model instance,
// or is any object other than an item.
// tags:
// extension
},
fetchItemByIdentity: function(/* object */ keywordArgs){
// summary:
// Given the identity of an item, this method returns the item that has
// that identity through the onItem callback. Conforming implementations
// should return null if there is no item with the given identity.
// Implementations of fetchItemByIdentity() may sometimes return an item
// from a local cache and may sometimes fetch an item from a remote server.
// tags:
// extension
},
getIdentity: function(/* item */ item){
// summary:
// Returns identity for an item
// tags:
// extension
},
getLabel: function(/*dojo.data.Item*/ item){
// summary:
// Get the label for an item
// tags:
// extension
},
// =======================================================================
// 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.
// tags:
// extension
},
pasteItem: function(/*Item*/ childItem, /*Item*/ oldParentItem, /*Item*/ newParentItem, /*Boolean*/ bCopy){
// summary:
// Move or copy an item from one parent item to another.
// Used in drag & drop.
// If oldParentItem is specified and bCopy is false, childItem is removed from oldParentItem.
// If newParentItem is specified, childItem is attached to newParentItem.
// tags:
// extension
},
// =======================================================================
// 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
}
});