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