source: Dev/branches/rest-dojo-ui/client/dojo/dnd/Source.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 14.6 KB
Line 
1define(["../main", "./Selector", "./Manager"], function(dojo, Selector, Manager) {
2        // module:
3        //              dojo/dnd/Source
4        // summary:
5        //              TODOC
6
7/*=====
8Selector = dojo.dnd.Selector;
9=====*/
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/*=====
28dojo.dnd.__SourceArgs = function(){
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        this.isSource = isSource;
58        this.accept = accept;
59        this.autoSync = autoSync;
60        this.copyOnly = copyOnly;
61        this.delay = delay;
62        this.horizontal = horizontal;
63        this.selfCopy = selfCopy;
64        this.selfAccept = selfAccept;
65        this.withHandles = withHandles;
66        this.generateText = true;
67}
68=====*/
69
70// For back-compat, remove in 2.0.
71if(!dojo.isAsync){
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
75        })
76}
77
78return dojo.declare("dojo.dnd.Source", Selector, {
79        // summary:
80        //              a Source object, which can be used as a DnD source, or a DnD target
81
82        // object attributes (for markup)
83        isSource: true,
84        horizontal: false,
85        copyOnly: false,
86        selfCopy: false,
87        selfAccept: true,
88        skipForm: false,
89        withHandles: false,
90        autoSync: false,
91        delay: 0, // pixels
92        accept: ["text"],
93        generateText: true,
94
95        constructor: function(/*DOMNode|String*/node, /*dojo.dnd.__SourceArgs?*/params){
96                // summary:
97                //              a constructor of the Source
98                // node:
99                //              node or node's id to build the source on
100                // params:
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;
105                if(type.length){
106                        this.accept = {};
107                        for(var i = 0; i < type.length; ++i){
108                                this.accept[type[i]] = 1;
109                        }
110                }
111                // class-specific variables
112                this.isDragging = false;
113                this.mouseDown = false;
114                this.targetAnchor = null;
115                this.targetBox = null;
116                this.before = true;
117                this._lastX = 0;
118                this._lastY = 0;
119                // states
120                this.sourceState  = "";
121                if(this.isSource){
122                        dojo.addClass(this.node, "dojoDndSource");
123                }
124                this.targetState  = "";
125                if(this.accept){
126                        dojo.addClass(this.node, "dojoDndTarget");
127                }
128                if(this.horizontal){
129                        dojo.addClass(this.node, "dojoDndHorizontal");
130                }
131                // set up events
132                this.topics = [
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")
137                ];
138        },
139
140        // methods
141        checkAcceptance: function(source, nodes){
142                // summary:
143                //              checks if the target can accept nodes from this source
144                // source: Object
145                //              the source which provides items
146                // nodes: Array
147                //              the list of transferred items
148                if(this == source){
149                        return !this.copyOnly || this.selfAccept;
150                }
151                for(var i = 0; i < nodes.length; ++i){
152                        var type = source.getItem(nodes[i].id).type;
153                        // type instanceof Array
154                        var flag = false;
155                        for(var j = 0; j < type.length; ++j){
156                                if(type[j] in this.accept){
157                                        flag = true;
158                                        break;
159                                }
160                        }
161                        if(!flag){
162                                return false;   // Boolean
163                        }
164                }
165                return true;    // Boolean
166        },
167        copyState: function(keyPressed, self){
168                // summary:
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
173                // self: Boolean?
174                //              optional flag that means that we are about to drop on itself
175
176                if(keyPressed){ return true; }
177                if(arguments.length < 2){
178                        self = this == Manager.manager().target;
179                }
180                if(self){
181                        if(this.copyOnly){
182                                return this.selfCopy;
183                        }
184                }else{
185                        return this.copyOnly;
186                }
187                return false;   // Boolean
188        },
189        destroy: function(){
190                // summary:
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;
195        },
196
197        // mouse event processors
198        onMouseMove: function(e){
199                // summary:
200                //              event processor for onmousemove
201                // e: Event
202                //              mouse event
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();
210                                if(nodes.length){
211                                        m.startDrag(this, nodes, this.copyState(dojo.isCopyKey(e), true));
212                                }
213                        }
214                }
215                if(this.isDragging){
216                        // calculate before/after
217                        var before = false;
218                        if(this.current){
219                                if(!this.targetBox || this.targetAnchor != this.current){
220                                        this.targetBox = dojo.position(this.current, true);
221                                }
222                                if(this.horizontal){
223                                        before = (e.pageX - this.targetBox.x) < (this.targetBox.w / 2);
224                                }else{
225                                        before = (e.pageY - this.targetBox.y) < (this.targetBox.h / 2);
226                                }
227                        }
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));
231                        }
232                }
233        },
234        onMouseDown: function(e){
235                // summary:
236                //              event processor for onmousedown
237                // e: Event
238                //              mouse event
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);
244                }
245        },
246        onMouseUp: function(e){
247                // summary:
248                //              event processor for onmouseup
249                // e: Event
250                //              mouse event
251                if(this.mouseDown){
252                        this.mouseDown = false;
253                        dojo.dnd.Source.superclass.onMouseUp.call(this, e);
254                }
255        },
256
257        // topic event processors
258        onDndSourceOver: function(source){
259                // summary:
260                //              topic event processor for /dnd/source/over, called when detected a current source
261                // source: Object
262                //              the source which has the mouse over it
263                if(this != source){
264                        this.mouseDown = false;
265                        if(this.targetAnchor){
266                                this._unmarkTargetAnchor();
267                        }
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)));
271                }
272        },
273        onDndStart: function(source, nodes, copy){
274                // summary:
275                //              topic event processor for /dnd/start, called to initiate the DnD operation
276                // source: Object
277                //              the source which provides items
278                // nodes: Array
279                //              the list of transferred items
280                // copy: Boolean
281                //              copy items, if true, move items otherwise
282                if(this.autoSync){ this.sync(); }
283                if(this.isSource){
284                        this._changeState("Source", this == source ? (copy ? "Copied" : "Moved") : "");
285                }
286                var accepted = this.accept && this.checkAcceptance(source, nodes);
287                this._changeState("Target", accepted ? "" : "Disabled");
288                if(this == source){
289                        Manager.manager().overSource(this);
290                }
291                this.isDragging = true;
292        },
293        onDndDrop: function(source, nodes, copy, target){
294                // summary:
295                //              topic event processor for /dnd/drop, called to finish the DnD operation
296                // source: Object
297                //              the source which provides items
298                // nodes: Array
299                //              the list of transferred items
300                // copy: Boolean
301                //              copy items, if true, move items otherwise
302                // target: Object
303                //              the target which accepts items
304                if(this == target){
305                        // this one is for us => move nodes!
306                        this.onDrop(source, nodes, copy);
307                }
308                this.onDndCancel();
309        },
310        onDndCancel: function(){
311                // summary:
312                //              topic event processor for /dnd/cancel, called to cancel the DnD operation
313                if(this.targetAnchor){
314                        this._unmarkTargetAnchor();
315                        this.targetAnchor = null;
316                }
317                this.before = true;
318                this.isDragging = false;
319                this.mouseDown = false;
320                this._changeState("Source", "");
321                this._changeState("Target", "");
322        },
323
324        // local events
325        onDrop: function(source, nodes, copy){
326                // summary:
327                //              called only on the current target, when drop is performed
328                // source: Object
329                //              the source which provides items
330                // nodes: Array
331                //              the list of transferred items
332                // copy: Boolean
333                //              copy items, if true, move items otherwise
334
335                if(this != source){
336                        this.onDropExternal(source, nodes, copy);
337                }else{
338                        this.onDropInternal(nodes, copy);
339                }
340        },
341        onDropExternal: function(source, nodes, copy){
342                // summary:
343                //              called only on the current target, when drop is performed
344                //              from an external source
345                // source: Object
346                //              the source which provides items
347                // nodes: Array
348                //              the list of transferred items
349                // copy: Boolean
350                //              copy items, if true, move items otherwise
351
352                var oldCreator = this._normalizedCreator;
353                // transferring nodes from the source to the target
354                if(this.creator){
355                        // use defined creator
356                        this._normalizedCreator = function(node, hint){
357                                return oldCreator.call(this, source.getItem(node.id).data, hint);
358                        };
359                }else{
360                        // we have no creator defined => move/clone nodes
361                        if(copy){
362                                // 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};
368                                };
369                        }else{
370                                // move nodes
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};
375                                };
376                        }
377                }
378                this.selectNone();
379                if(!copy && !this.creator){
380                        source.selectNone();
381                }
382                this.insertNodes(true, nodes, this.before, this.current);
383                if(!copy && this.creator){
384                        source.deleteSelectedNodes();
385                }
386                this._normalizedCreator = oldCreator;
387        },
388        onDropInternal: function(nodes, copy){
389                // summary:
390                //              called only on the current target, when drop is performed
391                //              from the same target/source
392                // nodes: Array
393                //              the list of transferred items
394                // copy: Boolean
395                //              copy items, if true, move items otherwise
396
397                var oldCreator = this._normalizedCreator;
398                // transferring nodes within the single source
399                if(this.current && this.current.id in this.selection){
400                        // do nothing
401                        return;
402                }
403                if(copy){
404                        if(this.creator){
405                                // create new copies of data items
406                                this._normalizedCreator = function(node, hint){
407                                        return oldCreator.call(this, this.getItem(node.id).data, hint);
408                                };
409                        }else{
410                                // clone nodes
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};
416                                };
417                        }
418                }else{
419                        // move nodes
420                        if(!this.current){
421                                // do nothing
422                                return;
423                        }
424                        this._normalizedCreator = function(node, hint){
425                                var t = this.getItem(node.id);
426                                return {node: node, data: t.data, type: t.type};
427                        };
428                }
429                this._removeSelection();
430                this.insertNodes(true, nodes, this.before, this.current);
431                this._normalizedCreator = oldCreator;
432        },
433        onDraggingOver: function(){
434                // summary:
435                //              called during the active DnD operation, when items
436                //              are dragged over this target, and it is not disabled
437        },
438        onDraggingOut: function(){
439                // summary:
440                //              called during the active DnD operation, when items
441                //              are dragged away from this target, and it is not disabled
442        },
443
444        // utilities
445        onOverEvent: function(){
446                // summary:
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();
452                }
453        },
454        onOutEvent: function(){
455                // summary:
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();
461                }
462        },
463        _markTargetAnchor: function(before){
464                // summary:
465                //              assigns a class to the current target anchor based on "before" status
466                // before: Boolean
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");
471                }
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");
477                }
478        },
479        _unmarkTargetAnchor: function(){
480                // summary:
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;
486                this.before = true;
487        },
488        _markDndStatus: function(copy){
489                // summary:
490                //              changes source's state based on "copy" status
491                this._changeState("Source", copy ? "Copied" : "Moved");
492        },
493        _legalMouseDown: function(e){
494                // summary:
495                //              checks if user clicked on "approved" items
496                // e: Event
497                //              mouse event
498
499                // accept only the left mouse button
500                if(!dojo.mouseButtons.isLeft(e)){ return false; }
501
502                if(!this.withHandles){ return true; }
503
504                // check for handles
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; }
508                }
509                return false;   // Boolean
510        }
511});
512
513});
Note: See TracBrowser for help on using the repository browser.