/* This file is part of Ext JS 4.2 Copyright (c) 2011-2013 Sencha Inc Contact: http://www.sencha.com/contact GNU General Public License Usage 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. If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact. Build date: 2013-05-16 14:36:50 (f9be68accb407158ba2b1be2c226a6ce1f649314) */ /* * This is a derivative of the similarly named class in the YUI Library. * The original license: * Copyright (c) 2006, Yahoo! Inc. All rights reserved. * Code licensed under the BSD License: * http://developer.yahoo.net/yui/license.txt */ /** * DragDropManager is a singleton that tracks the element interaction for * all DragDrop items in the window. Generally, you will not call * this class directly, but it does have helper methods that could * be useful in your DragDrop implementations. */ Ext.define('Ext.dd.DragDropManager', { singleton: true, requires: ['Ext.util.Region'], uses: ['Ext.tip.QuickTipManager'], // shorter ClassName, to save bytes and use internally alternateClassName: ['Ext.dd.DragDropMgr', 'Ext.dd.DDM'], /** * @property {String[]} ids * Two dimensional Array of registered DragDrop objects. The first * dimension is the DragDrop item group, the second the DragDrop * object. * @private */ ids: {}, /** * @property {String[]} handleIds * Array of element ids defined as drag handles. Used to determine * if the element that generated the mousedown event is actually the * handle and not the html element itself. * @private */ handleIds: {}, /** * @property {Ext.dd.DragDrop} dragCurrent * the DragDrop object that is currently being dragged * @private */ dragCurrent: null, /** * @property {Ext.dd.DragDrop[]} dragOvers * the DragDrop object(s) that are being hovered over * @private */ dragOvers: {}, /** * @property {Number} deltaX * the X distance between the cursor and the object being dragged * @private */ deltaX: 0, /** * @property {Number} deltaY * the Y distance between the cursor and the object being dragged * @private */ deltaY: 0, /** * @property {Boolean} preventDefault * Flag to determine if we should prevent the default behavior of the * events we define. By default this is true, but this can be set to * false if you need the default behavior (not recommended) */ preventDefault: true, /** * @property {Boolean} stopPropagation * Flag to determine if we should stop the propagation of the events * we generate. This is true by default but you may want to set it to * false if the html element contains other features that require the * mouse click. */ stopPropagation: true, /** * Internal flag that is set to true when drag and drop has been * intialized * @property initialized * @private */ initialized: false, /** * All drag and drop can be disabled. * @property locked * @private */ locked: false, /** * Called the first time an element is registered. * @private */ init: function() { this.initialized = true; }, /** * @property {Number} POINT * In point mode, drag and drop interaction is defined by the * location of the cursor during the drag/drop */ POINT: 0, /** * @property {Number} INTERSECT * In intersect mode, drag and drop interaction is defined by the * overlap of two or more drag and drop objects. */ INTERSECT: 1, /** * @property {Number} mode * The current drag and drop mode. Default: POINT */ mode: 0, /** * @property {Boolean} [notifyOccluded=false] * This config is only provided to provide old, usually unwanted drag/drop behaviour. * * From ExtJS 4.1.0 onwards, when drop targets are contained in floating, absolutely positioned elements * such as in {@link Ext.window.Window Windows}, which may overlap each other, `over` and `drop` events * are only delivered to the topmost drop target at the mouse position. * * If all targets below that in zIndex order should also receive notifications, set * `notifyOccluded` to `true`. */ notifyOccluded: false, /** * @property {String} dragCls * @readonly * Class to add to the {@link Ext.dd.DragDrop#getDragEl dragged element} of a DragDrop instance. */ dragCls: Ext.baseCSSPrefix + 'dd-drag-current', /** * Runs method on all drag and drop objects * @private */ _execOnAll: function(sMethod, args) { var i, j, oDD; for (i in this.ids) { for (j in this.ids[i]) { oDD = this.ids[i][j]; if (! this.isTypeOfDD(oDD)) { continue; } oDD[sMethod].apply(oDD, args); } } }, /** * Drag and drop initialization. Sets up the global event handlers * @private */ _onLoad: function() { this.init(); var Event = Ext.EventManager; Event.on(document, "mouseup", this.handleMouseUp, this, true); Event.on(document, "mousemove", this.handleMouseMove, this, true); Event.on(window, "unload", this._onUnload, this, true); Event.on(window, "resize", this._onResize, this, true); // Event.on(window, "mouseout", this._test); }, /** * Reset constraints on all drag and drop objs * @private */ _onResize: function(e) { this._execOnAll("resetConstraints", []); }, /** * Lock all drag and drop functionality */ lock: function() { this.locked = true; }, /** * Unlock all drag and drop functionality */ unlock: function() { this.locked = false; }, /** * Is drag and drop locked? * @return {Boolean} True if drag and drop is locked, false otherwise. */ isLocked: function() { return this.locked; }, /** * @property {Object} locationCache * Location cache that is set for all drag drop objects when a drag is * initiated, cleared when the drag is finished. * @private */ locationCache: {}, /** * @property {Boolean} useCache * Set useCache to false if you want to force object the lookup of each * drag and drop linked element constantly during a drag. */ useCache: true, /** * @property {Number} clickPixelThresh * The number of pixels that the mouse needs to move after the * mousedown before the drag is initiated. Default=3; */ clickPixelThresh: 3, /** * @property {Number} clickTimeThresh * The number of milliseconds after the mousedown event to initiate the * drag if we don't get a mouseup event. Default=350 */ clickTimeThresh: 350, /** * @property {Boolean} dragThreshMet * Flag that indicates that either the drag pixel threshold or the * mousdown time threshold has been met * @private */ dragThreshMet: false, /** * @property {Object} clickTimeout * Timeout used for the click time threshold * @private */ clickTimeout: null, /** * @property {Number} startX * The X position of the mousedown event stored for later use when a * drag threshold is met. * @private */ startX: 0, /** * @property {Number} startY * The Y position of the mousedown event stored for later use when a * drag threshold is met. * @private */ startY: 0, /** * Each DragDrop instance must be registered with the DragDropManager. * This is executed in DragDrop.init() * @param {Ext.dd.DragDrop} oDD the DragDrop object to register * @param {String} sGroup the name of the group this element belongs to */ regDragDrop: function(oDD, sGroup) { if (!this.initialized) { this.init(); } if (!this.ids[sGroup]) { this.ids[sGroup] = {}; } this.ids[sGroup][oDD.id] = oDD; }, /** * Removes the supplied dd instance from the supplied group. Executed * by DragDrop.removeFromGroup, so don't call this function directly. * @private */ removeDDFromGroup: function(oDD, sGroup) { if (!this.ids[sGroup]) { this.ids[sGroup] = {}; } var obj = this.ids[sGroup]; if (obj && obj[oDD.id]) { delete obj[oDD.id]; } }, /** * Unregisters a drag and drop item. This is executed in * DragDrop.unreg, use that method instead of calling this directly. * @private */ _remove: function(oDD) { for (var g in oDD.groups) { if (g && this.ids[g] && this.ids[g][oDD.id]) { delete this.ids[g][oDD.id]; } } delete this.handleIds[oDD.id]; }, /** * Each DragDrop handle element must be registered. This is done * automatically when executing DragDrop.setHandleElId() * @param {String} sDDId the DragDrop id this element is a handle for * @param {String} sHandleId the id of the element that is the drag * handle */ regHandle: function(sDDId, sHandleId) { if (!this.handleIds[sDDId]) { this.handleIds[sDDId] = {}; } this.handleIds[sDDId][sHandleId] = sHandleId; }, /** * Utility function to determine if a given element has been * registered as a drag drop item. * @param {String} id the element id to check * @return {Boolean} true if this element is a DragDrop item, * false otherwise */ isDragDrop: function(id) { return ( this.getDDById(id) ) ? true : false; }, /** * Returns the drag and drop instances that are in all groups the * passed in instance belongs to. * @param {Ext.dd.DragDrop} p_oDD the obj to get related data for * @param {Boolean} bTargetsOnly if true, only return targetable objs * @return {Ext.dd.DragDrop[]} the related instances */ getRelated: function(p_oDD, bTargetsOnly) { var oDDs = [], i, j, dd; for (i in p_oDD.groups) { for (j in this.ids[i]) { dd = this.ids[i][j]; if (! this.isTypeOfDD(dd)) { continue; } if (!bTargetsOnly || dd.isTarget) { oDDs[oDDs.length] = dd; } } } return oDDs; }, /** * Returns true if the specified dd target is a legal target for * the specifice drag obj * @param {Ext.dd.DragDrop} oDD the drag obj * @param {Ext.dd.DragDrop} oTargetDD the target * @return {Boolean} true if the target is a legal target for the * dd obj */ isLegalTarget: function (oDD, oTargetDD) { var targets = this.getRelated(oDD, true), i, len; for (i=0, len=targets.length;i me.clickPixelThresh || diffY > me.clickPixelThresh) { me.startDrag(me.startX, me.startY); } } if (me.dragThreshMet) { current.b4Drag(e); current.onDrag(e); if (!current.moveOnly) { me.fireEvents(e, false); } } me.stopEvent(e); return true; }, /** * Iterates over all of the DragDrop elements to find ones we are * hovering over or dropping on * @param {Event} e the event * @param {Boolean} isDrop is this a drop op or a mouseover op? * @private */ fireEvents: function(e, isDrop) { var me = this, dragCurrent = me.dragCurrent, dragEl, oldDragElTop, mousePoint = e.getPoint(), overTarget, overTargetEl, allTargets = [], oldOvers = [], // cache the previous dragOver array outEvts = [], overEvts = [], dropEvts = [], enterEvts = [], xy, needsSort, i, len, sGroup; // If the user did the mouse up outside of the window, we could // get here even though we have ended the drag. if (!dragCurrent || dragCurrent.isLocked()) { return; } // 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 if (!me.notifyOccluded && (!Ext.supports.PointerEvents || Ext.isIE10m || Ext.isOpera) && !(dragCurrent.deltaX < 0 || dragCurrent.deltaY < 0)) { dragEl = dragCurrent.getDragEl(); oldDragElTop = dragEl.style.top; dragEl.style.top = '-10000px'; xy = e.getXY(); e.target = document.elementFromPoint(xy[0], xy[1]); dragEl.style.top = oldDragElTop; } // Check to see if the object(s) we were hovering over is no longer // being hovered over so we can fire the onDragOut event for (i in me.dragOvers) { overTarget = me.dragOvers[i]; if (!me.isTypeOfDD(overTarget)) { continue; } // If notifyOccluded set, we use mouse position if (me.notifyOccluded) { if (!this.isOverTarget(mousePoint, overTarget, me.mode)) { outEvts.push(overTarget); } } // Otherwise we use event source of the mousemove event else { if (!e.within(overTarget.getEl())) { outEvts.push(overTarget); } } oldOvers[i] = true; delete me.dragOvers[i]; } // 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. // This is preparatory to seeing which one(s) we are currently over // Begin by iterating through the ddGroups of which the dragCurrent is a member for (sGroup in dragCurrent.groups) { if ("string" != typeof sGroup) { continue; } // Loop over the registered members of each group, testing each as a potential target for (i in me.ids[sGroup]) { overTarget = me.ids[sGroup][i]; // The target is valid if it is a DD type // And it's got a DOM element // And it's configured to be a drop target // And it's not locked // And the DOM element is fully visible with no hidden ancestors // And it's either not the dragCurrent, or, if it is, tha dragCurrent is configured to not ignore itself. if (me.isTypeOfDD(overTarget) && (overTargetEl = overTarget.getEl()) && (overTarget.isTarget) && (!overTarget.isLocked()) && (Ext.fly(overTargetEl).isVisible(true)) && ((overTarget != dragCurrent) || (dragCurrent.ignoreSelf === false))) { // If notifyOccluded set, we use mouse position if (me.notifyOccluded) { // Only sort by zIndex if there were some which had a floating zIndex value if ((overTarget.zIndex = me.getZIndex(overTargetEl)) !== -1) { needsSort = true; } allTargets.push(overTarget); } // Otherwise we use event source of the mousemove event else { if (e.within(overTarget.getEl())) { allTargets.push(overTarget); break; } } } } } // If there were floating targets, sort the highest zIndex to the top if (needsSort) { Ext.Array.sort(allTargets, me.byZIndex); } // Loop through possible targets, notifying the one(s) we are over. // Usually we only deliver events to the topmost. for (i = 0, len = allTargets.length; i < len; i++) { overTarget = allTargets[i]; // If we are over the overTarget, queue it up to recieve an event of whatever type we are handling if (me.isOverTarget(mousePoint, overTarget, me.mode)) { // look for drop interactions if (isDrop) { dropEvts.push( overTarget ); // look for drag enter and drag over interactions } else { // initial drag over: dragEnter fires if (!oldOvers[overTarget.id]) { enterEvts.push( overTarget ); // subsequent drag overs: dragOver fires } else { overEvts.push( overTarget ); } me.dragOvers[overTarget.id] = overTarget; } // Unless this DragDropManager has been explicitly configured to deliver events to multiple targets, then we are done. if (!me.notifyOccluded) { break; } } } if (me.mode) { if (outEvts.length) { dragCurrent.b4DragOut(e, outEvts); dragCurrent.onDragOut(e, outEvts); } if (enterEvts.length) { dragCurrent.onDragEnter(e, enterEvts); } if (overEvts.length) { dragCurrent.b4DragOver(e, overEvts); dragCurrent.onDragOver(e, overEvts); } if (dropEvts.length) { dragCurrent.b4DragDrop(e, dropEvts); dragCurrent.onDragDrop(e, dropEvts); } } else { // fire dragout events for (i=0, len=outEvts.length; i