Project

General

Profile

1 601 doc
/*
2
Copyright (c) 2007, Yahoo! Inc. All rights reserved.
3
Code licensed under the BSD License:
4
http://developer.yahoo.net/yui/license.txt
5
version: 2.4.1
6
*/
7
/**
8
 * The drag and drop utility provides a framework for building drag and drop
9
 * applications.  In addition to enabling drag and drop for specific elements,
10
 * the drag and drop elements are tracked by the manager class, and the
11
 * interactions between the various elements are tracked during the drag and
12
 * the implementing code is notified about these important moments.
13
 * @module dragdrop
14
 * @title Drag and Drop
15
 * @requires yahoo,dom,event
16
 * @namespace YAHOO.util
17
 */
18
19
// Only load the library once.  Rewriting the manager class would orphan
20
// existing drag and drop instances.
21
if (!YAHOO.util.DragDropMgr) {
22
23
/**
24
 * DragDropMgr is a singleton that tracks the element interaction for
25
 * all DragDrop items in the window.  Generally, you will not call
26
 * this class directly, but it does have helper methods that could
27
 * be useful in your DragDrop implementations.
28
 * @class DragDropMgr
29
 * @static
30
 */
31
YAHOO.util.DragDropMgr = function() {
32
33
    var Event = YAHOO.util.Event;
34
35
    return {
36
37
        /**
38
         * Two dimensional Array of registered DragDrop objects.  The first
39
         * dimension is the DragDrop item group, the second the DragDrop
40
         * object.
41
         * @property ids
42
         * @type {string: string}
43
         * @private
44
         * @static
45
         */
46
        ids: {},
47
48
        /**
49
         * Array of element ids defined as drag handles.  Used to determine
50
         * if the element that generated the mousedown event is actually the
51
         * handle and not the html element itself.
52
         * @property handleIds
53
         * @type {string: string}
54
         * @private
55
         * @static
56
         */
57
        handleIds: {},
58
59
        /**
60
         * the DragDrop object that is currently being dragged
61
         * @property dragCurrent
62
         * @type DragDrop
63
         * @private
64
         * @static
65
         **/
66
        dragCurrent: null,
67
68
        /**
69
         * the DragDrop object(s) that are being hovered over
70
         * @property dragOvers
71
         * @type Array
72
         * @private
73
         * @static
74
         */
75
        dragOvers: {},
76
77
        /**
78
         * the X distance between the cursor and the object being dragged
79
         * @property deltaX
80
         * @type int
81
         * @private
82
         * @static
83
         */
84
        deltaX: 0,
85
86
        /**
87
         * the Y distance between the cursor and the object being dragged
88
         * @property deltaY
89
         * @type int
90
         * @private
91
         * @static
92
         */
93
        deltaY: 0,
94
95
        /**
96
         * Flag to determine if we should prevent the default behavior of the
97
         * events we define. By default this is true, but this can be set to
98
         * false if you need the default behavior (not recommended)
99
         * @property preventDefault
100
         * @type boolean
101
         * @static
102
         */
103
        preventDefault: true,
104
105
        /**
106
         * Flag to determine if we should stop the propagation of the events
107
         * we generate. This is true by default but you may want to set it to
108
         * false if the html element contains other features that require the
109
         * mouse click.
110
         * @property stopPropagation
111
         * @type boolean
112
         * @static
113
         */
114
        stopPropagation: true,
115
116
        /**
117
         * Internal flag that is set to true when drag and drop has been
118
         * initialized
119
         * @property initialized
120
         * @private
121
         * @static
122
         */
123
        initialized: false,
124
125
        /**
126
         * All drag and drop can be disabled.
127
         * @property locked
128
         * @private
129
         * @static
130
         */
131
        locked: false,
132
133
        /**
134
         * Provides additional information about the the current set of
135
         * interactions.  Can be accessed from the event handlers. It
136
         * contains the following properties:
137
         *
138
         *       out:       onDragOut interactions
139
         *       enter:     onDragEnter interactions
140
         *       over:      onDragOver interactions
141
         *       drop:      onDragDrop interactions
142
         *       point:     The location of the cursor
143
         *       draggedRegion: The location of dragged element at the time
144
         *                      of the interaction
145
         *       sourceRegion: The location of the source elemtn at the time
146
         *                     of the interaction
147
         *       validDrop: boolean
148
         * @property interactionInfo
149
         * @type object
150
         * @static
151
         */
152
        interactionInfo: null,
153
154
        /**
155
         * Called the first time an element is registered.
156
         * @method init
157
         * @private
158
         * @static
159
         */
160
        init: function() {
161
            this.initialized = true;
162
        },
163
164
        /**
165
         * In point mode, drag and drop interaction is defined by the
166
         * location of the cursor during the drag/drop
167
         * @property POINT
168
         * @type int
169
         * @static
170
         * @final
171
         */
172
        POINT: 0,
173
174
        /**
175
         * In intersect mode, drag and drop interaction is defined by the
176
         * cursor position or the amount of overlap of two or more drag and
177
         * drop objects.
178
         * @property INTERSECT
179
         * @type int
180
         * @static
181
         * @final
182
         */
183
        INTERSECT: 1,
184
185
        /**
186
         * In intersect mode, drag and drop interaction is defined only by the
187
         * overlap of two or more drag and drop objects.
188
         * @property STRICT_INTERSECT
189
         * @type int
190
         * @static
191
         * @final
192
         */
193
        STRICT_INTERSECT: 2,
194
195
        /**
196
         * The current drag and drop mode.  Default: POINT
197
         * @property mode
198
         * @type int
199
         * @static
200
         */
201
        mode: 0,
202
203
        /**
204
         * Runs method on all drag and drop objects
205
         * @method _execOnAll
206
         * @private
207
         * @static
208
         */
209
        _execOnAll: function(sMethod, args) {
210
            for (var i in this.ids) {
211
                for (var j in this.ids[i]) {
212
                    var oDD = this.ids[i][j];
213
                    if (! this.isTypeOfDD(oDD)) {
214
                        continue;
215
                    }
216
                    oDD[sMethod].apply(oDD, args);
217
                }
218
            }
219
        },
220
221
        /**
222
         * Drag and drop initialization.  Sets up the global event handlers
223
         * @method _onLoad
224
         * @private
225
         * @static
226
         */
227
        _onLoad: function() {
228
229
            this.init();
230
231
            YAHOO.log("DragDropMgr onload", "info", "DragDropMgr");
232
233
            Event.on(document, "mouseup",   this.handleMouseUp, this, true);
234
            Event.on(document, "mousemove", this.handleMouseMove, this, true);
235
            Event.on(window,   "unload",    this._onUnload, this, true);
236
            Event.on(window,   "resize",    this._onResize, this, true);
237
            // Event.on(window,   "mouseout",    this._test);
238
239
        },
240
241
        /**
242
         * Reset constraints on all drag and drop objs
243
         * @method _onResize
244
         * @private
245
         * @static
246
         */
247
        _onResize: function(e) {
248
            YAHOO.log("window resize", "info", "DragDropMgr");
249
            this._execOnAll("resetConstraints", []);
250
        },
251
252
        /**
253
         * Lock all drag and drop functionality
254
         * @method lock
255
         * @static
256
         */
257
        lock: function() { this.locked = true; },
258
259
        /**
260
         * Unlock all drag and drop functionality
261
         * @method unlock
262
         * @static
263
         */
264
        unlock: function() { this.locked = false; },
265
266
        /**
267
         * Is drag and drop locked?
268
         * @method isLocked
269
         * @return {boolean} True if drag and drop is locked, false otherwise.
270
         * @static
271
         */
272
        isLocked: function() { return this.locked; },
273
274
        /**
275
         * Location cache that is set for all drag drop objects when a drag is
276
         * initiated, cleared when the drag is finished.
277
         * @property locationCache
278
         * @private
279
         * @static
280
         */
281
        locationCache: {},
282
283
        /**
284
         * Set useCache to false if you want to force object the lookup of each
285
         * drag and drop linked element constantly during a drag.
286
         * @property useCache
287
         * @type boolean
288
         * @static
289
         */
290
        useCache: true,
291
292
        /**
293
         * The number of pixels that the mouse needs to move after the
294
         * mousedown before the drag is initiated.  Default=3;
295
         * @property clickPixelThresh
296
         * @type int
297
         * @static
298
         */
299
        clickPixelThresh: 3,
300
301
        /**
302
         * The number of milliseconds after the mousedown event to initiate the
303
         * drag if we don't get a mouseup event. Default=1000
304
         * @property clickTimeThresh
305
         * @type int
306
         * @static
307
         */
308
        clickTimeThresh: 1000,
309
310
        /**
311
         * Flag that indicates that either the drag pixel threshold or the
312
         * mousdown time threshold has been met
313
         * @property dragThreshMet
314
         * @type boolean
315
         * @private
316
         * @static
317
         */
318
        dragThreshMet: false,
319
320
        /**
321
         * Timeout used for the click time threshold
322
         * @property clickTimeout
323
         * @type Object
324
         * @private
325
         * @static
326
         */
327
        clickTimeout: null,
328
329
        /**
330
         * The X position of the mousedown event stored for later use when a
331
         * drag threshold is met.
332
         * @property startX
333
         * @type int
334
         * @private
335
         * @static
336
         */
337
        startX: 0,
338
339
        /**
340
         * The Y position of the mousedown event stored for later use when a
341
         * drag threshold is met.
342
         * @property startY
343
         * @type int
344
         * @private
345
         * @static
346
         */
347
        startY: 0,
348
349
        /**
350
         * Flag to determine if the drag event was fired from the click timeout and
351
         * not the mouse move threshold.
352
         * @property fromTimeout
353
         * @type boolean
354
         * @private
355
         * @static
356
         */
357
        fromTimeout: false,
358
359
        /**
360
         * Each DragDrop instance must be registered with the DragDropMgr.
361
         * This is executed in DragDrop.init()
362
         * @method regDragDrop
363
         * @param {DragDrop} oDD the DragDrop object to register
364
         * @param {String} sGroup the name of the group this element belongs to
365
         * @static
366
         */
367
        regDragDrop: function(oDD, sGroup) {
368
            if (!this.initialized) { this.init(); }
369
370
            if (!this.ids[sGroup]) {
371
                this.ids[sGroup] = {};
372
            }
373
            this.ids[sGroup][oDD.id] = oDD;
374
        },
375
376
        /**
377
         * Removes the supplied dd instance from the supplied group. Executed
378
         * by DragDrop.removeFromGroup, so don't call this function directly.
379
         * @method removeDDFromGroup
380
         * @private
381
         * @static
382
         */
383
        removeDDFromGroup: function(oDD, sGroup) {
384
            if (!this.ids[sGroup]) {
385
                this.ids[sGroup] = {};
386
            }
387
388
            var obj = this.ids[sGroup];
389
            if (obj && obj[oDD.id]) {
390
                delete obj[oDD.id];
391
            }
392
        },
393
394
        /**
395
         * Unregisters a drag and drop item.  This is executed in
396
         * DragDrop.unreg, use that method instead of calling this directly.
397
         * @method _remove
398
         * @private
399
         * @static
400
         */
401
        _remove: function(oDD) {
402
            for (var g in oDD.groups) {
403
                if (g && this.ids[g][oDD.id]) {
404
                    delete this.ids[g][oDD.id];
405
                    //YAHOO.log("NEW LEN " + this.ids.length, "warn");
406
                }
407
            }
408
            delete this.handleIds[oDD.id];
409
        },
410
411
        /**
412
         * Each DragDrop handle element must be registered.  This is done
413
         * automatically when executing DragDrop.setHandleElId()
414
         * @method regHandle
415
         * @param {String} sDDId the DragDrop id this element is a handle for
416
         * @param {String} sHandleId the id of the element that is the drag
417
         * handle
418
         * @static
419
         */
420
        regHandle: function(sDDId, sHandleId) {
421
            if (!this.handleIds[sDDId]) {
422
                this.handleIds[sDDId] = {};
423
            }
424
            this.handleIds[sDDId][sHandleId] = sHandleId;
425
        },
426
427
        /**
428
         * Utility function to determine if a given element has been
429
         * registered as a drag drop item.
430
         * @method isDragDrop
431
         * @param {String} id the element id to check
432
         * @return {boolean} true if this element is a DragDrop item,
433
         * false otherwise
434
         * @static
435
         */
436
        isDragDrop: function(id) {
437
            return ( this.getDDById(id) ) ? true : false;
438
        },
439
440
        /**
441
         * Returns the drag and drop instances that are in all groups the
442
         * passed in instance belongs to.
443
         * @method getRelated
444
         * @param {DragDrop} p_oDD the obj to get related data for
445
         * @param {boolean} bTargetsOnly if true, only return targetable objs
446
         * @return {DragDrop[]} the related instances
447
         * @static
448
         */
449
        getRelated: function(p_oDD, bTargetsOnly) {
450
            var oDDs = [];
451
            for (var i in p_oDD.groups) {
452
                for (var j in this.ids[i]) {
453
                    var dd = this.ids[i][j];
454
                    if (! this.isTypeOfDD(dd)) {
455
                        continue;
456
                    }
457
                    if (!bTargetsOnly || dd.isTarget) {
458
                        oDDs[oDDs.length] = dd;
459
                    }
460
                }
461
            }
462
463
            return oDDs;
464
        },
465
466
        /**
467
         * Returns true if the specified dd target is a legal target for
468
         * the specifice drag obj
469
         * @method isLegalTarget
470
         * @param {DragDrop} the drag obj
471
         * @param {DragDrop} the target
472
         * @return {boolean} true if the target is a legal target for the
473
         * dd obj
474
         * @static
475
         */
476
        isLegalTarget: function (oDD, oTargetDD) {
477
            var targets = this.getRelated(oDD, true);
478
            for (var i=0, len=targets.length;i<len;++i) {
479
                if (targets[i].id == oTargetDD.id) {
480
                    return true;
481
                }
482
            }
483
484
            return false;
485
        },
486
487
        /**
488
         * My goal is to be able to transparently determine if an object is
489
         * typeof DragDrop, and the exact subclass of DragDrop.  typeof
490
         * returns "object", oDD.constructor.toString() always returns
491
         * "DragDrop" and not the name of the subclass.  So for now it just
492
         * evaluates a well-known variable in DragDrop.
493
         * @method isTypeOfDD
494
         * @param {Object} the object to evaluate
495
         * @return {boolean} true if typeof oDD = DragDrop
496
         * @static
497
         */
498
        isTypeOfDD: function (oDD) {
499
            return (oDD && oDD.__ygDragDrop);
500
        },
501
502
        /**
503
         * Utility function to determine if a given element has been
504
         * registered as a drag drop handle for the given Drag Drop object.
505
         * @method isHandle
506
         * @param {String} id the element id to check
507
         * @return {boolean} true if this element is a DragDrop handle, false
508
         * otherwise
509
         * @static
510
         */
511
        isHandle: function(sDDId, sHandleId) {
512
            return ( this.handleIds[sDDId] &&
513
                            this.handleIds[sDDId][sHandleId] );
514
        },
515
516
        /**
517
         * Returns the DragDrop instance for a given id
518
         * @method getDDById
519
         * @param {String} id the id of the DragDrop object
520
         * @return {DragDrop} the drag drop object, null if it is not found
521
         * @static
522
         */
523
        getDDById: function(id) {
524
            for (var i in this.ids) {
525
                if (this.ids[i][id]) {
526
                    return this.ids[i][id];
527
                }
528
            }
529
            return null;
530
        },
531
532
        /**
533
         * Fired after a registered DragDrop object gets the mousedown event.
534
         * Sets up the events required to track the object being dragged
535
         * @method handleMouseDown
536
         * @param {Event} e the event
537
         * @param oDD the DragDrop object being dragged
538
         * @private
539
         * @static
540
         */
541
        handleMouseDown: function(e, oDD) {
542
543
            this.currentTarget = YAHOO.util.Event.getTarget(e);
544
545
            this.dragCurrent = oDD;
546
547
            var el = oDD.getEl();
548
549
            // track start position
550
            this.startX = YAHOO.util.Event.getPageX(e);
551
            this.startY = YAHOO.util.Event.getPageY(e);
552
553
            this.deltaX = this.startX - el.offsetLeft;
554
            this.deltaY = this.startY - el.offsetTop;
555
556
            this.dragThreshMet = false;
557
558
            this.clickTimeout = setTimeout(
559
                    function() {
560
                        var DDM = YAHOO.util.DDM;
561
                        DDM.startDrag(DDM.startX, DDM.startY);
562
                        DDM.fromTimeout = true;
563
                    },
564
                    this.clickTimeThresh );
565
        },
566
567
        /**
568
         * Fired when either the drag pixel threshol or the mousedown hold
569
         * time threshold has been met.
570
         * @method startDrag
571
         * @param x {int} the X position of the original mousedown
572
         * @param y {int} the Y position of the original mousedown
573
         * @static
574
         */
575
        startDrag: function(x, y) {
576
            YAHOO.log("firing drag start events", "info", "DragDropMgr");
577
            clearTimeout(this.clickTimeout);
578
            var dc = this.dragCurrent;
579
            if (dc) {
580
                dc.b4StartDrag(x, y);
581
            }
582
            if (dc) {
583
                dc.startDrag(x, y);
584
            }
585
            this.dragThreshMet = true;
586
        },
587
588
        /**
589
         * Internal function to handle the mouseup event.  Will be invoked
590
         * from the context of the document.
591
         * @method handleMouseUp
592
         * @param {Event} e the event
593
         * @private
594
         * @static
595
         */
596
        handleMouseUp: function(e) {
597
            if (this.dragCurrent) {
598
                clearTimeout(this.clickTimeout);
599
600
                if (this.dragThreshMet) {
601
                    YAHOO.log("mouseup detected - completing drag", "info", "DragDropMgr");
602
                    if (this.fromTimeout) {
603
                        YAHOO.log('fromTimeout is true (mouse didn\'t move), call handleMouseDown so we can get the dragOver event', 'info', 'DragDropMgr');
604
                        this.handleMouseMove(e);
605
                    }
606
                    this.fromTimeout = false;
607
                    this.fireEvents(e, true);
608
                } else {
609
                    YAHOO.log("drag threshold not met", "info", "DragDropMgr");
610
                }
611
612
                this.stopDrag(e);
613
614
                this.stopEvent(e);
615
            }
616
        },
617
618
        /**
619
         * Utility to stop event propagation and event default, if these
620
         * features are turned on.
621
         * @method stopEvent
622
         * @param {Event} e the event as returned by this.getEvent()
623
         * @static
624
         */
625
        stopEvent: function(e) {
626
            if (this.stopPropagation) {
627
                YAHOO.util.Event.stopPropagation(e);
628
            }
629
630
            if (this.preventDefault) {
631
                YAHOO.util.Event.preventDefault(e);
632
            }
633
        },
634
635
        /**
636
         * Ends the current drag, cleans up the state, and fires the endDrag
637
         * and mouseUp events.  Called internally when a mouseup is detected
638
         * during the drag.  Can be fired manually during the drag by passing
639
         * either another event (such as the mousemove event received in onDrag)
640
         * or a fake event with pageX and pageY defined (so that endDrag and
641
         * onMouseUp have usable position data.).  Alternatively, pass true
642
         * for the silent parameter so that the endDrag and onMouseUp events
643
         * are skipped (so no event data is needed.)
644
         *
645
         * @method stopDrag
646
         * @param {Event} e the mouseup event, another event (or a fake event)
647
         *                  with pageX and pageY defined, or nothing if the
648
         *                  silent parameter is true
649
         * @param {boolean} silent skips the enddrag and mouseup events if true
650
         * @static
651
         */
652
        stopDrag: function(e, silent) {
653
            // YAHOO.log("mouseup - removing event handlers");
654
655
            // Fire the drag end event for the item that was dragged
656
            if (this.dragCurrent && !silent) {
657
                if (this.dragThreshMet) {
658
                    YAHOO.log("firing endDrag events", "info", "DragDropMgr");
659
                    this.dragCurrent.b4EndDrag(e);
660
                    this.dragCurrent.endDrag(e);
661
                }
662
663
                YAHOO.log("firing dragdrop onMouseUp event", "info", "DragDropMgr");
664
                this.dragCurrent.onMouseUp(e);
665
            }
666
667
            this.dragCurrent = null;
668
            this.dragOvers = {};
669
        },
670
671
        /**
672
         * Internal function to handle the mousemove event.  Will be invoked
673
         * from the context of the html element.
674
         *
675
         * @TODO figure out what we can do about mouse events lost when the
676
         * user drags objects beyond the window boundary.  Currently we can
677
         * detect this in internet explorer by verifying that the mouse is
678
         * down during the mousemove event.  Firefox doesn't give us the
679
         * button state on the mousemove event.
680
         * @method handleMouseMove
681
         * @param {Event} e the event
682
         * @private
683
         * @static
684
         */
685
        handleMouseMove: function(e) {
686
            //YAHOO.log("handlemousemove");
687
688
            var dc = this.dragCurrent;
689
            if (dc) {
690
                // YAHOO.log("no current drag obj");
691
692
                // var button = e.which || e.button;
693
                // YAHOO.log("which: " + e.which + ", button: "+ e.button);
694
695
                // check for IE mouseup outside of page boundary
696
                if (YAHOO.util.Event.isIE && !e.button) {
697
                    YAHOO.log("button failure", "info", "DragDropMgr");
698
                    this.stopEvent(e);
699
                    return this.handleMouseUp(e);
700
                }
701
702
                if (!this.dragThreshMet) {
703
                    var diffX = Math.abs(this.startX - YAHOO.util.Event.getPageX(e));
704
                    var diffY = Math.abs(this.startY - YAHOO.util.Event.getPageY(e));
705
                    // YAHOO.log("diffX: " + diffX + "diffY: " + diffY);
706
                    if (diffX > this.clickPixelThresh ||
707
                                diffY > this.clickPixelThresh) {
708
                        YAHOO.log("pixel threshold met", "info", "DragDropMgr");
709
                        this.startDrag(this.startX, this.startY);
710
                    }
711
                }
712
713
                if (this.dragThreshMet) {
714
                    dc.b4Drag(e);
715
                    if (dc) {
716
                        dc.onDrag(e);
717
                    }
718
                    if (dc) {
719
                        this.fireEvents(e, false);
720
                    }
721
                }
722
723
                this.stopEvent(e);
724
            }
725
        },
726
727
        /**
728
         * Iterates over all of the DragDrop elements to find ones we are
729
         * hovering over or dropping on
730
         * @method fireEvents
731
         * @param {Event} e the event
732
         * @param {boolean} isDrop is this a drop op or a mouseover op?
733
         * @private
734
         * @static
735
         */
736
        fireEvents: function(e, isDrop) {
737
            var dc = this.dragCurrent;
738
739
            // If the user did the mouse up outside of the window, we could
740
            // get here even though we have ended the drag.
741
            // If the config option dragOnly is true, bail out and don't fire the events
742
            if (!dc || dc.isLocked() || dc.dragOnly) {
743
                return;
744
            }
745
746
            var x = YAHOO.util.Event.getPageX(e),
747
                y = YAHOO.util.Event.getPageY(e),
748
                pt = new YAHOO.util.Point(x,y),
749
                pos = dc.getTargetCoord(pt.x, pt.y),
750
                el = dc.getDragEl(),
751
                curRegion = new YAHOO.util.Region( pos.y,
752
                                               pos.x + el.offsetWidth,
753
                                               pos.y + el.offsetHeight,
754
                                               pos.x ),
755
756
                oldOvers = [], // cache the previous dragOver array
757
                outEvts   = [],
758
                overEvts  = [],
759
                dropEvts  = [],
760
                enterEvts = [],
761
                inGroupsObj  = {},
762
                inGroups  = [];
763
764
765
            // Check to see if the object(s) we were hovering over is no longer
766
            // being hovered over so we can fire the onDragOut event
767
            for (var i in this.dragOvers) {
768
769
                var ddo = this.dragOvers[i];
770
771
                if (! this.isTypeOfDD(ddo)) {
772
                    continue;
773
                }
774
                if (! this.isOverTarget(pt, ddo, this.mode, curRegion)) {
775
                    outEvts.push( ddo );
776
                }
777
778
                oldOvers[i] = true;
779
                delete this.dragOvers[i];
780
            }
781
782
            for (var sGroup in dc.groups) {
783
                // YAHOO.log("Processing group " + sGroup);
784
785
                if ("string" != typeof sGroup) {
786
                    continue;
787
                }
788
789
                for (i in this.ids[sGroup]) {
790
                    var oDD = this.ids[sGroup][i];
791
                    if (! this.isTypeOfDD(oDD)) {
792
                        continue;
793
                    }
794
795
                    if (oDD.isTarget && !oDD.isLocked() && oDD != dc) {
796
                        if (this.isOverTarget(pt, oDD, this.mode, curRegion)) {
797
                            inGroupsObj[sGroup] = true;
798
                            // look for drop interactions
799
                            if (isDrop) {
800
                                dropEvts.push( oDD );
801
                            // look for drag enter and drag over interactions
802
                            } else {
803
804
                                // initial drag over: dragEnter fires
805
                                if (!oldOvers[oDD.id]) {
806
                                    enterEvts.push( oDD );
807
                                // subsequent drag overs: dragOver fires
808
                                } else {
809
                                    overEvts.push( oDD );
810
                                }
811
812
                                this.dragOvers[oDD.id] = oDD;
813
                            }
814
                        }
815
                    }
816
                }
817
            }
818
819
            this.interactionInfo = {
820
                out:       outEvts,
821
                enter:     enterEvts,
822
                over:      overEvts,
823
                drop:      dropEvts,
824
                point:     pt,
825
                draggedRegion:    curRegion,
826
                sourceRegion: this.locationCache[dc.id],
827
                validDrop: isDrop
828
            };
829
830
831
            for (var inG in inGroupsObj) {
832
                inGroups.push(inG);
833
            }
834
835
            // notify about a drop that did not find a target
836
            if (isDrop && !dropEvts.length) {
837
                YAHOO.log(dc.id + " dropped, but not on a target", "info", "DragDropMgr");
838
                this.interactionInfo.validDrop = false;
839
                dc.onInvalidDrop(e);
840
            }
841
842
843
            if (this.mode) {
844
                if (outEvts.length) {
845
                    YAHOO.log(dc.id+" onDragOut: " + outEvts, "info", "DragDropMgr");
846
                    dc.b4DragOut(e, outEvts);
847
                    if (dc) {
848
                        dc.onDragOut(e, outEvts);
849
                    }
850
                }
851
852
                if (enterEvts.length) {
853
                    YAHOO.log(dc.id+" onDragEnter: " + enterEvts + " (group: " + inGroups + ")", "info", "DragDropMgr");
854
                    if (dc) {
855
                        dc.onDragEnter(e, enterEvts, inGroups);
856
                    }
857
                }
858
859
                if (overEvts.length) {
860
                    YAHOO.log(dc.id+" onDragOver: " + overEvts + " (group: " + inGroups + ")", "info", "DragDropMgr");
861
                    if (dc) {
862
                        dc.b4DragOver(e, overEvts, inGroups);
863
                    }
864
865
                    if (dc) {
866
                        dc.onDragOver(e, overEvts, inGroups);
867
                    }
868
                }
869
870
                if (dropEvts.length) {
871
                    YAHOO.log(dc.id+" onDragDrop: " + dropEvts + " (group: " + inGroups + ")", "info", "DragDropMgr");
872
                    if (dc) {
873
                        dc.b4DragDrop(e, dropEvts, inGroups);
874
                    }
875
                    if (dc) {
876
                        dc.onDragDrop(e, dropEvts, inGroups);
877
                    }
878
                }
879
880
            } else {
881
                // fire dragout events
882
                var len = 0;
883
                for (i=0, len=outEvts.length; i<len; ++i) {
884
                    YAHOO.log(dc.id+" onDragOut: " + outEvts[i].id, "info", "DragDropMgr");
885
                    if (dc) {
886
                        dc.b4DragOut(e, outEvts[i].id, inGroups[0]);
887
                    }
888
                    if (dc) {
889
                        dc.onDragOut(e, outEvts[i].id, inGroups[0]);
890
                    }
891
                }
892
893
                // fire enter events
894
                for (i=0,len=enterEvts.length; i<len; ++i) {
895
                    YAHOO.log(dc.id + " onDragEnter " + enterEvts[i].id + " (group: " + inGroups + ")", "info", "DragDropMgr");
896
                    // dc.b4DragEnter(e, oDD.id);
897
898
                    if (dc) {
899
                        dc.onDragEnter(e, enterEvts[i].id, inGroups[0]);
900
                    }
901
                }
902
903
                // fire over events
904
                for (i=0,len=overEvts.length; i<len; ++i) {
905
                    YAHOO.log(dc.id + " onDragOver " + overEvts[i].id + " (group: " + inGroups + ")", "info", "DragDropMgr");
906
                    if (dc) {
907
                        dc.b4DragOver(e, overEvts[i].id, inGroups[0]);
908
                    }
909
                    if (dc) {
910
                        dc.onDragOver(e, overEvts[i].id, inGroups[0]);
911
                    }
912
                }
913
914
                // fire drop events
915
                for (i=0, len=dropEvts.length; i<len; ++i) {
916
                    YAHOO.log(dc.id + " dropped on " + dropEvts[i].id + " (group: " + inGroups + ")", "info", "DragDropMgr");
917
                    if (dc) {
918
                        dc.b4DragDrop(e, dropEvts[i].id, inGroups[0]);
919
                    }
920
                    if (dc) {
921
                        dc.onDragDrop(e, dropEvts[i].id, inGroups[0]);
922
                    }
923
                }
924
925
            }
926
        },
927
928
        /**
929
         * Helper function for getting the best match from the list of drag
930
         * and drop objects returned by the drag and drop events when we are
931
         * in INTERSECT mode.  It returns either the first object that the
932
         * cursor is over, or the object that has the greatest overlap with
933
         * the dragged element.
934
         * @method getBestMatch
935
         * @param  {DragDrop[]} dds The array of drag and drop objects
936
         * targeted
937
         * @return {DragDrop}       The best single match
938
         * @static
939
         */
940
        getBestMatch: function(dds) {
941
            var winner = null;
942
943
            var len = dds.length;
944
945
            if (len == 1) {
946
                winner = dds[0];
947
            } else {
948
                // Loop through the targeted items
949
                for (var i=0; i<len; ++i) {
950
                    var dd = dds[i];
951
                    // If the cursor is over the object, it wins.  If the
952
                    // cursor is over multiple matches, the first one we come
953
                    // to wins.
954
                    if (this.mode == this.INTERSECT && dd.cursorIsOver) {
955
                        winner = dd;
956
                        break;
957
                    // Otherwise the object with the most overlap wins
958
                    } else {
959
                        if (!winner || !winner.overlap || (dd.overlap &&
960
                            winner.overlap.getArea() < dd.overlap.getArea())) {
961
                            winner = dd;
962
                        }
963
                    }
964
                }
965
            }
966
967
            return winner;
968
        },
969
970
        /**
971
         * Refreshes the cache of the top-left and bottom-right points of the
972
         * drag and drop objects in the specified group(s).  This is in the
973
         * format that is stored in the drag and drop instance, so typical
974
         * usage is:
975
         * <code>
976
         * YAHOO.util.DragDropMgr.refreshCache(ddinstance.groups);
977
         * </code>
978
         * Alternatively:
979
         * <code>
980
         * YAHOO.util.DragDropMgr.refreshCache({group1:true, group2:true});
981
         * </code>
982
         * @TODO this really should be an indexed array.  Alternatively this
983
         * method could accept both.
984
         * @method refreshCache
985
         * @param {Object} groups an associative array of groups to refresh
986
         * @static
987
         */
988
        refreshCache: function(groups) {
989
            YAHOO.log("refreshing element location cache", "info", "DragDropMgr");
990
991
            // refresh everything if group array is not provided
992
            var g = groups || this.ids;
993
994
            for (var sGroup in g) {
995
                if ("string" != typeof sGroup) {
996
                    continue;
997
                }
998
                for (var i in this.ids[sGroup]) {
999
                    var oDD = this.ids[sGroup][i];
1000
1001
                    if (this.isTypeOfDD(oDD)) {
1002
                        var loc = this.getLocation(oDD);
1003
                        if (loc) {
1004
                            this.locationCache[oDD.id] = loc;
1005
                        } else {
1006
                            delete this.locationCache[oDD.id];
1007
YAHOO.log("Could not get the loc for " + oDD.id, "warn", "DragDropMgr");
1008
                        }
1009
                    }
1010
                }
1011
            }
1012
        },
1013
1014
        /**
1015
         * This checks to make sure an element exists and is in the DOM.  The
1016
         * main purpose is to handle cases where innerHTML is used to remove
1017
         * drag and drop objects from the DOM.  IE provides an 'unspecified
1018
         * error' when trying to access the offsetParent of such an element
1019
         * @method verifyEl
1020
         * @param {HTMLElement} el the element to check
1021
         * @return {boolean} true if the element looks usable
1022
         * @static
1023
         */
1024
        verifyEl: function(el) {
1025
            try {
1026
                if (el) {
1027
                    var parent = el.offsetParent;
1028
                    if (parent) {
1029
                        return true;
1030
                    }
1031
                }
1032
            } catch(e) {
1033
                YAHOO.log("detected problem with an element", "info", "DragDropMgr");
1034
            }
1035
1036
            return false;
1037
        },
1038
1039
        /**
1040
         * Returns a Region object containing the drag and drop element's position
1041
         * and size, including the padding configured for it
1042
         * @method getLocation
1043
         * @param {DragDrop} oDD the drag and drop object to get the
1044
         *                       location for
1045
         * @return {YAHOO.util.Region} a Region object representing the total area
1046
         *                             the element occupies, including any padding
1047
         *                             the instance is configured for.
1048
         * @static
1049
         */
1050
        getLocation: function(oDD) {
1051
            if (! this.isTypeOfDD(oDD)) {
1052
                YAHOO.log(oDD + " is not a DD obj", "info", "DragDropMgr");
1053
                return null;
1054
            }
1055
1056
            var el = oDD.getEl(), pos, x1, x2, y1, y2, t, r, b, l;
1057
1058
            try {
1059
                pos= YAHOO.util.Dom.getXY(el);
1060
            } catch (e) { }
1061
1062
            if (!pos) {
1063
                YAHOO.log("getXY failed", "info", "DragDropMgr");
1064
                return null;
1065
            }
1066
1067
            x1 = pos[0];
1068
            x2 = x1 + el.offsetWidth;
1069
            y1 = pos[1];
1070
            y2 = y1 + el.offsetHeight;
1071
1072
            t = y1 - oDD.padding[0];
1073
            r = x2 + oDD.padding[1];
1074
            b = y2 + oDD.padding[2];
1075
            l = x1 - oDD.padding[3];
1076
1077
            return new YAHOO.util.Region( t, r, b, l );
1078
        },
1079
1080
        /**
1081
         * Checks the cursor location to see if it over the target
1082
         * @method isOverTarget
1083
         * @param {YAHOO.util.Point} pt The point to evaluate
1084
         * @param {DragDrop} oTarget the DragDrop object we are inspecting
1085
         * @param {boolean} intersect true if we are in intersect mode
1086
         * @param {YAHOO.util.Region} pre-cached location of the dragged element
1087
         * @return {boolean} true if the mouse is over the target
1088
         * @private
1089
         * @static
1090
         */
1091
        isOverTarget: function(pt, oTarget, intersect, curRegion) {
1092
            // use cache if available
1093
            var loc = this.locationCache[oTarget.id];
1094
            if (!loc || !this.useCache) {
1095
                YAHOO.log("cache not populated", "info", "DragDropMgr");
1096
                loc = this.getLocation(oTarget);
1097
                this.locationCache[oTarget.id] = loc;
1098
1099
                YAHOO.log("cache: " + loc, "info", "DragDropMgr");
1100
            }
1101
1102
            if (!loc) {
1103
                YAHOO.log("could not get the location of the element", "info", "DragDropMgr");
1104
                return false;
1105
            }
1106
1107
            //YAHOO.log("loc: " + loc + ", pt: " + pt);
1108
            oTarget.cursorIsOver = loc.contains( pt );
1109
1110
            // DragDrop is using this as a sanity check for the initial mousedown
1111
            // in this case we are done.  In POINT mode, if the drag obj has no
1112
            // contraints, we are done. Otherwise we need to evaluate the
1113
            // region the target as occupies to determine if the dragged element
1114
            // overlaps with it.
1115
1116
            var dc = this.dragCurrent;
1117
            if (!dc || (!intersect && !dc.constrainX && !dc.constrainY)) {
1118
1119
                //if (oTarget.cursorIsOver) {
1120
                    //YAHOO.log("over " + oTarget + ", " + loc + ", " + pt, "warn");
1121
                //}
1122
                return oTarget.cursorIsOver;
1123
            }
1124
1125
            oTarget.overlap = null;
1126
1127
            // Get the current location of the drag element, this is the
1128
            // location of the mouse event less the delta that represents
1129
            // where the original mousedown happened on the element.  We
1130
            // need to consider constraints and ticks as well.
1131
1132
            if (!curRegion) {
1133
                var pos = dc.getTargetCoord(pt.x, pt.y);
1134
                var el = dc.getDragEl();
1135
                curRegion = new YAHOO.util.Region( pos.y,
1136
                                                   pos.x + el.offsetWidth,
1137
                                                   pos.y + el.offsetHeight,
1138
                                                   pos.x );
1139
            }
1140
1141
            var overlap = curRegion.intersect(loc);
1142
1143
            if (overlap) {
1144
                oTarget.overlap = overlap;
1145
                return (intersect) ? true : oTarget.cursorIsOver;
1146
            } else {
1147
                return false;
1148
            }
1149
        },
1150
1151
        /**
1152
         * unload event handler
1153
         * @method _onUnload
1154
         * @private
1155
         * @static
1156
         */
1157
        _onUnload: function(e, me) {
1158
            this.unregAll();
1159
        },
1160
1161
        /**
1162
         * Cleans up the drag and drop events and objects.
1163
         * @method unregAll
1164
         * @private
1165
         * @static
1166
         */
1167
        unregAll: function() {
1168
            YAHOO.log("unregister all", "info", "DragDropMgr");
1169
1170
            if (this.dragCurrent) {
1171
                this.stopDrag();
1172
                this.dragCurrent = null;
1173
            }
1174
1175
            this._execOnAll("unreg", []);
1176
1177
            //for (var i in this.elementCache) {
1178
                //delete this.elementCache[i];
1179
            //}
1180
            //this.elementCache = {};
1181
1182
            this.ids = {};
1183
        },
1184
1185
        /**
1186
         * A cache of DOM elements
1187
         * @property elementCache
1188
         * @private
1189
         * @static
1190
         * @deprecated elements are not cached now
1191
         */
1192
        elementCache: {},
1193
1194
        /**
1195
         * Get the wrapper for the DOM element specified
1196
         * @method getElWrapper
1197
         * @param {String} id the id of the element to get
1198
         * @return {YAHOO.util.DDM.ElementWrapper} the wrapped element
1199
         * @private
1200
         * @deprecated This wrapper isn't that useful
1201
         * @static
1202
         */
1203
        getElWrapper: function(id) {
1204
            var oWrapper = this.elementCache[id];
1205
            if (!oWrapper || !oWrapper.el) {
1206
                oWrapper = this.elementCache[id] =
1207
                    new this.ElementWrapper(YAHOO.util.Dom.get(id));
1208
            }
1209
            return oWrapper;
1210
        },
1211
1212
        /**
1213
         * Returns the actual DOM element
1214
         * @method getElement
1215
         * @param {String} id the id of the elment to get
1216
         * @return {Object} The element
1217
         * @deprecated use YAHOO.util.Dom.get instead
1218
         * @static
1219
         */
1220
        getElement: function(id) {
1221
            return YAHOO.util.Dom.get(id);
1222
        },
1223
1224
        /**
1225
         * Returns the style property for the DOM element (i.e.,
1226
         * document.getElById(id).style)
1227
         * @method getCss
1228
         * @param {String} id the id of the elment to get
1229
         * @return {Object} The style property of the element
1230
         * @deprecated use YAHOO.util.Dom instead
1231
         * @static
1232
         */
1233
        getCss: function(id) {
1234
            var el = YAHOO.util.Dom.get(id);
1235
            return (el) ? el.style : null;
1236
        },
1237
1238
        /**
1239
         * Inner class for cached elements
1240
         * @class DragDropMgr.ElementWrapper
1241
         * @for DragDropMgr
1242
         * @private
1243
         * @deprecated
1244
         */
1245
        ElementWrapper: function(el) {
1246
                /**
1247
                 * The element
1248
                 * @property el
1249
                 */
1250
                this.el = el || null;
1251
                /**
1252
                 * The element id
1253
                 * @property id
1254
                 */
1255
                this.id = this.el && el.id;
1256
                /**
1257
                 * A reference to the style property
1258
                 * @property css
1259
                 */
1260
                this.css = this.el && el.style;
1261
            },
1262
1263
        /**
1264
         * Returns the X position of an html element
1265
         * @method getPosX
1266
         * @param el the element for which to get the position
1267
         * @return {int} the X coordinate
1268
         * @for DragDropMgr
1269
         * @deprecated use YAHOO.util.Dom.getX instead
1270
         * @static
1271
         */
1272
        getPosX: function(el) {
1273
            return YAHOO.util.Dom.getX(el);
1274
        },
1275
1276
        /**
1277
         * Returns the Y position of an html element
1278
         * @method getPosY
1279
         * @param el the element for which to get the position
1280
         * @return {int} the Y coordinate
1281
         * @deprecated use YAHOO.util.Dom.getY instead
1282
         * @static
1283
         */
1284
        getPosY: function(el) {
1285
            return YAHOO.util.Dom.getY(el);
1286
        },
1287
1288
        /**
1289
         * Swap two nodes.  In IE, we use the native method, for others we
1290
         * emulate the IE behavior
1291
         * @method swapNode
1292
         * @param n1 the first node to swap
1293
         * @param n2 the other node to swap
1294
         * @static
1295
         */
1296
        swapNode: function(n1, n2) {
1297
            if (n1.swapNode) {
1298
                n1.swapNode(n2);
1299
            } else {
1300
                var p = n2.parentNode;
1301
                var s = n2.nextSibling;
1302
1303
                if (s == n1) {
1304
                    p.insertBefore(n1, n2);
1305
                } else if (n2 == n1.nextSibling) {
1306
                    p.insertBefore(n2, n1);
1307
                } else {
1308
                    n1.parentNode.replaceChild(n2, n1);
1309
                    p.insertBefore(n1, s);
1310
                }
1311
            }
1312
        },
1313
1314
        /**
1315
         * Returns the current scroll position
1316
         * @method getScroll
1317
         * @private
1318
         * @static
1319
         */
1320
        getScroll: function () {
1321
            var t, l, dde=document.documentElement, db=document.body;
1322
            if (dde && (dde.scrollTop || dde.scrollLeft)) {
1323
                t = dde.scrollTop;
1324
                l = dde.scrollLeft;
1325
            } else if (db) {
1326
                t = db.scrollTop;
1327
                l = db.scrollLeft;
1328
            } else {
1329
                YAHOO.log("could not get scroll property", "info", "DragDropMgr");
1330
            }
1331
            return { top: t, left: l };
1332
        },
1333
1334
        /**
1335
         * Returns the specified element style property
1336
         * @method getStyle
1337
         * @param {HTMLElement} el          the element
1338
         * @param {string}      styleProp   the style property
1339
         * @return {string} The value of the style property
1340
         * @deprecated use YAHOO.util.Dom.getStyle
1341
         * @static
1342
         */
1343
        getStyle: function(el, styleProp) {
1344
            return YAHOO.util.Dom.getStyle(el, styleProp);
1345
        },
1346
1347
        /**
1348
         * Gets the scrollTop
1349
         * @method getScrollTop
1350
         * @return {int} the document's scrollTop
1351
         * @static
1352
         */
1353
        getScrollTop: function () { return this.getScroll().top; },
1354
1355
        /**
1356
         * Gets the scrollLeft
1357
         * @method getScrollLeft
1358
         * @return {int} the document's scrollTop
1359
         * @static
1360
         */
1361
        getScrollLeft: function () { return this.getScroll().left; },
1362
1363
        /**
1364
         * Sets the x/y position of an element to the location of the
1365
         * target element.
1366
         * @method moveToEl
1367
         * @param {HTMLElement} moveEl      The element to move
1368
         * @param {HTMLElement} targetEl    The position reference element
1369
         * @static
1370
         */
1371
        moveToEl: function (moveEl, targetEl) {
1372
            var aCoord = YAHOO.util.Dom.getXY(targetEl);
1373
            YAHOO.log("moveToEl: " + aCoord, "info", "DragDropMgr");
1374
            YAHOO.util.Dom.setXY(moveEl, aCoord);
1375
        },
1376
1377
        /**
1378
         * Gets the client height
1379
         * @method getClientHeight
1380
         * @return {int} client height in px
1381
         * @deprecated use YAHOO.util.Dom.getViewportHeight instead
1382
         * @static
1383
         */
1384
        getClientHeight: function() {
1385
            return YAHOO.util.Dom.getViewportHeight();
1386
        },
1387
1388
        /**
1389
         * Gets the client width
1390
         * @method getClientWidth
1391
         * @return {int} client width in px
1392
         * @deprecated use YAHOO.util.Dom.getViewportWidth instead
1393
         * @static
1394
         */
1395
        getClientWidth: function() {
1396
            return YAHOO.util.Dom.getViewportWidth();
1397
        },
1398
1399
        /**
1400
         * Numeric array sort function
1401
         * @method numericSort
1402
         * @static
1403
         */
1404
        numericSort: function(a, b) { return (a - b); },
1405
1406
        /**
1407
         * Internal counter
1408
         * @property _timeoutCount
1409
         * @private
1410
         * @static
1411
         */
1412
        _timeoutCount: 0,
1413
1414
        /**
1415
         * Trying to make the load order less important.  Without this we get
1416
         * an error if this file is loaded before the Event Utility.
1417
         * @method _addListeners
1418
         * @private
1419
         * @static
1420
         */
1421
        _addListeners: function() {
1422
            var DDM = YAHOO.util.DDM;
1423
            if ( YAHOO.util.Event && document ) {
1424
                DDM._onLoad();
1425
            } else {
1426
                if (DDM._timeoutCount > 2000) {
1427
                    YAHOO.log("DragDrop requires the Event Utility", "error", "DragDropMgr");
1428
                } else {
1429
                    setTimeout(DDM._addListeners, 10);
1430
                    if (document && document.body) {
1431
                        DDM._timeoutCount += 1;
1432
                    }
1433
                }
1434
            }
1435
        },
1436
1437
        /**
1438
         * Recursively searches the immediate parent and all child nodes for
1439
         * the handle element in order to determine wheter or not it was
1440
         * clicked.
1441
         * @method handleWasClicked
1442
         * @param node the html element to inspect
1443
         * @static
1444
         */
1445
        handleWasClicked: function(node, id) {
1446
            if (this.isHandle(id, node.id)) {
1447
                YAHOO.log("clicked node is a handle", "info", "DragDropMgr");
1448
                return true;
1449
            } else {
1450
                // check to see if this is a text node child of the one we want
1451
                var p = node.parentNode;
1452
                // YAHOO.log("p: " + p);
1453
1454
                while (p) {
1455
                    if (this.isHandle(id, p.id)) {
1456
                        return true;
1457
                    } else {
1458
                        YAHOO.log(p.id + " is not a handle", "info", "DragDropMgr");
1459
                        p = p.parentNode;
1460
                    }
1461
                }
1462
            }
1463
1464
            return false;
1465
        }
1466
1467
    };
1468
1469
}();
1470
1471
// shorter alias, save a few bytes
1472
YAHOO.util.DDM = YAHOO.util.DragDropMgr;
1473
YAHOO.util.DDM._addListeners();
1474
1475
}
1476
1477
(function() {
1478
1479
var Event=YAHOO.util.Event;
1480
var Dom=YAHOO.util.Dom;
1481
1482
/**
1483
 * Defines the interface and base operation of items that that can be
1484
 * dragged or can be drop targets.  It was designed to be extended, overriding
1485
 * the event handlers for startDrag, onDrag, onDragOver, onDragOut.
1486
 * Up to three html elements can be associated with a DragDrop instance:
1487
 * <ul>
1488
 * <li>linked element: the element that is passed into the constructor.
1489
 * This is the element which defines the boundaries for interaction with
1490
 * other DragDrop objects.</li>
1491
 * <li>handle element(s): The drag operation only occurs if the element that
1492
 * was clicked matches a handle element.  By default this is the linked
1493
 * element, but there are times that you will want only a portion of the
1494
 * linked element to initiate the drag operation, and the setHandleElId()
1495
 * method provides a way to define this.</li>
1496
 * <li>drag element: this represents an the element that would be moved along
1497
 * with the cursor during a drag operation.  By default, this is the linked
1498
 * element itself as in {@link YAHOO.util.DD}.  setDragElId() lets you define
1499
 * a separate element that would be moved, as in {@link YAHOO.util.DDProxy}
1500
 * </li>
1501
 * </ul>
1502
 * This class should not be instantiated until the onload event to ensure that
1503
 * the associated elements are available.
1504
 * The following would define a DragDrop obj that would interact with any
1505
 * other DragDrop obj in the "group1" group:
1506
 * <pre>
1507
 *  dd = new YAHOO.util.DragDrop("div1", "group1");
1508
 * </pre>
1509
 * Since none of the event handlers have been implemented, nothing would
1510
 * actually happen if you were to run the code above.  Normally you would
1511
 * override this class or one of the default implementations, but you can
1512
 * also override the methods you want on an instance of the class...
1513
 * <pre>
1514
 *  dd.onDragDrop = function(e, id) {
1515
 *  &nbsp;&nbsp;alert("dd was dropped on " + id);
1516
 *  }
1517
 * </pre>
1518
 * @namespace YAHOO.util
1519
 * @class DragDrop
1520
 * @constructor
1521
 * @param {String} id of the element that is linked to this instance
1522
 * @param {String} sGroup the group of related DragDrop objects
1523
 * @param {object} config an object containing configurable attributes
1524
 *                Valid properties for DragDrop:
1525
 *                    padding, isTarget, maintainOffset, primaryButtonOnly,
1526
 */
1527
YAHOO.util.DragDrop = function(id, sGroup, config) {
1528
    if (id) {
1529
        this.init(id, sGroup, config);
1530
    }
1531
};
1532
1533
YAHOO.util.DragDrop.prototype = {
1534
1535
    /**
1536
     * The id of the element associated with this object.  This is what we
1537
     * refer to as the "linked element" because the size and position of
1538
     * this element is used to determine when the drag and drop objects have
1539
     * interacted.
1540
     * @property id
1541
     * @type String
1542
     */
1543
    id: null,
1544
1545
    /**
1546
     * Configuration attributes passed into the constructor
1547
     * @property config
1548
     * @type object
1549
     */
1550
    config: null,
1551
1552
    /**
1553
     * The id of the element that will be dragged.  By default this is same
1554
     * as the linked element , but could be changed to another element. Ex:
1555
     * YAHOO.util.DDProxy
1556
     * @property dragElId
1557
     * @type String
1558
     * @private
1559
     */
1560
    dragElId: null,
1561
1562
    /**
1563
     * the id of the element that initiates the drag operation.  By default
1564
     * this is the linked element, but could be changed to be a child of this
1565
     * element.  This lets us do things like only starting the drag when the
1566
     * header element within the linked html element is clicked.
1567
     * @property handleElId
1568
     * @type String
1569
     * @private
1570
     */
1571
    handleElId: null,
1572
1573
    /**
1574
     * An associative array of HTML tags that will be ignored if clicked.
1575
     * @property invalidHandleTypes
1576
     * @type {string: string}
1577
     */
1578
    invalidHandleTypes: null,
1579
1580
    /**
1581
     * An associative array of ids for elements that will be ignored if clicked
1582
     * @property invalidHandleIds
1583
     * @type {string: string}
1584
     */
1585
    invalidHandleIds: null,
1586
1587
    /**
1588
     * An indexted array of css class names for elements that will be ignored
1589
     * if clicked.
1590
     * @property invalidHandleClasses
1591
     * @type string[]
1592
     */
1593
    invalidHandleClasses: null,
1594
1595
    /**
1596
     * The linked element's absolute X position at the time the drag was
1597
     * started
1598
     * @property startPageX
1599
     * @type int
1600
     * @private
1601
     */
1602
    startPageX: 0,
1603
1604
    /**
1605
     * The linked element's absolute X position at the time the drag was
1606
     * started
1607
     * @property startPageY
1608
     * @type int
1609
     * @private
1610
     */
1611
    startPageY: 0,
1612
1613
    /**
1614
     * The group defines a logical collection of DragDrop objects that are
1615
     * related.  Instances only get events when interacting with other
1616
     * DragDrop object in the same group.  This lets us define multiple
1617
     * groups using a single DragDrop subclass if we want.
1618
     * @property groups
1619
     * @type {string: string}
1620
     */
1621
    groups: null,
1622
1623
    /**
1624
     * Individual drag/drop instances can be locked.  This will prevent
1625
     * onmousedown start drag.
1626
     * @property locked
1627
     * @type boolean
1628
     * @private
1629
     */
1630
    locked: false,
1631
1632
    /**
1633
     * Lock this instance
1634
     * @method lock
1635
     */
1636
    lock: function() { this.locked = true; },
1637
1638
    /**
1639
     * Unlock this instace
1640
     * @method unlock
1641
     */
1642
    unlock: function() { this.locked = false; },
1643
1644
    /**
1645
     * By default, all instances can be a drop target.  This can be disabled by
1646
     * setting isTarget to false.
1647
     * @property isTarget
1648
     * @type boolean
1649
     */
1650
    isTarget: true,
1651
1652
    /**
1653
     * The padding configured for this drag and drop object for calculating
1654
     * the drop zone intersection with this object.
1655
     * @property padding
1656
     * @type int[]
1657
     */
1658
    padding: null,
1659
    /**
1660
     * If this flag is true, do not fire drop events. The element is a drag only element (for movement not dropping)
1661
     * @property dragOnly
1662
     * @type Boolean
1663
     */
1664
    dragOnly: false,
1665
1666
    /**
1667
     * Cached reference to the linked element
1668
     * @property _domRef
1669
     * @private
1670
     */
1671
    _domRef: null,
1672
1673
    /**
1674
     * Internal typeof flag
1675
     * @property __ygDragDrop
1676
     * @private
1677
     */
1678
    __ygDragDrop: true,
1679
1680
    /**
1681
     * Set to true when horizontal contraints are applied
1682
     * @property constrainX
1683
     * @type boolean
1684
     * @private
1685
     */
1686
    constrainX: false,
1687
1688
    /**
1689
     * Set to true when vertical contraints are applied
1690
     * @property constrainY
1691
     * @type boolean
1692
     * @private
1693
     */
1694
    constrainY: false,
1695
1696
    /**
1697
     * The left constraint
1698
     * @property minX
1699
     * @type int
1700
     * @private
1701
     */
1702
    minX: 0,
1703
1704
    /**
1705
     * The right constraint
1706
     * @property maxX
1707
     * @type int
1708
     * @private
1709
     */
1710
    maxX: 0,
1711
1712
    /**
1713
     * The up constraint
1714
     * @property minY
1715
     * @type int
1716
     * @type int
1717
     * @private
1718
     */
1719
    minY: 0,
1720
1721
    /**
1722
     * The down constraint
1723
     * @property maxY
1724
     * @type int
1725
     * @private
1726
     */
1727
    maxY: 0,
1728
1729
    /**
1730
     * The difference between the click position and the source element's location
1731
     * @property deltaX
1732
     * @type int
1733
     * @private
1734
     */
1735
    deltaX: 0,
1736
1737
    /**
1738
     * The difference between the click position and the source element's location
1739
     * @property deltaY
1740
     * @type int
1741
     * @private
1742
     */
1743
    deltaY: 0,
1744
1745
    /**
1746
     * Maintain offsets when we resetconstraints.  Set to true when you want
1747
     * the position of the element relative to its parent to stay the same
1748
     * when the page changes
1749
     *
1750
     * @property maintainOffset
1751
     * @type boolean
1752
     */
1753
    maintainOffset: false,
1754
1755
    /**
1756
     * Array of pixel locations the element will snap to if we specified a
1757
     * horizontal graduation/interval.  This array is generated automatically
1758
     * when you define a tick interval.
1759
     * @property xTicks
1760
     * @type int[]
1761
     */
1762
    xTicks: null,
1763
1764
    /**
1765
     * Array of pixel locations the element will snap to if we specified a
1766
     * vertical graduation/interval.  This array is generated automatically
1767
     * when you define a tick interval.
1768
     * @property yTicks
1769
     * @type int[]
1770
     */
1771
    yTicks: null,
1772
1773
    /**
1774
     * By default the drag and drop instance will only respond to the primary
1775
     * button click (left button for a right-handed mouse).  Set to true to
1776
     * allow drag and drop to start with any mouse click that is propogated
1777
     * by the browser
1778
     * @property primaryButtonOnly
1779
     * @type boolean
1780
     */
1781
    primaryButtonOnly: true,
1782
1783
    /**
1784
     * The availabe property is false until the linked dom element is accessible.
1785
     * @property available
1786
     * @type boolean
1787
     */
1788
    available: false,
1789
1790
    /**
1791
     * By default, drags can only be initiated if the mousedown occurs in the
1792
     * region the linked element is.  This is done in part to work around a
1793
     * bug in some browsers that mis-report the mousedown if the previous
1794
     * mouseup happened outside of the window.  This property is set to true
1795
     * if outer handles are defined.
1796
     *
1797
     * @property hasOuterHandles
1798
     * @type boolean
1799
     * @default false
1800
     */
1801
    hasOuterHandles: false,
1802
1803
    /**
1804
     * Property that is assigned to a drag and drop object when testing to
1805
     * see if it is being targeted by another dd object.  This property
1806
     * can be used in intersect mode to help determine the focus of
1807
     * the mouse interaction.  DDM.getBestMatch uses this property first to
1808
     * determine the closest match in INTERSECT mode when multiple targets
1809
     * are part of the same interaction.
1810
     * @property cursorIsOver
1811
     * @type boolean
1812
     */
1813
    cursorIsOver: false,
1814
1815
    /**
1816
     * Property that is assigned to a drag and drop object when testing to
1817
     * see if it is being targeted by another dd object.  This is a region
1818
     * that represents the area the draggable element overlaps this target.
1819
     * DDM.getBestMatch uses this property to compare the size of the overlap
1820
     * to that of other targets in order to determine the closest match in
1821
     * INTERSECT mode when multiple targets are part of the same interaction.
1822
     * @property overlap
1823
     * @type YAHOO.util.Region
1824
     */
1825
    overlap: null,
1826
1827
    /**
1828
     * Code that executes immediately before the startDrag event
1829
     * @method b4StartDrag
1830
     * @private
1831
     */
1832
    b4StartDrag: function(x, y) { },
1833
1834
    /**
1835
     * Abstract method called after a drag/drop object is clicked
1836
     * and the drag or mousedown time thresholds have beeen met.
1837
     * @method startDrag
1838
     * @param {int} X click location
1839
     * @param {int} Y click location
1840
     */
1841
    startDrag: function(x, y) { /* override this */ },
1842
1843
    /**
1844
     * Code that executes immediately before the onDrag event
1845
     * @method b4Drag
1846
     * @private
1847
     */
1848
    b4Drag: function(e) { },
1849
1850
    /**
1851
     * Abstract method called during the onMouseMove event while dragging an
1852
     * object.
1853
     * @method onDrag
1854
     * @param {Event} e the mousemove event
1855
     */
1856
    onDrag: function(e) { /* override this */ },
1857
1858
    /**
1859
     * Abstract method called when this element fist begins hovering over
1860
     * another DragDrop obj
1861
     * @method onDragEnter
1862
     * @param {Event} e the mousemove event
1863
     * @param {String|DragDrop[]} id In POINT mode, the element
1864
     * id this is hovering over.  In INTERSECT mode, an array of one or more
1865
     * dragdrop items being hovered over.
1866
     */
1867
    onDragEnter: function(e, id) { /* override this */ },
1868
1869
    /**
1870
     * Code that executes immediately before the onDragOver event
1871
     * @method b4DragOver
1872
     * @private
1873
     */
1874
    b4DragOver: function(e) { },
1875
1876
    /**
1877
     * Abstract method called when this element is hovering over another
1878
     * DragDrop obj
1879
     * @method onDragOver
1880
     * @param {Event} e the mousemove event
1881
     * @param {String|DragDrop[]} id In POINT mode, the element
1882
     * id this is hovering over.  In INTERSECT mode, an array of dd items
1883
     * being hovered over.
1884
     */
1885
    onDragOver: function(e, id) { /* override this */ },
1886
1887
    /**
1888
     * Code that executes immediately before the onDragOut event
1889
     * @method b4DragOut
1890
     * @private
1891
     */
1892
    b4DragOut: function(e) { },
1893
1894
    /**
1895
     * Abstract method called when we are no longer hovering over an element
1896
     * @method onDragOut
1897
     * @param {Event} e the mousemove event
1898
     * @param {String|DragDrop[]} id In POINT mode, the element
1899
     * id this was hovering over.  In INTERSECT mode, an array of dd items
1900
     * that the mouse is no longer over.
1901
     */
1902
    onDragOut: function(e, id) { /* override this */ },
1903
1904
    /**
1905
     * Code that executes immediately before the onDragDrop event
1906
     * @method b4DragDrop
1907
     * @private
1908
     */
1909
    b4DragDrop: function(e) { },
1910
1911
    /**
1912
     * Abstract method called when this item is dropped on another DragDrop
1913
     * obj
1914
     * @method onDragDrop
1915
     * @param {Event} e the mouseup event
1916
     * @param {String|DragDrop[]} id In POINT mode, the element
1917
     * id this was dropped on.  In INTERSECT mode, an array of dd items this
1918
     * was dropped on.
1919
     */
1920
    onDragDrop: function(e, id) { /* override this */ },
1921
1922
    /**
1923
     * Abstract method called when this item is dropped on an area with no
1924
     * drop target
1925
     * @method onInvalidDrop
1926
     * @param {Event} e the mouseup event
1927
     */
1928
    onInvalidDrop: function(e) { /* override this */ },
1929
1930
    /**
1931
     * Code that executes immediately before the endDrag event
1932
     * @method b4EndDrag
1933
     * @private
1934
     */
1935
    b4EndDrag: function(e) { },
1936
1937
    /**
1938
     * Fired when we are done dragging the object
1939
     * @method endDrag
1940
     * @param {Event} e the mouseup event
1941
     */
1942
    endDrag: function(e) { /* override this */ },
1943
1944
    /**
1945
     * Code executed immediately before the onMouseDown event
1946
     * @method b4MouseDown
1947
     * @param {Event} e the mousedown event
1948
     * @private
1949
     */
1950
    b4MouseDown: function(e) {  },
1951
1952
    /**
1953
     * Event handler that fires when a drag/drop obj gets a mousedown
1954
     * @method onMouseDown
1955
     * @param {Event} e the mousedown event
1956
     */
1957
    onMouseDown: function(e) { /* override this */ },
1958
1959
    /**
1960
     * Event handler that fires when a drag/drop obj gets a mouseup
1961
     * @method onMouseUp
1962
     * @param {Event} e the mouseup event
1963
     */
1964
    onMouseUp: function(e) { /* override this */ },
1965
1966
    /**
1967
     * Override the onAvailable method to do what is needed after the initial
1968
     * position was determined.
1969
     * @method onAvailable
1970
     */
1971
    onAvailable: function () {
1972
        //this.logger.log("onAvailable (base)");
1973
    },
1974
1975
    /**
1976
     * Returns a reference to the linked element
1977
     * @method getEl
1978
     * @return {HTMLElement} the html element
1979
     */
1980
    getEl: function() {
1981
        if (!this._domRef) {
1982
            this._domRef = Dom.get(this.id);
1983
        }
1984
1985
        return this._domRef;
1986
    },
1987
1988
    /**
1989
     * Returns a reference to the actual element to drag.  By default this is
1990
     * the same as the html element, but it can be assigned to another
1991
     * element. An example of this can be found in YAHOO.util.DDProxy
1992
     * @method getDragEl
1993
     * @return {HTMLElement} the html element
1994
     */
1995
    getDragEl: function() {
1996
        return Dom.get(this.dragElId);
1997
    },
1998
1999
    /**
2000
     * Sets up the DragDrop object.  Must be called in the constructor of any
2001
     * YAHOO.util.DragDrop subclass
2002
     * @method init
2003
     * @param id the id of the linked element
2004
     * @param {String} sGroup the group of related items
2005
     * @param {object} config configuration attributes
2006
     */
2007
    init: function(id, sGroup, config) {
2008
        this.initTarget(id, sGroup, config);
2009
        Event.on(this._domRef || this.id, "mousedown",
2010
                        this.handleMouseDown, this, true);
2011
        // Event.on(this.id, "selectstart", Event.preventDefault);
2012
    },
2013
2014
    /**
2015
     * Initializes Targeting functionality only... the object does not
2016
     * get a mousedown handler.
2017
     * @method initTarget
2018
     * @param id the id of the linked element
2019
     * @param {String} sGroup the group of related items
2020
     * @param {object} config configuration attributes
2021
     */
2022
    initTarget: function(id, sGroup, config) {
2023
2024
        // configuration attributes
2025
        this.config = config || {};
2026
2027
        // create a local reference to the drag and drop manager
2028
        this.DDM = YAHOO.util.DDM;
2029
2030
        // initialize the groups object
2031
        this.groups = {};
2032
2033
        // assume that we have an element reference instead of an id if the
2034
        // parameter is not a string
2035
        if (typeof id !== "string") {
2036
            YAHOO.log("id is not a string, assuming it is an HTMLElement");
2037
            this._domRef = id;
2038
            id = Dom.generateId(id);
2039
        }
2040
2041
        // set the id
2042
        this.id = id;
2043
2044
        // add to an interaction group
2045
        this.addToGroup((sGroup) ? sGroup : "default");
2046
2047
        // We don't want to register this as the handle with the manager
2048
        // so we just set the id rather than calling the setter.
2049
        this.handleElId = id;
2050
2051
        Event.onAvailable(id, this.handleOnAvailable, this, true);
2052
2053
        // create a logger instance
2054
        this.logger = (YAHOO.widget.LogWriter) ?
2055
                new YAHOO.widget.LogWriter(this.toString()) : YAHOO;
2056
2057
        // the linked element is the element that gets dragged by default
2058
        this.setDragElId(id);
2059
2060
        // by default, clicked anchors will not start drag operations.
2061
        // @TODO what else should be here?  Probably form fields.
2062
        this.invalidHandleTypes = { A: "A" };
2063
        this.invalidHandleIds = {};
2064
        this.invalidHandleClasses = [];
2065
2066
        this.applyConfig();
2067
    },
2068
2069
    /**
2070
     * Applies the configuration parameters that were passed into the constructor.
2071
     * This is supposed to happen at each level through the inheritance chain.  So
2072
     * a DDProxy implentation will execute apply config on DDProxy, DD, and
2073
     * DragDrop in order to get all of the parameters that are available in
2074
     * each object.
2075
     * @method applyConfig
2076
     */
2077
    applyConfig: function() {
2078
2079
        // configurable properties:
2080
        //    padding, isTarget, maintainOffset, primaryButtonOnly
2081
        this.padding           = this.config.padding || [0, 0, 0, 0];
2082
        this.isTarget          = (this.config.isTarget !== false);
2083
        this.maintainOffset    = (this.config.maintainOffset);
2084
        this.primaryButtonOnly = (this.config.primaryButtonOnly !== false);
2085
        this.dragOnly = ((this.config.dragOnly === true) ? true : false);
2086
    },
2087
2088
    /**
2089
     * Executed when the linked element is available
2090
     * @method handleOnAvailable
2091
     * @private
2092
     */
2093
    handleOnAvailable: function() {
2094
        //this.logger.log("handleOnAvailable");
2095
        this.available = true;
2096
        this.resetConstraints();
2097
        this.onAvailable();
2098
    },
2099
2100
     /**
2101
     * Configures the padding for the target zone in px.  Effectively expands
2102
     * (or reduces) the virtual object size for targeting calculations.
2103
     * Supports css-style shorthand; if only one parameter is passed, all sides
2104
     * will have that padding, and if only two are passed, the top and bottom
2105
     * will have the first param, the left and right the second.
2106
     * @method setPadding
2107
     * @param {int} iTop    Top pad
2108
     * @param {int} iRight  Right pad
2109
     * @param {int} iBot    Bot pad
2110
     * @param {int} iLeft   Left pad
2111
     */
2112
    setPadding: function(iTop, iRight, iBot, iLeft) {
2113
        // this.padding = [iLeft, iRight, iTop, iBot];
2114
        if (!iRight && 0 !== iRight) {
2115
            this.padding = [iTop, iTop, iTop, iTop];
2116
        } else if (!iBot && 0 !== iBot) {
2117
            this.padding = [iTop, iRight, iTop, iRight];
2118
        } else {
2119
            this.padding = [iTop, iRight, iBot, iLeft];
2120
        }
2121
    },
2122
2123
    /**
2124
     * Stores the initial placement of the linked element.
2125
     * @method setInitialPosition
2126
     * @param {int} diffX   the X offset, default 0
2127
     * @param {int} diffY   the Y offset, default 0
2128
     * @private
2129
     */
2130
    setInitPosition: function(diffX, diffY) {
2131
        var el = this.getEl();
2132
2133
        if (!this.DDM.verifyEl(el)) {
2134
            this.logger.log(this.id + " element is broken");
2135
            return;
2136
        }
2137
2138
        var dx = diffX || 0;
2139
        var dy = diffY || 0;
2140
2141
        var p = Dom.getXY( el );
2142
2143
        this.initPageX = p[0] - dx;
2144
        this.initPageY = p[1] - dy;
2145
2146
        this.lastPageX = p[0];
2147
        this.lastPageY = p[1];
2148
2149
        this.logger.log(this.id + " initial position: " + this.initPageX +
2150
                ", " + this.initPageY);
2151
2152
2153
        this.setStartPosition(p);
2154
    },
2155
2156
    /**
2157
     * Sets the start position of the element.  This is set when the obj
2158
     * is initialized, the reset when a drag is started.
2159
     * @method setStartPosition
2160
     * @param pos current position (from previous lookup)
2161
     * @private
2162
     */
2163
    setStartPosition: function(pos) {
2164
        var p = pos || Dom.getXY(this.getEl());
2165
2166
        this.deltaSetXY = null;
2167
2168
        this.startPageX = p[0];
2169
        this.startPageY = p[1];
2170
    },
2171
2172
    /**
2173
     * Add this instance to a group of related drag/drop objects.  All
2174
     * instances belong to at least one group, and can belong to as many
2175
     * groups as needed.
2176
     * @method addToGroup
2177
     * @param sGroup {string} the name of the group
2178
     */
2179
    addToGroup: function(sGroup) {
2180
        this.groups[sGroup] = true;
2181
        this.DDM.regDragDrop(this, sGroup);
2182
    },
2183
2184
    /**
2185
     * Remove's this instance from the supplied interaction group
2186
     * @method removeFromGroup
2187
     * @param {string}  sGroup  The group to drop
2188
     */
2189
    removeFromGroup: function(sGroup) {
2190
        this.logger.log("Removing from group: " + sGroup);
2191
        if (this.groups[sGroup]) {
2192
            delete this.groups[sGroup];
2193
        }
2194
2195
        this.DDM.removeDDFromGroup(this, sGroup);
2196
    },
2197
2198
    /**
2199
     * Allows you to specify that an element other than the linked element
2200
     * will be moved with the cursor during a drag
2201
     * @method setDragElId
2202
     * @param id {string} the id of the element that will be used to initiate the drag
2203
     */
2204
    setDragElId: function(id) {
2205
        this.dragElId = id;
2206
    },
2207
2208
    /**
2209
     * Allows you to specify a child of the linked element that should be
2210
     * used to initiate the drag operation.  An example of this would be if
2211
     * you have a content div with text and links.  Clicking anywhere in the
2212
     * content area would normally start the drag operation.  Use this method
2213
     * to specify that an element inside of the content div is the element
2214
     * that starts the drag operation.
2215
     * @method setHandleElId
2216
     * @param id {string} the id of the element that will be used to
2217
     * initiate the drag.
2218
     */
2219
    setHandleElId: function(id) {
2220
        if (typeof id !== "string") {
2221
            YAHOO.log("id is not a string, assuming it is an HTMLElement");
2222
            id = Dom.generateId(id);
2223
        }
2224
        this.handleElId = id;
2225
        this.DDM.regHandle(this.id, id);
2226
    },
2227
2228
    /**
2229
     * Allows you to set an element outside of the linked element as a drag
2230
     * handle
2231
     * @method setOuterHandleElId
2232
     * @param id the id of the element that will be used to initiate the drag
2233
     */
2234
    setOuterHandleElId: function(id) {
2235
        if (typeof id !== "string") {
2236
            YAHOO.log("id is not a string, assuming it is an HTMLElement");
2237
            id = Dom.generateId(id);
2238
        }
2239
        this.logger.log("Adding outer handle event: " + id);
2240
        Event.on(id, "mousedown",
2241
                this.handleMouseDown, this, true);
2242
        this.setHandleElId(id);
2243
2244
        this.hasOuterHandles = true;
2245
    },
2246
2247
    /**
2248
     * Remove all drag and drop hooks for this element
2249
     * @method unreg
2250
     */
2251
    unreg: function() {
2252
        this.logger.log("DragDrop obj cleanup " + this.id);
2253
        Event.removeListener(this.id, "mousedown",
2254
                this.handleMouseDown);
2255
        this._domRef = null;
2256
        this.DDM._remove(this);
2257
    },
2258
2259
    /**
2260
     * Returns true if this instance is locked, or the drag drop mgr is locked
2261
     * (meaning that all drag/drop is disabled on the page.)
2262
     * @method isLocked
2263
     * @return {boolean} true if this obj or all drag/drop is locked, else
2264
     * false
2265
     */
2266
    isLocked: function() {
2267
        return (this.DDM.isLocked() || this.locked);
2268
    },
2269
2270
    /**
2271
     * Fired when this object is clicked
2272
     * @method handleMouseDown
2273
     * @param {Event} e
2274
     * @param {YAHOO.util.DragDrop} oDD the clicked dd object (this dd obj)
2275
     * @private
2276
     */
2277
    handleMouseDown: function(e, oDD) {
2278
2279
        var button = e.which || e.button;
2280
        this.logger.log("button: " + button);
2281
2282
        if (this.primaryButtonOnly && button > 1) {
2283
            this.logger.log("Mousedown was not produced by the primary button");
2284
            return;
2285
        }
2286
2287
        if (this.isLocked()) {
2288
            this.logger.log("Drag and drop is disabled, aborting");
2289
            return;
2290
        }
2291
2292
        this.logger.log("mousedown " + this.id);
2293
2294
        this.logger.log("firing onMouseDown events");
2295
2296
        // firing the mousedown events prior to calculating positions
2297
        var b4Return = this.b4MouseDown(e);
2298
        var mDownReturn = this.onMouseDown(e);
2299
2300
        if ((b4Return === false) || (mDownReturn === false)) {
2301
            this.logger.log('b4MouseDown or onMouseDown returned false, exiting drag');
2302
            return;
2303
        }
2304
2305
        this.DDM.refreshCache(this.groups);
2306
        // var self = this;
2307
        // setTimeout( function() { self.DDM.refreshCache(self.groups); }, 0);
2308
2309
        // Only process the event if we really clicked within the linked
2310
        // element.  The reason we make this check is that in the case that
2311
        // another element was moved between the clicked element and the
2312
        // cursor in the time between the mousedown and mouseup events. When
2313
        // this happens, the element gets the next mousedown event
2314
        // regardless of where on the screen it happened.
2315
        var pt = new YAHOO.util.Point(Event.getPageX(e), Event.getPageY(e));
2316
        if (!this.hasOuterHandles && !this.DDM.isOverTarget(pt, this) )  {
2317
                this.logger.log("Click was not over the element: " + this.id);
2318
        } else {
2319
            if (this.clickValidator(e)) {
2320
2321
                this.logger.log("click was a valid handle");
2322
2323
                // set the initial element position
2324
                this.setStartPosition();
2325
2326
                // start tracking mousemove distance and mousedown time to
2327
                // determine when to start the actual drag
2328
                this.DDM.handleMouseDown(e, this);
2329
2330
                // this mousedown is mine
2331
                this.DDM.stopEvent(e);
2332
            } else {
2333
2334
this.logger.log("clickValidator returned false, drag not initiated");
2335
2336
            }
2337
        }
2338
    },
2339
2340
    /**
2341
     * @method clickValidator
2342
     * @description Method validates that the clicked element
2343
     * was indeed the handle or a valid child of the handle
2344
     * @param {Event} e
2345
     * @private
2346
     */
2347
    clickValidator: function(e) {
2348
        var target = Event.getTarget(e);
2349
        return ( this.isValidHandleChild(target) &&
2350
                    (this.id == this.handleElId ||
2351
                        this.DDM.handleWasClicked(target, this.id)) );
2352
    },
2353
2354
    /**
2355
     * Finds the location the element should be placed if we want to move
2356
     * it to where the mouse location less the click offset would place us.
2357
     * @method getTargetCoord
2358
     * @param {int} iPageX the X coordinate of the click
2359
     * @param {int} iPageY the Y coordinate of the click
2360
     * @return an object that contains the coordinates (Object.x and Object.y)
2361
     * @private
2362
     */
2363
    getTargetCoord: function(iPageX, iPageY) {
2364
2365
        // this.logger.log("getTargetCoord: " + iPageX + ", " + iPageY);
2366
2367
        var x = iPageX - this.deltaX;
2368
        var y = iPageY - this.deltaY;
2369
2370
        if (this.constrainX) {
2371
            if (x < this.minX) { x = this.minX; }
2372
            if (x > this.maxX) { x = this.maxX; }
2373
        }
2374
2375
        if (this.constrainY) {
2376
            if (y < this.minY) { y = this.minY; }
2377
            if (y > this.maxY) { y = this.maxY; }
2378
        }
2379
2380
        x = this.getTick(x, this.xTicks);
2381
        y = this.getTick(y, this.yTicks);
2382
2383
        // this.logger.log("getTargetCoord " +
2384
                // " iPageX: " + iPageX +
2385
                // " iPageY: " + iPageY +
2386
                // " x: " + x + ", y: " + y);
2387
2388
        return {x:x, y:y};
2389
    },
2390
2391
    /**
2392
     * Allows you to specify a tag name that should not start a drag operation
2393
     * when clicked.  This is designed to facilitate embedding links within a
2394
     * drag handle that do something other than start the drag.
2395
     * @method addInvalidHandleType
2396
     * @param {string} tagName the type of element to exclude
2397
     */
2398
    addInvalidHandleType: function(tagName) {
2399
        var type = tagName.toUpperCase();
2400
        this.invalidHandleTypes[type] = type;
2401
    },
2402
2403
    /**
2404
     * Lets you to specify an element id for a child of a drag handle
2405
     * that should not initiate a drag
2406
     * @method addInvalidHandleId
2407
     * @param {string} id the element id of the element you wish to ignore
2408
     */
2409
    addInvalidHandleId: function(id) {
2410
        if (typeof id !== "string") {
2411
            YAHOO.log("id is not a string, assuming it is an HTMLElement");
2412
            id = Dom.generateId(id);
2413
        }
2414
        this.invalidHandleIds[id] = id;
2415
    },
2416
2417
2418
    /**
2419
     * Lets you specify a css class of elements that will not initiate a drag
2420
     * @method addInvalidHandleClass
2421
     * @param {string} cssClass the class of the elements you wish to ignore
2422
     */
2423
    addInvalidHandleClass: function(cssClass) {
2424
        this.invalidHandleClasses.push(cssClass);
2425
    },
2426
2427
    /**
2428
     * Unsets an excluded tag name set by addInvalidHandleType
2429
     * @method removeInvalidHandleType
2430
     * @param {string} tagName the type of element to unexclude
2431
     */
2432
    removeInvalidHandleType: function(tagName) {
2433
        var type = tagName.toUpperCase();
2434
        // this.invalidHandleTypes[type] = null;
2435
        delete this.invalidHandleTypes[type];
2436
    },
2437
2438
    /**
2439
     * Unsets an invalid handle id
2440
     * @method removeInvalidHandleId
2441
     * @param {string} id the id of the element to re-enable
2442
     */
2443
    removeInvalidHandleId: function(id) {
2444
        if (typeof id !== "string") {
2445
            YAHOO.log("id is not a string, assuming it is an HTMLElement");
2446
            id = Dom.generateId(id);
2447
        }
2448
        delete this.invalidHandleIds[id];
2449
    },
2450
2451
    /**
2452
     * Unsets an invalid css class
2453
     * @method removeInvalidHandleClass
2454
     * @param {string} cssClass the class of the element(s) you wish to
2455
     * re-enable
2456
     */
2457
    removeInvalidHandleClass: function(cssClass) {
2458
        for (var i=0, len=this.invalidHandleClasses.length; i<len; ++i) {
2459
            if (this.invalidHandleClasses[i] == cssClass) {
2460
                delete this.invalidHandleClasses[i];
2461
            }
2462
        }
2463
    },
2464
2465
    /**
2466
     * Checks the tag exclusion list to see if this click should be ignored
2467
     * @method isValidHandleChild
2468
     * @param {HTMLElement} node the HTMLElement to evaluate
2469
     * @return {boolean} true if this is a valid tag type, false if not
2470
     */
2471
    isValidHandleChild: function(node) {
2472
2473
        var valid = true;
2474
        // var n = (node.nodeName == "#text") ? node.parentNode : node;
2475
        var nodeName;
2476
        try {
2477
            nodeName = node.nodeName.toUpperCase();
2478
        } catch(e) {
2479
            nodeName = node.nodeName;
2480
        }
2481
        valid = valid && !this.invalidHandleTypes[nodeName];
2482
        valid = valid && !this.invalidHandleIds[node.id];
2483
2484
        for (var i=0, len=this.invalidHandleClasses.length; valid && i<len; ++i) {
2485
            valid = !Dom.hasClass(node, this.invalidHandleClasses[i]);
2486
        }
2487
2488
        this.logger.log("Valid handle? ... " + valid);
2489
2490
        return valid;
2491
2492
    },
2493
2494
    /**
2495
     * Create the array of horizontal tick marks if an interval was specified
2496
     * in setXConstraint().
2497
     * @method setXTicks
2498
     * @private
2499
     */
2500
    setXTicks: function(iStartX, iTickSize) {
2501
        this.xTicks = [];
2502
        this.xTickSize = iTickSize;
2503
2504
        var tickMap = {};
2505
2506
        for (var i = this.initPageX; i >= this.minX; i = i - iTickSize) {
2507
            if (!tickMap[i]) {
2508
                this.xTicks[this.xTicks.length] = i;
2509
                tickMap[i] = true;
2510
            }
2511
        }
2512
2513
        for (i = this.initPageX; i <= this.maxX; i = i + iTickSize) {
2514
            if (!tickMap[i]) {
2515
                this.xTicks[this.xTicks.length] = i;
2516
                tickMap[i] = true;
2517
            }
2518
        }
2519
2520
        this.xTicks.sort(this.DDM.numericSort) ;
2521
        this.logger.log("xTicks: " + this.xTicks.join());
2522
    },
2523
2524
    /**
2525
     * Create the array of vertical tick marks if an interval was specified in
2526
     * setYConstraint().
2527
     * @method setYTicks
2528
     * @private
2529
     */
2530
    setYTicks: function(iStartY, iTickSize) {
2531
        // this.logger.log("setYTicks: " + iStartY + ", " + iTickSize
2532
               // + ", " + this.initPageY + ", " + this.minY + ", " + this.maxY );
2533
        this.yTicks = [];
2534
        this.yTickSize = iTickSize;
2535
2536
        var tickMap = {};
2537
2538
        for (var i = this.initPageY; i >= this.minY; i = i - iTickSize) {
2539
            if (!tickMap[i]) {
2540
                this.yTicks[this.yTicks.length] = i;
2541
                tickMap[i] = true;
2542
            }
2543
        }
2544
2545
        for (i = this.initPageY; i <= this.maxY; i = i + iTickSize) {
2546
            if (!tickMap[i]) {
2547
                this.yTicks[this.yTicks.length] = i;
2548
                tickMap[i] = true;
2549
            }
2550
        }
2551
2552
        this.yTicks.sort(this.DDM.numericSort) ;
2553
        this.logger.log("yTicks: " + this.yTicks.join());
2554
    },
2555
2556
    /**
2557
     * By default, the element can be dragged any place on the screen.  Use
2558
     * this method to limit the horizontal travel of the element.  Pass in
2559
     * 0,0 for the parameters if you want to lock the drag to the y axis.
2560
     * @method setXConstraint
2561
     * @param {int} iLeft the number of pixels the element can move to the left
2562
     * @param {int} iRight the number of pixels the element can move to the
2563
     * right
2564
     * @param {int} iTickSize optional parameter for specifying that the
2565
     * element
2566
     * should move iTickSize pixels at a time.
2567
     */
2568
    setXConstraint: function(iLeft, iRight, iTickSize) {
2569
        this.leftConstraint = parseInt(iLeft, 10);
2570
        this.rightConstraint = parseInt(iRight, 10);
2571
2572
        this.minX = this.initPageX - this.leftConstraint;
2573
        this.maxX = this.initPageX + this.rightConstraint;
2574
        if (iTickSize) { this.setXTicks(this.initPageX, iTickSize); }
2575
2576
        this.constrainX = true;
2577
        this.logger.log("initPageX:" + this.initPageX + " minX:" + this.minX +
2578
                " maxX:" + this.maxX);
2579
    },
2580
2581
    /**
2582
     * Clears any constraints applied to this instance.  Also clears ticks
2583
     * since they can't exist independent of a constraint at this time.
2584
     * @method clearConstraints
2585
     */
2586
    clearConstraints: function() {
2587
        this.logger.log("Clearing constraints");
2588
        this.constrainX = false;
2589
        this.constrainY = false;
2590
        this.clearTicks();
2591
    },
2592
2593
    /**
2594
     * Clears any tick interval defined for this instance
2595
     * @method clearTicks
2596
     */
2597
    clearTicks: function() {
2598
        this.logger.log("Clearing ticks");
2599
        this.xTicks = null;
2600
        this.yTicks = null;
2601
        this.xTickSize = 0;
2602
        this.yTickSize = 0;
2603
    },
2604
2605
    /**
2606
     * By default, the element can be dragged any place on the screen.  Set
2607
     * this to limit the vertical travel of the element.  Pass in 0,0 for the
2608
     * parameters if you want to lock the drag to the x axis.
2609
     * @method setYConstraint
2610
     * @param {int} iUp the number of pixels the element can move up
2611
     * @param {int} iDown the number of pixels the element can move down
2612
     * @param {int} iTickSize optional parameter for specifying that the
2613
     * element should move iTickSize pixels at a time.
2614
     */
2615
    setYConstraint: function(iUp, iDown, iTickSize) {
2616
        this.logger.log("setYConstraint: " + iUp + "," + iDown + "," + iTickSize);
2617
        this.topConstraint = parseInt(iUp, 10);
2618
        this.bottomConstraint = parseInt(iDown, 10);
2619
2620
        this.minY = this.initPageY - this.topConstraint;
2621
        this.maxY = this.initPageY + this.bottomConstraint;
2622
        if (iTickSize) { this.setYTicks(this.initPageY, iTickSize); }
2623
2624
        this.constrainY = true;
2625
2626
        this.logger.log("initPageY:" + this.initPageY + " minY:" + this.minY +
2627
                " maxY:" + this.maxY);
2628
    },
2629
2630
    /**
2631
     * resetConstraints must be called if you manually reposition a dd element.
2632
     * @method resetConstraints
2633
     */
2634
    resetConstraints: function() {
2635
2636
        //this.logger.log("resetConstraints");
2637
2638
        // Maintain offsets if necessary
2639
        if (this.initPageX || this.initPageX === 0) {
2640
            //this.logger.log("init pagexy: " + this.initPageX + ", " +
2641
                               //this.initPageY);
2642
            //this.logger.log("last pagexy: " + this.lastPageX + ", " +
2643
                               //this.lastPageY);
2644
            // figure out how much this thing has moved
2645
            var dx = (this.maintainOffset) ? this.lastPageX - this.initPageX : 0;
2646
            var dy = (this.maintainOffset) ? this.lastPageY - this.initPageY : 0;
2647
2648
            this.setInitPosition(dx, dy);
2649
2650
        // This is the first time we have detected the element's position
2651
        } else {
2652
            this.setInitPosition();
2653
        }
2654
2655
        if (this.constrainX) {
2656
            this.setXConstraint( this.leftConstraint,
2657
                                 this.rightConstraint,
2658
                                 this.xTickSize        );
2659
        }
2660
2661
        if (this.constrainY) {
2662
            this.setYConstraint( this.topConstraint,
2663
                                 this.bottomConstraint,
2664
                                 this.yTickSize         );
2665
        }
2666
    },
2667
2668
    /**
2669
     * Normally the drag element is moved pixel by pixel, but we can specify
2670
     * that it move a number of pixels at a time.  This method resolves the
2671
     * location when we have it set up like this.
2672
     * @method getTick
2673
     * @param {int} val where we want to place the object
2674
     * @param {int[]} tickArray sorted array of valid points
2675
     * @return {int} the closest tick
2676
     * @private
2677
     */
2678
    getTick: function(val, tickArray) {
2679
2680
        if (!tickArray) {
2681
            // If tick interval is not defined, it is effectively 1 pixel,
2682
            // so we return the value passed to us.
2683
            return val;
2684
        } else if (tickArray[0] >= val) {
2685
            // The value is lower than the first tick, so we return the first
2686
            // tick.
2687
            return tickArray[0];
2688
        } else {
2689
            for (var i=0, len=tickArray.length; i<len; ++i) {
2690
                var next = i + 1;
2691
                if (tickArray[next] && tickArray[next] >= val) {
2692
                    var diff1 = val - tickArray[i];
2693
                    var diff2 = tickArray[next] - val;
2694
                    return (diff2 > diff1) ? tickArray[i] : tickArray[next];
2695
                }
2696
            }
2697
2698
            // The value is larger than the last tick, so we return the last
2699
            // tick.
2700
            return tickArray[tickArray.length - 1];
2701
        }
2702
    },
2703
2704
    /**
2705
     * toString method
2706
     * @method toString
2707
     * @return {string} string representation of the dd obj
2708
     */
2709
    toString: function() {
2710
        return ("DragDrop " + this.id);
2711
    }
2712
2713
};
2714
2715
})();
2716
/**
2717
 * A DragDrop implementation where the linked element follows the
2718
 * mouse cursor during a drag.
2719
 * @class DD
2720
 * @extends YAHOO.util.DragDrop
2721
 * @constructor
2722
 * @param {String} id the id of the linked element
2723
 * @param {String} sGroup the group of related DragDrop items
2724
 * @param {object} config an object containing configurable attributes
2725
 *                Valid properties for DD:
2726
 *                    scroll
2727
 */
2728
YAHOO.util.DD = function(id, sGroup, config) {
2729
    if (id) {
2730
        this.init(id, sGroup, config);
2731
    }
2732
};
2733
2734
YAHOO.extend(YAHOO.util.DD, YAHOO.util.DragDrop, {
2735
2736
    /**
2737
     * When set to true, the utility automatically tries to scroll the browser
2738
     * window wehn a drag and drop element is dragged near the viewport boundary.
2739
     * Defaults to true.
2740
     * @property scroll
2741
     * @type boolean
2742
     */
2743
    scroll: true,
2744
2745
    /**
2746
     * Sets the pointer offset to the distance between the linked element's top
2747
     * left corner and the location the element was clicked
2748
     * @method autoOffset
2749
     * @param {int} iPageX the X coordinate of the click
2750
     * @param {int} iPageY the Y coordinate of the click
2751
     */
2752
    autoOffset: function(iPageX, iPageY) {
2753
        var x = iPageX - this.startPageX;
2754
        var y = iPageY - this.startPageY;
2755
        this.setDelta(x, y);
2756
        // this.logger.log("autoOffset el pos: " + aCoord + ", delta: " + x + "," + y);
2757
    },
2758
2759
    /**
2760
     * Sets the pointer offset.  You can call this directly to force the
2761
     * offset to be in a particular location (e.g., pass in 0,0 to set it
2762
     * to the center of the object, as done in YAHOO.widget.Slider)
2763
     * @method setDelta
2764
     * @param {int} iDeltaX the distance from the left
2765
     * @param {int} iDeltaY the distance from the top
2766
     */
2767
    setDelta: function(iDeltaX, iDeltaY) {
2768
        this.deltaX = iDeltaX;
2769
        this.deltaY = iDeltaY;
2770
        this.logger.log("deltaX:" + this.deltaX + ", deltaY:" + this.deltaY);
2771
    },
2772
2773
    /**
2774
     * Sets the drag element to the location of the mousedown or click event,
2775
     * maintaining the cursor location relative to the location on the element
2776
     * that was clicked.  Override this if you want to place the element in a
2777
     * location other than where the cursor is.
2778
     * @method setDragElPos
2779
     * @param {int} iPageX the X coordinate of the mousedown or drag event
2780
     * @param {int} iPageY the Y coordinate of the mousedown or drag event
2781
     */
2782
    setDragElPos: function(iPageX, iPageY) {
2783
        // the first time we do this, we are going to check to make sure
2784
        // the element has css positioning
2785
2786
        var el = this.getDragEl();
2787
        this.alignElWithMouse(el, iPageX, iPageY);
2788
    },
2789
2790
    /**
2791
     * Sets the element to the location of the mousedown or click event,
2792
     * maintaining the cursor location relative to the location on the element
2793
     * that was clicked.  Override this if you want to place the element in a
2794
     * location other than where the cursor is.
2795
     * @method alignElWithMouse
2796
     * @param {HTMLElement} el the element to move
2797
     * @param {int} iPageX the X coordinate of the mousedown or drag event
2798
     * @param {int} iPageY the Y coordinate of the mousedown or drag event
2799
     */
2800
    alignElWithMouse: function(el, iPageX, iPageY) {
2801
        var oCoord = this.getTargetCoord(iPageX, iPageY);
2802
        // this.logger.log("****alignElWithMouse : " + el.id + ", " + aCoord + ", " + el.style.display);
2803
2804
        if (!this.deltaSetXY) {
2805
            var aCoord = [oCoord.x, oCoord.y];
2806
            YAHOO.util.Dom.setXY(el, aCoord);
2807
            var newLeft = parseInt( YAHOO.util.Dom.getStyle(el, "left"), 10 );
2808
            var newTop  = parseInt( YAHOO.util.Dom.getStyle(el, "top" ), 10 );
2809
2810
            this.deltaSetXY = [ newLeft - oCoord.x, newTop - oCoord.y ];
2811
        } else {
2812
            YAHOO.util.Dom.setStyle(el, "left", (oCoord.x + this.deltaSetXY[0]) + "px");
2813
            YAHOO.util.Dom.setStyle(el, "top",  (oCoord.y + this.deltaSetXY[1]) + "px");
2814
            //el.style.left = (oCoord.x + this.deltaSetXY[0]) + "px";
2815
            //el.style.top = (oCoord.y + this.deltaSetXY[1]) + "px";
2816
        }
2817
2818
        this.cachePosition(oCoord.x, oCoord.y);
2819
        //DAV
2820
        var self = this;
2821
        setTimeout(function() {
2822
            self.autoScroll.call(self, oCoord.x, oCoord.y, el.offsetHeight, el.offsetWidth);
2823
        }, 0);
2824
    },
2825
2826
    /**
2827
     * Saves the most recent position so that we can reset the constraints and
2828
     * tick marks on-demand.  We need to know this so that we can calculate the
2829
     * number of pixels the element is offset from its original position.
2830
     * @method cachePosition
2831
     * @param iPageX the current x position (optional, this just makes it so we
2832
     * don't have to look it up again)
2833
     * @param iPageY the current y position (optional, this just makes it so we
2834
     * don't have to look it up again)
2835
     */
2836
    cachePosition: function(iPageX, iPageY) {
2837
        if (iPageX) {
2838
            this.lastPageX = iPageX;
2839
            this.lastPageY = iPageY;
2840
        } else {
2841
            var aCoord = YAHOO.util.Dom.getXY(this.getEl());
2842
            this.lastPageX = aCoord[0];
2843
            this.lastPageY = aCoord[1];
2844
        }
2845
    },
2846
2847
    /**
2848
     * Auto-scroll the window if the dragged object has been moved beyond the
2849
     * visible window boundary.
2850
     * @method autoScroll
2851
     * @param {int} x the drag element's x position
2852
     * @param {int} y the drag element's y position
2853
     * @param {int} h the height of the drag element
2854
     * @param {int} w the width of the drag element
2855
     * @private
2856
     */
2857
    autoScroll: function(x, y, h, w) {
2858
2859
        if (this.scroll) {
2860
            // The client height
2861
            var clientH = this.DDM.getClientHeight();
2862
2863
            // The client width
2864
            var clientW = this.DDM.getClientWidth();
2865
2866
            // The amt scrolled down
2867
            var st = this.DDM.getScrollTop();
2868
2869
            // The amt scrolled right
2870
            var sl = this.DDM.getScrollLeft();
2871
2872
            // Location of the bottom of the element
2873
            var bot = h + y;
2874
2875
            // Location of the right of the element
2876
            var right = w + x;
2877
2878
            // The distance from the cursor to the bottom of the visible area,
2879
            // adjusted so that we don't scroll if the cursor is beyond the
2880
            // element drag constraints
2881
            var toBot = (clientH + st - y - this.deltaY);
2882
2883
            // The distance from the cursor to the right of the visible area
2884
            var toRight = (clientW + sl - x - this.deltaX);
2885
2886
            // this.logger.log( " x: " + x + " y: " + y + " h: " + h +
2887
            // " clientH: " + clientH + " clientW: " + clientW +
2888
            // " st: " + st + " sl: " + sl + " bot: " + bot +
2889
            // " right: " + right + " toBot: " + toBot + " toRight: " + toRight);
2890
2891
            // How close to the edge the cursor must be before we scroll
2892
            // var thresh = (document.all) ? 100 : 40;
2893
            var thresh = 40;
2894
2895
            // How many pixels to scroll per autoscroll op.  This helps to reduce
2896
            // clunky scrolling. IE is more sensitive about this ... it needs this
2897
            // value to be higher.
2898
            var scrAmt = (document.all) ? 80 : 30;
2899
2900
            // Scroll down if we are near the bottom of the visible page and the
2901
            // obj extends below the crease
2902
            if ( bot > clientH && toBot < thresh ) {
2903
                window.scrollTo(sl, st + scrAmt);
2904
            }
2905
2906
            // Scroll up if the window is scrolled down and the top of the object
2907
            // goes above the top border
2908
            if ( y < st && st > 0 && y - st < thresh ) {
2909
                window.scrollTo(sl, st - scrAmt);
2910
            }
2911
2912
            // Scroll right if the obj is beyond the right border and the cursor is
2913
            // near the border.
2914
            if ( right > clientW && toRight < thresh ) {
2915
                window.scrollTo(sl + scrAmt, st);
2916
            }
2917
2918
            // Scroll left if the window has been scrolled to the right and the obj
2919
            // extends past the left border
2920
            if ( x < sl && sl > 0 && x - sl < thresh ) {
2921
                window.scrollTo(sl - scrAmt, st);
2922
            }
2923
        }
2924
    },
2925
2926
    /*
2927
     * Sets up config options specific to this class. Overrides
2928
     * YAHOO.util.DragDrop, but all versions of this method through the
2929
     * inheritance chain are called
2930
     */
2931
    applyConfig: function() {
2932
        YAHOO.util.DD.superclass.applyConfig.call(this);
2933
        this.scroll = (this.config.scroll !== false);
2934
    },
2935
2936
    /*
2937
     * Event that fires prior to the onMouseDown event.  Overrides
2938
     * YAHOO.util.DragDrop.
2939
     */
2940
    b4MouseDown: function(e) {
2941
        this.setStartPosition();
2942
        // this.resetConstraints();
2943
        this.autoOffset(YAHOO.util.Event.getPageX(e),
2944
                            YAHOO.util.Event.getPageY(e));
2945
    },
2946
2947
    /*
2948
     * Event that fires prior to the onDrag event.  Overrides
2949
     * YAHOO.util.DragDrop.
2950
     */
2951
    b4Drag: function(e) {
2952
        this.setDragElPos(YAHOO.util.Event.getPageX(e),
2953
                            YAHOO.util.Event.getPageY(e));
2954
    },
2955
2956
    toString: function() {
2957
        return ("DD " + this.id);
2958
    }
2959
2960
    //////////////////////////////////////////////////////////////////////////
2961
    // Debugging ygDragDrop events that can be overridden
2962
    //////////////////////////////////////////////////////////////////////////
2963
    /*
2964
    startDrag: function(x, y) {
2965
        this.logger.log(this.id.toString()  + " startDrag");
2966
    },
2967
2968
    onDrag: function(e) {
2969
        this.logger.log(this.id.toString() + " onDrag");
2970
    },
2971
2972
    onDragEnter: function(e, id) {
2973
        this.logger.log(this.id.toString() + " onDragEnter: " + id);
2974
    },
2975
2976
    onDragOver: function(e, id) {
2977
        this.logger.log(this.id.toString() + " onDragOver: " + id);
2978
    },
2979
2980
    onDragOut: function(e, id) {
2981
        this.logger.log(this.id.toString() + " onDragOut: " + id);
2982
    },
2983
2984
    onDragDrop: function(e, id) {
2985
        this.logger.log(this.id.toString() + " onDragDrop: " + id);
2986
    },
2987
2988
    endDrag: function(e) {
2989
        this.logger.log(this.id.toString() + " endDrag");
2990
    }
2991
2992
    */
2993
2994
});
2995
/**
2996
 * A DragDrop implementation that inserts an empty, bordered div into
2997
 * the document that follows the cursor during drag operations.  At the time of
2998
 * the click, the frame div is resized to the dimensions of the linked html
2999
 * element, and moved to the exact location of the linked element.
3000
 *
3001
 * References to the "frame" element refer to the single proxy element that
3002
 * was created to be dragged in place of all DDProxy elements on the
3003
 * page.
3004
 *
3005
 * @class DDProxy
3006
 * @extends YAHOO.util.DD
3007
 * @constructor
3008
 * @param {String} id the id of the linked html element
3009
 * @param {String} sGroup the group of related DragDrop objects
3010
 * @param {object} config an object containing configurable attributes
3011
 *                Valid properties for DDProxy in addition to those in DragDrop:
3012
 *                   resizeFrame, centerFrame, dragElId
3013
 */
3014
YAHOO.util.DDProxy = function(id, sGroup, config) {
3015
    if (id) {
3016
        this.init(id, sGroup, config);
3017
        this.initFrame();
3018
    }
3019
};
3020
3021
/**
3022
 * The default drag frame div id
3023
 * @property YAHOO.util.DDProxy.dragElId
3024
 * @type String
3025
 * @static
3026
 */
3027
YAHOO.util.DDProxy.dragElId = "ygddfdiv";
3028
3029
YAHOO.extend(YAHOO.util.DDProxy, YAHOO.util.DD, {
3030
3031
    /**
3032
     * By default we resize the drag frame to be the same size as the element
3033
     * we want to drag (this is to get the frame effect).  We can turn it off
3034
     * if we want a different behavior.
3035
     * @property resizeFrame
3036
     * @type boolean
3037
     */
3038
    resizeFrame: true,
3039
3040
    /**
3041
     * By default the frame is positioned exactly where the drag element is, so
3042
     * we use the cursor offset provided by YAHOO.util.DD.  Another option that works only if
3043
     * you do not have constraints on the obj is to have the drag frame centered
3044
     * around the cursor.  Set centerFrame to true for this effect.
3045
     * @property centerFrame
3046
     * @type boolean
3047
     */
3048
    centerFrame: false,
3049
3050
    /**
3051
     * Creates the proxy element if it does not yet exist
3052
     * @method createFrame
3053
     */
3054
    createFrame: function() {
3055
        var self=this, body=document.body;
3056
3057
        if (!body || !body.firstChild) {
3058
            setTimeout( function() { self.createFrame(); }, 50 );
3059
            return;
3060
        }
3061
3062
        var div=this.getDragEl(), Dom=YAHOO.util.Dom;
3063
3064
        if (!div) {
3065
            div    = document.createElement("div");
3066
            div.id = this.dragElId;
3067
            var s  = div.style;
3068
3069
            s.position   = "absolute";
3070
            s.visibility = "hidden";
3071
            s.cursor     = "move";
3072
            s.border     = "2px solid #aaa";
3073
            s.zIndex     = 999;
3074
            s.height     = "25px";
3075
            s.width      = "25px";
3076
3077
            var _data = document.createElement('div');
3078
            Dom.setStyle(_data, 'height', '100%');
3079
            Dom.setStyle(_data, 'width', '100%');
3080
            /**
3081
            * If the proxy element has no background-color, then it is considered to the "transparent" by Internet Explorer.
3082
            * Since it is "transparent" then the events pass through it to the iframe below.
3083
            * So creating a "fake" div inside the proxy element and giving it a background-color, then setting it to an
3084
            * opacity of 0, it appears to not be there, however IE still thinks that it is so the events never pass through.
3085
            */
3086
            Dom.setStyle(_data, 'background-color', '#ccc');
3087
            Dom.setStyle(_data, 'opacity', '0');
3088
            div.appendChild(_data);
3089
3090
            // appendChild can blow up IE if invoked prior to the window load event
3091
            // while rendering a table.  It is possible there are other scenarios
3092
            // that would cause this to happen as well.
3093
            body.insertBefore(div, body.firstChild);
3094
        }
3095
    },
3096
3097
    /**
3098
     * Initialization for the drag frame element.  Must be called in the
3099
     * constructor of all subclasses
3100
     * @method initFrame
3101
     */
3102
    initFrame: function() {
3103
        this.createFrame();
3104
    },
3105
3106
    applyConfig: function() {
3107
        //this.logger.log("DDProxy applyConfig");
3108
        YAHOO.util.DDProxy.superclass.applyConfig.call(this);
3109
3110
        this.resizeFrame = (this.config.resizeFrame !== false);
3111
        this.centerFrame = (this.config.centerFrame);
3112
        this.setDragElId(this.config.dragElId || YAHOO.util.DDProxy.dragElId);
3113
    },
3114
3115
    /**
3116
     * Resizes the drag frame to the dimensions of the clicked object, positions
3117
     * it over the object, and finally displays it
3118
     * @method showFrame
3119
     * @param {int} iPageX X click position
3120
     * @param {int} iPageY Y click position
3121
     * @private
3122
     */
3123
    showFrame: function(iPageX, iPageY) {
3124
        var el = this.getEl();
3125
        var dragEl = this.getDragEl();
3126
        var s = dragEl.style;
3127
3128
        this._resizeProxy();
3129
3130
        if (this.centerFrame) {
3131
            this.setDelta( Math.round(parseInt(s.width,  10)/2),
3132
                           Math.round(parseInt(s.height, 10)/2) );
3133
        }
3134
3135
        this.setDragElPos(iPageX, iPageY);
3136
3137
        YAHOO.util.Dom.setStyle(dragEl, "visibility", "visible");
3138
    },
3139
3140
    /**
3141
     * The proxy is automatically resized to the dimensions of the linked
3142
     * element when a drag is initiated, unless resizeFrame is set to false
3143
     * @method _resizeProxy
3144
     * @private
3145
     */
3146
    _resizeProxy: function() {
3147
        if (this.resizeFrame) {
3148
            var DOM    = YAHOO.util.Dom;
3149
            var el     = this.getEl();
3150
            var dragEl = this.getDragEl();
3151
3152
            var bt = parseInt( DOM.getStyle(dragEl, "borderTopWidth"    ), 10);
3153
            var br = parseInt( DOM.getStyle(dragEl, "borderRightWidth"  ), 10);
3154
            var bb = parseInt( DOM.getStyle(dragEl, "borderBottomWidth" ), 10);
3155
            var bl = parseInt( DOM.getStyle(dragEl, "borderLeftWidth"   ), 10);
3156
3157
            if (isNaN(bt)) { bt = 0; }
3158
            if (isNaN(br)) { br = 0; }
3159
            if (isNaN(bb)) { bb = 0; }
3160
            if (isNaN(bl)) { bl = 0; }
3161
3162
            this.logger.log("proxy size: " + bt + "  " + br + " " + bb + " " + bl);
3163
3164
            var newWidth  = Math.max(0, el.offsetWidth  - br - bl);
3165
            var newHeight = Math.max(0, el.offsetHeight - bt - bb);
3166
3167
            this.logger.log("Resizing proxy element");
3168
3169
            DOM.setStyle( dragEl, "width",  newWidth  + "px" );
3170
            DOM.setStyle( dragEl, "height", newHeight + "px" );
3171
        }
3172
    },
3173
3174
    // overrides YAHOO.util.DragDrop
3175
    b4MouseDown: function(e) {
3176
        this.setStartPosition();
3177
        var x = YAHOO.util.Event.getPageX(e);
3178
        var y = YAHOO.util.Event.getPageY(e);
3179
        this.autoOffset(x, y);
3180
3181
        // This causes the autoscroll code to kick off, which means autoscroll can
3182
        // happen prior to the check for a valid drag handle.
3183
        // this.setDragElPos(x, y);
3184
    },
3185
3186
    // overrides YAHOO.util.DragDrop
3187
    b4StartDrag: function(x, y) {
3188
        // show the drag frame
3189
        this.logger.log("start drag show frame, x: " + x + ", y: " + y);
3190
        this.showFrame(x, y);
3191
    },
3192
3193
    // overrides YAHOO.util.DragDrop
3194
    b4EndDrag: function(e) {
3195
        this.logger.log(this.id + " b4EndDrag");
3196
        YAHOO.util.Dom.setStyle(this.getDragEl(), "visibility", "hidden");
3197
    },
3198
3199
    // overrides YAHOO.util.DragDrop
3200
    // By default we try to move the element to the last location of the frame.
3201
    // This is so that the default behavior mirrors that of YAHOO.util.DD.
3202
    endDrag: function(e) {
3203
        var DOM = YAHOO.util.Dom;
3204
        this.logger.log(this.id + " endDrag");
3205
        var lel = this.getEl();
3206
        var del = this.getDragEl();
3207
3208
        // Show the drag frame briefly so we can get its position
3209
        // del.style.visibility = "";
3210
        DOM.setStyle(del, "visibility", "");
3211
3212
        // Hide the linked element before the move to get around a Safari
3213
        // rendering bug.
3214
        //lel.style.visibility = "hidden";
3215
        DOM.setStyle(lel, "visibility", "hidden");
3216
        YAHOO.util.DDM.moveToEl(lel, del);
3217
        //del.style.visibility = "hidden";
3218
        DOM.setStyle(del, "visibility", "hidden");
3219
        //lel.style.visibility = "";
3220
        DOM.setStyle(lel, "visibility", "");
3221
    },
3222
3223
    toString: function() {
3224
        return ("DDProxy " + this.id);
3225
    }
3226
3227
});
3228
/**
3229
 * A DragDrop implementation that does not move, but can be a drop
3230
 * target.  You would get the same result by simply omitting implementation
3231
 * for the event callbacks, but this way we reduce the processing cost of the
3232
 * event listener and the callbacks.
3233
 * @class DDTarget
3234
 * @extends YAHOO.util.DragDrop
3235
 * @constructor
3236
 * @param {String} id the id of the element that is a drop target
3237
 * @param {String} sGroup the group of related DragDrop objects
3238
 * @param {object} config an object containing configurable attributes
3239
 *                 Valid properties for DDTarget in addition to those in
3240
 *                 DragDrop:
3241
 *                    none
3242
 */
3243
YAHOO.util.DDTarget = function(id, sGroup, config) {
3244
    if (id) {
3245
        this.initTarget(id, sGroup, config);
3246
    }
3247
};
3248
3249
// YAHOO.util.DDTarget.prototype = new YAHOO.util.DragDrop();
3250
YAHOO.extend(YAHOO.util.DDTarget, YAHOO.util.DragDrop, {
3251
    toString: function() {
3252
        return ("DDTarget " + this.id);
3253
    }
3254
});
3255
YAHOO.register("dragdrop", YAHOO.util.DragDropMgr, {version: "2.4.1", build: "742"});