MDL-32843 import YUI 3.5.1
[moodle.git] / lib / yui / 3.5.1 / build / dd-drag / dd-drag.js
blob956820e1f612fd85881b0b4bbf6f90a4b450486b
1 /*
2 YUI 3.5.1 (build 22)
3 Copyright 2012 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
6 */
7 YUI.add('dd-drag', function(Y) {
10     /**
11      * Provides the ability to drag a Node.
12      * @module dd
13      * @submodule dd-drag
14      */     
15     /**
16      * Provides the ability to drag a Node.
17      * @class Drag
18      * @extends Base
19      * @constructor
20      * @namespace DD
21      */
23     var DDM = Y.DD.DDM,
24         NODE = 'node',
25         DRAGGING = 'dragging',
26         DRAG_NODE = 'dragNode',
27         OFFSET_HEIGHT = 'offsetHeight',
28         OFFSET_WIDTH = 'offsetWidth',        
29         /**
30         * @event drag:mouseup
31         * @description Handles the mouseup DOM event, does nothing internally just fires.
32         * @bubbles DDM
33         * @type {CustomEvent}
34         */
35         /**
36         * @event drag:mouseDown
37         * @description Handles the mousedown DOM event, checks to see if you have a valid handle then starts the drag timers.
38         * @preventable _defMouseDownFn
39         * @param {EventFacade} event An Event Facade object with the following specific property added:
40         * <dl><dt>ev</dt><dd>The original mousedown event.</dd></dl>
41         * @bubbles DDM
42         * @type {CustomEvent}
43         */
44         EV_MOUSE_DOWN = 'drag:mouseDown',
45         /**
46         * @event drag:afterMouseDown
47         * @description Fires after the mousedown event has been cleared.
48         * @param {EventFacade} event An Event Facade object with the following specific property added:
49         * <dl><dt>ev</dt><dd>The original mousedown event.</dd></dl>
50         * @bubbles DDM
51         * @type {CustomEvent}
52         */
53         EV_AFTER_MOUSE_DOWN = 'drag:afterMouseDown',
54         /**
55         * @event drag:removeHandle
56         * @description Fires after a handle is removed.
57         * @param {EventFacade} event An Event Facade object with the following specific property added:
58         * <dl><dt>handle</dt><dd>The handle that was removed.</dd></dl>
59         * @bubbles DDM
60         * @type {CustomEvent}
61         */
62         EV_REMOVE_HANDLE = 'drag:removeHandle',
63         /**
64         * @event drag:addHandle
65         * @description Fires after a handle is added.
66         * @param {EventFacade} event An Event Facade object with the following specific property added:
67         * <dl><dt>handle</dt><dd>The handle that was added.</dd></dl>
68         * @bubbles DDM
69         * @type {CustomEvent}
70         */
71         EV_ADD_HANDLE = 'drag:addHandle',
72         /**
73         * @event drag:removeInvalid
74         * @description Fires after an invalid selector is removed.
75         * @param {EventFacade} event An Event Facade object with the following specific property added:
76         * <dl><dt>handle</dt><dd>The handle that was removed.</dd></dl>
77         * @bubbles DDM
78         * @type {CustomEvent}
79         */
80         EV_REMOVE_INVALID = 'drag:removeInvalid',
81         /**
82         * @event drag:addInvalid
83         * @description Fires after an invalid selector is added.
84         * @param {EventFacade} event An Event Facade object with the following specific property added:
85         * <dl><dt>handle</dt><dd>The handle that was added.</dd></dl>
86         * @bubbles DDM
87         * @type {CustomEvent}
88         */
89         EV_ADD_INVALID = 'drag:addInvalid',
90         /**
91         * @event drag:start
92         * @description Fires at the start of a drag operation.
93         * @param {EventFacade} event An Event Facade object with the following specific property added:
94         * <dl>
95         * <dt>pageX</dt><dd>The original node position X.</dd>
96         * <dt>pageY</dt><dd>The original node position Y.</dd>
97         * <dt>startTime</dt><dd>The startTime of the event. getTime on the current Date object.</dd>
98         * </dl>
99         * @bubbles DDM
100         * @type {CustomEvent}
101         */
102         EV_START = 'drag:start',
103         /**
104         * @event drag:end
105         * @description Fires at the end of a drag operation.
106         * @param {EventFacade} event An Event Facade object with the following specific property added:
107         * <dl>
108         * <dt>pageX</dt><dd>The current node position X.</dd>
109         * <dt>pageY</dt><dd>The current node position Y.</dd>
110         * <dt>startTime</dt><dd>The startTime of the event, from the start event.</dd>
111         * <dt>endTime</dt><dd>The endTime of the event. getTime on the current Date object.</dd>
112         * </dl>
113         * @bubbles DDM
114         * @type {CustomEvent}
115         */
116         EV_END = 'drag:end',
117         /**
118         * @event drag:drag
119         * @description Fires every mousemove during a drag operation.
120         * @param {EventFacade} event An Event Facade object with the following specific property added:
121         * <dl>
122         * <dt>pageX</dt><dd>The current node position X.</dd>
123         * <dt>pageY</dt><dd>The current node position Y.</dd>
124         * <dt>scroll</dt><dd>Should a scroll action occur.</dd>
125         * <dt>info</dt><dd>Object hash containing calculated XY arrays: start, xy, delta, offset</dd>
126         * </dl>
127         * @bubbles DDM
128         * @type {CustomEvent}
129         */
130         EV_DRAG = 'drag:drag',
131         /**
132         * @event drag:align
133         * @preventable _defAlignFn
134         * @description Fires when this node is aligned.
135         * @param {EventFacade} event An Event Facade object with the following specific property added:
136         * <dl>
137         * <dt>pageX</dt><dd>The current node position X.</dd>
138         * <dt>pageY</dt><dd>The current node position Y.</dd>
139         * </dl>
140         * @bubbles DDM
141         * @type {CustomEvent}
142         */
143         EV_ALIGN = 'drag:align',
144         /**
145         * @event drag:over
146         * @description Fires when this node is over a Drop Target. (Fired from dd-drop)
147         * @param {EventFacade} event An Event Facade object with the following specific property added:
148         * <dl>
149         * <dt>drop</dt><dd>The drop object at the time of the event.</dd>
150         * <dt>drag</dt><dd>The drag object at the time of the event.</dd>
151         * </dl>
152         * @bubbles DDM
153         * @type {CustomEvent}
154         */
155         /**
156         * @event drag:enter
157         * @description Fires when this node enters a Drop Target. (Fired from dd-drop)
158         * @param {EventFacade} event An Event Facade object with the following specific property added:
159         * <dl>
160         * <dt>drop</dt><dd>The drop object at the time of the event.</dd>
161         * <dt>drag</dt><dd>The drag object at the time of the event.</dd>
162         * </dl>
163         * @bubbles DDM
164         * @type {CustomEvent}
165         */
166         /**
167         * @event drag:exit
168         * @description Fires when this node exits a Drop Target. (Fired from dd-drop)
169         * @param {EventFacade} event An Event Facade object with the following specific property added:
170         * <dl>
171         * <dt>drop</dt><dd>The drop object at the time of the event.</dd>
172         * </dl>
173         * @bubbles DDM
174         * @type {CustomEvent}
175         */
176         /**
177         * @event drag:drophit
178         * @description Fires when this node is dropped on a valid Drop Target. (Fired from dd-ddm-drop)
179         * @param {EventFacade} event An Event Facade object with the following specific property added:
180         * <dl>
181         * <dt>drop</dt><dd>The best guess on what was dropped on.</dd>
182         * <dt>drag</dt><dd>The drag object at the time of the event.</dd>
183         * <dt>others</dt><dd>An array of all the other drop targets that was dropped on.</dd>
184         * </dl>
185         * @bubbles DDM
186         * @type {CustomEvent}
187         */
188         /**
189         * @event drag:dropmiss
190         * @description Fires when this node is dropped on an invalid Drop Target. (Fired from dd-ddm-drop)
191         * @param {EventFacade} event An Event Facade object with the following specific property added:
192         * <dl>
193         * <dt>pageX</dt><dd>The current node position X.</dd>
194         * <dt>pageY</dt><dd>The current node position Y.</dd>
195         * </dl>
196         * @bubbles DDM
197         * @type {CustomEvent}
198         */
199     
200     Drag = function(o) {
201         this._lazyAddAttrs = false;
202         Drag.superclass.constructor.apply(this, arguments);
204         var valid = DDM._regDrag(this);
205         if (!valid) {
206             Y.error('Failed to register node, already in use: ' + o.node);
207         }
208     };
210     Drag.NAME = 'drag';
211     
212     /**
213     * This property defaults to "mousedown", but when drag-gestures is loaded, it is changed to "gesturemovestart"
214     * @static
215     * @property START_EVENT
216     */
217     Drag.START_EVENT = 'mousedown';
219     Drag.ATTRS = {
220         /**
221         * @attribute node
222         * @description Y.Node instance to use as the element to initiate a drag operation
223         * @type Node
224         */
225         node: {
226             setter: function(node) {
227                 if (this._canDrag(node)) {
228                     return node;
229                 }
230                 var n = Y.one(node);
231                 if (!n) {
232                     Y.error('DD.Drag: Invalid Node Given: ' + node);
233                 }
234                 return n;
235             }
236         },
237         /**
238         * @attribute dragNode
239         * @description Y.Node instance to use as the draggable element, defaults to node
240         * @type Node
241         */
242         dragNode: {
243             setter: function(node) {
244                 if (this._canDrag(node)) {
245                     return node;
246                 }
247                 var n = Y.one(node);
248                 if (!n) {
249                     Y.error('DD.Drag: Invalid dragNode Given: ' + node);
250                 }
251                 return n;
252             }
253         },
254         /**
255         * @attribute offsetNode
256         * @description Offset the drag element by the difference in cursor position: default true
257         * @type Boolean
258         */
259         offsetNode: {
260             value: true
261         },
262         /**
263         * @attribute startCentered
264         * @description Center the dragNode to the mouse position on drag:start: default false
265         * @type Boolean
266         */
267         startCentered: {
268             value: false
269         },
270         /**
271         * @attribute clickPixelThresh
272         * @description The number of pixels to move to start a drag operation, default is 3.
273         * @type Number
274         */
275         clickPixelThresh: {
276             value: DDM.get('clickPixelThresh')
277         },
278         /**
279         * @attribute clickTimeThresh
280         * @description The number of milliseconds a mousedown has to pass to start a drag operation, default is 1000.
281         * @type Number
282         */
283         clickTimeThresh: {
284             value: DDM.get('clickTimeThresh')
285         },
286         /**
287         * @attribute lock
288         * @description Set to lock this drag element so that it can't be dragged: default false.
289         * @type Boolean
290         */
291         lock: {
292             value: false,
293             setter: function(lock) {
294                 if (lock) {
295                     this.get(NODE).addClass(DDM.CSS_PREFIX + '-locked');
296                 } else {
297                     this.get(NODE).removeClass(DDM.CSS_PREFIX + '-locked');
298                 }
299                 return lock;
300             }
301         },
302         /**
303         * @attribute data
304         * @description A payload holder to store arbitrary data about this drag object, can be used to store any value.
305         * @type Mixed
306         */
307         data: {
308             value: false
309         },
310         /**
311         * @attribute move
312         * @description If this is false, the drag element will not move with the cursor: default true. Can be used to "resize" the element.
313         * @type Boolean
314         */
315         move: {
316             value: true
317         },
318         /**
319         * @attribute useShim
320         * @description Use the protective shim on all drag operations: default true. Only works with dd-ddm, not dd-ddm-base.
321         * @type Boolean
322         */
323         useShim: {
324             value: true
325         },
326         /**
327         * @attribute activeHandle
328         * @description This config option is set by Drag to inform you of which handle fired the drag event (in the case that there are several handles): default false.
329         * @type Node
330         */
331         activeHandle: {
332             value: false
333         },
334         /**
335         * @attribute primaryButtonOnly
336         * @description By default a drag operation will only begin if the mousedown occurred with the primary mouse button. Setting this to false will allow for all mousedown events to trigger a drag.
337         * @type Boolean
338         */
339         primaryButtonOnly: {
340             value: true
341         },
342         /**
343         * @attribute dragging
344         * @description This attribute is not meant to be used by the implementor, it is meant to be used as an Event tracker so you can listen for it to change.
345         * @type Boolean
346         */
347         dragging: {
348             value: false
349         },
350         parent: {
351             value: false
352         },
353         /**
354         * @attribute target
355         * @description This attribute only works if the dd-drop module has been loaded. It will make this node a drop target as well as draggable.
356         * @type Boolean
357         */
358         target: {
359             value: false,
360             setter: function(config) {
361                 this._handleTarget(config);
362                 return config;
363             }
364         },
365         /**
366         * @attribute dragMode
367         * @description This attribute only works if the dd-drop module is active. It will set the dragMode (point, intersect, strict) of this Drag instance.
368         * @type String
369         */
370         dragMode: {
371             value: null,
372             setter: function(mode) {
373                 return DDM._setDragMode(mode);
374             }
375         },
376         /**
377         * @attribute groups
378         * @description Array of groups to add this drag into.
379         * @type Array
380         */
381         groups: {
382             value: ['default'],
383             getter: function() {
384                 if (!this._groups) {
385                     this._groups = {};
386                 }
387                 var ret = [];
388                 Y.each(this._groups, function(v, k) {
389                     ret[ret.length] = k;
390                 });
391                 return ret;
392             },
393             setter: function(g) {
394                 this._groups = {};
395                 Y.each(g, function(v, k) {
396                     this._groups[v] = true;
397                 }, this);
398                 return g;
399             }
400         },
401         /**
402         * @attribute handles
403         * @description Array of valid handles to add. Adding something here will set all handles, even if previously added with addHandle
404         * @type Array
405         */
406         handles: {
407             value: null,
408             setter: function(g) {
409                 if (g) {
410                     this._handles = {};
411                     Y.each(g, function(v, k) {
412                         var key = v;
413                         if (v instanceof Y.Node || v instanceof Y.NodeList) {
414                             key = v._yuid;
415                         }
416                         this._handles[key] = v;
417                     }, this);
418                 } else {
419                     this._handles = null;
420                 }
421                 return g;
422             }
423         },
424         /**
425         * @deprecated
426         * @attribute bubbles
427         * @description Controls the default bubble parent for this Drag instance. Default: Y.DD.DDM. Set to false to disable bubbling. Use bubbleTargets in config
428         * @type Object
429         */
430         bubbles: {
431             setter: function(t) {
432                 this.addTarget(t);
433                 return t;
434             }
435         },
436         /**
437         * @attribute haltDown
438         * @description Should the mousedown event be halted. Default: true
439         * @type Boolean
440         */
441         haltDown: {
442             value: true
443         }
444     };
446     Y.extend(Drag, Y.Base, {
447         /**
448         * Checks the object for the methods needed to drag the object around. 
449         * Normally this would be a node instance, but in the case of Graphics, it
450         * may be an SVG node or something similar.
451         * @method _canDrag
452         * @private
453         * @param {Object} n The object to check
454         * @return {Boolean} True or false if the Object contains the methods needed to Drag
455         */
456         _canDrag: function(n) {
457             if (n && n.setXY && n.getXY && n.test && n.contains) {
458                 return true;
459             }
460             return false;
461         },
462         /**
463         * @private
464         * @property _bubbleTargets
465         * @description The default bubbleTarget for this object. Default: Y.DD.DDM
466         */
467         _bubbleTargets: Y.DD.DDM,
468         /**
469         * @method addToGroup
470         * @description Add this Drag instance to a group, this should be used for on-the-fly group additions.
471         * @param {String} g The group to add this Drag Instance to.
472         * @return {Self}
473         * @chainable
474         */
475         addToGroup: function(g) {
476             this._groups[g] = true;
477             DDM._activateTargets();
478             return this;
479         },
480         /**
481         * @method removeFromGroup
482         * @description Remove this Drag instance from a group, this should be used for on-the-fly group removals.
483         * @param {String} g The group to remove this Drag Instance from.
484         * @return {Self}
485         * @chainable
486         */
487         removeFromGroup: function(g) {
488             delete this._groups[g];
489             DDM._activateTargets();
490             return this;
491         },
492         /**
493         * @property target
494         * @description This will be a reference to the Drop instance associated with this drag if the target: true config attribute is set..
495         * @type {Object}
496         */
497         target: null,
498         /**
499         * @private
500         * @method _handleTarget
501         * @description Attribute handler for the target config attribute.
502         * @param {Boolean/Object} config The Config
503         */
504         _handleTarget: function(config) {
505             if (Y.DD.Drop) {
506                 if (config === false) {
507                     if (this.target) {
508                         DDM._unregTarget(this.target);
509                         this.target = null;
510                     }
511                     return false;
512                 } else {
513                     if (!Y.Lang.isObject(config)) {
514                         config = {};
515                     }
516                     config.bubbleTargets = ('bubbleTargets' in config) ? config.bubbleTargets : Y.Object.values(this._yuievt.targets);
517                     config.node = this.get(NODE);
518                     config.groups = config.groups || this.get('groups');
519                     this.target = new Y.DD.Drop(config);
520                 }
521             } else {
522                 return false;
523             }
524         },
525         /**
526         * @private
527         * @property _groups
528         * @description Storage Array for the groups this drag belongs to.
529         * @type {Array}
530         */
531         _groups: null,
532         /**
533         * @private
534         * @method _createEvents
535         * @description This method creates all the events for this Event Target and publishes them so we get Event Bubbling.
536         */
537         _createEvents: function() {
538             
539             this.publish(EV_MOUSE_DOWN, {
540                 defaultFn: this._defMouseDownFn,
541                 queuable: false,
542                 emitFacade: true,
543                 bubbles: true,
544                 prefix: 'drag'
545             });
546             
547             this.publish(EV_ALIGN, {
548                 defaultFn: this._defAlignFn,
549                 queuable: false,
550                 emitFacade: true,
551                 bubbles: true,
552                 prefix: 'drag'
553             });
554             
555             this.publish(EV_DRAG, {
556                 defaultFn: this._defDragFn,
557                 queuable: false,
558                 emitFacade: true,
559                 bubbles: true,
560                 prefix: 'drag'
561             });
562             
563             this.publish(EV_END, {
564                 defaultFn: this._defEndFn,
565                 preventedFn: this._prevEndFn,
566                 queuable: false,
567                 emitFacade: true,
568                 bubbles: true,
569                 prefix: 'drag'
570             });
571             
572             var ev = [
573                 EV_AFTER_MOUSE_DOWN,
574                 EV_REMOVE_HANDLE,
575                 EV_ADD_HANDLE,
576                 EV_REMOVE_INVALID,
577                 EV_ADD_INVALID,
578                 EV_START,
579                 'drag:drophit',
580                 'drag:dropmiss',
581                 'drag:over',
582                 'drag:enter',
583                 'drag:exit'
584             ];
585             
586             Y.each(ev, function(v, k) {
587                 this.publish(v, {
588                     type: v,
589                     emitFacade: true,
590                     bubbles: true,
591                     preventable: false,
592                     queuable: false,
593                     prefix: 'drag'
594                 });
595             }, this);
596         },
597         /**
598         * @private
599         * @property _ev_md
600         * @description A private reference to the mousedown DOM event
601         * @type {EventFacade}
602         */
603         _ev_md: null,
604         /**
605         * @private
606         * @property _startTime
607         * @description The getTime of the mousedown event. Not used, just here in case someone wants/needs to use it.
608         * @type Date
609         */
610         _startTime: null,
611         /**
612         * @private
613         * @property _endTime
614         * @description The getTime of the mouseup event. Not used, just here in case someone wants/needs to use it.
615         * @type Date
616         */
617         _endTime: null,
618         /**
619         * @private
620         * @property _handles
621         * @description A private hash of the valid drag handles
622         * @type {Object}
623         */
624         _handles: null,
625         /**
626         * @private
627         * @property _invalids
628         * @description A private hash of the invalid selector strings
629         * @type {Object}
630         */
631         _invalids: null,
632         /**
633         * @private
634         * @property _invalidsDefault
635         * @description A private hash of the default invalid selector strings: {'textarea': true, 'input': true, 'a': true, 'button': true, 'select': true}
636         * @type {Object}
637         */
638         _invalidsDefault: {'textarea': true, 'input': true, 'a': true, 'button': true, 'select': true },
639         /**
640         * @private
641         * @property _dragThreshMet
642         * @description Private flag to see if the drag threshhold was met
643         * @type {Boolean}
644         */
645         _dragThreshMet: null,
646         /**
647         * @private
648         * @property _fromTimeout
649         * @description Flag to determine if the drag operation came from a timeout
650         * @type {Boolean}
651         */
652         _fromTimeout: null,
653         /**
654         * @private
655         * @property _clickTimeout
656         * @description Holder for the setTimeout call
657         * @type {Boolean}
658         */
659         _clickTimeout: null,
660         /**
661         * @property deltaXY
662         * @description The offset of the mouse position to the element's position
663         * @type {Array}
664         */
665         deltaXY: null,
666         /**
667         * @property startXY
668         * @description The initial mouse position
669         * @type {Array}
670         */
671         startXY: null,
672         /**
673         * @property nodeXY
674         * @description The initial element position
675         * @type {Array}
676         */
677         nodeXY: null,
678         /**
679         * @property lastXY
680         * @description The position of the element as it's moving (for offset calculations)
681         * @type {Array}
682         */
683         lastXY: null,
684         /**
685         * @property actXY
686         * @description The xy that the node will be set to. Changing this will alter the position as it's dragged.
687         * @type {Array}
688         */
689         actXY: null,
690         /**
691         * @property realXY
692         * @description The real xy position of the node.
693         * @type {Array}
694         */
695         realXY: null,
696         /**
697         * @property mouseXY
698         * @description The XY coords of the mousemove
699         * @type {Array}
700         */
701         mouseXY: null,
702         /**
703         * @property region
704         * @description A region object associated with this drag, used for checking regions while dragging.
705         * @type Object
706         */
707         region: null,       
708         /**
709         * @private
710         * @method _handleMouseUp
711         * @description Handler for the mouseup DOM event
712         * @param {EventFacade} ev The Event
713         */
714         _handleMouseUp: function(ev) {
715             this.fire('drag:mouseup');
716             this._fixIEMouseUp();
717             if (DDM.activeDrag) {
718                 DDM._end();
719             }
720         },
721         /** 
722         * @private
723         * @method _fixDragStart
724         * @description The function we use as the ondragstart handler when we start a drag in Internet Explorer. This keeps IE from blowing up on images as drag handles.
725         * @param {Event} e The Event
726         */
727         _fixDragStart: function(e) {
728             e.preventDefault();
729         },
730         /** 
731         * @private
732         * @method _ieSelectFix
733         * @description The function we use as the onselectstart handler when we start a drag in Internet Explorer
734         */
735         _ieSelectFix: function() {
736             return false;
737         },
738         /** 
739         * @private
740         * @property _ieSelectBack
741         * @description We will hold a copy of the current "onselectstart" method on this property, and reset it after we are done using it.
742         */
743         _ieSelectBack: null,
744         /**
745         * @private
746         * @method _fixIEMouseDown
747         * @description This method copies the onselectstart listner on the document to the _ieSelectFix property
748         */
749         _fixIEMouseDown: function(e) {
750             if (Y.UA.ie) {
751                 this._ieSelectBack = Y.config.doc.body.onselectstart;
752                 Y.config.doc.body.onselectstart = this._ieSelectFix;
753             }           
754         },
755         /**
756         * @private
757         * @method _fixIEMouseUp
758         * @description This method copies the _ieSelectFix property back to the onselectstart listner on the document.
759         */
760         _fixIEMouseUp: function() {
761             if (Y.UA.ie) {
762                 Y.config.doc.body.onselectstart = this._ieSelectBack;
763             }           
764         },
765         /**
766         * @private
767         * @method _handleMouseDownEvent
768         * @description Handler for the mousedown DOM event
769         * @param {EventFacade} ev  The Event
770         */
771         _handleMouseDownEvent: function(ev) {
772             this.fire(EV_MOUSE_DOWN, { ev: ev });
773         },
774         /**
775         * @private
776         * @method _defMouseDownFn
777         * @description Handler for the mousedown DOM event
778         * @param {EventFacade} e  The Event
779         */
780         _defMouseDownFn: function(e) {
781             var ev = e.ev;
783             this._dragThreshMet = false;
784             this._ev_md = ev;
785             
786             if (this.get('primaryButtonOnly') && ev.button > 1) {
787                 return false;
788             }
789             if (this.validClick(ev)) {
790                 this._fixIEMouseDown(ev);
791                 if (this.get('haltDown')) {
792                     ev.halt();
793                 } else {
794                     ev.preventDefault();
795                 }
796                 
797                 this._setStartPosition([ev.pageX, ev.pageY]);
799                 DDM.activeDrag = this;
800                 
801                 this._clickTimeout = Y.later(this.get('clickTimeThresh'), this, this._timeoutCheck);
802             }
803             this.fire(EV_AFTER_MOUSE_DOWN, { ev: ev });
804         },
805         /**
806         * @method validClick
807         * @description Method first checks to see if we have handles, if so it validates the click against the handle. Then if it finds a valid handle, it checks it against the invalid handles list. Returns true if a good handle was used, false otherwise.
808         * @param {EventFacade} ev  The Event
809         * @return {Boolean}
810         */
811         validClick: function(ev) {
812             var r = false, n = false,
813             tar = ev.target,
814             hTest = null,
815             els = null,
816             nlist = null,
817             set = false;
818             if (this._handles) {
819                 Y.each(this._handles, function(i, n) {
820                     if (i instanceof Y.Node || i instanceof Y.NodeList) {
821                         if (!r) {
822                             nlist = i;
823                             if (nlist instanceof Y.Node) {
824                                 nlist = new Y.NodeList(i._node);
825                             }
826                             nlist.each(function(nl) {
827                                 if (nl.contains(tar)) {
828                                     r = true;
829                                 }
830                             });
831                         }
832                     } else if (Y.Lang.isString(n)) {
833                         //Am I this or am I inside this
834                         if (tar.test(n + ', ' + n + ' *') && !hTest) {
835                             hTest = n;
836                             r = true;
837                         }
838                     }
839                 });
840             } else {
841                 n = this.get(NODE);
842                 if (n.contains(tar) || n.compareTo(tar)) {
843                     r = true;
844                 }
845             }
846             if (r) {
847                 if (this._invalids) {
848                     Y.each(this._invalids, function(i, n) {
849                         if (Y.Lang.isString(n)) {
850                             //Am I this or am I inside this
851                             if (tar.test(n + ', ' + n + ' *')) {
852                                 r = false;
853                             }
854                         }
855                     });
856                 }
857             }
858             if (r) {
859                 if (hTest) {
860                     els = ev.currentTarget.all(hTest);
861                     set = false;
862                     els.each(function(n, i) {
863                         if ((n.contains(tar) || n.compareTo(tar)) && !set) {
864                             set = true;
865                             this.set('activeHandle', n);
866                         }
867                     }, this);
868                 } else {
869                     this.set('activeHandle', this.get(NODE));
870                 }
871             }
872             return r;
873         },
874         /**
875         * @private
876         * @method _setStartPosition
877         * @description Sets the current position of the Element and calculates the offset
878         * @param {Array} xy The XY coords to set the position to.
879         */
880         _setStartPosition: function(xy) {
881             this.startXY = xy;
882             
883             this.nodeXY = this.lastXY = this.realXY = this.get(NODE).getXY();
884             
885             if (this.get('offsetNode')) {
886                 this.deltaXY = [(this.startXY[0] - this.nodeXY[0]), (this.startXY[1] - this.nodeXY[1])];
887             } else {
888                 this.deltaXY = [0, 0];
889             }
890         },
891         /**
892         * @private
893         * @method _timeoutCheck
894         * @description The method passed to setTimeout to determine if the clickTimeThreshold was met.
895         */
896         _timeoutCheck: function() {
897             if (!this.get('lock') && !this._dragThreshMet && this._ev_md) {
898                 this._fromTimeout = this._dragThreshMet = true;
899                 this.start();
900                 this._alignNode([this._ev_md.pageX, this._ev_md.pageY], true);
901             }
902         },
903         /**
904         * @method removeHandle
905         * @description Remove a Selector added by addHandle
906         * @param {String} str The selector for the handle to be removed. 
907         * @return {Self}
908         * @chainable
909         */
910         removeHandle: function(str) {
911             var key = str;
912             if (str instanceof Y.Node || str instanceof Y.NodeList) {
913                 key = str._yuid;
914             }
915             if (this._handles[key]) {
916                 delete this._handles[key];
917                 this.fire(EV_REMOVE_HANDLE, { handle: str });
918             }
919             return this;
920         },
921         /**
922         * @method addHandle
923         * @description Add a handle to a drag element. Drag only initiates when a mousedown happens on this element.
924         * @param {String} str The selector to test for a valid handle. Must be a child of the element.
925         * @return {Self}
926         * @chainable
927         */
928         addHandle: function(str) {
929             if (!this._handles) {
930                 this._handles = {};
931             }
932             var key = str;
933             if (str instanceof Y.Node || str instanceof Y.NodeList) {
934                 key = str._yuid;
935             }
936             this._handles[key] = str;
937             this.fire(EV_ADD_HANDLE, { handle: str });
938             return this;
939         },
940         /**
941         * @method removeInvalid
942         * @description Remove an invalid handle added by addInvalid
943         * @param {String} str The invalid handle to remove from the internal list.
944         * @return {Self}
945         * @chainable
946         */
947         removeInvalid: function(str) {
948             if (this._invalids[str]) {
949                 this._invalids[str] = null;
950                 delete this._invalids[str];
951                 this.fire(EV_REMOVE_INVALID, { handle: str });
952             }
953             return this;
954         },
955         /**
956         * @method addInvalid
957         * @description Add a selector string to test the handle against. If the test passes the drag operation will not continue.
958         * @param {String} str The selector to test against to determine if this is an invalid drag handle.
959         * @return {Self}
960         * @chainable
961         */
962         addInvalid: function(str) {
963             if (Y.Lang.isString(str)) {
964                 this._invalids[str] = true;
965                 this.fire(EV_ADD_INVALID, { handle: str });
966             }
967             return this;
968         },
969         /**
970         * @private
971         * @method initializer
972         * @description Internal init handler
973         */
974         initializer: function(cfg) {
976             this.get(NODE).dd = this;
978             if (!this.get(NODE).get('id')) {
979                 var id = Y.stamp(this.get(NODE));
980                 this.get(NODE).set('id', id);
981             }
983             this.actXY = [];
984             
985             this._invalids = Y.clone(this._invalidsDefault, true);
987             this._createEvents();
988             
989             if (!this.get(DRAG_NODE)) {
990                 this.set(DRAG_NODE, this.get(NODE));
991             }
993             //Fix for #2528096
994             //Don't prep the DD instance until all plugins are loaded.
995             this.on('initializedChange', Y.bind(this._prep, this));
997             //Shouldn't have to do this..
998             this.set('groups', this.get('groups'));
999         },
1000         /**
1001         * @private
1002         * @method _prep
1003         * @description Attach event listners and add classname
1004         */
1005         _prep: function() {
1006             this._dragThreshMet = false;
1007             var node = this.get(NODE);
1008             node.addClass(DDM.CSS_PREFIX + '-draggable');
1009             node.on(Drag.START_EVENT, Y.bind(this._handleMouseDownEvent, this));
1010             node.on('mouseup', Y.bind(this._handleMouseUp, this));
1011             node.on('dragstart', Y.bind(this._fixDragStart, this));
1012         },
1013         /**
1014         * @private
1015         * @method _unprep
1016         * @description Detach event listeners and remove classname
1017         */
1018         _unprep: function() {
1019             var node = this.get(NODE);
1020             node.removeClass(DDM.CSS_PREFIX + '-draggable');
1021             node.detachAll('mouseup');
1022             node.detachAll('dragstart');
1023             node.detachAll(Drag.START_EVENT);
1024             this.mouseXY = [];
1025             this.deltaXY = [0,0];
1026             this.startXY = [];
1027             this.nodeXY = [];
1028             this.lastXY = [];
1029             this.actXY = [];
1030             this.realXY = [];
1031         },
1032         /**
1033         * @method start
1034         * @description Starts the drag operation
1035         * @return {Self}
1036         * @chainable
1037         */
1038         start: function() {
1039             if (!this.get('lock') && !this.get(DRAGGING)) {
1040                 var node = this.get(NODE), ow, oh, xy;
1041                 this._startTime = (new Date()).getTime();
1043                 DDM._start();
1044                 node.addClass(DDM.CSS_PREFIX + '-dragging');
1045                 this.fire(EV_START, {
1046                     pageX: this.nodeXY[0],
1047                     pageY: this.nodeXY[1],
1048                     startTime: this._startTime
1049                 });
1050                 node = this.get(DRAG_NODE);
1051                 xy = this.nodeXY;
1052                 
1053                 ow = node.get(OFFSET_WIDTH);
1054                 oh = node.get(OFFSET_HEIGHT);
1055                 
1056                 if (this.get('startCentered')) {
1057                     this._setStartPosition([xy[0] + (ow / 2), xy[1] + (oh / 2)]);
1058                 }
1059                 
1060                 
1061                 this.region = {
1062                     '0': xy[0], 
1063                     '1': xy[1],
1064                     area: 0,
1065                     top: xy[1],
1066                     right: xy[0] + ow,
1067                     bottom: xy[1] + oh,
1068                     left: xy[0]
1069                 };
1070                 this.set(DRAGGING, true);
1071             }
1072             return this;
1073         },
1074         /**
1075         * @method end
1076         * @description Ends the drag operation
1077         * @return {Self}
1078         * @chainable
1079         */
1080         end: function() {
1081             this._endTime = (new Date()).getTime();
1082             if (this._clickTimeout) {
1083                 this._clickTimeout.cancel();
1084             }
1085             this._dragThreshMet = this._fromTimeout = false;
1087             if (!this.get('lock') && this.get(DRAGGING)) {
1088                 this.fire(EV_END, {
1089                     pageX: this.lastXY[0],
1090                     pageY: this.lastXY[1],
1091                     startTime: this._startTime,
1092                     endTime: this._endTime
1093                 });
1094             }
1095             this.get(NODE).removeClass(DDM.CSS_PREFIX + '-dragging');
1096             this.set(DRAGGING, false);
1097             this.deltaXY = [0, 0];
1099             return this;
1100         },
1101         /**
1102         * @private
1103         * @method _defEndFn
1104         * @description Handler for fixing the selection in IE
1105         */
1106         _defEndFn: function(e) {
1107             this._fixIEMouseUp();
1108             this._ev_md = null;
1109         },
1110         /**
1111         * @private
1112         * @method _prevEndFn
1113         * @description Handler for preventing the drag:end event. It will reset the node back to it's start position
1114         */
1115         _prevEndFn: function(e) {
1116             this._fixIEMouseUp();
1117             //Bug #1852577
1118             this.get(DRAG_NODE).setXY(this.nodeXY);
1119             this._ev_md = null;
1120             this.region = null;
1121         },
1122         /**
1123         * @private
1124         * @method _align
1125         * @description Calculates the offsets and set's the XY that the element will move to.
1126         * @param {Array} xy The xy coords to align with.
1127         */
1128         _align: function(xy) {
1129             this.fire(EV_ALIGN, {pageX: xy[0], pageY: xy[1] });
1130         },
1131         /**
1132         * @private
1133         * @method _defAlignFn
1134         * @description Calculates the offsets and set's the XY that the element will move to.
1135         * @param {EventFacade} e The drag:align event.
1136         */
1137         _defAlignFn: function(e) {
1138             this.actXY = [e.pageX - this.deltaXY[0], e.pageY - this.deltaXY[1]];
1139         },
1140         /**
1141         * @private
1142         * @method _alignNode
1143         * @description This method performs the alignment before the element move.
1144         * @param {Array} eXY The XY to move the element to, usually comes from the mousemove DOM event.
1145         */
1146         _alignNode: function(eXY) {
1147             this._align(eXY);
1148             this._moveNode();
1149         },
1150         /**
1151         * @private
1152         * @method _moveNode
1153         * @description This method performs the actual element move.
1154         */
1155         _moveNode: function(scroll) {
1156             //if (!this.get(DRAGGING)) {
1157             //    return;
1158             //}
1159             var diffXY = [], diffXY2 = [], startXY = this.nodeXY, xy = this.actXY;
1161             diffXY[0] = (xy[0] - this.lastXY[0]);
1162             diffXY[1] = (xy[1] - this.lastXY[1]);
1164             diffXY2[0] = (xy[0] - this.nodeXY[0]);
1165             diffXY2[1] = (xy[1] - this.nodeXY[1]);
1168             this.region = {
1169                 '0': xy[0], 
1170                 '1': xy[1],
1171                 area: 0,
1172                 top: xy[1],
1173                 right: xy[0] + this.get(DRAG_NODE).get(OFFSET_WIDTH),
1174                 bottom: xy[1] + this.get(DRAG_NODE).get(OFFSET_HEIGHT),
1175                 left: xy[0]
1176             };
1178             this.fire(EV_DRAG, {
1179                 pageX: xy[0],
1180                 pageY: xy[1],
1181                 scroll: scroll,
1182                 info: {
1183                     start: startXY,
1184                     xy: xy,
1185                     delta: diffXY,
1186                     offset: diffXY2
1187                 } 
1188             });
1189             
1190             this.lastXY = xy;
1191         },
1192         /**
1193         * @private
1194         * @method _defDragFn
1195         * @description Default function for drag:drag. Fired from _moveNode.
1196         * @param {EventFacade} ev The drag:drag event
1197         */
1198         _defDragFn: function(e) {
1199             if (this.get('move')) {
1200                 if (e.scroll) {
1201                     e.scroll.node.set('scrollTop', e.scroll.top);
1202                     e.scroll.node.set('scrollLeft', e.scroll.left);
1203                 }
1204                 this.get(DRAG_NODE).setXY([e.pageX, e.pageY]);
1205                 this.realXY = [e.pageX, e.pageY];
1206             }
1207         },
1208         /**
1209         * @private
1210         * @method _move
1211         * @description Fired from DragDropMgr (DDM) on mousemove.
1212         * @param {EventFacade} ev The mousemove DOM event
1213         */
1214         _move: function(ev) {
1215             if (this.get('lock')) {
1216                 return false;
1217             } else {
1218                 this.mouseXY = [ev.pageX, ev.pageY];
1219                 if (!this._dragThreshMet) {
1220                     var diffX = Math.abs(this.startXY[0] - ev.pageX),
1221                     diffY = Math.abs(this.startXY[1] - ev.pageY);
1222                     if (diffX > this.get('clickPixelThresh') || diffY > this.get('clickPixelThresh')) {
1223                         this._dragThreshMet = true;
1224                         this.start();
1225                         this._alignNode([ev.pageX, ev.pageY]);
1226                     }
1227                 } else {
1228                     if (this._clickTimeout) {
1229                         this._clickTimeout.cancel();
1230                     }
1231                     this._alignNode([ev.pageX, ev.pageY]);
1232                 }
1233             }
1234         },
1235         /**
1236         * @method stopDrag
1237         * @description Method will forcefully stop a drag operation. For example calling this from inside an ESC keypress handler will stop this drag.
1238         * @return {Self}
1239         * @chainable
1240         */
1241         stopDrag: function() {
1242             if (this.get(DRAGGING)) {
1243                 DDM._end();
1244             }
1245             return this;
1246         },
1247         /**
1248         * @private
1249         * @method destructor
1250         * @description Lifecycle destructor, unreg the drag from the DDM and remove listeners
1251         */
1252         destructor: function() {
1253             this._unprep();
1254             if (this.target) {
1255                 this.target.destroy();
1256             }
1257             DDM._unregDrag(this);
1258         }
1259     });
1260     Y.namespace('DD');    
1261     Y.DD.Drag = Drag;
1266 }, '3.5.1' ,{skinnable:false, requires:['dd-ddm-base']});