/** * A wrapper around a DOM element that allows it to receive drops. * * ## Validity of drag operations * * There are certain conditions that govern whether a {@link Ext.drag.Source source} * and a target can interact. By default (without configuration), all {@link Ext.drag.Source sources} * and targets can interact with each other, the conditions are evaluated in this order: * * ### {@link #isDisabled Disabled State} * If the target is disabled, the {@link Ext.drag.Source source} * cannot interact with it. * * ### {@link #groups Groups} * Both the {@link Ext.drag.Source source} and target can belong to multiple groups. * They may interact if: * - Neither has a group * - Both have one (or more) of the same group * * ### {@link #accepts Accept} * This method is called each time a {@link Ext.drag.Source source} enters this * target. If the method returns `false`, the drag is not considered valid. * * ## Asynchronous drop processing * * When the drop completes, the {@link #drop} event will fire, however the underlying data * may not be ready to be consumed. By returning a {@link Ext.Promise Promise} from the data, * it allows either: * - The data to be fetched (either from a remote source or generated if expensive). * - Any validation to take place before the drop is finalized. * * Once the promise is {@link Ext.Promise#resolve resolved} or {@link Ext.Promise#resolve rejected}, * further processing can be completed. * * Validation example: * * * var confirmSource = new Ext.drag.Source({ * element: dragEl, * describe: function(info) { * // Provide the data up front * info.setData('records', theRecords); * } * }); * * var confirmTarget = new Ext.drag.Target({ * element: dropEl, * listeners: { * drop: function(target, info) { * Ext.MessageBox.confirm('Really', 'Are you sure?', function(btn) { * if (btn === 'yes') { * info.getData('records').then(function(data) { * // Process the data * }); * } * }); * } * } * }); * * * Remote data example: * * var fetchSource = new Ext.drag.Source({ * element: dragEl, * // The resulting drag data will be a binary blob * // of image data, we don't want to fetch it up front, so * // pass a callback to be executed when data is requested. * describe: function(info) { * info.setData('image', function() { * return Ext.Ajax.request({ * url: 'data.json' * // some options * }).then(function(result) { * var imageData; * // Do some post-processing * return imageData; * }, function() { * return Ext.Promise.reject('Something went wrong!'); * }); * }); * } * }); * * var fetchTarget = new Ext.drag.Target({ * element: dropEl, * accepts: function(info) { * return info.types.indexOf('image') > -1; * }, * listeners: { * drop: function(target, info) { * info.getData('image').then(function() { * // All good, show the image * }, function() { * // Handle failure case * }); * } * } * }); * */Ext.define('Ext.drag.Target', { extend: 'Ext.drag.Item', requires: ['Ext.drag.Manager'], defaultIdPrefix: 'target-', config: { /** * @cfg {String} invalidCls * A class to add to the {@link #element} when an * invalid drag is over this target. */ invalidCls: '', /** * @cfg {String} validCls * A class to add to the {@link #element} when an * invalid drag is over this target. */ validCls: '' }, /** * @cfg {Function} accepts * See {@link #method-accepts}. */ /** * @event beforedrop * Fires before a valid drop occurs. Return `false` to prevent the drop from * completing. * * @param {Ext.drag.Target} this This target. * @param {Ext.drag.Info} info The drag info. */ /** * @event drop * Fires when a valid drop occurs. * * @param {Ext.drag.Target} this This target. * @param {Ext.drag.Info} info The drag info. */ /** * @event dragenter * Fires when a drag enters this target. * * @param {Ext.drag.Target} this This target. * @param {Ext.drag.Info} info The drag info. */ /** * @event dragleave * Fires when a source leaves this target. * * @param {Ext.drag.Target} this This target. * @param {Ext.drag.Info} info The drag info. */ /** * @event dragmove * Fires when a drag moves while inside this target. * * @param {Ext.drag.Target} this This target. * @param {Ext.drag.Info} info The drag info. */ constructor: function(config) { var me = this, accepts = config && config.accepts; if (accepts) { me.accepts = accepts; // Don't mutate the object the user passed. Need to do this // here otherwise initConfig will complain about writing over // the method. config = Ext.apply({}, config); delete config.accepts; } me.callParent([config]); Ext.drag.Manager.register(me); }, /** * Called each time a {@link Ext.drag.Source source} enters this target. * Allows this target to indicate whether it will interact with * the given drag. Determined after {@link #isDisabled} and * {@link #groups} checks. If either of the aforementioned conditions * means the target is not valid, this will not be called. * * Defaults to returning `true`. * * @param {Ext.drag.Info} info The drag info. * @return {Boolean} `true` if the drag is valid for this target. * * @protected */ accepts: function(info) { return true; }, /** * @inheritdoc */ disable: function() { this.callParent(); this.setupListeners(null); }, /** * @inheritdoc */ enable: function() { this.callParent(); this.setupListeners(); }, /** * @method * Called before a drag finishes on this target. Return `false` to veto * the drop. * @param {Ext.drag.Info} info The drag info. * @return {Boolean} `false` to veto the drop. * * @protected * @template */ beforeDrop: Ext.emptyFn, /** * @method * Called when a drag is dropped on this target. * @param {Ext.drag.Info} info The drag info. * * @protected * @template */ onDrop: Ext.emptyFn, /** * @method * Called when a drag enters this target. * @param {Ext.drag.Info} info The drag info. * * @protected * @template */ onDragEnter: Ext.emptyFn, /** * @method * Called when a source leaves this target. * @param {Ext.drag.Info} info The drag info. * * @protected * @template */ onDragLeave: Ext.emptyFn, /** * @method * Called when a drag is moved while inside this target. * @param {Ext.drag.Info} info The drag info. * * @protected * @template */ onDragMove: Ext.emptyFn, updateInvalidCls: function(invalidCls, oldInvalidCls) { var info = this.info; this.doUpdateCls(info && !info.valid, invalidCls, oldInvalidCls); }, updateValidCls: function(validCls, oldValidCls) { var info = this.info; this.doUpdateCls(info && info.valid, validCls, oldValidCls); }, destroy: function() { Ext.drag.Manager.unregister(this); this.callParent(); }, privates: { /** * Removes a class and replaces it with a new one, if the old class * was already on the element. * * @param {Boolean} needsAdd `true` if the new class needs adding. * @param {String} cls The new class to add. * @param {String} oldCls The old class to remove. * * @private */ doUpdateCls: function(needsAdd, cls, oldCls) { var el = this.getElement(); if (oldCls) { el.removeCls(oldCls); } if (cls && needsAdd) { el.addCls(cls); } }, /** * @inheritdoc */ getElListeners: function() { return { dragenter: 'handleNativeDragEnter', dragleave: 'handleNativeDragLeave', dragover: 'handleNativeDragMove', drop: 'handleNativeDrop' }; }, /** * Called when a drag is dropped on this target. * @param {Ext.drag.Info} info The drag info. * * @private */ handleDrop: function(info) { var me = this, hasListeners = me.hasListeners, valid = info.valid; me.getElement().removeCls([me.getInvalidCls(), me.getValidCls()]); if (valid && me.beforeDrop(info) !== false) { if (hasListeners.beforedrop && me.fireEvent('beforedrop', me, info) === false) { return false; } me.onDrop(info); if (hasListeners.drop) { me.fireEvent('drop', me, info); } } else { return false; } }, /** * Called when a drag enters this target. * @param {Ext.drag.Info} info The drag info. * * @private */ handleDragEnter: function(info) { var me = this, cls = info.valid ? me.getValidCls() : me.getInvalidCls(); if (cls) { me.getElement().addCls(cls); } me.onDragEnter(info); if (me.hasListeners.dragenter) { me.fireEvent('dragenter', me, info); } }, /** * Called when a source leaves this target. * @param {Ext.drag.Info} info The drag info. * * @private */ handleDragLeave: function(info) { var me = this; me.getElement().removeCls([me.getInvalidCls(), me.getValidCls()]); me.onDragLeave(info); if (me.hasListeners.dragleave) { me.fireEvent('dragleave', me, info); } }, /** * Called when a drag is moved while inside this target. * @param {Ext.drag.Info} info The drag info. * * @private */ handleDragMove: function(info) { var me = this; me.onDragMove(info); if (me.hasListeners.dragmove) { me.fireEvent('dragmove', me, info); } }, /** * Handle a native drag enter. * @param {Ext.event.Event} e The event. * * @private */ handleNativeDragEnter: function(e) { var me = this, info = Ext.drag.Manager.getNativeDragInfo(e); info.onNativeDragEnter(me, e); if (me.hasListeners.dragenter) { me.fireEvent('dragenter', me, info); } }, /** * Handle a native drag leave. * @param {Ext.event.Event} e The event. * * @private */ handleNativeDragLeave: function(e) { var me = this, info = Ext.drag.Manager.getNativeDragInfo(e); info.onNativeDragLeave(me, e); if (me.hasListeners.dragleave) { me.fireEvent('dragleave', me, info); } }, /** * Handle a native drag move. * @param {Ext.event.Event} e The event. * * @private */ handleNativeDragMove: function(e) { var me = this, info = Ext.drag.Manager.getNativeDragInfo(e); info.onNativeDragMove(me, e); if (me.hasListeners.dragmove) { me.fireEvent('dragmove', me, info); } }, /** * Handle a native drop. * @param {Ext.event.Event} e The event. * * @private */ handleNativeDrop: function(e) { var me = this, hasListeners = me.hasListeners, info = Ext.drag.Manager.getNativeDragInfo(e), valid = info.valid; info.onNativeDrop(me, e); if (valid) { if (hasListeners.beforedrop && me.fireEvent('beforedrop', me, info) === false) { return; } if (hasListeners.drop) { me.fireEvent('drop', me, info); } } } }});