/loggerhead/trunk

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/loggerhead/trunk

« back to all changes in this revision

Viewing changes to loggerhead/static/javascript/yui/build/dd/dd-drag.js

[rs=mwhudson] Merged the latest and greatest from trunk.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
Copyright (c) 2008, Yahoo! Inc. All rights reserved.
 
3
Code licensed under the BSD License:
 
4
http://developer.yahoo.net/yui/license.txt
 
5
version: 3.0.0pr1
 
6
*/
 
7
YUI.add('dd-drag', function(Y) {
 
8
 
 
9
    /**
 
10
     * The Drag & Drop Utility allows you to create a draggable interface efficiently, buffering you from browser-level abnormalities and enabling you to focus on the interesting logic surrounding your particular implementation. This component enables you to create a variety of standard draggable objects with just a few lines of code and then, using its extensive API, add your own specific implementation logic.
 
11
     * @module dd
 
12
     * @submodule dd-drag
 
13
     */     
 
14
    /**
 
15
     * This class provides the ability to drag a Node.
 
16
     * @class Drag
 
17
     * @extends Base
 
18
     * @constructor
 
19
     */
 
20
 
 
21
    var DDM = Y.DD.DDM,
 
22
        NODE = 'node',
 
23
        DRAG_NODE = 'dragNode',
 
24
        OFFSET_HEIGHT = 'offsetHeight',
 
25
        OFFSET_WIDTH = 'offsetWidth',        
 
26
        MOUSE_UP = 'mouseup',
 
27
        MOUSE_DOWN = 'mousedown',
 
28
        /**
 
29
        * @event drag:mouseDown
 
30
        * @description Handles the mousedown DOM event, checks to see if you have a valid handle then starts the drag timers.
 
31
        * @preventable _handleMouseDown
 
32
        * @param {Event} ev The mousedown event.
 
33
        * @bubbles DDM
 
34
        * @type Event.Custom
 
35
        */
 
36
        EV_MOUSE_DOWN = 'drag:mouseDown',
 
37
        /**
 
38
        * @event drag:afterMouseDown
 
39
        * @description Fires after the mousedown event has been cleared.
 
40
        * @param {Event} ev The mousedown event.
 
41
        * @bubbles DDM
 
42
        * @type Event.Custom
 
43
        */
 
44
        EV_AFTER_MOUSE_DOWN = 'drag:afterMouseDown',
 
45
        /**
 
46
        * @event drag:removeHandle
 
47
        * @description Fires after a handle is removed.
 
48
        * @bubbles DDM
 
49
        * @type Event.Custom
 
50
        */
 
51
        EV_REMOVE_HANDLE = 'drag:removeHandle',
 
52
        /**
 
53
        * @event drag:addHandle
 
54
        * @description Fires after a handle is added.
 
55
        * @bubbles DDM
 
56
        * @type Event.Custom
 
57
        */
 
58
        EV_ADD_HANDLE = 'drag:addHandle',
 
59
        /**
 
60
        * @event drag:removeInvalid
 
61
        * @description Fires after an invalid selector is removed.
 
62
        * @bubbles DDM
 
63
        * @type Event.Custom
 
64
        */
 
65
        EV_REMOVE_INVALID = 'drag:removeInvalid',
 
66
        /**
 
67
        * @event drag:addInvalid
 
68
        * @description Fires after an invalid selector is added.
 
69
        * @bubbles DDM
 
70
        * @type Event.Custom
 
71
        */
 
72
        EV_ADD_INVALID = 'drag:addInvalid',
 
73
        /**
 
74
        * @event drag:start
 
75
        * @description Fires at the start of a drag operation.
 
76
        * @bubbles DDM
 
77
        * @type Event.Custom
 
78
        */
 
79
        EV_START = 'drag:start',
 
80
        /**
 
81
        * @event drag:end
 
82
        * @description Fires at the end of a drag operation.
 
83
        * @bubbles DDM
 
84
        * @type Event.Custom
 
85
        */
 
86
        EV_END = 'drag:end',
 
87
        /**
 
88
        * @event drag:drag
 
89
        * @description Fires every mousemove during a drag operation.
 
90
        * @bubbles DDM
 
91
        * @type Event.Custom
 
92
        */
 
93
        EV_DRAG = 'drag:drag';
 
94
 
 
95
 
 
96
        /**
 
97
        * @event drag:over
 
98
        * @description Fires when this node is over a Drop Target. (Fired from dd-drop)
 
99
        * @bubbles DDM
 
100
        * @type Event.Custom
 
101
        */
 
102
        /**
 
103
        * @event drag:enter
 
104
        * @description Fires when this node enters a Drop Target. (Fired from dd-drop)
 
105
        * @bubbles DDM
 
106
        * @type Event.Custom
 
107
        */
 
108
        /**
 
109
        * @event drag:exit
 
110
        * @description Fires when this node exits a Drop Target. (Fired from dd-drop)
 
111
        * @bubbles DDM
 
112
        * @type Event.Custom
 
113
        */
 
114
        /**
 
115
        * @event drag:drophit
 
116
        * @description Fires when this node is dropped on a valid Drop Target. (Fired from dd-ddm-drop)
 
117
        * @bubbles DDM
 
118
        * @type Event.Custom
 
119
        */
 
120
        /**
 
121
        * @event drag:dropmiss
 
122
        * @description Fires when this node is dropped on an invalid Drop Target. (Fired from dd-ddm-drop)
 
123
        * @bubbles DDM
 
124
        * @type Event.Custom
 
125
        */
 
126
    
 
127
    var Drag = function() {
 
128
        Drag.superclass.constructor.apply(this, arguments);
 
129
 
 
130
        DDM._regDrag(this);
 
131
    };
 
132
    Drag.NAME = 'drag';
 
133
 
 
134
    Drag.ATTRS = {
 
135
        /**
 
136
        * @attribute node
 
137
        * @description Y.Node instanace to use as the element to initiate a drag operation
 
138
        * @type Node
 
139
        */
 
140
        node: {
 
141
            set: function(node) {
 
142
                var n = Y.Node.get(node);
 
143
                if (!n) {
 
144
                    Y.fail('DD.Drag: Invalid Node Given: ' + node);
 
145
                }
 
146
                return n;
 
147
            }
 
148
        },
 
149
        /**
 
150
        * @attribute dragNode
 
151
        * @description Y.Node instanace to use as the draggable element, defaults to node
 
152
        * @type Node
 
153
        */
 
154
        dragNode: {
 
155
            set: function(node) {
 
156
                var n = Y.Node.get(node);
 
157
                if (!n) {
 
158
                    Y.fail('DD.Drag: Invalid dragNode Given: ' + node);
 
159
                }
 
160
                return n;
 
161
            }
 
162
        },
 
163
        /**
 
164
        * @attribute offsetNode
 
165
        * @description Offset the drag element by the difference in cursor position: default true
 
166
        * @type Boolean
 
167
        */
 
168
        offsetNode: {
 
169
            value: true
 
170
        },
 
171
        /**
 
172
        * @attribute clickPixelThresh
 
173
        * @description The number of pixels to move to start a drag operation, default is 3.
 
174
        * @type Number
 
175
        */
 
176
        clickPixelThresh: {
 
177
            value: DDM.get('clickPixelThresh')
 
178
        },
 
179
        /**
 
180
        * @attribute clickTimeThresh
 
181
        * @description The number of milliseconds a mousedown has to pass to start a drag operation, default is 1000.
 
182
        * @type Number
 
183
        */
 
184
        clickTimeThresh: {
 
185
            value: DDM.get('clickTimeThresh')
 
186
        },
 
187
        /**
 
188
        * @attribute lock
 
189
        * @description Set to lock this drag element so that it can't be dragged: default false.
 
190
        * @type Boolean
 
191
        */
 
192
        lock: {
 
193
            value: false,
 
194
            set: function(lock) {
 
195
                if (lock) {
 
196
                    this.get(NODE).addClass(DDM.CSS_PREFIX + '-locked');
 
197
                } else {
 
198
                    this.get(NODE).removeClass(DDM.CSS_PREFIX + '-locked');
 
199
                }
 
200
            }
 
201
        },
 
202
        /**
 
203
        * @attribute data
 
204
        * @description A payload holder to store arbitrary data about this drag object, can be used to store any value.
 
205
        * @type Mixed
 
206
        */
 
207
        data: {
 
208
            value: false
 
209
        },
 
210
        /**
 
211
        * @attribute move
 
212
        * @description If this is false, the drag element will not move with the cursor: default true. Can be used to "resize" the element.
 
213
        * @type Boolean
 
214
        */
 
215
        move: {
 
216
            value: true
 
217
        },
 
218
        /**
 
219
        * @attribute useShim
 
220
        * @description Use the protective shim on all drag operations: default true. Only works with dd-ddm, not dd-ddm-base.
 
221
        * @type Boolean
 
222
        */
 
223
        useShim: {
 
224
            value: true
 
225
        },
 
226
        /**
 
227
        * @attribute activeHandle
 
228
        * @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.
 
229
        * @type Node
 
230
        */
 
231
        activeHandle: {
 
232
            value: false
 
233
        },
 
234
        /**
 
235
        * @attribute primaryButtonOnly
 
236
        * @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.
 
237
        * @type Boolean
 
238
        */
 
239
        primaryButtonOnly: {
 
240
            value: true
 
241
        },
 
242
        /**
 
243
        * @attribute dragging
 
244
        * @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.
 
245
        * @type Boolean
 
246
        */
 
247
        dragging: {
 
248
            value: false
 
249
        },
 
250
        /**
 
251
        * @attribute target
 
252
        * @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.
 
253
        * @type Boolean
 
254
        */
 
255
        target: {
 
256
            value: false,
 
257
            set: function(config) {
 
258
                this._handleTarget(config);
 
259
            }
 
260
        },
 
261
        /**
 
262
        * @attribute dragMode
 
263
        * @description This attribute only works if the dd-drop module is active. It will set the dragMode (point, intersect, strict) of this Drag instance.
 
264
        * @type String
 
265
        */
 
266
        dragMode: {
 
267
            value: null,
 
268
            set: function(mode) {
 
269
                return DDM._setDragMode(mode);
 
270
            }
 
271
        },
 
272
        /**
 
273
        * @attribute groups
 
274
        * @description Array of groups to add this drag into.
 
275
        * @type Array
 
276
        */
 
277
        groups: {
 
278
            value: ['default'],
 
279
            get: function() {
 
280
                if (!this._groups) {
 
281
                    this._groups = {};
 
282
                }
 
283
                var ret = [];
 
284
                Y.each(this._groups, function(v, k) {
 
285
                    ret[ret.length] = k;
 
286
                });
 
287
                return ret;
 
288
            },
 
289
            set: function(g) {
 
290
                this._groups = {};
 
291
                Y.each(g, function(v, k) {
 
292
                    this._groups[v] = true;
 
293
                }, this);
 
294
            }
 
295
        },
 
296
        /**
 
297
        * @attribute handles
 
298
        * @description Array of valid handles to add. Adding something here will set all handles, even if previously added with addHandle
 
299
        * @type Array
 
300
        */
 
301
        handles: {
 
302
            value: null,
 
303
            set: function(g) {
 
304
                if (g) {
 
305
                    this._handles = {};
 
306
                    Y.each(g, function(v, k) {
 
307
                        this._handles[v] = true;
 
308
                    }, this);
 
309
                } else {
 
310
                    this._handles = null;
 
311
                }
 
312
                return g;
 
313
            }
 
314
        }
 
315
    };
 
316
 
 
317
    Y.extend(Drag, Y.Base, {
 
318
        /**
 
319
        * @method addToGroup
 
320
        * @description Add this Drag instance to a group, this should be used for on-the-fly group additions.
 
321
        * @param {String} g The group to add this Drag Instance to.
 
322
        * @return {Self}
 
323
        * @chainable
 
324
        */
 
325
        addToGroup: function(g) {
 
326
            this._groups[g] = true;
 
327
            DDM._activateTargets();
 
328
            return this;
 
329
        },
 
330
        /**
 
331
        * @method removeFromGroup
 
332
        * @description Remove this Drag instance from a group, this should be used for on-the-fly group removals.
 
333
        * @param {String} g The group to remove this Drag Instance from.
 
334
        * @return {Self}
 
335
        * @chainable
 
336
        */
 
337
        removeFromGroup: function(g) {
 
338
            delete this._groups[g];
 
339
            DDM._activateTargets();
 
340
            return this;
 
341
        },
 
342
        /**
 
343
        * @property target
 
344
        * @description This will be a reference to the Drop instance associated with this drag if the target: true config attribute is set..
 
345
        * @type {Object}
 
346
        */
 
347
        target: null,
 
348
        /**
 
349
        * @private
 
350
        * @method _handleTarget
 
351
        * @description Attribute handler for the target config attribute.
 
352
        * @param {Boolean/Object}
 
353
        * @return {Boolean/Object}
 
354
        */
 
355
        _handleTarget: function(config) {
 
356
            if (Y.DD.Drop) {
 
357
                if (config === false) {
 
358
                    if (this.target) {
 
359
                        DDM._unregTarget(this.target);
 
360
                        this.target = null;
 
361
                    }
 
362
                    return false;
 
363
                } else {
 
364
                    if (!Y.Lang.isObject(config)) {
 
365
                        config = {};
 
366
                    }
 
367
                    config.node = this.get(NODE);
 
368
                    this.target = new Y.DD.Drop(config);
 
369
                }
 
370
            } else {
 
371
                return false;
 
372
            }
 
373
        },
 
374
        /**
 
375
        * @private
 
376
        * @property _groups
 
377
        * @description Storage Array for the groups this drag belongs to.
 
378
        * @type {Array}
 
379
        */
 
380
        _groups: null,
 
381
        /**
 
382
        * @private
 
383
        * @method _createEvents
 
384
        * @description This method creates all the events for this Event Target and publishes them so we get Event Bubbling.
 
385
        */
 
386
        _createEvents: function() {
 
387
            
 
388
            this.publish(EV_MOUSE_DOWN, {
 
389
                defaultFn: this._handleMouseDown,
 
390
                queuable: true,
 
391
                emitFacade: true,
 
392
                bubbles: true
 
393
            });
 
394
            
 
395
            var ev = [
 
396
                EV_AFTER_MOUSE_DOWN,
 
397
                EV_REMOVE_HANDLE,
 
398
                EV_ADD_HANDLE,
 
399
                EV_REMOVE_INVALID,
 
400
                EV_ADD_INVALID,
 
401
                EV_START,
 
402
                EV_END,
 
403
                EV_DRAG,
 
404
                'drag:drophit',
 
405
                'drag:dropmiss',
 
406
                'drag:over',
 
407
                'drag:enter',
 
408
                'drag:exit'
 
409
            ];
 
410
            
 
411
            Y.each(ev, function(v, k) {
 
412
                this.publish(v, {
 
413
                    type: v,
 
414
                    emitFacade: true,
 
415
                    bubbles: true,
 
416
                    preventable: false,
 
417
                    queuable: true
 
418
                });
 
419
            }, this);
 
420
            this.addTarget(DDM);
 
421
            
 
422
           
 
423
        },
 
424
        /**
 
425
        * @private
 
426
        * @property _ev_md
 
427
        * @description A private reference to the mousedown DOM event
 
428
        * @type {Event}
 
429
        */
 
430
        _ev_md: null,
 
431
        /**
 
432
        * @private
 
433
        * @property _startTime
 
434
        * @description The getTime of the mousedown event. Not used, just here in case someone wants/needs to use it.
 
435
        * @type Date
 
436
        */
 
437
        _startTime: null,
 
438
        /**
 
439
        * @private
 
440
        * @property _endTime
 
441
        * @description The getTime of the mouseup event. Not used, just here in case someone wants/needs to use it.
 
442
        * @type Date
 
443
        */
 
444
        _endTime: null,
 
445
        /**
 
446
        * @private
 
447
        * @property _handles
 
448
        * @description A private hash of the valid drag handles
 
449
        * @type {Object}
 
450
        */
 
451
        _handles: null,
 
452
        /**
 
453
        * @private
 
454
        * @property _invalids
 
455
        * @description A private hash of the invalid selector strings
 
456
        * @type {Object}
 
457
        */
 
458
        _invalids: null,
 
459
        /**
 
460
        * @private
 
461
        * @property _invalidsDefault
 
462
        * @description A private hash of the default invalid selector strings: {'textarea': true, 'input': true, 'a': true, 'button': true}
 
463
        * @type {Object}
 
464
        */
 
465
        _invalidsDefault: {'textarea': true, 'input': true, 'a': true, 'button': true},
 
466
        /**
 
467
        * @private
 
468
        * @property _dragThreshMet
 
469
        * @description Private flag to see if the drag threshhold was met
 
470
        * @type {Boolean}
 
471
        */
 
472
        _dragThreshMet: null,
 
473
        /**
 
474
        * @private
 
475
        * @property _fromTimeout
 
476
        * @description Flag to determine if the drag operation came from a timeout
 
477
        * @type {Boolean}
 
478
        */
 
479
        _fromTimeout: null,
 
480
        /**
 
481
        * @private
 
482
        * @property _clickTimeout
 
483
        * @description Holder for the setTimeout call
 
484
        * @type {Boolean}
 
485
        */
 
486
        _clickTimeout: null,
 
487
        /**
 
488
        * @property deltaXY
 
489
        * @description The offset of the mouse position to the element's position
 
490
        * @type {Array}
 
491
        */
 
492
        deltaXY: null,
 
493
        /**
 
494
        * @property startXY
 
495
        * @description The initial mouse position
 
496
        * @type {Array}
 
497
        */
 
498
        startXY: null,
 
499
        /**
 
500
        * @property nodeXY
 
501
        * @description The initial element position
 
502
        * @type {Array}
 
503
        */
 
504
        nodeXY: null,
 
505
        /**
 
506
        * @property lastXY
 
507
        * @description The position of the element as it's moving (for offset calculations)
 
508
        * @type {Array}
 
509
        */
 
510
        lastXY: null,
 
511
        /**
 
512
        * @property mouseXY
 
513
        * @description The XY coords of the mousemove
 
514
        * @type {Array}
 
515
        */
 
516
        mouseXY: null,
 
517
        /**
 
518
        * @property region
 
519
        * @description A region object associated with this drag, used for checking regions while dragging.
 
520
        * @type Object
 
521
        */
 
522
        region: null,       
 
523
        /**
 
524
        * @private
 
525
        * @method _handleMouseUp
 
526
        * @description Handler for the mouseup DOM event
 
527
        * @param {Event}
 
528
        */
 
529
        _handleMouseUp: function(ev) {
 
530
            this._fixIEMouseUp();
 
531
            if (DDM.activeDrag) {
 
532
                DDM._end();
 
533
            }
 
534
        },
 
535
        /** 
 
536
        * @private
 
537
        * @method _ieSelectFix
 
538
        * @description The function we use as the onselectstart handler when we start a drag in Internet Explorer
 
539
        */
 
540
        _ieSelectFix: function() {
 
541
            return false;
 
542
        },
 
543
        /** 
 
544
        * @private
 
545
        * @property _ieSelectBack
 
546
        * @description We will hold a copy of the current "onselectstart" method on this property, and reset it after we are done using it.
 
547
        */
 
548
        _ieSelectBack: null,
 
549
        /**
 
550
        * @private
 
551
        * @method _fixIEMouseDown
 
552
        * @description This method copies the onselectstart listner on the document to the _ieSelectFix property
 
553
        */
 
554
        _fixIEMouseDown: function() {
 
555
            if (Y.UA.ie) {
 
556
                this._ieSelectBack = Y.config.doc.body.onselectstart;
 
557
                Y.config.doc.body.onselectstart = this._ieSelectFix;
 
558
            }           
 
559
        },
 
560
        /**
 
561
        * @private
 
562
        * @method _fixIEMouseUp
 
563
        * @description This method copies the _ieSelectFix property back to the onselectstart listner on the document.
 
564
        */
 
565
        _fixIEMouseUp: function() {
 
566
            if (Y.UA.ie) {
 
567
                Y.config.doc.body.onselectstart = this._ieSelectBack;
 
568
            }           
 
569
        },
 
570
        /**
 
571
        * @private
 
572
        * @method _handleMouseDownEvent
 
573
        * @description Handler for the mousedown DOM event
 
574
        * @param {Event}
 
575
        */
 
576
        _handleMouseDownEvent: function(ev) {
 
577
            this.fire(EV_MOUSE_DOWN, { ev: ev });
 
578
        },
 
579
        /**
 
580
        * @private
 
581
        * @method _handleMouseDown
 
582
        * @description Handler for the mousedown DOM event
 
583
        * @param {Event}
 
584
        */
 
585
        _handleMouseDown: function(e) {
 
586
            var ev = e.ev;
 
587
            this._dragThreshMet = false;
 
588
            this._ev_md = ev;
 
589
            
 
590
            if (this.get('primaryButtonOnly') && ev.button > 1) {
 
591
                return false;
 
592
            }
 
593
            if (this.validClick(ev)) {
 
594
                this._fixIEMouseDown();
 
595
                ev.halt();
 
596
                this._setStartPosition([ev.pageX, ev.pageY]);
 
597
 
 
598
                DDM.activeDrag = this;
 
599
 
 
600
                var self = this;
 
601
                this._clickTimeout = setTimeout(function() {
 
602
                    self._timeoutCheck.call(self);
 
603
                }, this.get('clickTimeThresh'));
 
604
            }
 
605
            this.fire(EV_AFTER_MOUSE_DOWN, { ev: ev });
 
606
        },
 
607
        /**
 
608
        * @method validClick
 
609
        * @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.
 
610
        * @param {Event}
 
611
        * @return {Boolean}
 
612
        */
 
613
        validClick: function(ev) {
 
614
            var r = false,
 
615
            tar = ev.target,
 
616
            hTest = null;
 
617
            if (this._handles) {
 
618
                Y.each(this._handles, function(i, n) {
 
619
                    if (Y.Lang.isString(n)) {
 
620
                        //Am I this or am I inside this
 
621
                        if (tar.test(n + ', ' + n + ' *')) {
 
622
                            hTest = n;
 
623
                            r = true;
 
624
                        }
 
625
                    }
 
626
                });
 
627
            } else {
 
628
                if (this.get(NODE).contains(tar) || this.get(NODE).compareTo(tar)) {
 
629
                    r = true;
 
630
                }
 
631
            }
 
632
            if (r) {
 
633
                if (this._invalids) {
 
634
                    Y.each(this._invalids, function(i, n) {
 
635
                        if (Y.Lang.isString(n)) {
 
636
                            //Am I this or am I inside this
 
637
                            if (tar.test(n + ', ' + n + ' *')) {
 
638
                                r = false;
 
639
                            }
 
640
                        }
 
641
                    });
 
642
                }
 
643
            }
 
644
            if (r) {
 
645
                if (hTest) {
 
646
                    var els = ev.currentTarget.queryAll(hTest);
 
647
                    els.each(function(n, i) {
 
648
                        if (n.contains(tar) || n.compareTo(tar)) {
 
649
                            this.set('activeHandle', els.item(i));
 
650
                        }
 
651
                    }, this);
 
652
                } else {
 
653
                    this.set('activeHandle', this.get(NODE));
 
654
                }
 
655
            }
 
656
            return r;
 
657
        },
 
658
        /**
 
659
        * @private
 
660
        * @method _setStartPosition
 
661
        * @description Sets the current position of the Element and calculates the offset
 
662
        * @param {Array} xy The XY coords to set the position to.
 
663
        */
 
664
        _setStartPosition: function(xy) {
 
665
            this.startXY = xy;
 
666
            
 
667
            this.nodeXY = this.get(NODE).getXY();
 
668
            this.lastXY = this.nodeXY;
 
669
 
 
670
            if (this.get('offsetNode')) {
 
671
                this.deltaXY = [(this.startXY[0] - this.nodeXY[0]), (this.startXY[1] - this.nodeXY[1])];
 
672
            } else {
 
673
                this.deltaXY = [0, 0];
 
674
            }
 
675
        },
 
676
        /**
 
677
        * @private
 
678
        * @method _timeoutCheck
 
679
        * @description The method passed to setTimeout to determine if the clickTimeThreshold was met.
 
680
        */
 
681
        _timeoutCheck: function() {
 
682
            if (!this.get('lock')) {
 
683
                this._fromTimeout = true;
 
684
                this._dragThreshMet = true;
 
685
                this.start();
 
686
                this._moveNode([this._ev_md.pageX, this._ev_md.pageY], true);
 
687
            }
 
688
        },
 
689
        /**
 
690
        * @method removeHandle
 
691
        * @description Remove a Selector added by addHandle
 
692
        * @param {String} str The selector for the handle to be removed. 
 
693
        * @return {Self}
 
694
        * @chainable
 
695
        */
 
696
        removeHandle: function(str) {
 
697
            if (this._handles[str]) {
 
698
                delete this._handles[str];
 
699
                this.fire(EV_REMOVE_HANDLE, { handle: str });
 
700
            }
 
701
            return this;
 
702
        },
 
703
        /**
 
704
        * @method addHandle
 
705
        * @description Add a handle to a drag element. Drag only initiates when a mousedown happens on this element.
 
706
        * @param {String} str The selector to test for a valid handle. Must be a child of the element.
 
707
        * @return {Self}
 
708
        * @chainable
 
709
        */
 
710
        addHandle: function(str) {
 
711
            if (!this._handles) {
 
712
                this._handles = {};
 
713
            }
 
714
            if (Y.Lang.isString(str)) {
 
715
                this._handles[str] = true;
 
716
                this.fire(EV_ADD_HANDLE, { handle: str });
 
717
            }
 
718
            return this;
 
719
        },
 
720
        /**
 
721
        * @method removeInvalid
 
722
        * @description Remove an invalid handle added by addInvalid
 
723
        * @param {String} str The invalid handle to remove from the internal list.
 
724
        * @return {Self}
 
725
        * @chainable
 
726
        */
 
727
        removeInvalid: function(str) {
 
728
            if (this._invalids[str]) {
 
729
                delete this._handles[str];
 
730
                this.fire(EV_REMOVE_INVALID, { handle: str });
 
731
            }
 
732
            return this;
 
733
        },
 
734
        /**
 
735
        * @method addInvalid
 
736
        * @description Add a selector string to test the handle against. If the test passes the drag operation will not continue.
 
737
        * @param {String} str The selector to test against to determine if this is an invalid drag handle.
 
738
        * @return {Self}
 
739
        * @chainable
 
740
        */
 
741
        addInvalid: function(str) {
 
742
            if (Y.Lang.isString(str)) {
 
743
                this._invalids[str] = true;
 
744
                this.fire(EV_ADD_INVALID, { handle: str });
 
745
            } else {
 
746
            }
 
747
            return this;
 
748
        },
 
749
        /**
 
750
        * @private
 
751
        * @method initializer
 
752
        * @description Internal init handler
 
753
        */
 
754
        initializer: function() {
 
755
            //TODO give the node instance a copy of this object
 
756
            //Not supported in PR1 due to Y.Node.get calling a new under the hood.
 
757
            //this.get(NODE).dd = this;
 
758
 
 
759
            if (!this.get(NODE).get('id')) {
 
760
                var id = Y.stamp(this.get(NODE));
 
761
                this.get(NODE).set('id', id);
 
762
            }
 
763
            
 
764
 
 
765
            this._invalids = this._invalidsDefault;
 
766
 
 
767
            this._createEvents();
 
768
            
 
769
            if (!this.get(DRAG_NODE)) {
 
770
                this.set(DRAG_NODE, this.get(NODE));
 
771
            }
 
772
            this._prep();
 
773
            this._dragThreshMet = false;
 
774
        },
 
775
        /**
 
776
        * @private
 
777
        * @method _prep
 
778
        * @description Attach event listners and add classname
 
779
        */
 
780
        _prep: function() {
 
781
            var node = this.get(NODE);
 
782
            node.addClass(DDM.CSS_PREFIX + '-draggable');
 
783
            node.on(MOUSE_DOWN, this._handleMouseDownEvent, this, true);
 
784
            node.on(MOUSE_UP, this._handleMouseUp, this, true);
 
785
        },
 
786
        /**
 
787
        * @private
 
788
        * @method _unprep
 
789
        * @description Detach event listners and remove classname
 
790
        */
 
791
        _unprep: function() {
 
792
            var node = this.get(NODE);
 
793
            node.removeClass(DDM.CSS_PREFIX + '-draggable');
 
794
            node.detach(MOUSE_DOWN, this._handleMouseDownEvent, this, true);
 
795
            node.detach(MOUSE_UP, this._handleMouseUp, this, true);
 
796
        },
 
797
        /**
 
798
        * @method start
 
799
        * @description Starts the drag operation
 
800
        * @return {Self}
 
801
        * @chainable
 
802
        */
 
803
        start: function() {
 
804
            if (!this.get('lock') && !this.get('dragging')) {
 
805
                this.set('dragging', true);
 
806
                DDM._start(this.deltaXY, [this.get(NODE).get(OFFSET_HEIGHT), this.get(NODE).get(OFFSET_WIDTH)]);
 
807
                this.get(NODE).addClass(DDM.CSS_PREFIX + '-dragging');
 
808
                this.fire(EV_START, { pageX: this.nodeXY[0], pageY: this.nodeXY[1] });
 
809
                this.get(DRAG_NODE).on(MOUSE_UP, this._handleMouseUp, this, true);
 
810
                var xy = this.nodeXY;
 
811
 
 
812
                this._startTime = (new Date()).getTime();
 
813
                
 
814
                this.region = {
 
815
                    '0': xy[0], 
 
816
                    '1': xy[1],
 
817
                    area: 0,
 
818
                    top: xy[1],
 
819
                    right: xy[0] + this.get(NODE).get(OFFSET_WIDTH),
 
820
                    bottom: xy[1] + this.get(NODE).get(OFFSET_HEIGHT),
 
821
                    left: xy[0]
 
822
                };
 
823
                
 
824
            }
 
825
            return this;
 
826
        },
 
827
        /**
 
828
        * @method end
 
829
        * @description Ends the drag operation
 
830
        * @return {Self}
 
831
        * @chainable
 
832
        */
 
833
        end: function() {
 
834
            this._endTime = (new Date()).getTime();
 
835
            clearTimeout(this._clickTimeout);
 
836
            this._dragThreshMet = false;
 
837
            this._fromTimeout = false;
 
838
            if (!this.get('lock') && this.get('dragging')) {
 
839
                this.fire(EV_END, { pageX: this.lastXY[0], pageY: this.lastXY[1] });
 
840
            }
 
841
            this.get(NODE).removeClass(DDM.CSS_PREFIX + '-dragging');
 
842
            this.set('dragging', false);
 
843
            this.deltaXY = [0, 0];
 
844
            this.get(DRAG_NODE).detach(MOUSE_UP, this._handleMouseUp, this, true);
 
845
 
 
846
            return this;
 
847
        },
 
848
        /**
 
849
        * @private
 
850
        * @method _align
 
851
        * @description Calculates the offsets and set's the XY that the element will move to.
 
852
        * @param {Array} xy The xy coords to align with.
 
853
        * @return Array
 
854
        * @type {Array}
 
855
        */
 
856
        _align: function(xy) {
 
857
            return [xy[0] - this.deltaXY[0], xy[1] - this.deltaXY[1]];
 
858
        },
 
859
        /**
 
860
        * @private
 
861
        * @method _moveNode
 
862
        * @description This method performs the actual element move.
 
863
        * @param {Array} eXY The XY to move the element to, usually comes from the mousemove DOM event.
 
864
        * @param {Boolean} noFire If true, the drag:drag event will not fire.
 
865
        */
 
866
        _moveNode: function(eXY, noFire) {
 
867
            var xy = this._align(eXY), diffXY = [], diffXY2 = [];
 
868
 
 
869
            diffXY[0] = (xy[0] - this.lastXY[0]);
 
870
            diffXY[1] = (xy[1] - this.lastXY[1]);
 
871
 
 
872
            diffXY2[0] = (xy[0] - this.nodeXY[0]);
 
873
            diffXY2[1] = (xy[1] - this.nodeXY[1]);
 
874
 
 
875
            if (this.get('move')) {
 
876
                if (Y.UA.opera) {
 
877
                    this.get(DRAG_NODE).setXY(xy);
 
878
                } else {
 
879
                    DDM.setXY(this.get(DRAG_NODE), diffXY);
 
880
                }
 
881
            }
 
882
 
 
883
            this.region = {
 
884
                '0': xy[0], 
 
885
                '1': xy[1],
 
886
                area: 0,
 
887
                top: xy[1],
 
888
                right: xy[0] + this.get(NODE).get(OFFSET_WIDTH),
 
889
                bottom: xy[1] + this.get(NODE).get(OFFSET_HEIGHT),
 
890
                left: xy[0]
 
891
            };
 
892
 
 
893
            var startXY = this.nodeXY;
 
894
            if (!noFire) {
 
895
                this.fire(EV_DRAG, {
 
896
                    pageX: xy[0],
 
897
                    pageY: xy[1],
 
898
                    info: {
 
899
                        start: startXY,
 
900
                        xy: xy,
 
901
                        delta: diffXY,
 
902
                        offset: diffXY2
 
903
                    } 
 
904
                });
 
905
            }
 
906
            
 
907
            this.lastXY = xy;
 
908
        },
 
909
        /**
 
910
        * @private
 
911
        * @method _move
 
912
        * @description Fired from DragDropMgr (DDM) on mousemove.
 
913
        * @param {Event} ev The mousemove DOM event
 
914
        */
 
915
        _move: function(ev) {
 
916
            if (this.get('lock')) {
 
917
                return false;
 
918
            } else {
 
919
                this.mouseXY = [ev.pageX, ev.pageY];
 
920
                if (!this._dragThreshMet) {
 
921
                    var diffX = Math.abs(this.startXY[0] - ev.pageX);
 
922
                    var diffY = Math.abs(this.startXY[1] - ev.pageY);
 
923
                    if (diffX > this.get('clickPixelThresh') || diffY > this.get('clickPixelThresh')) {
 
924
                        this._dragThreshMet = true;
 
925
                        this.start();
 
926
                        this._moveNode([ev.pageX, ev.pageY]);
 
927
                    }
 
928
                
 
929
                } else {
 
930
                    clearTimeout(this._clickTimeout);
 
931
                    this._moveNode([ev.pageX, ev.pageY]);
 
932
                }
 
933
            }
 
934
        },
 
935
        /**
 
936
        * @method stopDrag
 
937
        * @description Method will forcefully stop a drag operation. For example calling this from inside an ESC keypress handler will stop this drag.
 
938
        * @return {Self}
 
939
        * @chainable
 
940
        */
 
941
        stopDrag: function() {
 
942
            if (this.get('dragging')) {
 
943
                DDM._end();
 
944
            }
 
945
            return this;
 
946
        },
 
947
        /**
 
948
        * @private
 
949
        * @method destructor
 
950
        * @description Lifecycle destructor, unreg the drag from the DDM and remove listeners
 
951
        */
 
952
        destructor: function() {
 
953
            DDM._unregDrag(this);
 
954
 
 
955
            this._unprep();
 
956
            if (this.target) {
 
957
                this.target.destroy();
 
958
            }
 
959
        }
 
960
    });
 
961
    Y.namespace('DD');    
 
962
    Y.DD.Drag = Drag;
 
963
 
 
964
 
 
965
 
 
966
}, '3.0.0pr1' ,{requires:['dd-ddm-base'], skinnable:false});