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