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