/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

  • Committer: Matt Nordhoff
  • Date: 2010-02-26 04:37:13 UTC
  • mfrom: (400 trunk)
  • mto: This revision was merged to the branch mainline in revision 401.
  • Revision ID: mnordhoff@mattnordhoff.com-20100226043713-7mw3r6dr9qowutmi
Merge trunk for NEWS

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