/** * Acts as a mediator between sources and targets. * * Typically this class will not be used in user code. * * @private */Ext.define('Ext.drag.Manager', { singleton: true, uses: [ 'Ext.mixin.Inheritable' ], /** * @property {String} dragCls * A class added to the body while a drag is active. * * @private */ dragCls: Ext.baseCSSPrefix + 'drag-body', // If we need to use the current mousemove target to find the over el, // but pointer-events is not supported, AND the delta position does not place the mouse outside // of the dragEl, temporarily move the dragEl away, and fake the mousemove target by using // document.elementFromPoint while it's out of the way. // The pointer events implementation is bugged in IE9/10 and opera, so fallback even if they // report that they support it. // IE8m do not support it so they will auto fall back pointerBug: Ext.isTouch || (!Ext.supports.CSSPointerEvents || Ext.isIE10m || Ext.isOpera), constructor: function() { this.targets = {}; this.nativeTargets = []; Ext.onReady(this.init, this); }, init: function() { // The purpose of listening for these events is to track when a // native drag enters the document so we can create and maintain // a single drag.Info object for it. Need to use a "stack-like" mechanism // to track while elements are being entered/left, keeping a count is // not sufficient because Gecko can fire multiple events for the // same element in some instances. So just keep pushing/removing the // element from the tracking array. Once we hit 0, the drag is out // of the document. On drop, we clear it manually because there is // no longer an active drag. Ext.getDoc().on({ scope: this, dragenter: { capture: true, fn: 'onNativeDragEnter' }, dragleave: 'onNativeDragLeave', dragover: 'onNativeDragOver', drop: 'onNativeDrop' }); }, destroy: function() { var me = this, targets = me.targets, key; me.destroying = true; for (key in targets) { targets[key].destroy(); } me.targets = null; me.callParent(); me.destroying = false; }, privates: { /** * A shim for elementFromPoint to allow RTL behaviour. * @param {Number} x The x coordinate. * @param {Number} y The y coordinate * @return {HTMLElement} The element. * * @private */ elementFromPoint: function(x, y) { if (Ext.rootInheritedState.rtl) { x = Ext.Element.getViewportWidth() - x; } return Ext.dom.Element.fromPagePoint(x, y, true); }, /** * Get the matching target (if any) at a particular point. * @param {Ext.drag.Info} info The drag info. * @return {Ext.drag.Target} The matching target, `null` if not found. * * @private */ getAtPoint: function(info) { var current = info.cursor.current, elementMap = info.elementMap, isUnderCursor = info.proxy.isUnderCursor, proxyEl = this.pointerBug && isUnderCursor ? info.proxy.element.dom : null, target, el; if (proxyEl) { proxyEl.style.visibility = 'hidden'; } el = this.elementFromPoint(current.x, current.y); if (proxyEl) { proxyEl.style.visibility = 'visible'; } while (el) { target = elementMap[el.id]; if (target) { return target; } el = el.parentNode; } return null; }, /** * Spins up an info object based on a native drag. * @param {Ext.event.Event} e The event. * @return {Ext.drag.Info} The info. Cached for a single drag. * * @private */ getNativeDragInfo: function(e) { var info = this.nativeDragInfo; if (!info) { this.nativeDragInfo = info = new Ext.drag.Info(); info.isNative = true; } return info; }, /** * Called on drag cancel. * @param {Ext.drag.Info} info The drag info. * @param {Ext.event.Event} e The event. * * @private */ onDragCancel: function() { Ext.getBody().removeCls(this.dragCls); }, /** * Called when drag completes. * @param {Ext.drag.Info} info The drag info. * @param {Ext.event.Event} e The event. * * @private */ onDragEnd: function(info, e) { info.finalize(); Ext.getBody().removeCls(this.dragCls); }, /** * Called for each drag movement. * @param {Ext.drag.Info} info The drag info. * @param {Ext.event.Event} e The event. * * @private */ onDragMove: function(info, e) { this.processDrag(info); }, /** * Called when drag starts. * @param {Ext.drag.Info} info The drag info. * @param {Ext.event.Event} e The event. * * @private */ onDragStart: function(info, e) { var me = this, source = info.source, targets = me.targets, groups = source.getGroups(), targetMap = {}, possibleTargets = {}, elementMap = {}, id, target, targetGroups, groupMap, groupOk, len, i; elementMap = {}; possibleTargets = {}; if (groups) { groupMap = Ext.Array.toMap(groups); } // Exclude any invalid targets so they don't get used during // a drag. This means targets that are locked or have groups that don't // match for (id in targets) { target = targets[id]; if (!target.isDisabled()) { groupOk = false; targetGroups = target.getGroups(); // If neither has groups, proceed. Otherwise, it // can only be correct if both have groups, then we // need to check if they intersect. If one has groups // and not the other it's not possible to intersect. if (!groupMap && !targetGroups) { groupOk = true; } else if (groupMap && targetGroups) { for (i = 0, len = targetGroups.length; i < len; ++i) { if (groupMap[targetGroups[i]]) { groupOk = true; break; } } } if (groupOk) { possibleTargets[id] = target; } } targetMap[id] = target; elementMap[target.getElement().id] = target; } info.possibleTargets = possibleTargets; info.targetMap = targetMap; info.elementMap = elementMap; Ext.getBody().addCls(me.dragCls); me.processDrag(info); }, /** * Handle a native dragenter event from outside the browser. * @param {Ext.event.Event} e The event. * * @private */ onNativeDragEnter: function(e) { var nativeTargets = this.nativeTargets, target = e.target; // Need to preventDefault to stop browser navigating to the dropped item. e.preventDefault(); if (nativeTargets[nativeTargets.length - 1] !== target) { nativeTargets.push(target); } }, /** * Handle a native dragleave event. * @param {Ext.event.Event} e The event. * * @private */ onNativeDragLeave: function(e) { var nativeTargets = this.nativeTargets; Ext.Array.remove(nativeTargets, e.target); if (nativeTargets.length === 0) { this.nativeDragInfo = null; } }, /** * Handle a native dragover event. * @param {Ext.event.Event} e The event. * * @private */ onNativeDragOver: function(e) { // Need to preventDefault to stop browser navigating to the dropped item. e.preventDefault(); }, /** * Handle a native drop event. * @param {Ext.event.Event} e The event. * * @private */ onNativeDrop: function(e) { // Need to preventDefault to stop browser navigating to the dropped item. e.preventDefault(); this.nativeTargets.length = 0; this.nativeDragInfo = null; }, /** * Process a drag movement. * @param {Ext.drag.Info} info The drag info. * * @private */ processDrag: function(info) { info.setActive(this.getAtPoint(info)); }, /** * Register a target with this group. This is intended to * be called by the target. * @param {Ext.drag.Target} target The target. * * @private */ register: function(target) { this.targets[target.getId()] = target; }, /** * Unregister a target with this group. This is intended to * be called by the target. * @param {Ext.drag.Target} target The target. * * @private */ unregister: function(target) { var id; if (this.destroying) { return; } id = target.getId(); this.targets[id] = null; delete this.targets[id]; } }});