/** * A mixin that gives Ext.Component and Ext.Widget the ability to process the "delegate" * event option. * @private */Ext.define('Ext.mixin.ComponentDelegation', { extend: 'Ext.Mixin', mixinConfig: { id: 'componentDelegation' }, privates: { /** * @private * Adds a listeners with the "delegate" event option. Users should not invoke this * method directly. Use the "delegate" event option of * {@link Ext.util.Observable#addListener addListener} instead. */ addDelegatedListener: function(eventName, fn, scope, options, order, caller, manager) { var me = this, delegatedEvents, event, priority; eventName = Ext.canonicalEventName(eventName); // The following processing of the "order" option is typically done by the // doAddListener method of Ext.mixin.Observable, but that method does not // get called when adding a delegated listener, so we must do the conversion // of order to priority here. order = order || options.order; if (order) { priority = (options && options.priority); if (!priority) { // priority option takes precedence over order // do not mutate the user's options options = options ? Ext.Object.chain(options) : {}; options.priority = me.$orderToPriority[order]; } } //<debug> if (options.target) { Ext.raise("Cannot add '" + eventName + "' listener to component: '" + me.id + "' - 'delegate' and 'target' event options are incompatible."); } //</debug> // Delegated events are tracked in a map keyed by event name, where the values // are instances of Ext.util.Event that track all of the delegate listeners // for the given event name. delegatedEvents = me.$delegatedEvents || (me.$delegatedEvents = {}); event = delegatedEvents[eventName] || (delegatedEvents[eventName] = new Ext.util.Event(me, eventName)); if (event.addListener(fn, scope, options, caller, manager)) { me.$hasDelegatedListeners._incr_(eventName); } }, /** * @private * Clears all listeners that were attached using the "delegate" event option. * Users should not invoke this method directly. It is called automatically as * part of normal {@link Ext.util.Observable#clearListeners clearListeners} * processing. */ clearDelegatedListeners: function() { var me = this, delegatedEvents = me.$delegatedEvents, eventName, event, listenerCount; if (delegatedEvents) { for (eventName in delegatedEvents) { event = delegatedEvents[eventName]; listenerCount = event.listeners.length; event.clearListeners(); me.$hasDelegatedListeners._decr_(eventName, listenerCount); delete delegatedEvents[eventName]; } } }, /** * @private * Fires a delegated event. Users should not invoke this method directly. It * is called automatically by the framework as needed (see the "delegate" event * option of {@link Ext.util.Observable#addListener addListener} for more * details. */ doFireDelegatedEvent: function(eventName, args) { var me = this, ret = true, owner, delegatedEvents, event; // NOTE: $hasDelegatedListeners exists on the prototype of this mixin // which means it is inherited by both Ext.Component and Ext.Widget // This means that if any Component in the universe is listening for // the given eventName in a delegated manner, we need to traverse up the // hierarchy to see if that Component is in fact our ancestor, and if so // we need to fire the event on the ancestor. if (me.$hasDelegatedListeners[eventName]) { owner = me.getRefOwner(); while (owner) { delegatedEvents = owner.$delegatedEvents; if (delegatedEvents) { event = delegatedEvents[eventName]; if (event) { ret = event.fireDelegated(me, args); if (ret === false) { break; } } } owner = owner.getRefOwner(); } } return ret; }, /** * @private * Removes delegated listeners for a given eventName, function, and scope. * Users should not invoke this method directly. It is called automatically by * the framework as part of {@link #removeListener} processing. */ removeDelegatedListener: function(eventName, fn, scope) { var me = this, delegatedEvents = me.$delegatedEvents, event; if (delegatedEvents) { event = delegatedEvents[eventName]; if (event && event.removeListener(fn, scope)) { me.$hasDelegatedListeners._decr_(eventName); if (event.listeners.length === 0) { delete delegatedEvents[eventName]; } } } }, destroyComponentDelegation: function() { if (this.clearPropertiesOnDestroy) { this.$delegatedEvents = null; } } }, onClassMixedIn: function(T) { // When a Component listener is attached with the "delegate" option, it means // All components anywhere in the hierarchy MUST now fire the event just in case // the Component with the delegate listener is an ancestor of the component that // fired the event (otherwise the ancestor will not have a chance to intercept // and process the event - see doFireDelegatedEvent). To ensure that this happens // we chain the class-level hasListeners object of Ext.Component and Ext.Widget // to the single $hasDelegatedListeners object (see class-creation callback // of this class for more info) function HasListeners() {} T.prototype.HasListeners = T.HasListeners = HasListeners; HasListeners.prototype = T.hasListeners = new Ext.mixin.ComponentDelegation.HasDelegatedListeners(); }}, function(ComponentDelegation) { // Here We set up a HasListeners instance ($hasDelegatedListeners) that will be incremented // and decremented any time a Component or Widget adds or removes a listener using the // "delegate" event option. This HasListeners instance is stored on the prototype // of the ComponentDelegation mixin, and therefore will be applied to the prototype // of both Ext.Component and Ext.Widget. This means that Ext.Widget and Ext.Component // (intentionally) share the same $hasDelegatedListeners instance. To understand the // reason for this common instance one must first understand how delegated events are // fired: // // When any component or widget fires an event of any kind, it must call doFireDelegatedEvent // to process possible delegated listeners. The implementation of doFireDelegatedEvent // traverses up the component hierarchy searching for any ancestors that may be listening // for the event in a delegated manner; however, this traversal of the hierarchy can // be skipped if there are no known Components with delegated listeners for the given event. // The $hasDelegatedListeners instance is used to track whether or not there are any // delegated listeners for the given event name for this purpose. Since Ext.Widgets // and Ext.Components can be part of the same hierarchy they must share the same // $hasDelegatesListeners instance. function HasDelegatedListeners() {} ComponentDelegation.HasDelegatedListeners = HasDelegatedListeners; HasDelegatedListeners.prototype = ComponentDelegation.prototype.$hasDelegatedListeners = new Ext.mixin.Observable.HasListeners();});