]> git.wh0rd.org - tt-rss.git/blame - lib/dojo/dnd/Source.js.uncompressed.js
upgrade dojo to 1.8.3 (refs #570)
[tt-rss.git] / lib / dojo / dnd / Source.js.uncompressed.js
CommitLineData
f0cfe83e
AD
1define("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/*=====
28var __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.
61if(!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
68var 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
504return Source;
505
506});