1 if(!dojo._hasResource["dojo.dnd.Source"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2 dojo._hasResource["dojo.dnd.Source"] = true;
3 dojo.provide("dojo.dnd.Source");
5 dojo.require("dojo.dnd.Selector");
6 dojo.require("dojo.dnd.Manager");
10 "Horizontal"- if this is the horizontal container
13 "Moved" - this source is being moved
14 "Copied" - this source is being copied
17 "Disabled" - the target cannot accept an avatar
19 "" - item is not selected
20 "Before" - insert point is before the anchor
21 "After" - insert point is after the anchor
25 dojo.dnd.__SourceArgs = function(){
27 // a dict of parameters for DnD Source configuration. Note that any
28 // property on Source elements may be configured, but this is the
31 // can be used as a DnD source. Defaults to true.
33 // list of accepted types (text strings) for a target; defaults to
35 // horizontal: Boolean?
36 // a horizontal container, if true, vertical otherwise or when omitted
38 // always copy items, if true, use a state of Ctrl key otherwise
39 // withHandles: Boolean?
40 // allows dragging only by handles
41 this.isSource = isSource;
43 this.horizontal = horizontal;
44 this.copyOnly = copyOnly;
45 this.withHandles = withHandles;
49 dojo.declare("dojo.dnd.Source", dojo.dnd.Selector, {
50 // summary: a Source object, which can be used as a DnD source, or a DnD target
52 // object attributes (for markup)
60 constructor: function(/*DOMNode|String*/node, /*dojo.dnd.__SourceArgs?*/params){
62 // a constructor of the Source
64 // node or node's id to build the source on
66 // any property of this class may be configured via the params
67 // object which is mixed-in to the `dojo.dnd.Source` instance
68 dojo.mixin(this, dojo.mixin({}, params));
69 var type = this.accept;
72 for(var i = 0; i < type.length; ++i){
73 this.accept[type[i]] = 1;
76 // class-specific variables
77 this.isDragging = false;
78 this.mouseDown = false;
79 this.targetAnchor = null;
80 this.targetBox = null;
83 this.sourceState = "";
85 dojo.addClass(this.node, "dojoDndSource");
87 this.targetState = "";
89 dojo.addClass(this.node, "dojoDndTarget");
92 dojo.addClass(this.node, "dojoDndHorizontal");
96 dojo.subscribe("/dnd/source/over", this, "onDndSourceOver"),
97 dojo.subscribe("/dnd/start", this, "onDndStart"),
98 dojo.subscribe("/dnd/drop", this, "onDndDrop"),
99 dojo.subscribe("/dnd/cancel", this, "onDndCancel")
104 checkAcceptance: function(source, nodes){
105 // summary: checks, if the target can accept nodes from this source
106 // source: Object: the source which provides items
107 // nodes: Array: the list of transferred items
108 if(this == source){ return true; }
109 for(var i = 0; i < nodes.length; ++i){
110 var type = source.getItem(nodes[i].id).type;
111 // type instanceof Array
113 for(var j = 0; j < type.length; ++j){
114 if(type[j] in this.accept){
120 return false; // Boolean
123 return true; // Boolean
125 copyState: function(keyPressed){
126 // summary: Returns true, if we need to copy items, false to move.
127 // It is separated to be overwritten dynamically, if needed.
128 // keyPressed: Boolean: the "copy" was pressed
129 return this.copyOnly || keyPressed; // Boolean
132 // summary: prepares the object to be garbage-collected
133 dojo.dnd.Source.superclass.destroy.call(this);
134 dojo.forEach(this.topics, dojo.unsubscribe);
135 this.targetAnchor = null;
139 markupFactory: function(params, node){
140 params._skipStartup = true;
141 return new dojo.dnd.Source(node, params);
144 // mouse event processors
145 onMouseMove: function(e){
146 // summary: event processor for onmousemove
147 // e: Event: mouse event
148 if(this.isDragging && this.targetState == "Disabled"){ return; }
149 dojo.dnd.Source.superclass.onMouseMove.call(this, e);
150 var m = dojo.dnd.manager();
152 // calculate before/after
155 if(!this.targetBox || this.targetAnchor != this.current){
157 xy: dojo.coords(this.current, true),
158 w: this.current.offsetWidth,
159 h: this.current.offsetHeight
163 before = (e.pageX - this.targetBox.xy.x) < (this.targetBox.w / 2);
165 before = (e.pageY - this.targetBox.xy.y) < (this.targetBox.h / 2);
168 if(this.current != this.targetAnchor || before != this.before){
169 this._markTargetAnchor(before);
170 m.canDrop(!this.current || m.source != this || !(this.current.id in this.selection));
173 if(this.mouseDown && this.isSource){
174 var nodes = this.getSelectedNodes();
176 m.startDrag(this, nodes, this.copyState(dojo.dnd.getCopyKeyState(e)));
181 onMouseDown: function(e){
182 // summary: event processor for onmousedown
183 // e: Event: mouse event
184 if(this._legalMouseDown(e) && (!this.skipForm || !dojo.dnd.isFormElement(e))){
185 this.mouseDown = true;
186 this.mouseButton = e.button;
187 dojo.dnd.Source.superclass.onMouseDown.call(this, e);
190 onMouseUp: function(e){
191 // summary: event processor for onmouseup
192 // e: Event: mouse event
194 this.mouseDown = false;
195 dojo.dnd.Source.superclass.onMouseUp.call(this, e);
199 // topic event processors
200 onDndSourceOver: function(source){
201 // summary: topic event processor for /dnd/source/over, called when detected a current source
202 // source: Object: the source which has the mouse over it
204 this.mouseDown = false;
205 if(this.targetAnchor){
206 this._unmarkTargetAnchor();
208 }else if(this.isDragging){
209 var m = dojo.dnd.manager();
210 m.canDrop(this.targetState != "Disabled" && (!this.current || m.source != this || !(this.current.id in this.selection)));
213 onDndStart: function(source, nodes, copy){
214 // summary: topic event processor for /dnd/start, called to initiate the DnD operation
215 // source: Object: the source which provides items
216 // nodes: Array: the list of transferred items
217 // copy: Boolean: copy items, if true, move items otherwise
219 this._changeState("Source", this == source ? (copy ? "Copied" : "Moved") : "");
221 var accepted = this.accept && this.checkAcceptance(source, nodes);
222 this._changeState("Target", accepted ? "" : "Disabled");
223 if(accepted && this == source){
224 dojo.dnd.manager().overSource(this);
226 this.isDragging = true;
228 onDndDrop: function(source, nodes, copy){
229 // summary: topic event processor for /dnd/drop, called to finish the DnD operation
230 // source: Object: the source which provides items
231 // nodes: Array: the list of transferred items
232 // copy: Boolean: copy items, if true, move items otherwise
234 if(this.containerState != "Over"){ break; }
235 var oldCreator = this._normalizedCreator;
237 // transferring nodes from the source to the target
239 // use defined creator
240 this._normalizedCreator = function(node, hint){
241 return oldCreator.call(this, source.getItem(node.id).data, hint);
244 // we have no creator defined => move/clone nodes
247 this._normalizedCreator = function(node, hint){
248 var t = source.getItem(node.id);
249 var n = node.cloneNode(true);
250 n.id = dojo.dnd.getUniqueId();
251 return {node: n, data: t.data, type: t.type};
255 this._normalizedCreator = function(node, hint){
256 var t = source.getItem(node.id);
257 source.delItem(node.id);
258 return {node: node, data: t.data, type: t.type};
263 // transferring nodes within the single source
264 if(this.current && this.current.id in this.selection){ break; }
266 // use defined creator
268 // create new copies of data items
269 this._normalizedCreator = function(node, hint){
270 return oldCreator.call(this, source.getItem(node.id).data, hint);
274 if(!this.current){ break; }
275 this._normalizedCreator = function(node, hint){
276 var t = source.getItem(node.id);
277 return {node: node, data: t.data, type: t.type};
281 // we have no creator defined => move/clone nodes
284 this._normalizedCreator = function(node, hint){
285 var t = source.getItem(node.id);
286 var n = node.cloneNode(true);
287 n.id = dojo.dnd.getUniqueId();
288 return {node: n, data: t.data, type: t.type};
292 if(!this.current){ break; }
293 this._normalizedCreator = function(node, hint){
294 var t = source.getItem(node.id);
295 return {node: node, data: t.data, type: t.type};
300 this._removeSelection();
302 this._removeAnchor();
304 if(this != source && !copy && !this.creator){
307 this.insertNodes(true, nodes, this.before, this.current);
308 if(this != source && !copy && this.creator){
309 source.deleteSelectedNodes();
311 this._normalizedCreator = oldCreator;
315 onDndCancel: function(){
316 // summary: topic event processor for /dnd/cancel, called to cancel the DnD operation
317 if(this.targetAnchor){
318 this._unmarkTargetAnchor();
319 this.targetAnchor = null;
322 this.isDragging = false;
323 this.mouseDown = false;
324 delete this.mouseButton;
325 this._changeState("Source", "");
326 this._changeState("Target", "");
330 onOverEvent: function(){
331 // summary: this function is called once, when mouse is over our container
332 dojo.dnd.Source.superclass.onOverEvent.call(this);
333 dojo.dnd.manager().overSource(this);
335 onOutEvent: function(){
336 // summary: this function is called once, when mouse is out of our container
337 dojo.dnd.Source.superclass.onOutEvent.call(this);
338 dojo.dnd.manager().outSource(this);
340 _markTargetAnchor: function(before){
341 // summary: assigns a class to the current target anchor based on "before" status
342 // before: Boolean: insert before, if true, after otherwise
343 if(this.current == this.targetAnchor && this.before == before){ return; }
344 if(this.targetAnchor){
345 this._removeItemClass(this.targetAnchor, this.before ? "Before" : "After");
347 this.targetAnchor = this.current;
348 this.targetBox = null;
349 this.before = before;
350 if(this.targetAnchor){
351 this._addItemClass(this.targetAnchor, this.before ? "Before" : "After");
354 _unmarkTargetAnchor: function(){
355 // summary: removes a class of the current target anchor based on "before" status
356 if(!this.targetAnchor){ return; }
357 this._removeItemClass(this.targetAnchor, this.before ? "Before" : "After");
358 this.targetAnchor = null;
359 this.targetBox = null;
362 _markDndStatus: function(copy){
363 // summary: changes source's state based on "copy" status
364 this._changeState("Source", copy ? "Copied" : "Moved");
366 _legalMouseDown: function(e){
367 // summary: checks if user clicked on "approved" items
368 // e: Event: mouse event
369 if(!this.withHandles){ return true; }
370 for(var node = e.target; node && !dojo.hasClass(node, "dojoDndItem"); node = node.parentNode){
371 if(dojo.hasClass(node, "dojoDndHandle")){ return true; }
373 return false; // Boolean
377 dojo.declare("dojo.dnd.Target", dojo.dnd.Source, {
378 // summary: a Target object, which can be used as a DnD target
380 constructor: function(node, params){
381 // summary: a constructor of the Target --- see the Source constructor for details
382 this.isSource = false;
383 dojo.removeClass(this.node, "dojoDndSource");
387 markupFactory: function(params, node){
388 params._skipStartup = true;
389 return new dojo.dnd.Target(node, params);