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