]>
git.wh0rd.org - tt-rss.git/blob - lib/dijit/tree/dndSource.js
4fc4660cc7fff718d7e55541567e7a2d2eb41b1c
2 Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved.
3 Available via Academic Free License >= 2.1 OR the modified BSD license.
4 see: http://dojotoolkit.org/license for details
8 if(!dojo
._hasResource
["dijit.tree.dndSource"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
9 dojo
._hasResource
["dijit.tree.dndSource"] = true;
10 dojo
.provide("dijit.tree.dndSource");
11 dojo
.require("dijit.tree._dndSelector");
12 dojo
.require("dojo.dnd.Manager");
16 dijit.tree.__SourceArgs = function(){
18 // A dict of parameters for Tree source configuration.
20 // Can be used as a DnD source. Defaults to true.
22 // List of accepted types (text strings) for a target; defaults to
23 // ["text", "treeNode"]
25 // Copy items, if true, use a state of Ctrl key otherwise,
26 // dragThreshold: Number
27 // The move delay in pixels before detecting a drag; 0 by default
28 // betweenThreshold: Integer
29 // Distance from upper/lower edge of node to allow drop to reorder nodes
30 this.isSource = isSource;
32 this.autoSync = autoSync;
33 this.copyOnly = copyOnly;
34 this.dragThreshold = dragThreshold;
35 this.betweenThreshold = betweenThreshold;
39 dojo
.declare("dijit.tree.dndSource", dijit
.tree
._dndSelector
, {
41 // Handles drag and drop operations (as a source or a target) for `dijit.Tree`
43 // isSource: [private] Boolean
44 // Can be used as a DnD source.
48 // List of accepted types (text strings) for the Tree; defaults to
50 accept
: ["text", "treeNode"],
52 // copyOnly: [private] Boolean
53 // Copy items, if true, use a state of Ctrl key otherwise
56 // dragThreshold: Number
57 // The move delay in pixels before detecting a drag; 5 by default
60 // betweenThreshold: Integer
61 // Distance from upper/lower edge of node to allow drop to reorder nodes
64 constructor: function(/*dijit.Tree*/ tree
, /*dijit.tree.__SourceArgs*/ params
){
66 // a constructor of the Tree DnD Source
69 if(!params
){ params
= {}; }
70 dojo
.mixin(this, params
);
71 this.isSource
= typeof params
.isSource
== "undefined" ? true : params
.isSource
;
72 var type
= params
.accept
instanceof Array
? params
.accept
: ["text", "treeNode"];
76 for(var i
= 0; i
< type
.length
; ++i
){
77 this.accept
[type
[i
]] = 1;
81 // class-specific variables
82 this.isDragging
= false;
83 this.mouseDown
= false;
84 this.targetAnchor
= null; // DOMNode corresponding to the currently moused over TreeNode
85 this.targetBox
= null; // coordinates of this.targetAnchor
86 this.dropPosition
= ""; // whether mouse is over/after/before this.targetAnchor
91 this.sourceState
= "";
93 dojo
.addClass(this.node
, "dojoDndSource");
95 this.targetState
= "";
97 dojo
.addClass(this.node
, "dojoDndTarget");
102 dojo
.subscribe("/dnd/source/over", this, "onDndSourceOver"),
103 dojo
.subscribe("/dnd/start", this, "onDndStart"),
104 dojo
.subscribe("/dnd/drop", this, "onDndDrop"),
105 dojo
.subscribe("/dnd/cancel", this, "onDndCancel")
110 checkAcceptance: function(source
, nodes
){
112 // Checks if the target can accept nodes from this source
113 // source: dijit.tree.dndSource
114 // The source which provides items
116 // Array of DOM nodes corresponding to nodes being dropped, dijitTreeRow nodes if
117 // source is a dijit.Tree.
120 return true; // Boolean
123 copyState: function(keyPressed
){
125 // Returns true, if we need to copy items, false to move.
126 // It is separated to be overwritten dynamically, if needed.
127 // keyPressed: Boolean
128 // The "copy" control key was pressed
131 return this.copyOnly
|| keyPressed
; // Boolean
135 // Prepares the object to be garbage-collected.
136 this.inherited("destroy",arguments
);
137 dojo
.forEach(this.topics
, dojo
.unsubscribe
);
138 this.targetAnchor
= null;
141 _onDragMouse: function(e
){
143 // Helper method for processing onmousemove/onmouseover events while drag is in progress.
144 // Keeps track of current drop target.
146 var m
= dojo
.dnd
.manager(),
147 oldTarget
= this.targetAnchor
, // the TreeNode corresponding to TreeNode mouse was previously over
148 newTarget
= this.current
, // TreeNode corresponding to TreeNode mouse is currently over
149 oldDropPosition
= this.dropPosition
; // the previous drop position (over/before/after)
151 // calculate if user is indicating to drop the dragged node before, after, or over
152 // (i.e., to become a child of) the target node
153 var newDropPosition
= "Over";
154 if(newTarget
&& this.betweenThreshold
> 0){
155 // If mouse is over a new TreeNode, then get new TreeNode's position and size
156 if(!this.targetBox
|| oldTarget
!= newTarget
){
157 this.targetBox
= dojo
.position(newTarget
.rowNode
, true);
159 if((e
.pageY
- this.targetBox
.y
) <= this.betweenThreshold
){
160 newDropPosition
= "Before";
161 }else if((e
.pageY
- this.targetBox
.y
) >= (this.targetBox
.h
- this.betweenThreshold
)){
162 newDropPosition
= "After";
166 if(newTarget
!= oldTarget
|| newDropPosition
!= oldDropPosition
){
168 this._removeItemClass(oldTarget
.rowNode
, oldDropPosition
);
171 this._addItemClass(newTarget
.rowNode
, newDropPosition
);
174 // Check if it's ok to drop the dragged node on/before/after the target node.
177 }else if(newTarget
== this.tree
.rootNode
&& newDropPosition
!= "Over"){
178 // Can't drop before or after tree's root node; the dropped node would just disappear (at least visually)
180 }else if(m
.source
== this && (newTarget
.id
in this.selection
)){
181 // Guard against dropping onto yourself (TODO: guard against dropping onto your descendant, #7140)
183 }else if(this.checkItemAcceptance(newTarget
.rowNode
, m
.source
, newDropPosition
.toLowerCase())
184 && !this._isParentChildDrop(m
.source
, newTarget
.rowNode
)){
190 this.targetAnchor
= newTarget
;
191 this.dropPosition
= newDropPosition
;
195 onMouseMove: function(e
){
197 // Called for any onmousemove events over the Tree
199 // onmousemouse event
202 if(this.isDragging
&& this.targetState
== "Disabled"){ return; }
203 this.inherited(arguments
);
204 var m
= dojo
.dnd
.manager();
206 this._onDragMouse(e
);
208 if(this.mouseDown
&& this.isSource
&&
209 (Math
.abs(e
.pageX
-this._lastX
)>=this.dragThreshold
|| Math
.abs(e
.pageY
-this._lastY
)>=this.dragThreshold
)){
210 var nodes
= this.getSelectedTreeNodes();
212 if(nodes
.length
> 1){
213 //filter out all selected items which has one of their ancestor selected as well
214 var seen
= this.selection
, i
= 0, r
= [], n
, p
;
215 nextitem
: while((n
= nodes
[i
++])){
216 for(p
= n
.getParent(); p
&& p
!== this.tree
; p
= p
.getParent()){
217 if(seen
[p
.id
]){ //parent is already selected, skip this node
221 //this node does not have any ancestors selected, add it
226 nodes
= dojo
.map(nodes
, function(n
){return n
.domNode
});
227 m
.startDrag(this, nodes
, this.copyState(dojo
.isCopyKey(e
)));
233 onMouseDown: function(e
){
235 // Event processor for onmousedown
240 this.mouseDown
= true;
241 this.mouseButton
= e
.button
;
242 this._lastX
= e
.pageX
;
243 this._lastY
= e
.pageY
;
244 this.inherited(arguments
);
247 onMouseUp: function(e
){
249 // Event processor for onmouseup
255 this.mouseDown
= false;
256 this.inherited(arguments
);
260 onMouseOut: function(){
262 // Event processor for when mouse is moved away from a TreeNode
265 this.inherited(arguments
);
266 this._unmarkTargetAnchor();
269 checkItemAcceptance: function(target
, source
, position
){
271 // Stub function to be overridden if one wants to check for the ability to drop at the node/item level
273 // In the base case, this is called to check if target can become a child of source.
274 // When betweenThreshold is set, position="before" or "after" means that we
275 // are asking if the source node can be dropped before/after the target node.
277 // The dijitTreeRoot DOM node inside of the TreeNode that we are dropping on to
278 // Use dijit.getEnclosingWidget(target) to get the TreeNode.
279 // source: dijit.tree.dndSource
280 // The (set of) nodes we are dropping
282 // "over", "before", or "after"
288 // topic event processors
289 onDndSourceOver: function(source
){
291 // Topic event processor for /dnd/source/over, called when detected a current source.
293 // The dijit.tree.dndSource / dojo.dnd.Source which has the mouse over it
297 this.mouseDown
= false;
298 this._unmarkTargetAnchor();
299 }else if(this.isDragging
){
300 var m
= dojo
.dnd
.manager();
304 onDndStart: function(source
, nodes
, copy
){
306 // Topic event processor for /dnd/start, called to initiate the DnD operation
308 // The dijit.tree.dndSource / dojo.dnd.Source which is providing the items
310 // The list of transferred items, dndTreeNode nodes if dragging from a Tree
312 // Copy items, if true, move items otherwise
317 this._changeState("Source", this == source
? (copy
? "Copied" : "Moved") : "");
319 var accepted
= this.checkAcceptance(source
, nodes
);
321 this._changeState("Target", accepted
? "" : "Disabled");
324 dojo
.dnd
.manager().overSource(this);
327 this.isDragging
= true;
330 itemCreator: function(/*DomNode[]*/ nodes
, target
, /*dojo.dnd.Source*/ source
){
332 // Returns objects passed to `Tree.model.newItem()` based on DnD nodes
333 // dropped onto the tree. Developer must override this method to enable
334 // dropping from external sources onto this Tree, unless the Tree.model's items
335 // happen to look like {id: 123, name: "Apple" } with no other attributes.
337 // For each node in nodes[], which came from source, create a hash of name/value
338 // pairs to be passed to Tree.model.newItem(). Returns array of those hashes.
340 // Array of name/value hashes for each new item to be added to the Tree, like:
342 // | { id: 123, label: "apple", foo: "bar" },
343 // | { id: 456, label: "pear", zaz: "bam" }
348 // TODO: for 2.0 refactor so itemCreator() is called once per drag node, and
349 // make signature itemCreator(sourceItem, node, target) (or similar).
351 return dojo
.map(nodes
, function(node
){
354 "name": node
.textContent
|| node
.innerText
|| ""
359 onDndDrop: function(source
, nodes
, copy
){
361 // Topic event processor for /dnd/drop, called to finish the DnD operation.
363 // Updates data store items according to where node was dragged from and dropped
364 // to. The tree will then respond to those data store updates and redraw itself.
366 // The dijit.tree.dndSource / dojo.dnd.Source which is providing the items
368 // The list of transferred items, dndTreeNode nodes if dragging from a Tree
370 // Copy items, if true, move items otherwise
373 if(this.containerState
== "Over"){
374 var tree
= this.tree
,
376 target
= this.targetAnchor
,
377 requeryRoot
= false; // set to true iff top level items change
379 this.isDragging
= false;
381 // Compute the new parent item
382 var targetWidget
= target
;
385 newParentItem
= (targetWidget
&& targetWidget
.item
) || tree
.item
;
386 if(this.dropPosition
== "Before" || this.dropPosition
== "After"){
387 // TODO: if there is no parent item then disallow the drop.
388 // Actually this should be checked during onMouseMove too, to make the drag icon red.
389 newParentItem
= (targetWidget
.getParent() && targetWidget
.getParent().item
) || tree
.item
;
390 // Compute the insert index for reordering
391 insertIndex
= targetWidget
.getIndexInParent();
392 if(this.dropPosition
== "After"){
393 insertIndex
= targetWidget
.getIndexInParent() + 1;
396 newParentItem
= (targetWidget
&& targetWidget
.item
) || tree
.item
;
399 // If necessary, use this variable to hold array of hashes to pass to model.newItem()
400 // (one entry in the array for each dragged node).
403 dojo
.forEach(nodes
, function(node
, idx
){
404 // dojo.dnd.Item representing the thing being dropped.
405 // Don't confuse the use of item here (meaning a DnD item) with the
406 // uses below where item means dojo.data item.
407 var sourceItem
= source
.getItem(node
.id
);
409 // Information that's available if the source is another Tree
410 // (possibly but not necessarily this tree, possibly but not
411 // necessarily the same model as this Tree)
412 if(dojo
.indexOf(sourceItem
.type
, "treeNode") != -1){
413 var childTreeNode
= sourceItem
.data
,
414 childItem
= childTreeNode
.item
,
415 oldParentItem
= childTreeNode
.getParent().item
;
419 // This is a node from my own tree, and we are moving it, not copying.
420 // Remove item from old parent's children attribute.
421 // TODO: dijit.tree.dndSelector should implement deleteSelectedNodes()
422 // and this code should go there.
424 if(typeof insertIndex
== "number"){
425 if(newParentItem
== oldParentItem
&& childTreeNode
.getIndexInParent() < insertIndex
){
429 model
.pasteItem(childItem
, oldParentItem
, newParentItem
, copy
, insertIndex
);
430 }else if(model
.isItem(childItem
)){
431 // Item from same model
432 // (maybe we should only do this branch if the source is a tree?)
433 model
.pasteItem(childItem
, oldParentItem
, newParentItem
, copy
, insertIndex
);
435 // Get the hash to pass to model.newItem(). A single call to
436 // itemCreator() returns an array of hashes, one for each drag source node.
438 newItemsParams
= this.itemCreator(nodes
, target
.rowNode
, source
);
441 // Create new item in the tree, based on the drag source.
442 model
.newItem(newItemsParams
[idx
], newParentItem
, insertIndex
);
446 // Expand the target node (if it's currently collapsed) so the user can see
447 // where their node was dropped. In particular since that node is still selected.
448 this.tree
._expandNode(targetWidget
);
453 onDndCancel: function(){
455 // Topic event processor for /dnd/cancel, called to cancel the DnD operation
458 this._unmarkTargetAnchor();
459 this.isDragging
= false;
460 this.mouseDown
= false;
461 delete this.mouseButton
;
462 this._changeState("Source", "");
463 this._changeState("Target", "");
466 // When focus moves in/out of the entire Tree
467 onOverEvent: function(){
469 // This method is called when mouse is moved over our container (like onmouseenter)
472 this.inherited(arguments
);
473 dojo
.dnd
.manager().overSource(this);
475 onOutEvent: function(){
477 // This method is called when mouse is moved out of our container (like onmouseleave)
480 this._unmarkTargetAnchor();
481 var m
= dojo
.dnd
.manager();
487 this.inherited(arguments
);
490 _isParentChildDrop: function(source
, targetRow
){
492 // Checks whether the dragged items are parent rows in the tree which are being
493 // dragged into their own children.
496 // The DragSource object.
499 // The tree row onto which the dragged nodes are being dropped.
504 // If the dragged object is not coming from the tree this widget belongs to,
505 // it cannot be invalid.
506 if(!source
.tree
|| source
.tree
!= this.tree
){
511 var root
= source
.tree
.domNode
;
512 var ids
= source
.selection
;
514 var node
= targetRow
.parentNode
;
516 // Iterate up the DOM hierarchy from the target drop row,
517 // checking of any of the dragged nodes have the same ID.
518 while(node
!= root
&& !ids
[node
.id
]){
519 node
= node
.parentNode
;
522 return node
.id
&& ids
[node
.id
];
525 _unmarkTargetAnchor: function(){
527 // Removes hover class of the current target anchor
530 if(!this.targetAnchor
){ return; }
531 this._removeItemClass(this.targetAnchor
.rowNode
, this.dropPosition
);
532 this.targetAnchor
= null;
533 this.targetBox
= null;
534 this.dropPosition
= null;
537 _markDndStatus: function(copy
){
539 // Changes source's state based on "copy" status
540 this._changeState("Source", copy
? "Copied" : "Moved");