Project

General

Profile

1
/*
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"});
(2-2/5)