1 define("dojo/dnd/Source", ["../main", "./Selector", "./Manager"], function(dojo, Selector, Manager) {
8 Selector = dojo.dnd.Selector;
13 "Horizontal"- if this is the horizontal container
16 "Moved" - this source is being moved
17 "Copied" - this source is being copied
20 "Disabled" - the target cannot accept an avatar
22 "" - item is not selected
23 "Before" - insert point is before the anchor
24 "After" - insert point is after the anchor
28 dojo.dnd.__SourceArgs = function(){
30 // a dict of parameters for DnD Source configuration. Note that any
31 // property on Source elements may be configured, but this is the
34 // can be used as a DnD source. Defaults to true.
36 // list of accepted types (text strings) for a target; defaults to
39 // if true refreshes the node list on every operation; false by default
41 // copy items, if true, use a state of Ctrl key otherwise,
42 // see selfCopy and selfAccept for more details
44 // the move delay in pixels before detecting a drag; 0 by default
45 // horizontal: Boolean?
46 // a horizontal container, if true, vertical otherwise or when omitted
48 // copy items by default when dropping on itself,
49 // false by default, works only if copyOnly is true
50 // selfAccept: Boolean?
51 // accept its own items when copyOnly is true,
52 // true by default, works only if copyOnly is true
53 // withHandles: Boolean?
54 // allows dragging only by handles, false by default
55 // generateText: Boolean?
56 // generate text node for drag and drop, true by default
57 this.isSource = isSource;
59 this.autoSync = autoSync;
60 this.copyOnly = copyOnly;
62 this.horizontal = horizontal;
63 this.selfCopy = selfCopy;
64 this.selfAccept = selfAccept;
65 this.withHandles = withHandles;
66 this.generateText = true;
70 // For back-compat, remove in 2.0.
72 dojo.ready(0, function(){
73 var requires = ["dojo/dnd/AutoSource", "dojo/dnd/Target"];
74 require(requires); // use indirection so modules not rolled into a build
78 return dojo.declare("dojo.dnd.Source", Selector, {
80 // a Source object, which can be used as a DnD source, or a DnD target
82 // object attributes (for markup)
95 constructor: function(/*DOMNode|String*/node, /*dojo.dnd.__SourceArgs?*/params){
97 // a constructor of the Source
99 // node or node's id to build the source on
101 // any property of this class may be configured via the params
102 // object which is mixed-in to the `dojo.dnd.Source` instance
103 dojo.mixin(this, dojo.mixin({}, params));
104 var type = this.accept;
107 for(var i = 0; i < type.length; ++i){
108 this.accept[type[i]] = 1;
111 // class-specific variables
112 this.isDragging = false;
113 this.mouseDown = false;
114 this.targetAnchor = null;
115 this.targetBox = null;
120 this.sourceState = "";
122 dojo.addClass(this.node, "dojoDndSource");
124 this.targetState = "";
126 dojo.addClass(this.node, "dojoDndTarget");
129 dojo.addClass(this.node, "dojoDndHorizontal");
133 dojo.subscribe("/dnd/source/over", this, "onDndSourceOver"),
134 dojo.subscribe("/dnd/start", this, "onDndStart"),
135 dojo.subscribe("/dnd/drop", this, "onDndDrop"),
136 dojo.subscribe("/dnd/cancel", this, "onDndCancel")
141 checkAcceptance: function(source, nodes){
143 // checks if the target can accept nodes from this source
145 // the source which provides items
147 // the list of transferred items
149 return !this.copyOnly || this.selfAccept;
151 for(var i = 0; i < nodes.length; ++i){
152 var type = source.getItem(nodes[i].id).type;
153 // type instanceof Array
155 for(var j = 0; j < type.length; ++j){
156 if(type[j] in this.accept){
162 return false; // Boolean
165 return true; // Boolean
167 copyState: function(keyPressed, self){
169 // Returns true if we need to copy items, false to move.
170 // It is separated to be overwritten dynamically, if needed.
171 // keyPressed: Boolean
172 // the "copy" key was pressed
174 // optional flag that means that we are about to drop on itself
176 if(keyPressed){ return true; }
177 if(arguments.length < 2){
178 self = this == Manager.manager().target;
182 return this.selfCopy;
185 return this.copyOnly;
187 return false; // Boolean
191 // prepares the object to be garbage-collected
192 dojo.dnd.Source.superclass.destroy.call(this);
193 dojo.forEach(this.topics, dojo.unsubscribe);
194 this.targetAnchor = null;
197 // mouse event processors
198 onMouseMove: function(e){
200 // event processor for onmousemove
203 if(this.isDragging && this.targetState == "Disabled"){ return; }
204 dojo.dnd.Source.superclass.onMouseMove.call(this, e);
205 var m = Manager.manager();
206 if(!this.isDragging){
207 if(this.mouseDown && this.isSource &&
208 (Math.abs(e.pageX - this._lastX) > this.delay || Math.abs(e.pageY - this._lastY) > this.delay)){
209 var nodes = this.getSelectedNodes();
211 m.startDrag(this, nodes, this.copyState(dojo.isCopyKey(e), true));
216 // calculate before/after
219 if(!this.targetBox || this.targetAnchor != this.current){
220 this.targetBox = dojo.position(this.current, true);
223 before = (e.pageX - this.targetBox.x) < (this.targetBox.w / 2);
225 before = (e.pageY - this.targetBox.y) < (this.targetBox.h / 2);
228 if(this.current != this.targetAnchor || before != this.before){
229 this._markTargetAnchor(before);
230 m.canDrop(!this.current || m.source != this || !(this.current.id in this.selection));
234 onMouseDown: function(e){
236 // event processor for onmousedown
239 if(!this.mouseDown && this._legalMouseDown(e) && (!this.skipForm || !dojo.dnd.isFormElement(e))){
240 this.mouseDown = true;
241 this._lastX = e.pageX;
242 this._lastY = e.pageY;
243 dojo.dnd.Source.superclass.onMouseDown.call(this, e);
246 onMouseUp: function(e){
248 // event processor for onmouseup
252 this.mouseDown = false;
253 dojo.dnd.Source.superclass.onMouseUp.call(this, e);
257 // topic event processors
258 onDndSourceOver: function(source){
260 // topic event processor for /dnd/source/over, called when detected a current source
262 // the source which has the mouse over it
264 this.mouseDown = false;
265 if(this.targetAnchor){
266 this._unmarkTargetAnchor();
268 }else if(this.isDragging){
269 var m = Manager.manager();
270 m.canDrop(this.targetState != "Disabled" && (!this.current || m.source != this || !(this.current.id in this.selection)));
273 onDndStart: function(source, nodes, copy){
275 // topic event processor for /dnd/start, called to initiate the DnD operation
277 // the source which provides items
279 // the list of transferred items
281 // copy items, if true, move items otherwise
282 if(this.autoSync){ this.sync(); }
284 this._changeState("Source", this == source ? (copy ? "Copied" : "Moved") : "");
286 var accepted = this.accept && this.checkAcceptance(source, nodes);
287 this._changeState("Target", accepted ? "" : "Disabled");
289 Manager.manager().overSource(this);
291 this.isDragging = true;
293 onDndDrop: function(source, nodes, copy, target){
295 // topic event processor for /dnd/drop, called to finish the DnD operation
297 // the source which provides items
299 // the list of transferred items
301 // copy items, if true, move items otherwise
303 // the target which accepts items
305 // this one is for us => move nodes!
306 this.onDrop(source, nodes, copy);
310 onDndCancel: function(){
312 // topic event processor for /dnd/cancel, called to cancel the DnD operation
313 if(this.targetAnchor){
314 this._unmarkTargetAnchor();
315 this.targetAnchor = null;
318 this.isDragging = false;
319 this.mouseDown = false;
320 this._changeState("Source", "");
321 this._changeState("Target", "");
325 onDrop: function(source, nodes, copy){
327 // called only on the current target, when drop is performed
329 // the source which provides items
331 // the list of transferred items
333 // copy items, if true, move items otherwise
336 this.onDropExternal(source, nodes, copy);
338 this.onDropInternal(nodes, copy);
341 onDropExternal: function(source, nodes, copy){
343 // called only on the current target, when drop is performed
344 // from an external source
346 // the source which provides items
348 // the list of transferred items
350 // copy items, if true, move items otherwise
352 var oldCreator = this._normalizedCreator;
353 // transferring nodes from the source to the target
355 // use defined creator
356 this._normalizedCreator = function(node, hint){
357 return oldCreator.call(this, source.getItem(node.id).data, hint);
360 // we have no creator defined => move/clone nodes
363 this._normalizedCreator = function(node, hint){
364 var t = source.getItem(node.id);
365 var n = node.cloneNode(true);
366 n.id = dojo.dnd.getUniqueId();
367 return {node: n, data: t.data, type: t.type};
371 this._normalizedCreator = function(node, hint){
372 var t = source.getItem(node.id);
373 source.delItem(node.id);
374 return {node: node, data: t.data, type: t.type};
379 if(!copy && !this.creator){
382 this.insertNodes(true, nodes, this.before, this.current);
383 if(!copy && this.creator){
384 source.deleteSelectedNodes();
386 this._normalizedCreator = oldCreator;
388 onDropInternal: function(nodes, copy){
390 // called only on the current target, when drop is performed
391 // from the same target/source
393 // the list of transferred items
395 // copy items, if true, move items otherwise
397 var oldCreator = this._normalizedCreator;
398 // transferring nodes within the single source
399 if(this.current && this.current.id in this.selection){
405 // create new copies of data items
406 this._normalizedCreator = function(node, hint){
407 return oldCreator.call(this, this.getItem(node.id).data, hint);
411 this._normalizedCreator = function(node, hint){
412 var t = this.getItem(node.id);
413 var n = node.cloneNode(true);
414 n.id = dojo.dnd.getUniqueId();
415 return {node: n, data: t.data, type: t.type};
424 this._normalizedCreator = function(node, hint){
425 var t = this.getItem(node.id);
426 return {node: node, data: t.data, type: t.type};
429 this._removeSelection();
430 this.insertNodes(true, nodes, this.before, this.current);
431 this._normalizedCreator = oldCreator;
433 onDraggingOver: function(){
435 // called during the active DnD operation, when items
436 // are dragged over this target, and it is not disabled
438 onDraggingOut: function(){
440 // called during the active DnD operation, when items
441 // are dragged away from this target, and it is not disabled
445 onOverEvent: function(){
447 // this function is called once, when mouse is over our container
448 dojo.dnd.Source.superclass.onOverEvent.call(this);
449 Manager.manager().overSource(this);
450 if(this.isDragging && this.targetState != "Disabled"){
451 this.onDraggingOver();
454 onOutEvent: function(){
456 // this function is called once, when mouse is out of our container
457 dojo.dnd.Source.superclass.onOutEvent.call(this);
458 Manager.manager().outSource(this);
459 if(this.isDragging && this.targetState != "Disabled"){
460 this.onDraggingOut();
463 _markTargetAnchor: function(before){
465 // assigns a class to the current target anchor based on "before" status
467 // insert before, if true, after otherwise
468 if(this.current == this.targetAnchor && this.before == before){ return; }
469 if(this.targetAnchor){
470 this._removeItemClass(this.targetAnchor, this.before ? "Before" : "After");
472 this.targetAnchor = this.current;
473 this.targetBox = null;
474 this.before = before;
475 if(this.targetAnchor){
476 this._addItemClass(this.targetAnchor, this.before ? "Before" : "After");
479 _unmarkTargetAnchor: function(){
481 // removes a class of the current target anchor based on "before" status
482 if(!this.targetAnchor){ return; }
483 this._removeItemClass(this.targetAnchor, this.before ? "Before" : "After");
484 this.targetAnchor = null;
485 this.targetBox = null;
488 _markDndStatus: function(copy){
490 // changes source's state based on "copy" status
491 this._changeState("Source", copy ? "Copied" : "Moved");
493 _legalMouseDown: function(e){
495 // checks if user clicked on "approved" items
499 // accept only the left mouse button
500 if(!dojo.mouseButtons.isLeft(e)){ return false; }
502 if(!this.withHandles){ return true; }
505 for(var node = e.target; node && node !== this.node; node = node.parentNode){
506 if(dojo.hasClass(node, "dojoDndHandle")){ return true; }
507 if(dojo.hasClass(node, "dojoDndItem") || dojo.hasClass(node, "dojoDndIgnore")){ break; }
509 return false; // Boolean