]>
git.wh0rd.org - tt-rss.git/blob - lib/dijit/tree/dndSource.js.uncompressed.js
1 define("dijit/tree/dndSource", [
2 "dojo/_base/array", // array.forEach array.indexOf array.map
3 "dojo/_base/connect", // isCopyKey
4 "dojo/_base/declare", // declare
5 "dojo/dom-class", // domClass.add
6 "dojo/dom-geometry", // domGeometry.position
7 "dojo/_base/lang", // lang.mixin lang.hitch
8 "dojo/on", // subscribe
11 "dojo/dnd/Manager", // DNDManager.manager
13 ], function(array
, connect
, declare
, domClass
, domGeometry
, lang
, on
, touch
, topic
, DNDManager
, _dndSelector
){
16 // dijit/tree/dndSource
18 // Handles drag and drop operations (as a source or a target) for `dijit.Tree`
21 dijit.tree.__SourceArgs = function(){
23 // A dict of parameters for Tree source configuration.
25 // Can be used as a DnD source. Defaults to true.
27 // List of accepted types (text strings) for a target; defaults to
28 // ["text", "treeNode"]
30 // Copy items, if true, use a state of Ctrl key otherwise,
31 // dragThreshold: Number
32 // The move delay in pixels before detecting a drag; 0 by default
33 // betweenThreshold: Integer
34 // Distance from upper/lower edge of node to allow drop to reorder nodes
35 this.isSource = isSource;
37 this.autoSync = autoSync;
38 this.copyOnly = copyOnly;
39 this.dragThreshold = dragThreshold;
40 this.betweenThreshold = betweenThreshold;
44 return declare("dijit.tree.dndSource", _dndSelector
, {
46 // Handles drag and drop operations (as a source or a target) for `dijit.Tree`
48 // isSource: [private] Boolean
49 // Can be used as a DnD source.
53 // List of accepted types (text strings) for the Tree; defaults to
55 accept
: ["text", "treeNode"],
57 // copyOnly: [private] Boolean
58 // Copy items, if true, use a state of Ctrl key otherwise
61 // dragThreshold: Number
62 // The move delay in pixels before detecting a drag; 5 by default
65 // betweenThreshold: Integer
66 // Distance from upper/lower edge of node to allow drop to reorder nodes
69 constructor: function(/*dijit.Tree*/ tree
, /*dijit.tree.__SourceArgs*/ params
){
71 // a constructor of the Tree DnD Source
74 if(!params
){ params
= {}; }
75 lang
.mixin(this, params
);
76 this.isSource
= typeof params
.isSource
== "undefined" ? true : params
.isSource
;
77 var type
= params
.accept
instanceof Array
? params
.accept
: ["text", "treeNode"];
81 for(var i
= 0; i
< type
.length
; ++i
){
82 this.accept
[type
[i
]] = 1;
86 // class-specific variables
87 this.isDragging
= false;
88 this.mouseDown
= false;
89 this.targetAnchor
= null; // DOMNode corresponding to the currently moused over TreeNode
90 this.targetBox
= null; // coordinates of this.targetAnchor
91 this.dropPosition
= ""; // whether mouse is over/after/before this.targetAnchor
96 this.sourceState
= "";
98 domClass
.add(this.node
, "dojoDndSource");
100 this.targetState
= "";
102 domClass
.add(this.node
, "dojoDndTarget");
107 topic
.subscribe("/dnd/source/over", lang
.hitch(this, "onDndSourceOver")),
108 topic
.subscribe("/dnd/start", lang
.hitch(this, "onDndStart")),
109 topic
.subscribe("/dnd/drop", lang
.hitch(this, "onDndDrop")),
110 topic
.subscribe("/dnd/cancel", lang
.hitch(this, "onDndCancel"))
115 checkAcceptance: function(/*===== source, nodes =====*/){
117 // Checks if the target can accept nodes from this source
118 // source: dijit.tree.dndSource
119 // The source which provides items
121 // Array of DOM nodes corresponding to nodes being dropped, dijitTreeRow nodes if
122 // source is a dijit.Tree.
125 return true; // Boolean
128 copyState: function(keyPressed
){
130 // Returns true, if we need to copy items, false to move.
131 // It is separated to be overwritten dynamically, if needed.
132 // keyPressed: Boolean
133 // The "copy" control key was pressed
136 return this.copyOnly
|| keyPressed
; // Boolean
140 // Prepares the object to be garbage-collected.
141 this.inherited(arguments
);
143 while(h
= this.topics
.pop()){ h
.remove(); }
144 this.targetAnchor
= null;
147 _onDragMouse: function(e
){
149 // Helper method for processing onmousemove/onmouseover events while drag is in progress.
150 // Keeps track of current drop target.
152 var m
= DNDManager
.manager(),
153 oldTarget
= this.targetAnchor
, // the TreeNode corresponding to TreeNode mouse was previously over
154 newTarget
= this.current
, // TreeNode corresponding to TreeNode mouse is currently over
155 oldDropPosition
= this.dropPosition
; // the previous drop position (over/before/after)
157 // calculate if user is indicating to drop the dragged node before, after, or over
158 // (i.e., to become a child of) the target node
159 var newDropPosition
= "Over";
160 if(newTarget
&& this.betweenThreshold
> 0){
161 // If mouse is over a new TreeNode, then get new TreeNode's position and size
162 if(!this.targetBox
|| oldTarget
!= newTarget
){
163 this.targetBox
= domGeometry
.position(newTarget
.rowNode
, true);
165 if((e
.pageY
- this.targetBox
.y
) <= this.betweenThreshold
){
166 newDropPosition
= "Before";
167 }else if((e
.pageY
- this.targetBox
.y
) >= (this.targetBox
.h
- this.betweenThreshold
)){
168 newDropPosition
= "After";
172 if(newTarget
!= oldTarget
|| newDropPosition
!= oldDropPosition
){
174 this._removeItemClass(oldTarget
.rowNode
, oldDropPosition
);
177 this._addItemClass(newTarget
.rowNode
, newDropPosition
);
180 // Check if it's ok to drop the dragged node on/before/after the target node.
183 }else if(newTarget
== this.tree
.rootNode
&& newDropPosition
!= "Over"){
184 // Can't drop before or after tree's root node; the dropped node would just disappear (at least visually)
187 // Guard against dropping onto yourself (TODO: guard against dropping onto your descendant, #7140)
188 var model
= this.tree
.model
,
190 if(m
.source
== this){
191 for(var dragId
in this.selection
){
192 var dragNode
= this.selection
[dragId
];
193 if(dragNode
.item
=== newTarget
.item
){
201 }else if(this.checkItemAcceptance(newTarget
.rowNode
, m
.source
, newDropPosition
.toLowerCase())
202 && !this._isParentChildDrop(m
.source
, newTarget
.rowNode
)){
209 this.targetAnchor
= newTarget
;
210 this.dropPosition
= newDropPosition
;
214 onMouseMove: function(e
){
216 // Called for any onmousemove/ontouchmove events over the Tree
218 // onmousemouse/ontouchmove event
221 if(this.isDragging
&& this.targetState
== "Disabled"){ return; }
222 this.inherited(arguments
);
223 var m
= DNDManager
.manager();
225 this._onDragMouse(e
);
227 if(this.mouseDown
&& this.isSource
&&
228 (Math
.abs(e
.pageX
-this._lastX
)>=this.dragThreshold
|| Math
.abs(e
.pageY
-this._lastY
)>=this.dragThreshold
)){
229 var nodes
= this.getSelectedTreeNodes();
231 if(nodes
.length
> 1){
232 //filter out all selected items which has one of their ancestor selected as well
233 var seen
= this.selection
, i
= 0, r
= [], n
, p
;
234 nextitem
: while((n
= nodes
[i
++])){
235 for(p
= n
.getParent(); p
&& p
!== this.tree
; p
= p
.getParent()){
236 if(seen
[p
.id
]){ //parent is already selected, skip this node
240 //this node does not have any ancestors selected, add it
245 nodes
= array
.map(nodes
, function(n
){return n
.domNode
});
246 m
.startDrag(this, nodes
, this.copyState(connect
.isCopyKey(e
)));
252 onMouseDown: function(e
){
254 // Event processor for onmousedown/ontouchstart
256 // onmousedown/ontouchend event
259 this.mouseDown
= true;
260 this.mouseButton
= e
.button
;
261 this._lastX
= e
.pageX
;
262 this._lastY
= e
.pageY
;
263 this.inherited(arguments
);
266 onMouseUp: function(e
){
268 // Event processor for onmouseup/ontouchend
270 // onmouseup/ontouchend event
274 this.mouseDown
= false;
275 this.inherited(arguments
);
279 onMouseOut: function(){
281 // Event processor for when mouse is moved away from a TreeNode
284 this.inherited(arguments
);
285 this._unmarkTargetAnchor();
288 checkItemAcceptance: function(/*===== target, source, position =====*/){
290 // Stub function to be overridden if one wants to check for the ability to drop at the node/item level
292 // In the base case, this is called to check if target can become a child of source.
293 // When betweenThreshold is set, position="before" or "after" means that we
294 // are asking if the source node can be dropped before/after the target node.
296 // The dijitTreeRoot DOM node inside of the TreeNode that we are dropping on to
297 // Use dijit.getEnclosingWidget(target) to get the TreeNode.
298 // source: dijit.tree.dndSource
299 // The (set of) nodes we are dropping
301 // "over", "before", or "after"
307 // topic event processors
308 onDndSourceOver: function(source
){
310 // Topic event processor for /dnd/source/over, called when detected a current source.
312 // The dijit.tree.dndSource / dojo.dnd.Source which has the mouse over it
316 this.mouseDown
= false;
317 this._unmarkTargetAnchor();
318 }else if(this.isDragging
){
319 var m
= DNDManager
.manager();
323 onDndStart: function(source
, nodes
, copy
){
325 // Topic event processor for /dnd/start, called to initiate the DnD operation
327 // The dijit.tree.dndSource / dojo.dnd.Source which is providing the items
329 // The list of transferred items, dndTreeNode nodes if dragging from a Tree
331 // Copy items, if true, move items otherwise
336 this._changeState("Source", this == source
? (copy
? "Copied" : "Moved") : "");
338 var accepted
= this.checkAcceptance(source
, nodes
);
340 this._changeState("Target", accepted
? "" : "Disabled");
343 DNDManager
.manager().overSource(this);
346 this.isDragging
= true;
349 itemCreator: function(nodes
/*===== , target, source =====*/){
351 // Returns objects passed to `Tree.model.newItem()` based on DnD nodes
352 // dropped onto the tree. Developer must override this method to enable
353 // dropping from external sources onto this Tree, unless the Tree.model's items
354 // happen to look like {id: 123, name: "Apple" } with no other attributes.
356 // For each node in nodes[], which came from source, create a hash of name/value
357 // pairs to be passed to Tree.model.newItem(). Returns array of those hashes.
360 // source: dojo.dnd.Source
362 // Array of name/value hashes for each new item to be added to the Tree, like:
364 // | { id: 123, label: "apple", foo: "bar" },
365 // | { id: 456, label: "pear", zaz: "bam" }
370 // TODO: for 2.0 refactor so itemCreator() is called once per drag node, and
371 // make signature itemCreator(sourceItem, node, target) (or similar).
373 return array
.map(nodes
, function(node
){
376 "name": node
.textContent
|| node
.innerText
|| ""
381 onDndDrop: function(source
, nodes
, copy
){
383 // Topic event processor for /dnd/drop, called to finish the DnD operation.
385 // Updates data store items according to where node was dragged from and dropped
386 // to. The tree will then respond to those data store updates and redraw itself.
388 // The dijit.tree.dndSource / dojo.dnd.Source which is providing the items
390 // The list of transferred items, dndTreeNode nodes if dragging from a Tree
392 // Copy items, if true, move items otherwise
395 if(this.containerState
== "Over"){
396 var tree
= this.tree
,
398 target
= this.targetAnchor
;
400 this.isDragging
= false;
402 // Compute the new parent item
405 newParentItem
= (target
&& target
.item
) || tree
.item
;
406 if(this.dropPosition
== "Before" || this.dropPosition
== "After"){
407 // TODO: if there is no parent item then disallow the drop.
408 // Actually this should be checked during onMouseMove too, to make the drag icon red.
409 newParentItem
= (target
.getParent() && target
.getParent().item
) || tree
.item
;
410 // Compute the insert index for reordering
411 insertIndex
= target
.getIndexInParent();
412 if(this.dropPosition
== "After"){
413 insertIndex
= target
.getIndexInParent() + 1;
416 newParentItem
= (target
&& target
.item
) || tree
.item
;
419 // If necessary, use this variable to hold array of hashes to pass to model.newItem()
420 // (one entry in the array for each dragged node).
423 array
.forEach(nodes
, function(node
, idx
){
424 // dojo.dnd.Item representing the thing being dropped.
425 // Don't confuse the use of item here (meaning a DnD item) with the
426 // uses below where item means dojo.data item.
427 var sourceItem
= source
.getItem(node
.id
);
429 // Information that's available if the source is another Tree
430 // (possibly but not necessarily this tree, possibly but not
431 // necessarily the same model as this Tree)
432 if(array
.indexOf(sourceItem
.type
, "treeNode") != -1){
433 var childTreeNode
= sourceItem
.data
,
434 childItem
= childTreeNode
.item
,
435 oldParentItem
= childTreeNode
.getParent().item
;
439 // This is a node from my own tree, and we are moving it, not copying.
440 // Remove item from old parent's children attribute.
441 // TODO: dijit.tree.dndSelector should implement deleteSelectedNodes()
442 // and this code should go there.
444 if(typeof insertIndex
== "number"){
445 if(newParentItem
== oldParentItem
&& childTreeNode
.getIndexInParent() < insertIndex
){
449 model
.pasteItem(childItem
, oldParentItem
, newParentItem
, copy
, insertIndex
);
450 }else if(model
.isItem(childItem
)){
451 // Item from same model
452 // (maybe we should only do this branch if the source is a tree?)
453 model
.pasteItem(childItem
, oldParentItem
, newParentItem
, copy
, insertIndex
);
455 // Get the hash to pass to model.newItem(). A single call to
456 // itemCreator() returns an array of hashes, one for each drag source node.
458 newItemsParams
= this.itemCreator(nodes
, target
.rowNode
, source
);
461 // Create new item in the tree, based on the drag source.
462 model
.newItem(newItemsParams
[idx
], newParentItem
, insertIndex
);
466 // Expand the target node (if it's currently collapsed) so the user can see
467 // where their node was dropped. In particular since that node is still selected.
468 this.tree
._expandNode(target
);
473 onDndCancel: function(){
475 // Topic event processor for /dnd/cancel, called to cancel the DnD operation
478 this._unmarkTargetAnchor();
479 this.isDragging
= false;
480 this.mouseDown
= false;
481 delete this.mouseButton
;
482 this._changeState("Source", "");
483 this._changeState("Target", "");
486 // When focus moves in/out of the entire Tree
487 onOverEvent: function(){
489 // This method is called when mouse is moved over our container (like onmouseenter)
492 this.inherited(arguments
);
493 DNDManager
.manager().overSource(this);
495 onOutEvent: function(){
497 // This method is called when mouse is moved out of our container (like onmouseleave)
500 this._unmarkTargetAnchor();
501 var m
= DNDManager
.manager();
507 this.inherited(arguments
);
510 _isParentChildDrop: function(source
, targetRow
){
512 // Checks whether the dragged items are parent rows in the tree which are being
513 // dragged into their own children.
516 // The DragSource object.
519 // The tree row onto which the dragged nodes are being dropped.
524 // If the dragged object is not coming from the tree this widget belongs to,
525 // it cannot be invalid.
526 if(!source
.tree
|| source
.tree
!= this.tree
){
531 var root
= source
.tree
.domNode
;
532 var ids
= source
.selection
;
534 var node
= targetRow
.parentNode
;
536 // Iterate up the DOM hierarchy from the target drop row,
537 // checking of any of the dragged nodes have the same ID.
538 while(node
!= root
&& !ids
[node
.id
]){
539 node
= node
.parentNode
;
542 return node
.id
&& ids
[node
.id
];
545 _unmarkTargetAnchor: function(){
547 // Removes hover class of the current target anchor
550 if(!this.targetAnchor
){ return; }
551 this._removeItemClass(this.targetAnchor
.rowNode
, this.dropPosition
);
552 this.targetAnchor
= null;
553 this.targetBox
= null;
554 this.dropPosition
= null;
557 _markDndStatus: function(copy
){
559 // Changes source's state based on "copy" status
560 this._changeState("Source", copy
? "Copied" : "Moved");