From f0cfe83e3725f9a3928da97a6e3085e79cb25309 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Mon, 18 Mar 2013 10:26:24 +0400 Subject: upgrade dojo to 1.8.3 (refs #570) --- lib/dijit/tree/ForestStoreModel.js | 2 +- lib/dijit/tree/ForestStoreModel.js.uncompressed.js | 279 ++++++++++ lib/dijit/tree/ObjectStoreModel.js | 2 + lib/dijit/tree/ObjectStoreModel.js.uncompressed.js | 250 +++++++++ lib/dijit/tree/TreeStoreModel.js | 2 +- lib/dijit/tree/TreeStoreModel.js.uncompressed.js | 379 ++++++++++++++ lib/dijit/tree/_dndContainer.js | 2 +- lib/dijit/tree/_dndContainer.js.uncompressed.js | 165 ++++++ lib/dijit/tree/_dndSelector.js | 2 +- lib/dijit/tree/_dndSelector.js.uncompressed.js | 356 +++++++++++++ lib/dijit/tree/dndSource.js | 2 +- lib/dijit/tree/dndSource.js.uncompressed.js | 564 +++++++++++++++++++++ lib/dijit/tree/model.js | 1 + lib/dijit/tree/model.js.uncompressed.js | 150 ++++++ 14 files changed, 2151 insertions(+), 5 deletions(-) create mode 100644 lib/dijit/tree/ForestStoreModel.js.uncompressed.js create mode 100644 lib/dijit/tree/ObjectStoreModel.js create mode 100644 lib/dijit/tree/ObjectStoreModel.js.uncompressed.js create mode 100644 lib/dijit/tree/TreeStoreModel.js.uncompressed.js create mode 100644 lib/dijit/tree/_dndContainer.js.uncompressed.js create mode 100644 lib/dijit/tree/_dndSelector.js.uncompressed.js create mode 100644 lib/dijit/tree/dndSource.js.uncompressed.js create mode 100644 lib/dijit/tree/model.js.uncompressed.js (limited to 'lib/dijit/tree') diff --git a/lib/dijit/tree/ForestStoreModel.js b/lib/dijit/tree/ForestStoreModel.js index d0b895335..c8520788b 100644 --- a/lib/dijit/tree/ForestStoreModel.js +++ b/lib/dijit/tree/ForestStoreModel.js @@ -1,2 +1,2 @@ //>>built -define("dijit/tree/ForestStoreModel",["dojo/_base/array","dojo/_base/declare","dojo/_base/lang","dojo/_base/window","./TreeStoreModel"],function(_1,_2,_3,_4,_5){return _2("dijit.tree.ForestStoreModel",_5,{rootId:"$root$",rootLabel:"ROOT",query:null,constructor:function(_6){this.root={store:this,root:true,id:_6.rootId,label:_6.rootLabel,children:_6.rootChildren};},mayHaveChildren:function(_7){return _7===this.root||this.inherited(arguments);},getChildren:function(_8,_9,_a){if(_8===this.root){if(this.root.children){_9(this.root.children);}else{this.store.fetch({query:this.query,onComplete:_3.hitch(this,function(_b){this.root.children=_b;_9(_b);}),onError:_a});}}else{this.inherited(arguments);}},isItem:function(_c){return (_c===this.root)?true:this.inherited(arguments);},fetchItemByIdentity:function(_d){if(_d.identity==this.root.id){var _e=_d.scope?_d.scope:_4.global;if(_d.onItem){_d.onItem.call(_e,this.root);}}else{this.inherited(arguments);}},getIdentity:function(_f){return (_f===this.root)?this.root.id:this.inherited(arguments);},getLabel:function(_10){return (_10===this.root)?this.root.label:this.inherited(arguments);},newItem:function(_11,_12,_13){if(_12===this.root){this.onNewRootItem(_11);return this.store.newItem(_11);}else{return this.inherited(arguments);}},onNewRootItem:function(){},pasteItem:function(_14,_15,_16,_17,_18){if(_15===this.root){if(!_17){this.onLeaveRoot(_14);}}this.inherited(arguments,[_14,_15===this.root?null:_15,_16===this.root?null:_16,_17,_18]);if(_16===this.root){this.onAddToRoot(_14);}},onAddToRoot:function(_19){},onLeaveRoot:function(_1a){},_requeryTop:function(){var _1b=this.root.children||[];this.store.fetch({query:this.query,onComplete:_3.hitch(this,function(_1c){this.root.children=_1c;if(_1b.length!=_1c.length||_1.some(_1b,function(_1d,idx){return _1c[idx]!=_1d;})){this.onChildrenChange(this.root,_1c);}})});},onNewItem:function(_1e,_1f){this._requeryTop();this.inherited(arguments);},onDeleteItem:function(_20){if(_1.indexOf(this.root.children,_20)!=-1){this._requeryTop();}this.inherited(arguments);},onSetItem:function(_21,_22,_23,_24){this._requeryTop();this.inherited(arguments);}});}); \ No newline at end of file +define("dijit/tree/ForestStoreModel",["dojo/_base/array","dojo/_base/declare","dojo/_base/kernel","dojo/_base/lang","./TreeStoreModel"],function(_1,_2,_3,_4,_5){return _2("dijit.tree.ForestStoreModel",_5,{rootId:"$root$",rootLabel:"ROOT",query:null,constructor:function(_6){this.root={store:this,root:true,id:_6.rootId,label:_6.rootLabel,children:_6.rootChildren};},mayHaveChildren:function(_7){return _7===this.root||this.inherited(arguments);},getChildren:function(_8,_9,_a){if(_8===this.root){if(this.root.children){_9(this.root.children);}else{this.store.fetch({query:this.query,onComplete:_4.hitch(this,function(_b){this.root.children=_b;_9(_b);}),onError:_a});}}else{this.inherited(arguments);}},isItem:function(_c){return (_c===this.root)?true:this.inherited(arguments);},fetchItemByIdentity:function(_d){if(_d.identity==this.root.id){var _e=_d.scope||_3.global;if(_d.onItem){_d.onItem.call(_e,this.root);}}else{this.inherited(arguments);}},getIdentity:function(_f){return (_f===this.root)?this.root.id:this.inherited(arguments);},getLabel:function(_10){return (_10===this.root)?this.root.label:this.inherited(arguments);},newItem:function(_11,_12,_13){if(_12===this.root){this.onNewRootItem(_11);return this.store.newItem(_11);}else{return this.inherited(arguments);}},onNewRootItem:function(){},pasteItem:function(_14,_15,_16,_17,_18){if(_15===this.root){if(!_17){this.onLeaveRoot(_14);}}this.inherited(arguments,[_14,_15===this.root?null:_15,_16===this.root?null:_16,_17,_18]);if(_16===this.root){this.onAddToRoot(_14);}},onAddToRoot:function(_19){},onLeaveRoot:function(_1a){},_requeryTop:function(){var _1b=this.root.children||[];this.store.fetch({query:this.query,onComplete:_4.hitch(this,function(_1c){this.root.children=_1c;if(_1b.length!=_1c.length||_1.some(_1b,function(_1d,idx){return _1c[idx]!=_1d;})){this.onChildrenChange(this.root,_1c);}})});},onNewItem:function(_1e,_1f){this._requeryTop();this.inherited(arguments);},onDeleteItem:function(_20){if(_1.indexOf(this.root.children,_20)!=-1){this._requeryTop();}this.inherited(arguments);},onSetItem:function(_21,_22,_23,_24){this._requeryTop();this.inherited(arguments);}});}); \ No newline at end of file diff --git a/lib/dijit/tree/ForestStoreModel.js.uncompressed.js b/lib/dijit/tree/ForestStoreModel.js.uncompressed.js new file mode 100644 index 000000000..2ef8d1546 --- /dev/null +++ b/lib/dijit/tree/ForestStoreModel.js.uncompressed.js @@ -0,0 +1,279 @@ +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); + } + +}); + +}); diff --git a/lib/dijit/tree/ObjectStoreModel.js b/lib/dijit/tree/ObjectStoreModel.js new file mode 100644 index 000000000..7021bec52 --- /dev/null +++ b/lib/dijit/tree/ObjectStoreModel.js @@ -0,0 +1,2 @@ +//>>built +define("dijit/tree/ObjectStoreModel",["dojo/_base/array","dojo/aspect","dojo/_base/declare","dojo/_base/lang","dojo/when"],function(_1,_2,_3,_4,_5){return _3("dijit.tree.ObjectStoreModel",null,{store:null,labelAttr:"name",root:null,query:null,constructor:function(_6){_4.mixin(this,_6);this.childrenCache={};},destroy:function(){for(var id in this.childrenCache){this.childrenCache[id].close&&this.childrenCache[id].close();}},getRoot:function(_7,_8){if(this.root){_7(this.root);}else{var _9;_5(_9=this.store.query(this.query),_4.hitch(this,function(_a){if(_a.length!=1){throw new Error("dijit.tree.ObjectStoreModel: root query returned "+_a.length+" items, but must return exactly one");}this.root=_a[0];_7(this.root);if(_9.observe){_9.observe(_4.hitch(this,function(_b){this.onChange(_b);}),true);}}),_8);}},mayHaveChildren:function(){return true;},getChildren:function(_c,_d,_e){var id=this.store.getIdentity(_c);if(this.childrenCache[id]){_5(this.childrenCache[id],_d,_e);return;}var _f=this.childrenCache[id]=this.store.getChildren(_c);_5(_f,_d,_e);if(_f.observe){_f.observe(_4.hitch(this,function(obj,_10,_11){this.onChange(obj);if(_10!=_11){_5(_f,_4.hitch(this,"onChildrenChange",_c));}}),true);}},isItem:function(){return true;},fetchItemByIdentity:function(_12){this.store.get(_12.identity).then(_4.hitch(_12.scope,_12.onItem),_4.hitch(_12.scope,_12.onError));},getIdentity:function(_13){return this.store.getIdentity(_13);},getLabel:function(_14){return _14[this.labelAttr];},newItem:function(_15,_16,_17,_18){return this.store.put(_15,{parent:_16,before:_18});},pasteItem:function(_19,_1a,_1b,_1c,_1d,_1e){if(!_1c){var _1f=[].concat(this.childrenCache[this.getIdentity(_1a)]),_20=_1.indexOf(_1f,_19);_1f.splice(_20,1);this.onChildrenChange(_1a,_1f);}return this.store.put(_19,{overwrite:true,parent:_1b,before:_1e});},onChange:function(){},onChildrenChange:function(){},onDelete:function(){}});}); \ No newline at end of file diff --git a/lib/dijit/tree/ObjectStoreModel.js.uncompressed.js b/lib/dijit/tree/ObjectStoreModel.js.uncompressed.js new file mode 100644 index 000000000..901f85f1b --- /dev/null +++ b/lib/dijit/tree/ObjectStoreModel.js.uncompressed.js @@ -0,0 +1,250 @@ +define("dijit/tree/ObjectStoreModel", [ + "dojo/_base/array", // array.filter array.forEach array.indexOf array.some + "dojo/aspect", // aspect.before, aspect.after + "dojo/_base/declare", // declare + "dojo/_base/lang", // lang.hitch + "dojo/when" +], function(array, aspect, declare, lang, when){ + + // module: + // dijit/tree/ObjectStoreModel + + return declare("dijit.tree.ObjectStoreModel", null, { + // summary: + // Implements dijit/tree/model connecting dijit/Tree to a dojo/store/api/Store that implements + // getChildren(). + // + // If getChildren() returns an array with an observe() method, then it will be leveraged to reflect + // store updates to the tree. So, this class will work best when: + // + // 1. the store implements dojo/store/Observable + // 2. getChildren() is implemented as a query to the server (i.e. it calls store.query()) + // + // Drag and Drop: To support drag and drop, besides implementing getChildren() + // and dojo/store/Observable, the store must support the parent option to put(). + // And in order to have child elements ordered according to how the user dropped them, + // put() must support the before option. + + // store: dojo/store/api/Store + // Underlying store + store: null, + + // labelAttr: String + // Get label for tree node from this attribute + labelAttr: "name", + + // root: [readonly] Object + // Pointer to the root item from the dojo/store/api/Store (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 + + lang.mixin(this, args); + + this.childrenCache = {}; // map from id to array of children + }, + + destroy: function(){ + // TODO: should cancel any in-progress processing of getRoot(), getChildren() + for(var id in this.childrenCache){ + this.childrenCache[id].close && this.childrenCache[id].close(); + } + }, + + // ======================================================================= + // 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{ + var res; + when(res = this.store.query(this.query), + lang.hitch(this, function(items){ + //console.log("queried root: ", res); + if(items.length != 1){ + throw new Error("dijit.tree.ObjectStoreModel: root query returned " + items.length + + " items, but must return exactly one"); + } + this.root = items[0]; + onItem(this.root); + + // Setup listener to detect if root item changes + if(res.observe){ + res.observe(lang.hitch(this, function(obj){ + // Presumably removedFrom == insertedInto == 1, and this call indicates item has changed. + //console.log("root changed: ", obj); + this.onChange(obj); + }), true); // true to listen for updates to obj + } + }), + onError + ); + } + }, + + mayHaveChildren: function(/*===== 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). + // + // Application code should override this method based on the data, for example + // it could be `return item.leaf == true;`. + // item: Object + // Item from the dojo/store + return true; + }, + + getChildren: function(/*Object*/ parentItem, /*function(items)*/ onComplete, /*function*/ onError){ + // summary: + // Calls onComplete() with array of child items of given parent item. + // parentItem: + // Item from the dojo/store + + var id = this.store.getIdentity(parentItem); + if(this.childrenCache[id]){ + when(this.childrenCache[id], onComplete, onError); + return; + } + + var res = this.childrenCache[id] = this.store.getChildren(parentItem); + + // User callback + when(res, onComplete, onError); + + // Setup listener in case children list changes, or the item(s) in the children list are + // updated in some way. + if(res.observe){ + res.observe(lang.hitch(this, function(obj, removedFrom, insertedInto){ + //console.log("observe on children of ", id, ": ", obj, removedFrom, insertedInto); + + // If removedFrom == insertedInto, this call indicates that the item has changed. + // Even if removedFrom != insertedInto, the item may have changed. + this.onChange(obj); + + if(removedFrom != insertedInto){ + // Indicates an item was added, removed, or re-parented. The children[] array (returned from + // res.then(...)) has already been updated (like a live collection), so just use it. + when(res, lang.hitch(this, "onChildrenChange", parentItem)); + } + }), true); // true means to notify on item changes + } + }, + + // ======================================================================= + // Inspecting items + + isItem: function(/*===== something =====*/){ + return true; // Boolean + }, + + fetchItemByIdentity: function(/* object */ keywordArgs){ + this.store.get(keywordArgs.identity).then( + lang.hitch(keywordArgs.scope, keywordArgs.onItem), + lang.hitch(keywordArgs.scope, keywordArgs.onError) + ); + }, + + getIdentity: function(/* item */ item){ + return this.store.getIdentity(item); // Object + }, + + getLabel: function(/*dojo/data/Item*/ item){ + // summary: + // Get the label for an item + return item[this.labelAttr]; // String + }, + + // ======================================================================= + // Write interface, for DnD + + newItem: function(/* dijit/tree/dndSource.__Item */ args, /*Item*/ parent, /*int?*/ insertIndex, /*Item*/ before){ + // 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. + + return this.store.put(args, { + parent: parent, + before: before + }); + }, + + pasteItem: function(/*Item*/ childItem, /*Item*/ oldParentItem, /*Item*/ newParentItem, + /*Boolean*/ bCopy, /*int?*/ insertIndex, /*Item*/ before){ + // summary: + // Move or copy an item from one parent item to another. + // Used in drag & drop + + if(!bCopy){ + // In order for DnD moves to work correctly, childItem needs to be orphaned from oldParentItem + // before being adopted by newParentItem. That way, the TreeNode is moved rather than + // an additional TreeNode being created, and the old TreeNode subsequently being deleted. + // The latter loses information such as selection and opened/closed children TreeNodes. + // Unfortunately simply calling this.store.put() will send notifications in a random order, based + // on when the TreeNodes in question originally appeared, and not based on the drag-from + // TreeNode vs. the drop-onto TreeNode. + + var oldParentChildren = [].concat(this.childrenCache[this.getIdentity(oldParentItem)]), // concat to make copy + index = array.indexOf(oldParentChildren, childItem); + oldParentChildren.splice(index, 1); + this.onChildrenChange(oldParentItem, oldParentChildren); + } + + return this.store.put(childItem, { + overwrite: true, + parent: newParentItem, + before: before + }); + }, + + // ======================================================================= + // 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(/*===== parent, newChildrenList =====*/){ + // summary: + // Callback to do notifications about new, updated, or deleted items. + // parent: dojo/data/Item + // newChildrenList: Object[] + // Items from the store + // tags: + // callback + }, + + onDelete: function(/*dojo/data/Item*/ /*===== item =====*/){ + // summary: + // Callback when an item has been deleted. + // Actually we have no way of knowing this with the new dojo.store API, + // so this method is never called (but it's left here since Tree connects + // to it). + // tags: + // callback + } + }); +}); diff --git a/lib/dijit/tree/TreeStoreModel.js b/lib/dijit/tree/TreeStoreModel.js index f61977c2f..bba7dc74c 100644 --- a/lib/dijit/tree/TreeStoreModel.js +++ b/lib/dijit/tree/TreeStoreModel.js @@ -1,2 +1,2 @@ //>>built -define("dijit/tree/TreeStoreModel",["dojo/_base/array","dojo/aspect","dojo/_base/declare","dojo/_base/json","dojo/_base/lang"],function(_1,_2,_3,_4,_5){return _3("dijit.tree.TreeStoreModel",null,{store:null,childrenAttrs:["children"],newItemIdAttr:"id",labelAttr:"",root:null,query:null,deferItemLoadingUntilExpand:false,constructor:function(_6){_5.mixin(this,_6);this.connects=[];var _7=this.store;if(!_7.getFeatures()["dojo.data.api.Identity"]){throw new Error("dijit.Tree: store must support dojo.data.Identity");}if(_7.getFeatures()["dojo.data.api.Notification"]){this.connects=this.connects.concat([_2.after(_7,"onNew",_5.hitch(this,"onNewItem"),true),_2.after(_7,"onDelete",_5.hitch(this,"onDeleteItem"),true),_2.after(_7,"onSet",_5.hitch(this,"onSetItem"),true)]);}},destroy:function(){var h;while(h=this.connects.pop()){h.remove();}},getRoot:function(_8,_9){if(this.root){_8(this.root);}else{this.store.fetch({query:this.query,onComplete:_5.hitch(this,function(_a){if(_a.length!=1){throw new Error(this.declaredClass+": query "+_4.stringify(this.query)+" returned "+_a.length+" items, but must return exactly one item");}this.root=_a[0];_8(this.root);}),onError:_9});}},mayHaveChildren:function(_b){return _1.some(this.childrenAttrs,function(_c){return this.store.hasAttribute(_b,_c);},this);},getChildren:function(_d,_e,_f){var _10=this.store;if(!_10.isItemLoaded(_d)){var _11=_5.hitch(this,arguments.callee);_10.loadItem({item:_d,onItem:function(_12){_11(_12,_e,_f);},onError:_f});return;}var _13=[];for(var i=0;i>built -define("dijit/tree/_dndContainer",["dojo/aspect","dojo/_base/declare","dojo/dom-class","dojo/_base/event","dojo/_base/lang","dojo/mouse","dojo/on"],function(_1,_2,_3,_4,_5,_6,on){return _2("dijit.tree._dndContainer",null,{constructor:function(_7,_8){this.tree=_7;this.node=_7.domNode;_5.mixin(this,_8);this.current=null;this.containerState="";_3.add(this.node,"dojoDndContainer");this.events=[on(this.node,_6.enter,_5.hitch(this,"onOverEvent")),on(this.node,_6.leave,_5.hitch(this,"onOutEvent")),_1.after(this.tree,"_onNodeMouseEnter",_5.hitch(this,"onMouseOver"),true),_1.after(this.tree,"_onNodeMouseLeave",_5.hitch(this,"onMouseOut"),true),on(this.node,"dragstart",_5.hitch(_4,"stop")),on(this.node,"selectstart",_5.hitch(_4,"stop"))];},destroy:function(){var h;while(h=this.events.pop()){h.remove();}this.node=this.parent=null;},onMouseOver:function(_9){this.current=_9;},onMouseOut:function(){this.current=null;},_changeState:function(_a,_b){var _c="dojoDnd"+_a;var _d=_a.toLowerCase()+"State";_3.replace(this.node,_c+_b,_c+this[_d]);this[_d]=_b;},_addItemClass:function(_e,_f){_3.add(_e,"dojoDndItem"+_f);},_removeItemClass:function(_10,_11){_3.remove(_10,"dojoDndItem"+_11);},onOverEvent:function(){this._changeState("Container","Over");},onOutEvent:function(){this._changeState("Container","");}});}); \ No newline at end of file +define("dijit/tree/_dndContainer",["dojo/aspect","dojo/_base/declare","dojo/dom-class","dojo/_base/event","dojo/_base/lang","dojo/on","dojo/touch"],function(_1,_2,_3,_4,_5,on,_6){return _2("dijit.tree._dndContainer",null,{constructor:function(_7,_8){this.tree=_7;this.node=_7.domNode;_5.mixin(this,_8);this.current=null;this.containerState="";_3.add(this.node,"dojoDndContainer");this.events=[on(this.node,_6.enter,_5.hitch(this,"onOverEvent")),on(this.node,_6.leave,_5.hitch(this,"onOutEvent")),_1.after(this.tree,"_onNodeMouseEnter",_5.hitch(this,"onMouseOver"),true),_1.after(this.tree,"_onNodeMouseLeave",_5.hitch(this,"onMouseOut"),true),on(this.node,"dragstart",_5.hitch(_4,"stop")),on(this.node,"selectstart",_5.hitch(_4,"stop"))];},destroy:function(){var h;while(h=this.events.pop()){h.remove();}this.node=this.parent=null;},onMouseOver:function(_9){this.current=_9;},onMouseOut:function(){this.current=null;},_changeState:function(_a,_b){var _c="dojoDnd"+_a;var _d=_a.toLowerCase()+"State";_3.replace(this.node,_c+_b,_c+this[_d]);this[_d]=_b;},_addItemClass:function(_e,_f){_3.add(_e,"dojoDndItem"+_f);},_removeItemClass:function(_10,_11){_3.remove(_10,"dojoDndItem"+_11);},onOverEvent:function(){this._changeState("Container","Over");},onOutEvent:function(){this._changeState("Container","");}});}); \ No newline at end of file diff --git a/lib/dijit/tree/_dndContainer.js.uncompressed.js b/lib/dijit/tree/_dndContainer.js.uncompressed.js new file mode 100644 index 000000000..c4ad84d01 --- /dev/null +++ b/lib/dijit/tree/_dndContainer.js.uncompressed.js @@ -0,0 +1,165 @@ +define("dijit/tree/_dndContainer", [ + "dojo/aspect", // aspect.after + "dojo/_base/declare", // declare + "dojo/dom-class", // domClass.add domClass.remove domClass.replace + "dojo/_base/event", // event.stop + "dojo/_base/lang", // lang.mixin lang.hitch + "dojo/on", + "dojo/touch" +], function(aspect, declare,domClass, event, lang, on, touch){ + + // module: + // dijit/tree/_dndContainer + + /*===== + var __Args = { + // 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 + }; + =====*/ + + return 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: __Args + // 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 + lang.mixin(this, params); + + // class-specific variables + this.current = null; // current TreeNode's DOM node + + // states + this.containerState = ""; + domClass.add(this.node, "dojoDndContainer"); + + // set up events + this.events = [ + // Mouse (or touch) enter/leave on Tree itself + on(this.node, touch.enter, lang.hitch(this, "onOverEvent")), + on(this.node, touch.leave, lang.hitch(this, "onOutEvent")), + + // switching between TreeNodes + aspect.after(this.tree, "_onNodeMouseEnter", lang.hitch(this, "onMouseOver"), true), + aspect.after(this.tree, "_onNodeMouseLeave", lang.hitch(this, "onMouseOut"), true), + + // cancel text selection and text dragging + on(this.node, "dragstart", lang.hitch(event, "stop")), + on(this.node, "selectstart", lang.hitch(event, "stop")) + ]; + }, + + destroy: function(){ + // summary: + // Prepares this object to be garbage-collected + + var h; + while(h = this.events.pop()){ h.remove(); } + + // this.clearItems(); + this.node = this.parent = null; + }, + + // mouse events + onMouseOver: function(widget /*===== , evt =====*/){ + // summary: + // Called when mouse is moved over a TreeNode + // widget: TreeNode + // evt: Event + // tags: + // protected + this.current = widget; + }, + + onMouseOut: function(/*===== widget, evt =====*/){ + // summary: + // Called when mouse is moved away from a TreeNode + // widget: TreeNode + // evt: Event + // 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"; + //domClass.replace(this.node, prefix + newState, prefix + this[state]); + domClass.replace(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 + domClass.add(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 + domClass.remove(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", ""); + } + }); +}); diff --git a/lib/dijit/tree/_dndSelector.js b/lib/dijit/tree/_dndSelector.js index f0567444f..2bb8bb1ed 100644 --- a/lib/dijit/tree/_dndSelector.js +++ b/lib/dijit/tree/_dndSelector.js @@ -1,2 +1,2 @@ //>>built -define("dijit/tree/_dndSelector",["dojo/_base/array","dojo/_base/connect","dojo/_base/declare","dojo/_base/lang","dojo/mouse","dojo/on","dojo/touch","dojo/_base/window","./_dndContainer"],function(_1,_2,_3,_4,_5,on,_6,_7,_8){return _3("dijit.tree._dndSelector",_8,{constructor:function(){this.selection={};this.anchor=null;this.tree.domNode.setAttribute("aria-multiselect",!this.singular);this.events.push(on(this.tree.domNode,_6.press,_4.hitch(this,"onMouseDown")),on(this.tree.domNode,_6.release,_4.hitch(this,"onMouseUp")),on(this.tree.domNode,_6.move,_4.hitch(this,"onMouseMove")));},singular:false,getSelectedTreeNodes:function(){var _9=[],_a=this.selection;for(var i in _a){_9.push(_a[i]);}return _9;},selectNone:function(){this.setSelection([]);return this;},destroy:function(){this.inherited(arguments);this.selection=this.anchor=null;},addTreeNode:function(_b,_c){this.setSelection(this.getSelectedTreeNodes().concat([_b]));if(_c){this.anchor=_b;}return _b;},removeTreeNode:function(_d){this.setSelection(this._setDifference(this.getSelectedTreeNodes(),[_d]));return _d;},isTreeNodeSelected:function(_e){return _e.id&&!!this.selection[_e.id];},setSelection:function(_f){var _10=this.getSelectedTreeNodes();_1.forEach(this._setDifference(_10,_f),_4.hitch(this,function(_11){_11.setSelected(false);if(this.anchor==_11){delete this.anchor;}delete this.selection[_11.id];}));_1.forEach(this._setDifference(_f,_10),_4.hitch(this,function(_12){_12.setSelected(true);this.selection[_12.id]=_12;}));this._updateSelectionProperties();},_setDifference:function(xs,ys){_1.forEach(ys,function(y){y.__exclude__=true;});var ret=_1.filter(xs,function(x){return !x.__exclude__;});_1.forEach(ys,function(y){delete y["__exclude__"];});return ret;},_updateSelectionProperties:function(){var _13=this.getSelectedTreeNodes();var _14=[],_15=[];_1.forEach(_13,function(_16){_15.push(_16);_14.push(_16.getTreePath());});var _17=_1.map(_15,function(_18){return _18.item;});this.tree._set("paths",_14);this.tree._set("path",_14[0]||[]);this.tree._set("selectedNodes",_15);this.tree._set("selectedNode",_15[0]||null);this.tree._set("selectedItems",_17);this.tree._set("selectedItem",_17[0]||null);},onMouseDown:function(e){if(!this.current||this.tree.isExpandoNode(e.target,this.current)){return;}if(!_5.isLeft(e)){return;}e.preventDefault();var _19=this.current,_1a=_2.isCopyKey(e),id=_19.id;if(!this.singular&&!e.shiftKey&&this.selection[id]){this._doDeselect=true;return;}else{this._doDeselect=false;}this.userSelect(_19,_1a,e.shiftKey);},onMouseUp:function(e){if(!this._doDeselect){return;}this._doDeselect=false;this.userSelect(this.current,_2.isCopyKey(e),e.shiftKey);},onMouseMove:function(){this._doDeselect=false;},_compareNodes:function(n1,n2){if(n1===n2){return 0;}if("sourceIndex" in document.documentElement){return n1.sourceIndex-n2.sourceIndex;}else{if("compareDocumentPosition" in document.documentElement){return n1.compareDocumentPosition(n2)&2?1:-1;}else{if(document.createRange){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");}}}},userSelect:function(_1b,_1c,_1d){if(this.singular){if(this.anchor==_1b&&_1c){this.selectNone();}else{this.setSelection([_1b]);this.anchor=_1b;}}else{if(_1d&&this.anchor){var cr=this._compareNodes(this.anchor.rowNode,_1b.rowNode),_1e,end,_1f=this.anchor;if(cr<0){_1e=_1f;end=_1b;}else{_1e=_1b;end=_1f;}var _20=[];while(_1e!=end){_20.push(_1e);_1e=this.tree._getNextNode(_1e);}_20.push(end);this.setSelection(_20);}else{if(this.selection[_1b.id]&&_1c){this.removeTreeNode(_1b);}else{if(_1c){this.addTreeNode(_1b,true);}else{this.setSelection([_1b]);this.anchor=_1b;}}}}},getItem:function(key){var _21=this.selection[key];return {data:_21,type:["treeNode"]};},forInSelectedItems:function(f,o){o=o||_7.global;for(var id in this.selection){f.call(o,this.getItem(id),id,this);}}});}); \ No newline at end of file +define("dijit/tree/_dndSelector",["dojo/_base/array","dojo/_base/connect","dojo/_base/declare","dojo/_base/Deferred","dojo/_base/kernel","dojo/_base/lang","dojo/cookie","dojo/mouse","dojo/on","dojo/touch","./_dndContainer"],function(_1,_2,_3,_4,_5,_6,_7,_8,on,_9,_a){return _3("dijit.tree._dndSelector",_a,{constructor:function(){this.selection={};this.anchor=null;if(!this.cookieName&&this.tree.id){this.cookieName=this.tree.id+"SaveSelectedCookie";}this.events.push(on(this.tree.domNode,_9.press,_6.hitch(this,"onMouseDown")),on(this.tree.domNode,_9.release,_6.hitch(this,"onMouseUp")),on(this.tree.domNode,_9.move,_6.hitch(this,"onMouseMove")));},singular:false,getSelectedTreeNodes:function(){var _b=[],_c=this.selection;for(var i in _c){_b.push(_c[i]);}return _b;},selectNone:function(){this.setSelection([]);return this;},destroy:function(){this.inherited(arguments);this.selection=this.anchor=null;},addTreeNode:function(_d,_e){this.setSelection(this.getSelectedTreeNodes().concat([_d]));if(_e){this.anchor=_d;}return _d;},removeTreeNode:function(_f){this.setSelection(this._setDifference(this.getSelectedTreeNodes(),[_f]));return _f;},isTreeNodeSelected:function(_10){return _10.id&&!!this.selection[_10.id];},setSelection:function(_11){var _12=this.getSelectedTreeNodes();_1.forEach(this._setDifference(_12,_11),_6.hitch(this,function(_13){_13.setSelected(false);if(this.anchor==_13){delete this.anchor;}delete this.selection[_13.id];}));_1.forEach(this._setDifference(_11,_12),_6.hitch(this,function(_14){_14.setSelected(true);this.selection[_14.id]=_14;}));this._updateSelectionProperties();},_setDifference:function(xs,ys){_1.forEach(ys,function(y){y.__exclude__=true;});var ret=_1.filter(xs,function(x){return !x.__exclude__;});_1.forEach(ys,function(y){delete y["__exclude__"];});return ret;},_updateSelectionProperties:function(){var _15=this.getSelectedTreeNodes();var _16=[],_17=[],_18=[];_1.forEach(_15,function(_19){var ary=_19.getTreePath(),_1a=this.tree.model;_17.push(_19);_16.push(ary);ary=_1.map(ary,function(_1b){return _1a.getIdentity(_1b);},this);_18.push(ary.join("/"));},this);var _1c=_1.map(_17,function(_1d){return _1d.item;});this.tree._set("paths",_16);this.tree._set("path",_16[0]||[]);this.tree._set("selectedNodes",_17);this.tree._set("selectedNode",_17[0]||null);this.tree._set("selectedItems",_1c);this.tree._set("selectedItem",_1c[0]||null);if(this.tree.persist&&_18.length>0){_7(this.cookieName,_18.join(","),{expires:365});}},_getSavedPaths:function(){var _1e=this.tree;if(_1e.persist&&_1e.dndController.cookieName){var _1f,_20=[];_1f=_7(_1e.dndController.cookieName);if(_1f){_20=_1.map(_1f.split(","),function(_21){return _21.split("/");});}return _20;}},onMouseDown:function(e){if(!this.current||this.tree.isExpandoNode(e.target,this.current)){return;}if(_8.isLeft(e)){e.preventDefault();}else{if(e.type!="touchstart"){return;}}var _22=this.current,_23=_2.isCopyKey(e),id=_22.id;if(!this.singular&&!e.shiftKey&&this.selection[id]){this._doDeselect=true;return;}else{this._doDeselect=false;}this.userSelect(_22,_23,e.shiftKey);},onMouseUp:function(e){if(!this._doDeselect){return;}this._doDeselect=false;this.userSelect(this.current,_2.isCopyKey(e),e.shiftKey);},onMouseMove:function(){this._doDeselect=false;},_compareNodes:function(n1,n2){if(n1===n2){return 0;}if("sourceIndex" in document.documentElement){return n1.sourceIndex-n2.sourceIndex;}else{if("compareDocumentPosition" in document.documentElement){return n1.compareDocumentPosition(n2)&2?1:-1;}else{if(document.createRange){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");}}}},userSelect:function(_24,_25,_26){if(this.singular){if(this.anchor==_24&&_25){this.selectNone();}else{this.setSelection([_24]);this.anchor=_24;}}else{if(_26&&this.anchor){var cr=this._compareNodes(this.anchor.rowNode,_24.rowNode),_27,end,_28=this.anchor;if(cr<0){_27=_28;end=_24;}else{_27=_24;end=_28;}var _29=[];while(_27!=end){_29.push(_27);_27=this.tree._getNextNode(_27);}_29.push(end);this.setSelection(_29);}else{if(this.selection[_24.id]&&_25){this.removeTreeNode(_24);}else{if(_25){this.addTreeNode(_24,true);}else{this.setSelection([_24]);this.anchor=_24;}}}}},getItem:function(key){var _2a=this.selection[key];return {data:_2a,type:["treeNode"]};},forInSelectedItems:function(f,o){o=o||_5.global;for(var id in this.selection){f.call(o,this.getItem(id),id,this);}}});}); \ No newline at end of file diff --git a/lib/dijit/tree/_dndSelector.js.uncompressed.js b/lib/dijit/tree/_dndSelector.js.uncompressed.js new file mode 100644 index 000000000..82423fb20 --- /dev/null +++ b/lib/dijit/tree/_dndSelector.js.uncompressed.js @@ -0,0 +1,356 @@ +define("dijit/tree/_dndSelector", [ + "dojo/_base/array", // array.filter array.forEach array.map + "dojo/_base/connect", // connect.isCopyKey + "dojo/_base/declare", // declare + "dojo/_base/Deferred", // Deferred + "dojo/_base/kernel", // global + "dojo/_base/lang", // lang.hitch + "dojo/cookie", // cookie + "dojo/mouse", // mouse.isLeft + "dojo/on", + "dojo/touch", + "./_dndContainer" +], function(array, connect, declare, Deferred, kernel, lang, cookie, mouse, on, touch, _dndContainer){ + + // module: + // dijit/tree/_dndSelector + + + return declare("dijit.tree._dndSelector", _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: Object + // (id to DomNode) map for every TreeNode that's currently selected. + // The DOMNode is the TreeNode.rowNode. + selection: {}, + =====*/ + + constructor: function(){ + // summary: + // Initialization + // tags: + // private + + this.selection={}; + this.anchor = null; + + if(!this.cookieName && this.tree.id){ + this.cookieName = this.tree.id + "SaveSelectedCookie"; + } + + this.events.push( + on(this.tree.domNode, touch.press, lang.hitch(this,"onMouseDown")), + on(this.tree.domNode, touch.release, lang.hitch(this,"onMouseUp")), + on(this.tree.domNode, touch.move, lang.hitch(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/Tree._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/Tree._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/Tree._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/Tree._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(); + array.forEach(this._setDifference(oldSelection, newSelection), lang.hitch(this, function(node){ + node.setSelected(false); + if(this.anchor == node){ + delete this.anchor; + } + delete this.selection[node.id]; + })); + array.forEach(this._setDifference(newSelection, oldSelection), lang.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. + + array.forEach(ys, function(y){ y.__exclude__ = true; }); + var ret = array.filter(xs, function(x){ return !x.__exclude__; }); + + // clean up after ourselves. + array.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 = [], selects = []; + array.forEach(selected, function(node){ + var ary = node.getTreePath(), model = this.tree.model; + nodes.push(node); + paths.push(ary); + ary = array.map(ary, function(item){ + return model.getIdentity(item); + }, this); + selects.push(ary.join("/")) + }, this); + var items = array.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); + if (this.tree.persist && selects.length > 0) { + cookie(this.cookieName, selects.join(","), {expires:365}); + } + }, + _getSavedPaths: function(){ + // summary: + // Returns paths of nodes that were selected previously and saved in the cookie. + + var tree = this.tree; + if(tree.persist && tree.dndController.cookieName){ + var oreo, paths = []; + oreo = cookie(tree.dndController.cookieName); + if(oreo){ + paths = array.map(oreo.split(","), function(path){ + return path.split("/"); + }) + } + return paths; + } + }, + // mouse events + onMouseDown: function(e){ + // summary: + // Event processor for onmousedown/ontouchstart + // e: Event + // onmousedown/ontouchstart event + // tags: + // protected + + // ignore click on expando node + if(!this.current || this.tree.isExpandoNode(e.target, this.current)){ return; } + + if(mouse.isLeft(e)){ + // Prevent text selection while dragging on desktop, see #16328. But don't call preventDefault() + // for mobile because it will break things completely, see #15838. + e.preventDefault(); + }else if(e.type != "touchstart"){ + // Ignore right click + return; + } + + var treeNode = this.current, + copy = connect.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/ontouchend + // e: Event + // onmouseup/ontouchend 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, connect.isCopyKey(e), e.shiftKey); + }, + onMouseMove: function(/*===== e =====*/){ + // summary: + // event processor for onmousemove/ontouchmove + // e: Event + // onmousemove/ontouchmove event + this._doDeselect = false; + }, + + _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"); + } + }, + + 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 = this._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; + } + var 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; + } + } + } + }, + + getItem: function(/*String*/ key){ + // summary: + // Returns the dojo/dnd/Container._Item (representing a dragged node) by it's key (id). + // Called by dojo/dnd/Source.checkAcceptance(). + // tags: + // protected + + var widget = this.selection[key]; + return { + data: widget, + type: ["treeNode"] + }; // dojo/dnd/Container._Item + }, + + forInSelectedItems: function(/*Function*/ f, /*Object?*/ o){ + // summary: + // Iterates over selected items; + // see `dojo/dnd/Container.forInItems()` for details + o = o || kernel.global; + for(var id in this.selection){ + // console.log("selected item id: " + id); + f.call(o, this.getItem(id), id, this); + } + } + }); +}); diff --git a/lib/dijit/tree/dndSource.js b/lib/dijit/tree/dndSource.js index a68af56c4..7698cd844 100644 --- a/lib/dijit/tree/dndSource.js +++ b/lib/dijit/tree/dndSource.js @@ -1,2 +1,2 @@ //>>built -define("dijit/tree/dndSource",["dojo/_base/array","dojo/_base/connect","dojo/_base/declare","dojo/dom-class","dojo/dom-geometry","dojo/_base/lang","dojo/on","dojo/touch","dojo/topic","dojo/dnd/Manager","./_dndSelector"],function(_1,_2,_3,_4,_5,_6,on,_7,_8,_9,_a){return _3("dijit.tree.dndSource",_a,{isSource:true,accept:["text","treeNode"],copyOnly:false,dragThreshold:5,betweenThreshold:0,constructor:function(_b,_c){if(!_c){_c={};}_6.mixin(this,_c);this.isSource=typeof _c.isSource=="undefined"?true:_c.isSource;var _d=_c.accept instanceof Array?_c.accept:["text","treeNode"];this.accept=null;if(_d.length){this.accept={};for(var i=0;i<_d.length;++i){this.accept[_d[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){_4.add(this.node,"dojoDndSource");}this.targetState="";if(this.accept){_4.add(this.node,"dojoDndTarget");}this.topics=[_8.subscribe("/dnd/source/over",_6.hitch(this,"onDndSourceOver")),_8.subscribe("/dnd/start",_6.hitch(this,"onDndStart")),_8.subscribe("/dnd/drop",_6.hitch(this,"onDndDrop")),_8.subscribe("/dnd/cancel",_6.hitch(this,"onDndCancel"))];},checkAcceptance:function(){return true;},copyState:function(_e){return this.copyOnly||_e;},destroy:function(){this.inherited(arguments);var h;while(h=this.topics.pop()){h.remove();}this.targetAnchor=null;},_onDragMouse:function(e){var m=_9.manager(),_f=this.targetAnchor,_10=this.current,_11=this.dropPosition;var _12="Over";if(_10&&this.betweenThreshold>0){if(!this.targetBox||_f!=_10){this.targetBox=_5.position(_10.rowNode,true);}if((e.pageY-this.targetBox.y)<=this.betweenThreshold){_12="Before";}else{if((e.pageY-this.targetBox.y)>=(this.targetBox.h-this.betweenThreshold)){_12="After";}}}if(_10!=_f||_12!=_11){if(_f){this._removeItemClass(_f.rowNode,_11);}if(_10){this._addItemClass(_10.rowNode,_12);}if(!_10){m.canDrop(false);}else{if(_10==this.tree.rootNode&&_12!="Over"){m.canDrop(false);}else{var _13=this.tree.model,_14=false;if(m.source==this){for(var _15 in this.selection){var _16=this.selection[_15];if(_16.item===_10.item){_14=true;break;}}}if(_14){m.canDrop(false);}else{if(this.checkItemAcceptance(_10.rowNode,m.source,_12.toLowerCase())&&!this._isParentChildDrop(m.source,_10.rowNode)){m.canDrop(true);}else{m.canDrop(false);}}}}this.targetAnchor=_10;this.dropPosition=_12;}},onMouseMove:function(e){if(this.isDragging&&this.targetState=="Disabled"){return;}this.inherited(arguments);var m=_9.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 _17=this.getSelectedTreeNodes();if(_17.length){if(_17.length>1){var _18=this.selection,i=0,r=[],n,p;nextitem:while((n=_17[i++])){for(p=n.getParent();p&&p!==this.tree;p=p.getParent()){if(_18[p.id]){continue nextitem;}}r.push(n);}_17=r;}_17=_1.map(_17,function(n){return n.domNode;});m.startDrag(this,_17,this.copyState(_2.isCopyKey(e)));}}}},onMouseDown:function(e){this.mouseDown=true;this.mouseButton=e.button;this._lastX=e.pageX;this._lastY=e.pageY;this.inherited(arguments);},onMouseUp:function(e){if(this.mouseDown){this.mouseDown=false;this.inherited(arguments);}},onMouseOut:function(){this.inherited(arguments);this._unmarkTargetAnchor();},checkItemAcceptance:function(){return true;},onDndSourceOver:function(_19){if(this!=_19){this.mouseDown=false;this._unmarkTargetAnchor();}else{if(this.isDragging){var m=_9.manager();m.canDrop(false);}}},onDndStart:function(_1a,_1b,_1c){if(this.isSource){this._changeState("Source",this==_1a?(_1c?"Copied":"Moved"):"");}var _1d=this.checkAcceptance(_1a,_1b);this._changeState("Target",_1d?"":"Disabled");if(this==_1a){_9.manager().overSource(this);}this.isDragging=true;},itemCreator:function(_1e){return _1.map(_1e,function(_1f){return {"id":_1f.id,"name":_1f.textContent||_1f.innerText||""};});},onDndDrop:function(_20,_21,_22){if(this.containerState=="Over"){var _23=this.tree,_24=_23.model,_25=this.targetAnchor;this.isDragging=false;var _26;var _27;_26=(_25&&_25.item)||_23.item;if(this.dropPosition=="Before"||this.dropPosition=="After"){_26=(_25.getParent()&&_25.getParent().item)||_23.item;_27=_25.getIndexInParent();if(this.dropPosition=="After"){_27=_25.getIndexInParent()+1;}}else{_26=(_25&&_25.item)||_23.item;}var _28;_1.forEach(_21,function(_29,idx){var _2a=_20.getItem(_29.id);if(_1.indexOf(_2a.type,"treeNode")!=-1){var _2b=_2a.data,_2c=_2b.item,_2d=_2b.getParent().item;}if(_20==this){if(typeof _27=="number"){if(_26==_2d&&_2b.getIndexInParent()<_27){_27-=1;}}_24.pasteItem(_2c,_2d,_26,_22,_27);}else{if(_24.isItem(_2c)){_24.pasteItem(_2c,_2d,_26,_22,_27);}else{if(!_28){_28=this.itemCreator(_21,_25.rowNode,_20);}_24.newItem(_28[idx],_26,_27);}}},this);this.tree._expandNode(_25);}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);_9.manager().overSource(this);},onOutEvent:function(){this._unmarkTargetAnchor();var m=_9.manager();if(this.isDragging){m.canDrop(false);}m.outSource(this);this.inherited(arguments);},_isParentChildDrop:function(_2e,_2f){if(!_2e.tree||_2e.tree!=this.tree){return false;}var _30=_2e.tree.domNode;var ids=_2e.selection;var _31=_2f.parentNode;while(_31!=_30&&!ids[_31.id]){_31=_31.parentNode;}return _31.id&&ids[_31.id];},_unmarkTargetAnchor:function(){if(!this.targetAnchor){return;}this._removeItemClass(this.targetAnchor.rowNode,this.dropPosition);this.targetAnchor=null;this.targetBox=null;this.dropPosition=null;},_markDndStatus:function(_32){this._changeState("Source",_32?"Copied":"Moved");}});}); \ No newline at end of file +define("dijit/tree/dndSource",["dojo/_base/array","dojo/_base/connect","dojo/_base/declare","dojo/dom-class","dojo/dom-geometry","dojo/_base/lang","dojo/on","dojo/touch","dojo/topic","dojo/dnd/Manager","./_dndSelector"],function(_1,_2,_3,_4,_5,_6,on,_7,_8,_9,_a){var _b=_3("dijit.tree.dndSource",_a,{isSource:true,accept:["text","treeNode"],copyOnly:false,dragThreshold:5,betweenThreshold:0,generateText:true,constructor:function(_c,_d){if(!_d){_d={};}_6.mixin(this,_d);var _e=_d.accept instanceof Array?_d.accept:["text","treeNode"];this.accept=null;if(_e.length){this.accept={};for(var i=0;i<_e.length;++i){this.accept[_e[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){_4.add(this.node,"dojoDndSource");}this.targetState="";if(this.accept){_4.add(this.node,"dojoDndTarget");}this.topics=[_8.subscribe("/dnd/source/over",_6.hitch(this,"onDndSourceOver")),_8.subscribe("/dnd/start",_6.hitch(this,"onDndStart")),_8.subscribe("/dnd/drop",_6.hitch(this,"onDndDrop")),_8.subscribe("/dnd/cancel",_6.hitch(this,"onDndCancel"))];},checkAcceptance:function(){return true;},copyState:function(_f){return this.copyOnly||_f;},destroy:function(){this.inherited(arguments);var h;while(h=this.topics.pop()){h.remove();}this.targetAnchor=null;},_onDragMouse:function(e,_10){var m=_9.manager(),_11=this.targetAnchor,_12=this.current,_13=this.dropPosition;var _14="Over";if(_12&&this.betweenThreshold>0){if(!this.targetBox||_11!=_12){this.targetBox=_5.position(_12.rowNode,true);}if((e.pageY-this.targetBox.y)<=this.betweenThreshold){_14="Before";}else{if((e.pageY-this.targetBox.y)>=(this.targetBox.h-this.betweenThreshold)){_14="After";}}}if(_10||_12!=_11||_14!=_13){if(_11){this._removeItemClass(_11.rowNode,_13);}if(_12){this._addItemClass(_12.rowNode,_14);}if(!_12){m.canDrop(false);}else{if(_12==this.tree.rootNode&&_14!="Over"){m.canDrop(false);}else{var _15=false;if(m.source==this){for(var _16 in this.selection){var _17=this.selection[_16];if(_17.item===_12.item){_15=true;break;}}}if(_15){m.canDrop(false);}else{if(this.checkItemAcceptance(_12.rowNode,m.source,_14.toLowerCase())&&!this._isParentChildDrop(m.source,_12.rowNode)){m.canDrop(true);}else{m.canDrop(false);}}}}this.targetAnchor=_12;this.dropPosition=_14;}},onMouseMove:function(e){if(this.isDragging&&this.targetState=="Disabled"){return;}this.inherited(arguments);var m=_9.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 _18=this.getSelectedTreeNodes();if(_18.length){if(_18.length>1){var _19=this.selection,i=0,r=[],n,p;nextitem:while((n=_18[i++])){for(p=n.getParent();p&&p!==this.tree;p=p.getParent()){if(_19[p.id]){continue nextitem;}}r.push(n);}_18=r;}_18=_1.map(_18,function(n){return n.domNode;});m.startDrag(this,_18,this.copyState(_2.isCopyKey(e)));this._onDragMouse(e,true);}}}},onMouseDown:function(e){this.mouseDown=true;this.mouseButton=e.button;this._lastX=e.pageX;this._lastY=e.pageY;this.inherited(arguments);},onMouseUp:function(e){if(this.mouseDown){this.mouseDown=false;this.inherited(arguments);}},onMouseOut:function(){this.inherited(arguments);this._unmarkTargetAnchor();},checkItemAcceptance:function(){return true;},onDndSourceOver:function(_1a){if(this!=_1a){this.mouseDown=false;this._unmarkTargetAnchor();}else{if(this.isDragging){var m=_9.manager();m.canDrop(false);}}},onDndStart:function(_1b,_1c,_1d){if(this.isSource){this._changeState("Source",this==_1b?(_1d?"Copied":"Moved"):"");}var _1e=this.checkAcceptance(_1b,_1c);this._changeState("Target",_1e?"":"Disabled");if(this==_1b){_9.manager().overSource(this);}this.isDragging=true;},itemCreator:function(_1f){return _1.map(_1f,function(_20){return {"id":_20.id,"name":_20.textContent||_20.innerText||""};});},onDndDrop:function(_21,_22,_23){if(this.containerState=="Over"){var _24=this.tree,_25=_24.model,_26=this.targetAnchor;this.isDragging=false;var _27;var _28;var _29;_27=(_26&&_26.item)||_24.item;if(this.dropPosition=="Before"||this.dropPosition=="After"){_27=(_26.getParent()&&_26.getParent().item)||_24.item;_28=_26.getIndexInParent();if(this.dropPosition=="After"){_28=_26.getIndexInParent()+1;_29=_26.getNextSibling()&&_26.getNextSibling().item;}else{_29=_26.item;}}else{_27=(_26&&_26.item)||_24.item;}var _2a;_1.forEach(_22,function(_2b,idx){var _2c=_21.getItem(_2b.id);if(_1.indexOf(_2c.type,"treeNode")!=-1){var _2d=_2c.data,_2e=_2d.item,_2f=_2d.getParent().item;}if(_21==this){if(typeof _28=="number"){if(_27==_2f&&_2d.getIndexInParent()<_28){_28-=1;}}_25.pasteItem(_2e,_2f,_27,_23,_28,_29);}else{if(_25.isItem(_2e)){_25.pasteItem(_2e,_2f,_27,_23,_28,_29);}else{if(!_2a){_2a=this.itemCreator(_22,_26.rowNode,_21);}_25.newItem(_2a[idx],_27,_28,_29);}}},this);this.tree._expandNode(_26);}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);_9.manager().overSource(this);},onOutEvent:function(){this._unmarkTargetAnchor();var m=_9.manager();if(this.isDragging){m.canDrop(false);}m.outSource(this);this.inherited(arguments);},_isParentChildDrop:function(_30,_31){if(!_30.tree||_30.tree!=this.tree){return false;}var _32=_30.tree.domNode;var ids=_30.selection;var _33=_31.parentNode;while(_33!=_32&&!ids[_33.id]){_33=_33.parentNode;}return _33.id&&ids[_33.id];},_unmarkTargetAnchor:function(){if(!this.targetAnchor){return;}this._removeItemClass(this.targetAnchor.rowNode,this.dropPosition);this.targetAnchor=null;this.targetBox=null;this.dropPosition=null;},_markDndStatus:function(_34){this._changeState("Source",_34?"Copied":"Moved");}});return _b;}); \ No newline at end of file diff --git a/lib/dijit/tree/dndSource.js.uncompressed.js b/lib/dijit/tree/dndSource.js.uncompressed.js new file mode 100644 index 000000000..2cd1b07ca --- /dev/null +++ b/lib/dijit/tree/dndSource.js.uncompressed.js @@ -0,0 +1,564 @@ +define("dijit/tree/dndSource", [ + "dojo/_base/array", // array.forEach array.indexOf array.map + "dojo/_base/connect", // isCopyKey + "dojo/_base/declare", // declare + "dojo/dom-class", // domClass.add + "dojo/dom-geometry", // domGeometry.position + "dojo/_base/lang", // lang.mixin lang.hitch + "dojo/on", // subscribe + "dojo/touch", + "dojo/topic", + "dojo/dnd/Manager", // DNDManager.manager + "./_dndSelector" +], function(array, connect, declare, domClass, domGeometry, lang, on, touch, topic, DNDManager, _dndSelector){ + +// module: +// dijit/tree/dndSource +// summary: +// Handles drag and drop operations (as a source or a target) for `dijit.Tree` + +/*===== +var __Item = { + // summary: + // New item to be added to the Tree, like: + // id: Anything + id: "", + // name: String + name: "" +}; +=====*/ + +var dndSource = declare("dijit.tree.dndSource", _dndSelector, { + // summary: + // Handles drag and drop operations (as a source or a target) for `dijit.Tree` + + // isSource: 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, + + // Flag used by Avatar.js to signal to generate text node when dragging + generateText: true, + + constructor: function(/*dijit/Tree*/ tree, /*dijit/tree/dndSource*/ params){ + // summary: + // a constructor of the Tree DnD Source + // tags: + // private + if(!params){ params = {}; } + lang.mixin(this, params); + 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){ + domClass.add(this.node, "dojoDndSource"); + } + this.targetState = ""; + if(this.accept){ + domClass.add(this.node, "dojoDndTarget"); + } + + // set up events + this.topics = [ + topic.subscribe("/dnd/source/over", lang.hitch(this, "onDndSourceOver")), + topic.subscribe("/dnd/start", lang.hitch(this, "onDndStart")), + topic.subscribe("/dnd/drop", lang.hitch(this, "onDndDrop")), + topic.subscribe("/dnd/cancel", lang.hitch(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(arguments); + var h; + while(h = this.topics.pop()){ h.remove(); } + this.targetAnchor = null; + }, + + _onDragMouse: function(e, firstTime){ + // summary: + // Helper method for processing onmousemove/onmouseover events while drag is in progress. + // Keeps track of current drop target. + // e: Event + // The mousemove event. + // firstTime: Boolean? + // If this flag is set, this is the first mouse move event of the drag, so call m.canDrop() etc. + // even if newTarget == null because the user quickly dragged a node in the Tree to a position + // over Tree.containerNode but not over any TreeNode (#7971) + + var m = DNDManager.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 = domGeometry.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(firstTime || 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{ + // Guard against dropping onto yourself (TODO: guard against dropping onto your descendant, #7140) + var sameId = false; + if(m.source == this){ + for(var dragId in this.selection){ + var dragNode = this.selection[dragId]; + if(dragNode.item === newTarget.item){ + sameId = true; + break; + } + } + } + if(sameId){ + 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/ontouchmove events over the Tree + // e: Event + // onmousemouse/ontouchmove event + // tags: + // private + if(this.isDragging && this.targetState == "Disabled"){ return; } + this.inherited(arguments); + var m = DNDManager.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 = array.map(nodes, function(n){return n.domNode}); + m.startDrag(this, nodes, this.copyState(connect.isCopyKey(e))); + this._onDragMouse(e, true); // because this may be the only mousemove event we get before the drop + } + } + } + }, + + onMouseDown: function(e){ + // summary: + // Event processor for onmousedown/ontouchstart + // e: Event + // onmousedown/ontouchend 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/ontouchend + // e: Event + // onmouseup/ontouchend 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 = DNDManager.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){ + DNDManager.manager().overSource(this); + } + + this.isDragging = true; + }, + + itemCreator: function(nodes /*===== , target, 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. + // nodes: DomNode[] + // target: DomNode + // source: dojo/dnd/Source + // returns: __Item[] + // Array of name/value hashes for each new item to be added to the Tree + // 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 array.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; + + this.isDragging = false; + + // Compute the new parent item + var newParentItem; + var insertIndex; + var before; // drop source before (aka previous sibling) of target + newParentItem = (target && target.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 = (target.getParent() && target.getParent().item) || tree.item; + // Compute the insert index for reordering + insertIndex = target.getIndexInParent(); + if(this.dropPosition == "After"){ + insertIndex = target.getIndexInParent() + 1; + before = target.getNextSibling() && target.getNextSibling().item; + }else{ + before = target.item; + } + }else{ + newParentItem = (target && target.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; + + array.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(array.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, before); + }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, before); + }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, before); + } + }, 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(target); + } + 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); + DNDManager.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 = DNDManager.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"); + } +}); + +/*===== +dndSource.__Item = __Item; +=====*/ + +return dndSource; +}); diff --git a/lib/dijit/tree/model.js b/lib/dijit/tree/model.js index 5add44298..69cd9b307 100644 --- a/lib/dijit/tree/model.js +++ b/lib/dijit/tree/model.js @@ -1 +1,2 @@ //>>built +define("dijit/tree/model",["dojo/_base/declare"],function(_1){return _1("dijit.tree.model",null,{destroy:function(){},getRoot:function(_2){},mayHaveChildren:function(_3){},getChildren:function(_4,_5){},isItem:function(_6){},fetchItemByIdentity:function(_7){},getIdentity:function(_8){},getLabel:function(_9){},newItem:function(_a,_b,_c,_d){},pasteItem:function(_e,_f,_10,_11,_12,_13){},onChange:function(_14){},onChildrenChange:function(_15,_16){}});}); \ No newline at end of file diff --git a/lib/dijit/tree/model.js.uncompressed.js b/lib/dijit/tree/model.js.uncompressed.js new file mode 100644 index 000000000..cae349890 --- /dev/null +++ b/lib/dijit/tree/model.js.uncompressed.js @@ -0,0 +1,150 @@ +define("dijit/tree/model", ["dojo/_base/declare"], function(declare){ + +return 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(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) + // item: dojo/data/Item + // tags: + // extension + }, + + getChildren: function(parentItem, onComplete){ + // summary: + // Calls onComplete() with array of child items of given parent item, all loaded. + // Throws exception on error. + // parentItem: dojo/data/Item + // onComplete: function(items) + // tags: + // extension + }, + + // ======================================================================= + // Inspecting items + + isItem: function(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(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){ + // summary: + // Returns identity for an item + // tags: + // extension + }, + + getLabel: function(item){ + // summary: + // Get the label for an item + // tags: + // extension + }, + + // ======================================================================= + // Write interface + + newItem: function(args, parent, insertIndex, before){ + // summary: + // Creates a new item. See `dojo/data/api/Write` for details on args. + // args: dijit/tree/dndSource.__Item + // parent: Item + // insertIndex: int? + // Allows to insert the new item as the n'th child of `parent`. + // before: Item? + // Insert the new item as the previous sibling of this item. `before` must be a child of `parent`. + // tags: + // extension + }, + + pasteItem: function(childItem, oldParentItem, newParentItem, bCopy, insertIndex, before){ + // 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. + // childItem: Item + // oldParentItem: Item + // newParentItem: Item + // bCopy: Boolean + // insertIndex: int? + // Allows to insert the new item as the n'th child of `parent`. + // before: Item? + // Insert the new item as the previous sibling of this item. `before` must be a child of `parent`. + // tags: + // extension + }, + + // ======================================================================= + // Callbacks + + onChange: function(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. + // item: dojo/data/Item + // tags: + // callback + }, + + onChildrenChange: function(parent, newChildrenList){ + // summary: + // Callback to do notifications about new, updated, or deleted items. + // parent: dojo/data/Item + // newChildrenList: dojo/data/Item[] + // tags: + // callback + } +}); + +}); \ No newline at end of file -- cgit v1.2.3