source: Dev/trunk/src/client/dojo/dnd/Source.js @ 485

Last change on this file since 485 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 14.7 KB
Line 
1define([
2        "../_base/array", "../_base/declare", "../_base/kernel", "../_base/lang",
3        "../dom-class", "../dom-geometry", "../mouse", "../ready", "../topic",
4        "./common", "./Selector", "./Manager"
5], function(array, 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});
Note: See TracBrowser for help on using the repository browser.