Upgrade to ExtJS 4.0.0 - Released 04/26/2011
[extjs.git] / src / dd / DragDropManager.js
1 /*
2  * This is a derivative of the similarly named class in the YUI Library.
3  * The original license:
4  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
5  * Code licensed under the BSD License:
6  * http://developer.yahoo.net/yui/license.txt
7  */
8
9
10 /**
11  * @class Ext.dd.DragDropManager
12  * DragDropManager is a singleton that tracks the element interaction for
13  * all DragDrop items in the window.  Generally, you will not call
14  * this class directly, but it does have helper methods that could
15  * be useful in your DragDrop implementations.
16  * @singleton
17  */
18 Ext.define('Ext.dd.DragDropManager', {
19     singleton: true,
20
21     requires: ['Ext.util.Region'],
22
23     uses: ['Ext.tip.QuickTipManager'],
24
25     // shorter ClassName, to save bytes and use internally
26     alternateClassName: ['Ext.dd.DragDropMgr', 'Ext.dd.DDM'],
27     
28     /**
29      * Two dimensional Array of registered DragDrop objects.  The first
30      * dimension is the DragDrop item group, the second the DragDrop
31      * object.
32      * @property ids
33      * @type String[]
34      * @private
35      * @static
36      */
37     ids: {},
38
39     /**
40      * Array of element ids defined as drag handles.  Used to determine
41      * if the element that generated the mousedown event is actually the
42      * handle and not the html element itself.
43      * @property handleIds
44      * @type String[]
45      * @private
46      * @static
47      */
48     handleIds: {},
49
50     /**
51      * the DragDrop object that is currently being dragged
52      * @property dragCurrent
53      * @type DragDrop
54      * @private
55      * @static
56      **/
57     dragCurrent: null,
58
59     /**
60      * the DragDrop object(s) that are being hovered over
61      * @property dragOvers
62      * @type Array
63      * @private
64      * @static
65      */
66     dragOvers: {},
67
68     /**
69      * the X distance between the cursor and the object being dragged
70      * @property deltaX
71      * @type int
72      * @private
73      * @static
74      */
75     deltaX: 0,
76
77     /**
78      * the Y distance between the cursor and the object being dragged
79      * @property deltaY
80      * @type int
81      * @private
82      * @static
83      */
84     deltaY: 0,
85
86     /**
87      * Flag to determine if we should prevent the default behavior of the
88      * events we define. By default this is true, but this can be set to
89      * false if you need the default behavior (not recommended)
90      * @property preventDefault
91      * @type boolean
92      * @static
93      */
94     preventDefault: true,
95
96     /**
97      * Flag to determine if we should stop the propagation of the events
98      * we generate. This is true by default but you may want to set it to
99      * false if the html element contains other features that require the
100      * mouse click.
101      * @property stopPropagation
102      * @type boolean
103      * @static
104      */
105     stopPropagation: true,
106
107     /**
108      * Internal flag that is set to true when drag and drop has been
109      * intialized
110      * @property initialized
111      * @private
112      * @static
113      */
114     initialized: false,
115
116     /**
117      * All drag and drop can be disabled.
118      * @property locked
119      * @private
120      * @static
121      */
122     locked: false,
123
124     /**
125      * Called the first time an element is registered.
126      * @method init
127      * @private
128      * @static
129      */
130     init: function() {
131         this.initialized = true;
132     },
133
134     /**
135      * In point mode, drag and drop interaction is defined by the
136      * location of the cursor during the drag/drop
137      * @property POINT
138      * @type int
139      * @static
140      */
141     POINT: 0,
142
143     /**
144      * In intersect mode, drag and drop interaction is defined by the
145      * overlap of two or more drag and drop objects.
146      * @property INTERSECT
147      * @type int
148      * @static
149      */
150     INTERSECT: 1,
151
152     /**
153      * The current drag and drop mode.  Default: POINT
154      * @property mode
155      * @type int
156      * @static
157      */
158     mode: 0,
159
160     /**
161      * Runs method on all drag and drop objects
162      * @method _execOnAll
163      * @private
164      * @static
165      */
166     _execOnAll: function(sMethod, args) {
167         for (var i in this.ids) {
168             for (var j in this.ids[i]) {
169                 var oDD = this.ids[i][j];
170                 if (! this.isTypeOfDD(oDD)) {
171                     continue;
172                 }
173                 oDD[sMethod].apply(oDD, args);
174             }
175         }
176     },
177
178     /**
179      * Drag and drop initialization.  Sets up the global event handlers
180      * @method _onLoad
181      * @private
182      * @static
183      */
184     _onLoad: function() {
185
186         this.init();
187
188         var Event = Ext.EventManager;
189         Event.on(document, "mouseup",   this.handleMouseUp, this, true);
190         Event.on(document, "mousemove", this.handleMouseMove, this, true);
191         Event.on(window,   "unload",    this._onUnload, this, true);
192         Event.on(window,   "resize",    this._onResize, this, true);
193         // Event.on(window,   "mouseout",    this._test);
194
195     },
196
197     /**
198      * Reset constraints on all drag and drop objs
199      * @method _onResize
200      * @private
201      * @static
202      */
203     _onResize: function(e) {
204         this._execOnAll("resetConstraints", []);
205     },
206
207     /**
208      * Lock all drag and drop functionality
209      * @method lock
210      * @static
211      */
212     lock: function() { this.locked = true; },
213
214     /**
215      * Unlock all drag and drop functionality
216      * @method unlock
217      * @static
218      */
219     unlock: function() { this.locked = false; },
220
221     /**
222      * Is drag and drop locked?
223      * @method isLocked
224      * @return {boolean} True if drag and drop is locked, false otherwise.
225      * @static
226      */
227     isLocked: function() { return this.locked; },
228
229     /**
230      * Location cache that is set for all drag drop objects when a drag is
231      * initiated, cleared when the drag is finished.
232      * @property locationCache
233      * @private
234      * @static
235      */
236     locationCache: {},
237
238     /**
239      * Set useCache to false if you want to force object the lookup of each
240      * drag and drop linked element constantly during a drag.
241      * @property useCache
242      * @type boolean
243      * @static
244      */
245     useCache: true,
246
247     /**
248      * The number of pixels that the mouse needs to move after the
249      * mousedown before the drag is initiated.  Default=3;
250      * @property clickPixelThresh
251      * @type int
252      * @static
253      */
254     clickPixelThresh: 3,
255
256     /**
257      * The number of milliseconds after the mousedown event to initiate the
258      * drag if we don't get a mouseup event. Default=350
259      * @property clickTimeThresh
260      * @type int
261      * @static
262      */
263     clickTimeThresh: 350,
264
265     /**
266      * Flag that indicates that either the drag pixel threshold or the
267      * mousdown time threshold has been met
268      * @property dragThreshMet
269      * @type boolean
270      * @private
271      * @static
272      */
273     dragThreshMet: false,
274
275     /**
276      * Timeout used for the click time threshold
277      * @property clickTimeout
278      * @type Object
279      * @private
280      * @static
281      */
282     clickTimeout: null,
283
284     /**
285      * The X position of the mousedown event stored for later use when a
286      * drag threshold is met.
287      * @property startX
288      * @type int
289      * @private
290      * @static
291      */
292     startX: 0,
293
294     /**
295      * The Y position of the mousedown event stored for later use when a
296      * drag threshold is met.
297      * @property startY
298      * @type int
299      * @private
300      * @static
301      */
302     startY: 0,
303
304     /**
305      * Each DragDrop instance must be registered with the DragDropManager.
306      * This is executed in DragDrop.init()
307      * @method regDragDrop
308      * @param {DragDrop} oDD the DragDrop object to register
309      * @param {String} sGroup the name of the group this element belongs to
310      * @static
311      */
312     regDragDrop: function(oDD, sGroup) {
313         if (!this.initialized) { this.init(); }
314
315         if (!this.ids[sGroup]) {
316             this.ids[sGroup] = {};
317         }
318         this.ids[sGroup][oDD.id] = oDD;
319     },
320
321     /**
322      * Removes the supplied dd instance from the supplied group. Executed
323      * by DragDrop.removeFromGroup, so don't call this function directly.
324      * @method removeDDFromGroup
325      * @private
326      * @static
327      */
328     removeDDFromGroup: function(oDD, sGroup) {
329         if (!this.ids[sGroup]) {
330             this.ids[sGroup] = {};
331         }
332
333         var obj = this.ids[sGroup];
334         if (obj && obj[oDD.id]) {
335             delete obj[oDD.id];
336         }
337     },
338
339     /**
340      * Unregisters a drag and drop item.  This is executed in
341      * DragDrop.unreg, use that method instead of calling this directly.
342      * @method _remove
343      * @private
344      * @static
345      */
346     _remove: function(oDD) {
347         for (var g in oDD.groups) {
348             if (g && this.ids[g] && this.ids[g][oDD.id]) {
349                 delete this.ids[g][oDD.id];
350             }
351         }
352         delete this.handleIds[oDD.id];
353     },
354
355     /**
356      * Each DragDrop handle element must be registered.  This is done
357      * automatically when executing DragDrop.setHandleElId()
358      * @method regHandle
359      * @param {String} sDDId the DragDrop id this element is a handle for
360      * @param {String} sHandleId the id of the element that is the drag
361      * handle
362      * @static
363      */
364     regHandle: function(sDDId, sHandleId) {
365         if (!this.handleIds[sDDId]) {
366             this.handleIds[sDDId] = {};
367         }
368         this.handleIds[sDDId][sHandleId] = sHandleId;
369     },
370
371     /**
372      * Utility function to determine if a given element has been
373      * registered as a drag drop item.
374      * @method isDragDrop
375      * @param {String} id the element id to check
376      * @return {boolean} true if this element is a DragDrop item,
377      * false otherwise
378      * @static
379      */
380     isDragDrop: function(id) {
381         return ( this.getDDById(id) ) ? true : false;
382     },
383
384     /**
385      * Returns the drag and drop instances that are in all groups the
386      * passed in instance belongs to.
387      * @method getRelated
388      * @param {DragDrop} p_oDD the obj to get related data for
389      * @param {boolean} bTargetsOnly if true, only return targetable objs
390      * @return {DragDrop[]} the related instances
391      * @static
392      */
393     getRelated: function(p_oDD, bTargetsOnly) {
394         var oDDs = [];
395         for (var i in p_oDD.groups) {
396             for (var j in this.ids[i]) {
397                 var dd = this.ids[i][j];
398                 if (! this.isTypeOfDD(dd)) {
399                     continue;
400                 }
401                 if (!bTargetsOnly || dd.isTarget) {
402                     oDDs[oDDs.length] = dd;
403                 }
404             }
405         }
406
407         return oDDs;
408     },
409
410     /**
411      * Returns true if the specified dd target is a legal target for
412      * the specifice drag obj
413      * @method isLegalTarget
414      * @param {DragDrop} oDD the drag obj
415      * @param {DragDrop} oTargetDD the target
416      * @return {boolean} true if the target is a legal target for the
417      * dd obj
418      * @static
419      */
420     isLegalTarget: function (oDD, oTargetDD) {
421         var targets = this.getRelated(oDD, true);
422         for (var i=0, len=targets.length;i<len;++i) {
423             if (targets[i].id == oTargetDD.id) {
424                 return true;
425             }
426         }
427
428         return false;
429     },
430
431     /**
432      * My goal is to be able to transparently determine if an object is
433      * typeof DragDrop, and the exact subclass of DragDrop.  typeof
434      * returns "object", oDD.constructor.toString() always returns
435      * "DragDrop" and not the name of the subclass.  So for now it just
436      * evaluates a well-known variable in DragDrop.
437      * @method isTypeOfDD
438      * @param {Object} the object to evaluate
439      * @return {boolean} true if typeof oDD = DragDrop
440      * @static
441      */
442     isTypeOfDD: function (oDD) {
443         return (oDD && oDD.__ygDragDrop);
444     },
445
446     /**
447      * Utility function to determine if a given element has been
448      * registered as a drag drop handle for the given Drag Drop object.
449      * @method isHandle
450      * @param {String} id the element id to check
451      * @return {boolean} true if this element is a DragDrop handle, false
452      * otherwise
453      * @static
454      */
455     isHandle: function(sDDId, sHandleId) {
456         return ( this.handleIds[sDDId] &&
457                         this.handleIds[sDDId][sHandleId] );
458     },
459
460     /**
461      * Returns the DragDrop instance for a given id
462      * @method getDDById
463      * @param {String} id the id of the DragDrop object
464      * @return {DragDrop} the drag drop object, null if it is not found
465      * @static
466      */
467     getDDById: function(id) {
468         for (var i in this.ids) {
469             if (this.ids[i][id]) {
470                 return this.ids[i][id];
471             }
472         }
473         return null;
474     },
475
476     /**
477      * Fired after a registered DragDrop object gets the mousedown event.
478      * Sets up the events required to track the object being dragged
479      * @method handleMouseDown
480      * @param {Event} e the event
481      * @param oDD the DragDrop object being dragged
482      * @private
483      * @static
484      */
485     handleMouseDown: function(e, oDD) {
486         if(Ext.tip.QuickTipManager){
487             Ext.tip.QuickTipManager.ddDisable();
488         }
489         if(this.dragCurrent){
490             // the original browser mouseup wasn't handled (e.g. outside FF browser window)
491             // so clean up first to avoid breaking the next drag
492             this.handleMouseUp(e);
493         }
494         
495         this.currentTarget = e.getTarget();
496         this.dragCurrent = oDD;
497
498         var el = oDD.getEl();
499
500         // track start position
501         this.startX = e.getPageX();
502         this.startY = e.getPageY();
503
504         this.deltaX = this.startX - el.offsetLeft;
505         this.deltaY = this.startY - el.offsetTop;
506
507         this.dragThreshMet = false;
508
509         this.clickTimeout = setTimeout(
510                 function() {
511                     var DDM = Ext.dd.DragDropManager;
512                     DDM.startDrag(DDM.startX, DDM.startY);
513                 },
514                 this.clickTimeThresh );
515     },
516
517     /**
518      * Fired when either the drag pixel threshol or the mousedown hold
519      * time threshold has been met.
520      * @method startDrag
521      * @param x {int} the X position of the original mousedown
522      * @param y {int} the Y position of the original mousedown
523      * @static
524      */
525     startDrag: function(x, y) {
526         clearTimeout(this.clickTimeout);
527         if (this.dragCurrent) {
528             this.dragCurrent.b4StartDrag(x, y);
529             this.dragCurrent.startDrag(x, y);
530         }
531         this.dragThreshMet = true;
532     },
533
534     /**
535      * Internal function to handle the mouseup event.  Will be invoked
536      * from the context of the document.
537      * @method handleMouseUp
538      * @param {Event} e the event
539      * @private
540      * @static
541      */
542     handleMouseUp: function(e) {
543
544         if(Ext.tip.QuickTipManager){
545             Ext.tip.QuickTipManager.ddEnable();
546         }
547         if (! this.dragCurrent) {
548             return;
549         }
550
551         clearTimeout(this.clickTimeout);
552
553         if (this.dragThreshMet) {
554             this.fireEvents(e, true);
555         } else {
556         }
557
558         this.stopDrag(e);
559
560         this.stopEvent(e);
561     },
562
563     /**
564      * Utility to stop event propagation and event default, if these
565      * features are turned on.
566      * @method stopEvent
567      * @param {Event} e the event as returned by this.getEvent()
568      * @static
569      */
570     stopEvent: function(e){
571         if(this.stopPropagation) {
572             e.stopPropagation();
573         }
574
575         if (this.preventDefault) {
576             e.preventDefault();
577         }
578     },
579
580     /**
581      * Internal function to clean up event handlers after the drag
582      * operation is complete
583      * @method stopDrag
584      * @param {Event} e the event
585      * @private
586      * @static
587      */
588     stopDrag: function(e) {
589         // Fire the drag end event for the item that was dragged
590         if (this.dragCurrent) {
591             if (this.dragThreshMet) {
592                 this.dragCurrent.b4EndDrag(e);
593                 this.dragCurrent.endDrag(e);
594             }
595
596             this.dragCurrent.onMouseUp(e);
597         }
598
599         this.dragCurrent = null;
600         this.dragOvers = {};
601     },
602
603     /**
604      * Internal function to handle the mousemove event.  Will be invoked
605      * from the context of the html element.
606      *
607      * @TODO figure out what we can do about mouse events lost when the
608      * user drags objects beyond the window boundary.  Currently we can
609      * detect this in internet explorer by verifying that the mouse is
610      * down during the mousemove event.  Firefox doesn't give us the
611      * button state on the mousemove event.
612      * @method handleMouseMove
613      * @param {Event} e the event
614      * @private
615      * @static
616      */
617     handleMouseMove: function(e) {
618         if (! this.dragCurrent) {
619             return true;
620         }
621         // var button = e.which || e.button;
622
623         // check for IE mouseup outside of page boundary
624         if (Ext.isIE && (e.button !== 0 && e.button !== 1 && e.button !== 2)) {
625             this.stopEvent(e);
626             return this.handleMouseUp(e);
627         }
628
629         if (!this.dragThreshMet) {
630             var diffX = Math.abs(this.startX - e.getPageX());
631             var diffY = Math.abs(this.startY - e.getPageY());
632             if (diffX > this.clickPixelThresh ||
633                         diffY > this.clickPixelThresh) {
634                 this.startDrag(this.startX, this.startY);
635             }
636         }
637
638         if (this.dragThreshMet) {
639             this.dragCurrent.b4Drag(e);
640             this.dragCurrent.onDrag(e);
641             if(!this.dragCurrent.moveOnly){
642                 this.fireEvents(e, false);
643             }
644         }
645
646         this.stopEvent(e);
647
648         return true;
649     },
650
651     /**
652      * Iterates over all of the DragDrop elements to find ones we are
653      * hovering over or dropping on
654      * @method fireEvents
655      * @param {Event} e the event
656      * @param {boolean} isDrop is this a drop op or a mouseover op?
657      * @private
658      * @static
659      */
660     fireEvents: function(e, isDrop) {
661         var dc = this.dragCurrent;
662
663         // If the user did the mouse up outside of the window, we could
664         // get here even though we have ended the drag.
665         if (!dc || dc.isLocked()) {
666             return;
667         }
668
669         var pt = e.getPoint();
670
671         // cache the previous dragOver array
672         var oldOvers = [];
673
674         var outEvts   = [];
675         var overEvts  = [];
676         var dropEvts  = [];
677         var enterEvts = [];
678
679         // Check to see if the object(s) we were hovering over is no longer
680         // being hovered over so we can fire the onDragOut event
681         for (var i in this.dragOvers) {
682
683             var ddo = this.dragOvers[i];
684
685             if (! this.isTypeOfDD(ddo)) {
686                 continue;
687             }
688
689             if (! this.isOverTarget(pt, ddo, this.mode)) {
690                 outEvts.push( ddo );
691             }
692
693             oldOvers[i] = true;
694             delete this.dragOvers[i];
695         }
696
697         for (var sGroup in dc.groups) {
698
699             if ("string" != typeof sGroup) {
700                 continue;
701             }
702
703             for (i in this.ids[sGroup]) {
704                 var oDD = this.ids[sGroup][i];
705                 if (! this.isTypeOfDD(oDD)) {
706                     continue;
707                 }
708
709                 if (oDD.isTarget && !oDD.isLocked() && ((oDD != dc) || (dc.ignoreSelf === false))) {
710                     if (this.isOverTarget(pt, oDD, this.mode)) {
711                         // look for drop interactions
712                         if (isDrop) {
713                             dropEvts.push( oDD );
714                         // look for drag enter and drag over interactions
715                         } else {
716
717                             // initial drag over: dragEnter fires
718                             if (!oldOvers[oDD.id]) {
719                                 enterEvts.push( oDD );
720                             // subsequent drag overs: dragOver fires
721                             } else {
722                                 overEvts.push( oDD );
723                             }
724
725                             this.dragOvers[oDD.id] = oDD;
726                         }
727                     }
728                 }
729             }
730         }
731
732         if (this.mode) {
733             if (outEvts.length) {
734                 dc.b4DragOut(e, outEvts);
735                 dc.onDragOut(e, outEvts);
736             }
737
738             if (enterEvts.length) {
739                 dc.onDragEnter(e, enterEvts);
740             }
741
742             if (overEvts.length) {
743                 dc.b4DragOver(e, overEvts);
744                 dc.onDragOver(e, overEvts);
745             }
746
747             if (dropEvts.length) {
748                 dc.b4DragDrop(e, dropEvts);
749                 dc.onDragDrop(e, dropEvts);
750             }
751
752         } else {
753             // fire dragout events
754             var len = 0;
755             for (i=0, len=outEvts.length; i<len; ++i) {
756                 dc.b4DragOut(e, outEvts[i].id);
757                 dc.onDragOut(e, outEvts[i].id);
758             }
759
760             // fire enter events
761             for (i=0,len=enterEvts.length; i<len; ++i) {
762                 // dc.b4DragEnter(e, oDD.id);
763                 dc.onDragEnter(e, enterEvts[i].id);
764             }
765
766             // fire over events
767             for (i=0,len=overEvts.length; i<len; ++i) {
768                 dc.b4DragOver(e, overEvts[i].id);
769                 dc.onDragOver(e, overEvts[i].id);
770             }
771
772             // fire drop events
773             for (i=0, len=dropEvts.length; i<len; ++i) {
774                 dc.b4DragDrop(e, dropEvts[i].id);
775                 dc.onDragDrop(e, dropEvts[i].id);
776             }
777
778         }
779
780         // notify about a drop that did not find a target
781         if (isDrop && !dropEvts.length) {
782             dc.onInvalidDrop(e);
783         }
784
785     },
786
787     /**
788      * Helper function for getting the best match from the list of drag
789      * and drop objects returned by the drag and drop events when we are
790      * in INTERSECT mode.  It returns either the first object that the
791      * cursor is over, or the object that has the greatest overlap with
792      * the dragged element.
793      * @method getBestMatch
794      * @param  {DragDrop[]} dds The array of drag and drop objects
795      * targeted
796      * @return {DragDrop}       The best single match
797      * @static
798      */
799     getBestMatch: function(dds) {
800         var winner = null;
801         // Return null if the input is not what we expect
802         //if (!dds || !dds.length || dds.length == 0) {
803            // winner = null;
804         // If there is only one item, it wins
805         //} else if (dds.length == 1) {
806
807         var len = dds.length;
808
809         if (len == 1) {
810             winner = dds[0];
811         } else {
812             // Loop through the targeted items
813             for (var i=0; i<len; ++i) {
814                 var dd = dds[i];
815                 // If the cursor is over the object, it wins.  If the
816                 // cursor is over multiple matches, the first one we come
817                 // to wins.
818                 if (dd.cursorIsOver) {
819                     winner = dd;
820                     break;
821                 // Otherwise the object with the most overlap wins
822                 } else {
823                     if (!winner ||
824                         winner.overlap.getArea() < dd.overlap.getArea()) {
825                         winner = dd;
826                     }
827                 }
828             }
829         }
830
831         return winner;
832     },
833
834     /**
835      * Refreshes the cache of the top-left and bottom-right points of the
836      * drag and drop objects in the specified group(s).  This is in the
837      * format that is stored in the drag and drop instance, so typical
838      * usage is:
839      * <code>
840      * Ext.dd.DragDropManager.refreshCache(ddinstance.groups);
841      * </code>
842      * Alternatively:
843      * <code>
844      * Ext.dd.DragDropManager.refreshCache({group1:true, group2:true});
845      * </code>
846      * @TODO this really should be an indexed array.  Alternatively this
847      * method could accept both.
848      * @method refreshCache
849      * @param {Object} groups an associative array of groups to refresh
850      * @static
851      */
852     refreshCache: function(groups) {
853         for (var sGroup in groups) {
854             if ("string" != typeof sGroup) {
855                 continue;
856             }
857             for (var i in this.ids[sGroup]) {
858                 var oDD = this.ids[sGroup][i];
859
860                 if (this.isTypeOfDD(oDD)) {
861                 // if (this.isTypeOfDD(oDD) && oDD.isTarget) {
862                     var loc = this.getLocation(oDD);
863                     if (loc) {
864                         this.locationCache[oDD.id] = loc;
865                     } else {
866                         delete this.locationCache[oDD.id];
867                         // this will unregister the drag and drop object if
868                         // the element is not in a usable state
869                         // oDD.unreg();
870                     }
871                 }
872             }
873         }
874     },
875
876     /**
877      * This checks to make sure an element exists and is in the DOM.  The
878      * main purpose is to handle cases where innerHTML is used to remove
879      * drag and drop objects from the DOM.  IE provides an 'unspecified
880      * error' when trying to access the offsetParent of such an element
881      * @method verifyEl
882      * @param {HTMLElement} el the element to check
883      * @return {boolean} true if the element looks usable
884      * @static
885      */
886     verifyEl: function(el) {
887         if (el) {
888             var parent;
889             if(Ext.isIE){
890                 try{
891                     parent = el.offsetParent;
892                 }catch(e){}
893             }else{
894                 parent = el.offsetParent;
895             }
896             if (parent) {
897                 return true;
898             }
899         }
900
901         return false;
902     },
903
904     /**
905      * Returns a Region object containing the drag and drop element's position
906      * and size, including the padding configured for it
907      * @method getLocation
908      * @param {DragDrop} oDD the drag and drop object to get the
909      *                       location for
910      * @return {Ext.util.Region} a Region object representing the total area
911      *                             the element occupies, including any padding
912      *                             the instance is configured for.
913      * @static
914      */
915     getLocation: function(oDD) {
916         if (! this.isTypeOfDD(oDD)) {
917             return null;
918         }
919
920         //delegate getLocation method to the
921         //drag and drop target.
922         if (oDD.getRegion) {
923             return oDD.getRegion();
924         }
925
926         var el = oDD.getEl(), pos, x1, x2, y1, y2, t, r, b, l;
927
928         try {
929             pos= Ext.core.Element.getXY(el);
930         } catch (e) { }
931
932         if (!pos) {
933             return null;
934         }
935
936         x1 = pos[0];
937         x2 = x1 + el.offsetWidth;
938         y1 = pos[1];
939         y2 = y1 + el.offsetHeight;
940
941         t = y1 - oDD.padding[0];
942         r = x2 + oDD.padding[1];
943         b = y2 + oDD.padding[2];
944         l = x1 - oDD.padding[3];
945
946         return Ext.create('Ext.util.Region', t, r, b, l);
947     },
948
949     /**
950      * Checks the cursor location to see if it over the target
951      * @method isOverTarget
952      * @param {Ext.util.Point} pt The point to evaluate
953      * @param {DragDrop} oTarget the DragDrop object we are inspecting
954      * @return {boolean} true if the mouse is over the target
955      * @private
956      * @static
957      */
958     isOverTarget: function(pt, oTarget, intersect) {
959         // use cache if available
960         var loc = this.locationCache[oTarget.id];
961         if (!loc || !this.useCache) {
962             loc = this.getLocation(oTarget);
963             this.locationCache[oTarget.id] = loc;
964
965         }
966
967         if (!loc) {
968             return false;
969         }
970
971         oTarget.cursorIsOver = loc.contains( pt );
972
973         // DragDrop is using this as a sanity check for the initial mousedown
974         // in this case we are done.  In POINT mode, if the drag obj has no
975         // contraints, we are also done. Otherwise we need to evaluate the
976         // location of the target as related to the actual location of the
977         // dragged element.
978         var dc = this.dragCurrent;
979         if (!dc || !dc.getTargetCoord ||
980                 (!intersect && !dc.constrainX && !dc.constrainY)) {
981             return oTarget.cursorIsOver;
982         }
983
984         oTarget.overlap = null;
985
986         // Get the current location of the drag element, this is the
987         // location of the mouse event less the delta that represents
988         // where the original mousedown happened on the element.  We
989         // need to consider constraints and ticks as well.
990         var pos = dc.getTargetCoord(pt.x, pt.y);
991
992         var el = dc.getDragEl();
993         var curRegion = Ext.create('Ext.util.Region', pos.y,
994                                                pos.x + el.offsetWidth,
995                                                pos.y + el.offsetHeight,
996                                                pos.x );
997
998         var overlap = curRegion.intersect(loc);
999
1000         if (overlap) {
1001             oTarget.overlap = overlap;
1002             return (intersect) ? true : oTarget.cursorIsOver;
1003         } else {
1004             return false;
1005         }
1006     },
1007
1008     /**
1009      * unload event handler
1010      * @method _onUnload
1011      * @private
1012      * @static
1013      */
1014     _onUnload: function(e, me) {
1015         Ext.dd.DragDropManager.unregAll();
1016     },
1017
1018     /**
1019      * Cleans up the drag and drop events and objects.
1020      * @method unregAll
1021      * @private
1022      * @static
1023      */
1024     unregAll: function() {
1025
1026         if (this.dragCurrent) {
1027             this.stopDrag();
1028             this.dragCurrent = null;
1029         }
1030
1031         this._execOnAll("unreg", []);
1032
1033         for (var i in this.elementCache) {
1034             delete this.elementCache[i];
1035         }
1036
1037         this.elementCache = {};
1038         this.ids = {};
1039     },
1040
1041     /**
1042      * A cache of DOM elements
1043      * @property elementCache
1044      * @private
1045      * @static
1046      */
1047     elementCache: {},
1048
1049     /**
1050      * Get the wrapper for the DOM element specified
1051      * @method getElWrapper
1052      * @param {String} id the id of the element to get
1053      * @return {Ext.dd.DDM.ElementWrapper} the wrapped element
1054      * @private
1055      * @deprecated This wrapper isn't that useful
1056      * @static
1057      */
1058     getElWrapper: function(id) {
1059         var oWrapper = this.elementCache[id];
1060         if (!oWrapper || !oWrapper.el) {
1061             oWrapper = this.elementCache[id] =
1062                 new this.ElementWrapper(Ext.getDom(id));
1063         }
1064         return oWrapper;
1065     },
1066
1067     /**
1068      * Returns the actual DOM element
1069      * @method getElement
1070      * @param {String} id the id of the elment to get
1071      * @return {Object} The element
1072      * @deprecated use Ext.lib.Ext.getDom instead
1073      * @static
1074      */
1075     getElement: function(id) {
1076         return Ext.getDom(id);
1077     },
1078
1079     /**
1080      * Returns the style property for the DOM element (i.e.,
1081      * document.getElById(id).style)
1082      * @method getCss
1083      * @param {String} id the id of the elment to get
1084      * @return {Object} The style property of the element
1085      * @static
1086      */
1087     getCss: function(id) {
1088         var el = Ext.getDom(id);
1089         return (el) ? el.style : null;
1090     },
1091
1092     /**
1093      * Inner class for cached elements
1094      * @class Ext.dd.DragDropManager.ElementWrapper
1095      * @for DragDropManager
1096      * @private
1097      * @deprecated
1098      */
1099     ElementWrapper: function(el) {
1100             /**
1101              * The element
1102              * @property el
1103              */
1104             this.el = el || null;
1105             /**
1106              * The element id
1107              * @property id
1108              */
1109             this.id = this.el && el.id;
1110             /**
1111              * A reference to the style property
1112              * @property css
1113              */
1114             this.css = this.el && el.style;
1115         },
1116
1117     /**
1118      * Returns the X position of an html element
1119      * @method getPosX
1120      * @param el the element for which to get the position
1121      * @return {int} the X coordinate
1122      * @for DragDropManager
1123      * @static
1124      */
1125     getPosX: function(el) {
1126         return Ext.core.Element.getX(el);
1127     },
1128
1129     /**
1130      * Returns the Y position of an html element
1131      * @method getPosY
1132      * @param el the element for which to get the position
1133      * @return {int} the Y coordinate
1134      * @static
1135      */
1136     getPosY: function(el) {
1137         return Ext.core.Element.getY(el);
1138     },
1139
1140     /**
1141      * Swap two nodes.  In IE, we use the native method, for others we
1142      * emulate the IE behavior
1143      * @method swapNode
1144      * @param n1 the first node to swap
1145      * @param n2 the other node to swap
1146      * @static
1147      */
1148     swapNode: function(n1, n2) {
1149         if (n1.swapNode) {
1150             n1.swapNode(n2);
1151         } else {
1152             var p = n2.parentNode;
1153             var s = n2.nextSibling;
1154
1155             if (s == n1) {
1156                 p.insertBefore(n1, n2);
1157             } else if (n2 == n1.nextSibling) {
1158                 p.insertBefore(n2, n1);
1159             } else {
1160                 n1.parentNode.replaceChild(n2, n1);
1161                 p.insertBefore(n1, s);
1162             }
1163         }
1164     },
1165
1166     /**
1167      * Returns the current scroll position
1168      * @method getScroll
1169      * @private
1170      * @static
1171      */
1172     getScroll: function () {
1173         var doc   = window.document,
1174             docEl = doc.documentElement,
1175             body  = doc.body,
1176             top   = 0,
1177             left  = 0;
1178             
1179         if (Ext.isGecko4) {
1180             top  = window.scrollYOffset;
1181             left = window.scrollXOffset;
1182         } else {
1183             if (docEl && (docEl.scrollTop || docEl.scrollLeft)) {
1184                 top  = docEl.scrollTop;
1185                 left = docEl.scrollLeft;
1186             } else if (body) {
1187                 top  = body.scrollTop;
1188                 left = body.scrollLeft;
1189             } 
1190         }
1191         return {
1192             top: top,
1193             left: left
1194         };
1195     },
1196
1197     /**
1198      * Returns the specified element style property
1199      * @method getStyle
1200      * @param {HTMLElement} el          the element
1201      * @param {string}      styleProp   the style property
1202      * @return {string} The value of the style property
1203      * @static
1204      */
1205     getStyle: function(el, styleProp) {
1206         return Ext.fly(el).getStyle(styleProp);
1207     },
1208
1209     /**
1210      * Gets the scrollTop
1211      * @method getScrollTop
1212      * @return {int} the document's scrollTop
1213      * @static
1214      */
1215     getScrollTop: function () {
1216         return this.getScroll().top;
1217     },
1218
1219     /**
1220      * Gets the scrollLeft
1221      * @method getScrollLeft
1222      * @return {int} the document's scrollTop
1223      * @static
1224      */
1225     getScrollLeft: function () {
1226         return this.getScroll().left;
1227     },
1228
1229     /**
1230      * Sets the x/y position of an element to the location of the
1231      * target element.
1232      * @method moveToEl
1233      * @param {HTMLElement} moveEl      The element to move
1234      * @param {HTMLElement} targetEl    The position reference element
1235      * @static
1236      */
1237     moveToEl: function (moveEl, targetEl) {
1238         var aCoord = Ext.core.Element.getXY(targetEl);
1239         Ext.core.Element.setXY(moveEl, aCoord);
1240     },
1241
1242     /**
1243      * Numeric array sort function
1244      * @method numericSort
1245      * @static
1246      */
1247     numericSort: function(a, b) {
1248         return (a - b);
1249     },
1250
1251     /**
1252      * Internal counter
1253      * @property _timeoutCount
1254      * @private
1255      * @static
1256      */
1257     _timeoutCount: 0,
1258
1259     /**
1260      * Trying to make the load order less important.  Without this we get
1261      * an error if this file is loaded before the Event Utility.
1262      * @method _addListeners
1263      * @private
1264      * @static
1265      */
1266     _addListeners: function() {
1267         if ( document ) {
1268             this._onLoad();
1269         } else {
1270             if (this._timeoutCount > 2000) {
1271             } else {
1272                 setTimeout(this._addListeners, 10);
1273                 if (document && document.body) {
1274                     this._timeoutCount += 1;
1275                 }
1276             }
1277         }
1278     },
1279
1280     /**
1281      * Recursively searches the immediate parent and all child nodes for
1282      * the handle element in order to determine wheter or not it was
1283      * clicked.
1284      * @method handleWasClicked
1285      * @param node the html element to inspect
1286      * @static
1287      */
1288     handleWasClicked: function(node, id) {
1289         if (this.isHandle(id, node.id)) {
1290             return true;
1291         } else {
1292             // check to see if this is a text node child of the one we want
1293             var p = node.parentNode;
1294
1295             while (p) {
1296                 if (this.isHandle(id, p.id)) {
1297                     return true;
1298                 } else {
1299                     p = p.parentNode;
1300                 }
1301             }
1302         }
1303
1304         return false;
1305     }
1306 }, function() {
1307     this._addListeners();
1308 });