]>
Commit | Line | Data |
---|---|---|
f0cfe83e AD |
1 | define("dojo/dnd/Source", [ |
2 | "../_base/array", "../_base/connect", "../_base/declare", "../_base/kernel", "../_base/lang", | |
3 | "../dom-class", "../dom-geometry", "../mouse", "../ready", "../topic", | |
4 | "./common", "./Selector", "./Manager" | |
5 | ], function(array, connect, declare, kernel, lang, domClass, domGeom, mouse, ready, topic, | |
6 | dnd, Selector, Manager){ | |
7 | ||
8 | // module: | |
9 | // dojo/dnd/Source | |
10 | ||
11 | /* | |
12 | Container property: | |
13 | "Horizontal"- if this is the horizontal container | |
14 | Source states: | |
15 | "" - normal state | |
16 | "Moved" - this source is being moved | |
17 | "Copied" - this source is being copied | |
18 | Target states: | |
19 | "" - normal state | |
20 | "Disabled" - the target cannot accept an avatar | |
21 | Target anchor state: | |
22 | "" - item is not selected | |
23 | "Before" - insert point is before the anchor | |
24 | "After" - insert point is after the anchor | |
25 | */ | |
26 | ||
27 | /*===== | |
28 | var __SourceArgs = { | |
29 | // summary: | |
30 | // a dict of parameters for DnD Source configuration. Note that any | |
31 | // property on Source elements may be configured, but this is the | |
32 | // short-list | |
33 | // isSource: Boolean? | |
34 | // can be used as a DnD source. Defaults to true. | |
35 | // accept: Array? | |
36 | // list of accepted types (text strings) for a target; defaults to | |
37 | // ["text"] | |
38 | // autoSync: Boolean | |
39 | // if true refreshes the node list on every operation; false by default | |
40 | // copyOnly: Boolean? | |
41 | // copy items, if true, use a state of Ctrl key otherwise, | |
42 | // see selfCopy and selfAccept for more details | |
43 | // delay: Number | |
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 | |
47 | // selfCopy: Boolean? | |
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 | }; | |
58 | =====*/ | |
59 | ||
60 | // For back-compat, remove in 2.0. | |
61 | if(!kernel.isAsync){ | |
62 | ready(0, function(){ | |
63 | var requires = ["dojo/dnd/AutoSource", "dojo/dnd/Target"]; | |
64 | require(requires); // use indirection so modules not rolled into a build | |
65 | }); | |
66 | } | |
67 | ||
68 | var Source = declare("dojo.dnd.Source", Selector, { | |
69 | // summary: | |
70 | // a Source object, which can be used as a DnD source, or a DnD target | |
71 | ||
72 | // object attributes (for markup) | |
73 | isSource: true, | |
74 | horizontal: false, | |
75 | copyOnly: false, | |
76 | selfCopy: false, | |
77 | selfAccept: true, | |
78 | skipForm: false, | |
79 | withHandles: false, | |
80 | autoSync: false, | |
81 | delay: 0, // pixels | |
82 | accept: ["text"], | |
83 | generateText: true, | |
84 | ||
85 | constructor: function(/*DOMNode|String*/ node, /*__SourceArgs?*/ params){ | |
86 | // summary: | |
87 | // a constructor of the Source | |
88 | // node: | |
89 | // node or node's id to build the source on | |
90 | // params: | |
91 | // any property of this class may be configured via the params | |
92 | // object which is mixed-in to the `dojo/dnd/Source` instance | |
93 | lang.mixin(this, lang.mixin({}, params)); | |
94 | var type = this.accept; | |
95 | if(type.length){ | |
96 | this.accept = {}; | |
97 | for(var i = 0; i < type.length; ++i){ | |
98 | this.accept[type[i]] = 1; | |
99 | } | |
100 | } | |
101 | // class-specific variables | |
102 | this.isDragging = false; | |
103 | this.mouseDown = false; | |
104 | this.targetAnchor = null; | |
105 | this.targetBox = null; | |
106 | this.before = true; | |
107 | this._lastX = 0; | |
108 | this._lastY = 0; | |
109 | // states | |
110 | this.sourceState = ""; | |
111 | if(this.isSource){ | |
112 | domClass.add(this.node, "dojoDndSource"); | |
113 | } | |
114 | this.targetState = ""; | |
115 | if(this.accept){ | |
116 | domClass.add(this.node, "dojoDndTarget"); | |
117 | } | |
118 | if(this.horizontal){ | |
119 | domClass.add(this.node, "dojoDndHorizontal"); | |
120 | } | |
121 | // set up events | |
122 | this.topics = [ | |
123 | topic.subscribe("/dnd/source/over", lang.hitch(this, "onDndSourceOver")), | |
124 | topic.subscribe("/dnd/start", lang.hitch(this, "onDndStart")), | |
125 | topic.subscribe("/dnd/drop", lang.hitch(this, "onDndDrop")), | |
126 | topic.subscribe("/dnd/cancel", lang.hitch(this, "onDndCancel")) | |
127 | ]; | |
128 | }, | |
129 | ||
130 | // methods | |
131 | checkAcceptance: function(source, nodes){ | |
132 | // summary: | |
133 | // checks if the target can accept nodes from this source | |
134 | // source: Object | |
135 | // the source which provides items | |
136 | // nodes: Array | |
137 | // the list of transferred items | |
138 | if(this == source){ | |
139 | return !this.copyOnly || this.selfAccept; | |
140 | } | |
141 | for(var i = 0; i < nodes.length; ++i){ | |
142 | var type = source.getItem(nodes[i].id).type; | |
143 | // type instanceof Array | |
144 | var flag = false; | |
145 | for(var j = 0; j < type.length; ++j){ | |
146 | if(type[j] in this.accept){ | |
147 | flag = true; | |
148 | break; | |
149 | } | |
150 | } | |
151 | if(!flag){ | |
152 | return false; // Boolean | |
153 | } | |
154 | } | |
155 | return true; // Boolean | |
156 | }, | |
157 | copyState: function(keyPressed, self){ | |
158 | // summary: | |
159 | // Returns true if we need to copy items, false to move. | |
160 | // It is separated to be overwritten dynamically, if needed. | |
161 | // keyPressed: Boolean | |
162 | // the "copy" key was pressed | |
163 | // self: Boolean? | |
164 | // optional flag that means that we are about to drop on itself | |
165 | ||
166 | if(keyPressed){ return true; } | |
167 | if(arguments.length < 2){ | |
168 | self = this == Manager.manager().target; | |
169 | } | |
170 | if(self){ | |
171 | if(this.copyOnly){ | |
172 | return this.selfCopy; | |
173 | } | |
174 | }else{ | |
175 | return this.copyOnly; | |
176 | } | |
177 | return false; // Boolean | |
178 | }, | |
179 | destroy: function(){ | |
180 | // summary: | |
181 | // prepares the object to be garbage-collected | |
182 | Source.superclass.destroy.call(this); | |
183 | array.forEach(this.topics, function(t){t.remove();}); | |
184 | this.targetAnchor = null; | |
185 | }, | |
186 | ||
187 | // mouse event processors | |
188 | onMouseMove: function(e){ | |
189 | // summary: | |
190 | // event processor for onmousemove | |
191 | // e: Event | |
192 | // mouse event | |
193 | if(this.isDragging && this.targetState == "Disabled"){ return; } | |
194 | Source.superclass.onMouseMove.call(this, e); | |
195 | var m = Manager.manager(); | |
196 | if(!this.isDragging){ | |
197 | if(this.mouseDown && this.isSource && | |
198 | (Math.abs(e.pageX - this._lastX) > this.delay || Math.abs(e.pageY - this._lastY) > this.delay)){ | |
199 | var nodes = this.getSelectedNodes(); | |
200 | if(nodes.length){ | |
201 | m.startDrag(this, nodes, this.copyState(dnd.getCopyKeyState(e), true)); | |
202 | } | |
203 | } | |
204 | } | |
205 | if(this.isDragging){ | |
206 | // calculate before/after | |
207 | var before = false; | |
208 | if(this.current){ | |
209 | if(!this.targetBox || this.targetAnchor != this.current){ | |
210 | this.targetBox = domGeom.position(this.current, true); | |
211 | } | |
212 | if(this.horizontal){ | |
213 | // In LTR mode, the left part of the object means "before", but in RTL mode it means "after". | |
214 | before = (e.pageX - this.targetBox.x < this.targetBox.w / 2) == domGeom.isBodyLtr(this.current.ownerDocument); | |
215 | }else{ | |
216 | before = (e.pageY - this.targetBox.y) < (this.targetBox.h / 2); | |
217 | } | |
218 | } | |
219 | if(this.current != this.targetAnchor || before != this.before){ | |
220 | this._markTargetAnchor(before); | |
221 | m.canDrop(!this.current || m.source != this || !(this.current.id in this.selection)); | |
222 | } | |
223 | } | |
224 | }, | |
225 | onMouseDown: function(e){ | |
226 | // summary: | |
227 | // event processor for onmousedown | |
228 | // e: Event | |
229 | // mouse event | |
230 | if(!this.mouseDown && this._legalMouseDown(e) && (!this.skipForm || !dnd.isFormElement(e))){ | |
231 | this.mouseDown = true; | |
232 | this._lastX = e.pageX; | |
233 | this._lastY = e.pageY; | |
234 | Source.superclass.onMouseDown.call(this, e); | |
235 | } | |
236 | }, | |
237 | onMouseUp: function(e){ | |
238 | // summary: | |
239 | // event processor for onmouseup | |
240 | // e: Event | |
241 | // mouse event | |
242 | if(this.mouseDown){ | |
243 | this.mouseDown = false; | |
244 | Source.superclass.onMouseUp.call(this, e); | |
245 | } | |
246 | }, | |
247 | ||
248 | // topic event processors | |
249 | onDndSourceOver: function(source){ | |
250 | // summary: | |
251 | // topic event processor for /dnd/source/over, called when detected a current source | |
252 | // source: Object | |
253 | // the source which has the mouse over it | |
254 | if(this !== source){ | |
255 | this.mouseDown = false; | |
256 | if(this.targetAnchor){ | |
257 | this._unmarkTargetAnchor(); | |
258 | } | |
259 | }else if(this.isDragging){ | |
260 | var m = Manager.manager(); | |
261 | m.canDrop(this.targetState != "Disabled" && (!this.current || m.source != this || !(this.current.id in this.selection))); | |
262 | } | |
263 | }, | |
264 | onDndStart: function(source, nodes, copy){ | |
265 | // summary: | |
266 | // topic event processor for /dnd/start, called to initiate the DnD operation | |
267 | // source: Object | |
268 | // the source which provides items | |
269 | // nodes: Array | |
270 | // the list of transferred items | |
271 | // copy: Boolean | |
272 | // copy items, if true, move items otherwise | |
273 | if(this.autoSync){ this.sync(); } | |
274 | if(this.isSource){ | |
275 | this._changeState("Source", this == source ? (copy ? "Copied" : "Moved") : ""); | |
276 | } | |
277 | var accepted = this.accept && this.checkAcceptance(source, nodes); | |
278 | this._changeState("Target", accepted ? "" : "Disabled"); | |
279 | if(this == source){ | |
280 | Manager.manager().overSource(this); | |
281 | } | |
282 | this.isDragging = true; | |
283 | }, | |
284 | onDndDrop: function(source, nodes, copy, target){ | |
285 | // summary: | |
286 | // topic event processor for /dnd/drop, called to finish the DnD operation | |
287 | // source: Object | |
288 | // the source which provides items | |
289 | // nodes: Array | |
290 | // the list of transferred items | |
291 | // copy: Boolean | |
292 | // copy items, if true, move items otherwise | |
293 | // target: Object | |
294 | // the target which accepts items | |
295 | if(this == target){ | |
296 | // this one is for us => move nodes! | |
297 | this.onDrop(source, nodes, copy); | |
298 | } | |
299 | this.onDndCancel(); | |
300 | }, | |
301 | onDndCancel: function(){ | |
302 | // summary: | |
303 | // topic event processor for /dnd/cancel, called to cancel the DnD operation | |
304 | if(this.targetAnchor){ | |
305 | this._unmarkTargetAnchor(); | |
306 | this.targetAnchor = null; | |
307 | } | |
308 | this.before = true; | |
309 | this.isDragging = false; | |
310 | this.mouseDown = false; | |
311 | this._changeState("Source", ""); | |
312 | this._changeState("Target", ""); | |
313 | }, | |
314 | ||
315 | // local events | |
316 | onDrop: function(source, nodes, copy){ | |
317 | // summary: | |
318 | // called only on the current target, when drop is performed | |
319 | // source: Object | |
320 | // the source which provides items | |
321 | // nodes: Array | |
322 | // the list of transferred items | |
323 | // copy: Boolean | |
324 | // copy items, if true, move items otherwise | |
325 | ||
326 | if(this != source){ | |
327 | this.onDropExternal(source, nodes, copy); | |
328 | }else{ | |
329 | this.onDropInternal(nodes, copy); | |
330 | } | |
331 | }, | |
332 | onDropExternal: function(source, nodes, copy){ | |
333 | // summary: | |
334 | // called only on the current target, when drop is performed | |
335 | // from an external source | |
336 | // source: Object | |
337 | // the source which provides items | |
338 | // nodes: Array | |
339 | // the list of transferred items | |
340 | // copy: Boolean | |
341 | // copy items, if true, move items otherwise | |
342 | ||
343 | var oldCreator = this._normalizedCreator; | |
344 | // transferring nodes from the source to the target | |
345 | if(this.creator){ | |
346 | // use defined creator | |
347 | this._normalizedCreator = function(node, hint){ | |
348 | return oldCreator.call(this, source.getItem(node.id).data, hint); | |
349 | }; | |
350 | }else{ | |
351 | // we have no creator defined => move/clone nodes | |
352 | if(copy){ | |
353 | // clone nodes | |
354 | this._normalizedCreator = function(node /*=====, hint =====*/){ | |
355 | var t = source.getItem(node.id); | |
356 | var n = node.cloneNode(true); | |
357 | n.id = dnd.getUniqueId(); | |
358 | return {node: n, data: t.data, type: t.type}; | |
359 | }; | |
360 | }else{ | |
361 | // move nodes | |
362 | this._normalizedCreator = function(node /*=====, hint =====*/){ | |
363 | var t = source.getItem(node.id); | |
364 | source.delItem(node.id); | |
365 | return {node: node, data: t.data, type: t.type}; | |
366 | }; | |
367 | } | |
368 | } | |
369 | this.selectNone(); | |
370 | if(!copy && !this.creator){ | |
371 | source.selectNone(); | |
372 | } | |
373 | this.insertNodes(true, nodes, this.before, this.current); | |
374 | if(!copy && this.creator){ | |
375 | source.deleteSelectedNodes(); | |
376 | } | |
377 | this._normalizedCreator = oldCreator; | |
378 | }, | |
379 | onDropInternal: function(nodes, copy){ | |
380 | // summary: | |
381 | // called only on the current target, when drop is performed | |
382 | // from the same target/source | |
383 | // nodes: Array | |
384 | // the list of transferred items | |
385 | // copy: Boolean | |
386 | // copy items, if true, move items otherwise | |
387 | ||
388 | var oldCreator = this._normalizedCreator; | |
389 | // transferring nodes within the single source | |
390 | if(this.current && this.current.id in this.selection){ | |
391 | // do nothing | |
392 | return; | |
393 | } | |
394 | if(copy){ | |
395 | if(this.creator){ | |
396 | // create new copies of data items | |
397 | this._normalizedCreator = function(node, hint){ | |
398 | return oldCreator.call(this, this.getItem(node.id).data, hint); | |
399 | }; | |
400 | }else{ | |
401 | // clone nodes | |
402 | this._normalizedCreator = function(node/*=====, hint =====*/){ | |
403 | var t = this.getItem(node.id); | |
404 | var n = node.cloneNode(true); | |
405 | n.id = dnd.getUniqueId(); | |
406 | return {node: n, data: t.data, type: t.type}; | |
407 | }; | |
408 | } | |
409 | }else{ | |
410 | // move nodes | |
411 | if(!this.current){ | |
412 | // do nothing | |
413 | return; | |
414 | } | |
415 | this._normalizedCreator = function(node /*=====, hint =====*/){ | |
416 | var t = this.getItem(node.id); | |
417 | return {node: node, data: t.data, type: t.type}; | |
418 | }; | |
419 | } | |
420 | this._removeSelection(); | |
421 | this.insertNodes(true, nodes, this.before, this.current); | |
422 | this._normalizedCreator = oldCreator; | |
423 | }, | |
424 | onDraggingOver: function(){ | |
425 | // summary: | |
426 | // called during the active DnD operation, when items | |
427 | // are dragged over this target, and it is not disabled | |
428 | }, | |
429 | onDraggingOut: function(){ | |
430 | // summary: | |
431 | // called during the active DnD operation, when items | |
432 | // are dragged away from this target, and it is not disabled | |
433 | }, | |
434 | ||
435 | // utilities | |
436 | onOverEvent: function(){ | |
437 | // summary: | |
438 | // this function is called once, when mouse is over our container | |
439 | Source.superclass.onOverEvent.call(this); | |
440 | Manager.manager().overSource(this); | |
441 | if(this.isDragging && this.targetState != "Disabled"){ | |
442 | this.onDraggingOver(); | |
443 | } | |
444 | }, | |
445 | onOutEvent: function(){ | |
446 | // summary: | |
447 | // this function is called once, when mouse is out of our container | |
448 | Source.superclass.onOutEvent.call(this); | |
449 | Manager.manager().outSource(this); | |
450 | if(this.isDragging && this.targetState != "Disabled"){ | |
451 | this.onDraggingOut(); | |
452 | } | |
453 | }, | |
454 | _markTargetAnchor: function(before){ | |
455 | // summary: | |
456 | // assigns a class to the current target anchor based on "before" status | |
457 | // before: Boolean | |
458 | // insert before, if true, after otherwise | |
459 | if(this.current == this.targetAnchor && this.before == before){ return; } | |
460 | if(this.targetAnchor){ | |
461 | this._removeItemClass(this.targetAnchor, this.before ? "Before" : "After"); | |
462 | } | |
463 | this.targetAnchor = this.current; | |
464 | this.targetBox = null; | |
465 | this.before = before; | |
466 | if(this.targetAnchor){ | |
467 | this._addItemClass(this.targetAnchor, this.before ? "Before" : "After"); | |
468 | } | |
469 | }, | |
470 | _unmarkTargetAnchor: function(){ | |
471 | // summary: | |
472 | // removes a class of the current target anchor based on "before" status | |
473 | if(!this.targetAnchor){ return; } | |
474 | this._removeItemClass(this.targetAnchor, this.before ? "Before" : "After"); | |
475 | this.targetAnchor = null; | |
476 | this.targetBox = null; | |
477 | this.before = true; | |
478 | }, | |
479 | _markDndStatus: function(copy){ | |
480 | // summary: | |
481 | // changes source's state based on "copy" status | |
482 | this._changeState("Source", copy ? "Copied" : "Moved"); | |
483 | }, | |
484 | _legalMouseDown: function(e){ | |
485 | // summary: | |
486 | // checks if user clicked on "approved" items | |
487 | // e: Event | |
488 | // mouse event | |
489 | ||
490 | // accept only the left mouse button, or the left finger | |
491 | if(e.type != "touchstart" && !mouse.isLeft(e)){ return false; } | |
492 | ||
493 | if(!this.withHandles){ return true; } | |
494 | ||
495 | // check for handles | |
496 | for(var node = e.target; node && node !== this.node; node = node.parentNode){ | |
497 | if(domClass.contains(node, "dojoDndHandle")){ return true; } | |
498 | if(domClass.contains(node, "dojoDndItem") || domClass.contains(node, "dojoDndIgnore")){ break; } | |
499 | } | |
500 | return false; // Boolean | |
501 | } | |
502 | }); | |
503 | ||
504 | return Source; | |
505 | ||
506 | }); |