// @tag core
/**
 * Base class that provides a common interface for publishing events. Subclasses are
 * expected to have a property "events" which is populated as event listeners register,
 * and, optionally, a property "listeners" with configured listeners defined.
 *
 * *Note*: This mixin requires the constructor to be called, which is typically done
 * during the construction of your object. The Observable constructor will call 
 * {@link #initConfig}, so it does not need to be called a second time.
 *
 * For example:
 *
 *     Ext.define('Employee', {
 *         mixins: ['Ext.mixin.Observable'],
 *
 *         config: {
 *             name: ''
 *         },
 *
 *         constructor: function (config) {
 *             // The `listeners` property is processed to add listeners and the config
 *             // is applied to the object.
 *             this.mixins.observable.constructor.call(this, config);
 *             // Config has been initialized
 *             console.log(this.getEmployeeName());
 *         }
 *     });
 *
 * This could then be used like this:
 *
 *     var newEmployee = new Employee({
 *         name: employeeName,
 *         listeners: {
 *             quit: function() {
 *                 // By default, "this" will be the object that fired the event.
 *                 alert(this.getName() + " has quit!");
 *             }
 *         }
 *     });
 */
Ext.define('Ext.mixin.Observable', function(Observable) {
    var emptyFn = Ext.emptyFn,
        emptyArray = [],
        arrayProto = Array.prototype,
        arraySlice = arrayProto.slice,
 
        // Destroyable class which removes listeners
        ListenerRemover = function(observable) {
 
            // Passed a ListenerRemover: return it
            if (observable instanceof ListenerRemover) {
                return observable;
            }
 
            this.observable = observable;
 
            // Called when addManagedListener is used with the event source as the second arg:
            // (owner, eventSource, args...)
            if (arguments[1].isObservable) {
                this.managedListeners = true;
            }
            
            this.args = arraySlice.call(arguments, 1);
        },
        
        // These properties should not be nulled during Base destroy(),
        // we will take care of them in destroyObservable()
        protectedProps = ['events', 'hasListeners', 'managedListeners', 'eventedBeforeEventNames'];
 
    ListenerRemover.prototype.destroy = function() {
        var me = this,
            args = me.args,
            observable = me.observable,
 
            // Extract the element reference from the options object.
            // Single arg form first, options is args[3] in multi arg form.
            elementName = args[0].element || (args[3] && args[3].element);
 
        // If it was an element listener, then the listener is added to that element reference.
        if (elementName) {
            //<debug>
            if (Ext.Array.indexOf(observable.referenceList, elementName) === -1) {
                Ext.Logger.error("Destroying event listener with an invalid element reference " +
                                 "of '" + elementName +
                                 "' for this component. Available values are: '" +
                                 observable.referenceList.join("', '") + "'", observable);
            }
            //</debug>
            
            observable = observable[elementName];
        }
 
        // If that observable is already destroyed, all its listeners were cleared
        if (!observable.destroyed) {
            observable[me.managedListeners ? 'mun' : 'un'].apply(observable, me.args);
        }
        
        me.destroy = Ext.emptyFn;
    };
 
    return {
        extend: 'Ext.Mixin',
        mixinConfig: {
            id: 'observable',
            after: {
                destroy: 'destroyObservable'
            }
        },
 
        requires: [
            'Ext.util.Event'
        ],
 
        mixins: ['Ext.mixin.Identifiable'],
 
        statics: {
            /**
            * Removes **all** added captures from the Observable.
            *
            * @param {Ext.util.Observable} o The Observable to release
            * @static
            */
            releaseCapture: function(o) {
                o.fireEventArgs = this.prototype.fireEventArgs;
            },
 
            /**
             * Starts capture on the specified Observable. All events will be passed to the supplied
             * function with the event name + standard signature of the event **before** the event
             * is fired. If the supplied function returns false, the event will not fire.
             *
             * @param {Ext.util.Observable} o The Observable to capture events from.
             * @param {Function} fn The function to call when an event is fired.
             * @param {Object} scope (optional) The scope (`this` reference) in which the function
             * is executed. Defaults to the Observable firing the event.
             * @static
             */
            capture: function(o, fn, scope) {
                // We're capturing calls to fireEventArgs to avoid duplication of events;
                // however fn expects fireEvent's signature so we have to convert it here.
                // To avoid unnecessary conversions, observe() below is aware of the changes
                // and will capture fireEventArgs instead.
                var newFn = function(eventName, args) {
                    return fn.apply(scope, [eventName].concat(args));
                };
                
                this.captureArgs(o, newFn, scope);
            },
            
            /**
             * @method
             * @private
             */
            captureArgs: function(o, fn, scope) {
                o.fireEventArgs = Ext.Function.createInterceptor(o.fireEventArgs, fn, scope);
            },
 
            /**
            * Sets observability on the passed class constructor.
            *
            * This makes any event fired on any instance of the passed class also fire a single
            * event through the **class** allowing for central handling of events on many instances
            * at once.
            *
            * Usage:
            *
            *     Ext.util.Observable.observe(Ext.data.Connection);
            *     Ext.data.Connection.on('beforerequest', function(con, options) {
            *         console.log('Ajax request made to ' + options.url);
            *     });
            *
            * @param {Function} cls The class constructor to make observable.
            * @param {Object} listeners An object containing a series of listeners to 
            * add. See {@link Ext.util.Observable#addListener addListener}.
            * @static
            */
            observe: function(cls, listeners) {
                if (cls) {
                    if (!cls.isObservable) {
                        Ext.applyIf(cls, new this());
                        this.captureArgs(cls.prototype, cls.fireEventArgs, cls);
                    }
                    
                    if (Ext.isObject(listeners)) {
                        cls.on(listeners);
                    }
                }
                
                return cls;
            },
 
            /**
            * @method prepareClass
            * Prepares a given class for observable instances. This method is called when a
            * class derives from this class or uses this class as a mixin.
            * @param {Function} T The class constructor to prepare.
            * @param {Ext.util.Observable} mixin The mixin if being used as a mixin.
            * @param {Object} data The raw class creation data if this is an extend.
            * @private
            */
            prepareClass: function(T, mixin, data) {
                // T.hasListeners is the object to track listeners on class T. This object's
                // prototype (__proto__) is the "hasListeners" of T.superclass.
 
                // Instances of T will create "hasListeners" that have T.hasListeners as their
                // immediate prototype (__proto__).
 
                var listeners = T.listeners = [],
                    // If this function was called as a result of an "onExtended", it will
                    // receive the class as "T", but the members will not yet have been
                    // applied to the prototype. If this is the case, just grab listeners
                    // off of the raw data object.
                    target = data || T.prototype,
                    targetListeners = target.listeners,
                    superListeners = mixin ? mixin.listeners : T.superclass.self.listeners,
                    scope, namedScope, i, len;
 
                // Process listeners that have been declared on the class body. These
                // listeners must not override each other, but each must be added
                // separately. This is accomplished by maintaining a nested array
                // of listeners for the class and it's superclasses/mixins
                if (superListeners) {
                    listeners.push(superListeners);
                }
 
                if (targetListeners) {
                    // Allow listener scope resolution mechanism to know if the listeners
                    // were declared on the class.  This is only necessary when scope
                    // is unspecified, or when scope is 'controller'.  We use special private
                    // named scopes of "self" and "self.controller" to indicate either
                    // unspecified scope, or scope declared as controller on the class
                    // body.  To avoid iterating the listeners object multiple times, we
                    // only put this special scope on the outermost object at this point
                    // and allow addListener to handle scope:'controller' declared on
                    // inner objects of the listeners config.
                    scope = targetListeners.scope;
                    
                    if (!scope) {
                        targetListeners.scope = 'self';
                    }
                    else {
                        namedScope = Ext._namedScopes[scope];
                        
                        if (namedScope && namedScope.isController) {
                            targetListeners.scope = 'self.controller';
                        }
                    }
 
                    listeners.push(targetListeners);
 
                    // After adding the target listeners to the declared listeners array
                    // we can delete it off of the prototype (or data object).  This ensures
                    // that we don't attempt to add the listeners twice, once during
                    // addDeclaredListeners, and again when we add this.listeners in the
                    // constructor.
                    target.listeners = null;
                }
 
                if (!T.HasListeners) {
                    // We create a HasListeners "class" for this class. The "prototype" of the
                    // HasListeners class is an instance of the HasListeners class associated
                    // with this class's super class (or with Observable).
                    // eslint-disable-next-line vars-on-top
                    var HasListeners = function() {},
                        SuperHL = T.superclass.HasListeners || (mixin && mixin.HasListeners) ||
                                Observable.HasListeners;
 
                    // Make the HasListener class available on the class and its prototype:
                    T.prototype.HasListeners = T.HasListeners = HasListeners;
 
                    // And connect its "prototype" to the new HasListeners of our super class
                    // (which is also the class-level "hasListeners" instance).
                    HasListeners.prototype = T.hasListeners = new SuperHL();
                }
                
                // Reusing a variable here
                scope = T.prototype.$noClearOnDestroy || {};
                
                for (= 0, len = protectedProps.length; i < len; i++) {
                    scope[protectedProps[i]] = true;
                }
                
                T.prototype.$noClearOnDestroy = scope;
            }
        },
 
        /* End Definitions */
 
        /**
         * @cfg {Object} listeners
         *
         * A config object containing one or more event handlers to be added to this object during
         * initialization. This should be a valid listeners config object as specified in the 
         * {@link Ext.util.Observable#addListener addListener} example for attaching 
         * multiple handlers at once.
         *
         * **DOM events from Ext JS {@link Ext.Component Components}**
         *
         * While _some_ Ext JS Component classes export selected DOM events (e.g. "click",
         * "mouseover" etc), this is usually only done when extra value can be added. For example
         * the {@link Ext.view.View DataView}'s **`{@link Ext.view.View#itemclick itemclick}`**
         * event passing the node clicked on. To access DOM events directly from a child element
         * of a Component, we need to specify the `element` option to identify the Component
         * property to add a DOM listener to:
         *
         *     new Ext.panel.Panel({
         *         width: 400,
         *         height: 200,
         *         dockedItems: [{
         *             xtype: 'toolbar'
         *         }],
         *         listeners: {
         *             click: {
         *                 element: 'el', //bind to the underlying el property on the panel
         *                 fn: function(){ console.log('click el'); }
         *             },
         *             dblclick: {
         *                 element: 'body', //bind to the underlying body property on the panel
         *                 fn: function(){ console.log('dblclick body'); }
         *             }
         *         }
         *     });
         */
 
        /**
         * @property {Boolean} isObservable
         * `true` in this class to identify an object as an instantiated Observable, or subclass
         * thereof.
         */
        isObservable: true,
        
        /**
         * @private
         * We don't want the base destructor to clear the prototype because
         * our destroyObservable handler must be called the very last. It will take care
         * of the prototype after completing Observable destruction sequence.
         */
        $vetoClearingPrototypeOnDestroy: true,
        
        /**
         * @private
         * Initial suspended call count. Incremented when {@link #suspendEvents} is called,
         * decremented when {@link #resumeEvents} is called.
         */
        eventsSuspended: 0,
 
        /**
         * @property {Object} hasListeners
         * @readonly
         * This object holds a key for any event that has a listener. The listener may be set
         * directly on the instance, or on its class or a super class (via {@link #observe}) or
         * on the {@link Ext.app.EventBus MVC EventBus}. The values of this object are truthy
         * (a non-zero number) and falsy (0 or undefined). They do not represent an exact count
         * of listeners. The value for an event is truthy if the event must be fired and is
         * falsy if there is no need to fire the event.
         * 
         * The intended use of this property is to avoid the expense of fireEvent calls when
         * there are no listeners. This can be particularly helpful when one would otherwise
         * have to call fireEvent hundreds or thousands of times. It is used like this:
         * 
         *      if (this.hasListeners.foo) {
         *          this.fireEvent('foo', this, arg1);
         *      }
         */
 
        constructor: function(config) {
            var me = this,
                self = me.self,
                declaredListeners, listeners, bubbleEvents, len, i;
 
            // Observable can be extended and/or mixed in at multiple levels in a Class
            // hierarchy, and may have its constructor invoked multiple times for a given
            // instance.  The following ensures we only perform initialization the first
            // time the constructor is called.
            if (me.$observableInitialized) {
                return;
            }
            
            me.$observableInitialized = true;
 
            // This double assignment is intentional - it works around a strange JIT
            // bug that prevents this.hasListeners from being assigned in some cases on
            // some versions of iOS and iOS simulator.
            // (This bug manifests itself in the unit tests for Ext.data.NodeInterface
            // where we repeatedly create tree nodes in each spec.  Sometimes node.hasListeners
            // is undefined immediately after node construction).
            // A similar issue occurs with the data property of Ext.data.Model (see
            // constructor)
            me.hasListeners = me.hasListeners = new me.HasListeners();
 
            me.eventedBeforeEventNames = {};
 
            me.events = me.events || {};
 
            declaredListeners = self.listeners;
            
            if (declaredListeners && !me._addDeclaredListeners(declaredListeners)) {
                // Nulling out declared listeners allows future instances to avoid
                // recursing into the declared listeners arrays if the first instance
                // discovers that there are no declarative listeners in its hierarchy
                self.listeners = null;
            }
 
            listeners = (config && config.listeners) || me.listeners;
 
            if (listeners) {
                if (listeners instanceof Array) {
                    // Support for listeners declared as an array:
                    //
                    //     listeners: [
                    //         { foo: fooHandler },
                    //         { bar: barHandler }
                    //     ]
                    for (= 0, len = listeners.length; i < len; ++i) {
                        me.addListener(listeners[i]);
                    }
                }
                else {
                    me.addListener(listeners);
                }
            }
 
            bubbleEvents = (config && config.bubbleEvents) || me.bubbleEvents;
 
            if (bubbleEvents) {
                me.enableBubble(bubbleEvents);
            }
 
            if (me.$applyConfigs) {
                // Ext.util.Observable applies config properties directly to the instance
                if (config) {
                    Ext.apply(me, config);
                }
            }
            else {
                // Ext.mixin.Observable uses the config system
                me.initConfig(config);
            }
 
            if (listeners) {
                // Set as an instance property to preempt the prototype in case any are set there.
                // Prevents listeners from being added multiple times if this constructor
                // is called more than once by multiple parties in the inheritance hierarchy
                me.listeners = null;
            }
        },
 
        onClassExtended: function(T, data) {
            if (!T.HasListeners) {
                // Some classes derive from us and some others derive from those classes. All
                // of these are passed to this method.
                Observable.prepareClass(T, T.prototype.$observableMixedIn ? undefined : data);
            }
        },
 
        /**
         * @private
         * Matches options property names within a listeners specification object - property names
         * which are never used as event names.
         */
        $eventOptions: {
            scope: 1,
            delay: 1,
            buffer: 1,
            onFrame: 1,
            single: 1,
            args: 1,
            destroyable: 1,
            priority: 1,
            order: 1
        },
 
        $orderToPriority: {
            before: 100,
            current: 0,
            after: -100
        },
 
        /**
         * Adds declarative listeners as nested arrays of listener objects.
         * @private
         * @param {Array} listeners 
         * @return {Boolean} `true` if any listeners were added
         */
        _addDeclaredListeners: function(listeners) {
            var me = this;
 
            if (listeners instanceof Array) {
                Ext.each(listeners, me._addDeclaredListeners, me);
            }
            else {
                me._addedDeclaredListeners = true;
                me.addListener(listeners);
            }
 
            return me._addedDeclaredListeners;
        },
 
        /**
         * The addManagedListener method is used when some object (call it "A") is listening 
         * to an event on another observable object ("B") and you want to remove that listener 
         * from "B" when "A" is destroyed. This is not an issue when "B" is destroyed because
         * all of its listeners will be removed at that time.
         *
         * Example:
         *
         *     Ext.define('Foo', {
         *         extend: 'Ext.Component',
         *     
         *         initComponent: function () {
         *             this.addManagedListener(MyApp.SomeSharedMenu, 'show', this.doSomething);
         *             this.callParent();
         *         }
         *     });
         *
         * As you can see, when an instance of Foo is destroyed, it ensures that the 'show' 
         * listener on the menu (`MyApp.SomeGlobalSharedMenu`) is also removed.
         *
         * As of version 5.1 it is no longer necessary to use this method in most cases because
         * listeners are automatically managed if the scope object provided to 
         * {@link Ext.util.Observable#addListener addListener} is an Observable instance. 
         * However, if the observable instance and scope are not the same object you 
         * still need to use `mon` or `addManagedListener` if you want the listener to be 
         * managed.
         *
         * @param {Ext.util.Observable/Ext.dom.Element} item The item to which to add
         * a listener/listeners.
         * @param {Object/String} ename The event name, or an object containing event name
         * properties.
         * @param {Function/String} fn (optional) If the `ename` parameter was an event 
         * name, this is the handler function or the name of a method on the specified 
         * `scope`.
         * @param {Object} scope (optional) If the `ename` parameter was an event name, this is
         * the scope (`this` reference) in which the handler function is executed.
         * @param {Object} options (optional) If the `ename` parameter was an event name, this is
         * the {@link Ext.util.Observable#addListener addListener} options.
         * @param {Boolean} noDestroy (private)
         * @return {Object} **Only when the `destroyable` option is specified.**
         *
         * A `Destroyable` object. An object which implements the `destroy` method which removes
         * all listeners added in this call. For example:
         *
         *     this.btnListeners = myButton.mon({
         *         destroyable: true
         *         mouseover:   function() { console.log('mouseover'); },
         *         mouseout:    function() { console.log('mouseout'); },
         *         click:       function() { console.log('click'); }
         *     });
         *
         * And when those listeners need to be removed:
         *
         *     Ext.destroy(this.btnListeners);
         *
         * or
         *
         *     this.btnListeners.destroy();
         */
        addManagedListener: function(item, ename, fn, scope, options, noDestroy) {
            var me = this,
                config, passedOptions;
            
            me.managedListeners = me.managedListeners || [];
 
            if (typeof ename !== 'string') {
                // When creating listeners using the object form, allow caller to override
                // the default of using the listeners object as options.
                // This is used by relayEvents, when adding its relayer so that it does not
                // contribute a spurious options param to the end of the arg list.
                passedOptions = arguments.length > 4 ? options : ename;
 
                options = ename;
                
                for (ename in options) {
                    if (options.hasOwnProperty(ename)) {
                        config = options[ename];
                        
                        if (!item.$eventOptions[ename]) {
                            // recurse, but pass the noDestroy parameter as true so that lots
                            // of individual Destroyables are not created.
                            // We create a single one at the end if necessary.
                            me.addManagedListener(item, ename, config.fn || config,
                                                  config.scope || options.scope || scope,
                                                  config.fn ? config : passedOptions, true);
                        }
                    }
                }
                
                if (options && options.destroyable) {
                    return new ListenerRemover(me, item, options);
                }
            }
            else {
                if (fn !== emptyFn) {
                    item.doAddListener(ename, fn, scope, options, null, me, me);
 
                    // The 'noDestroy' flag is sent if we're looping through a hash
                    // of listeners passing each one to addManagedListener separately
                    if (!noDestroy && options && options.destroyable) {
                        return new ListenerRemover(me, item, ename, fn, scope);
                    }
                }
            }
        },
 
        /**
         * Removes listeners that were added by the {@link #mon} method.
         *
         * @param {Ext.util.Observable/Ext.dom.Element} item The item from which to remove
         * a listener/listeners.
         * @param {Object/String} ename The event name, or an object containing event name
         * properties.
         * @param {Function} fn (optional) If the `ename` parameter was an event name, this is
         * the handler function.
         * @param {Object} scope (optional) If the `ename` parameter was an event name, this is
         * the scope (`this` reference) in which the handler function is executed.
         */
        removeManagedListener: function(item, ename, fn, scope) {
            var me = this,
                options, config, managedListeners, length, i;
            
            if (item.$observableDestroyed) {
                return;
            }
 
            if (typeof ename !== 'string') {
                options = ename;
                
                for (ename in options) {
                    if (options.hasOwnProperty(ename)) {
                        config = options[ename];
                        
                        if (!item.$eventOptions[ename]) {
                            me.removeManagedListener(item, ename, config.fn || config,
                                                     config.scope || options.scope || scope);
                        }
                    }
                }
            }
            else {
                managedListeners = me.managedListeners ? me.managedListeners.slice() : [];
 
                ename = Ext.canonicalEventName(ename);
 
                for (= 0, length = managedListeners.length; i < length; i++) {
                    me.removeManagedListenerItem(false, managedListeners[i], item, ename, fn,
                                                 scope);
                }
            }
        },
 
        /**
         * Fires the specified event with the passed parameters (minus the event name, plus
         * the `options` object passed to {@link Ext.util.Observable#addListener addListener}).
         *
         * An event may be set to bubble up an Observable parent hierarchy (See
         * {@link Ext.Component#getBubbleTarget}) by calling {@link #enableBubble}.
         *
         * @param {String} eventName The name of the event to fire.
         * @param {Object...} args Variable number of parameters are passed to handlers.
         * @return {Boolean} returns false if any of the handlers return false otherwise it
         * returns true.
         */
        fireEvent: function(eventName) {
            return this.fireEventArgs(eventName, arraySlice.call(arguments, 1));
        },
        
        /**
         * Gets the default scope for firing late bound events (string names with
         * no scope attached) at runtime.
         * @param {Object} [defaultScope=this] The default scope to return if none is found.
         * @return {Object} The default event scope
         * @protected
         */
        resolveListenerScope: function(defaultScope) {
            var namedScope = Ext._namedScopes[defaultScope];
            
            if (namedScope) {
                //<debug>
                if (namedScope.isController) {
                    Ext.raise('scope: "controller" can only be specified on classes ' +
                              'that derive from Ext.Component or Ext.Widget');
                }
                //</debug>
 
                if (namedScope.isSelf || namedScope.isThis) {
                    defaultScope = null;
                }
            }
            
            return defaultScope || this;
        },
 
        /**
         * Fires the specified event with the passed parameter list.
         *
         * An event may be set to bubble up an Observable parent hierarchy
         * (See {@link Ext.Component#getBubbleTarget}) by calling {@link #enableBubble}.
         *
         * @param {String} eventName The name of the event to fire.
         * @param {Object[]} args An array of parameters which are passed to handlers.
         * @return {Boolean} returns false if any of the handlers return false otherwise
         * it returns true.
         */
        fireEventArgs: function(eventName, args) {
            var me = this,
                // no need to make events since we need an Event with listeners
                events = me.events,
                ret = true,
                event;
 
            eventName = Ext.canonicalEventName(eventName);
            event = events && events[eventName];
            
            // Only continue firing the event if there are listeners to be informed.
            // Bubbled events will always have a listener count, so will be fired.
            if (me.hasListeners[eventName]) {
                ret = me.doFireEvent(eventName, args || emptyArray, event ? event.bubble : false);
            }
            
            return ret;
        },
 
        /**
         * Fires the specified event with the passed parameters and executes a function (action).
         * By default, the action function will be executed after any "before" event handlers
         * (as specified using the `order` option of 
         * `{@link Ext.util.Observable#addListener addListener}`), but before any other 
         * handlers are fired.  This gives the "before" handlers an opportunity to 
         * cancel the event by returning `false`, and prevent the action function from 
         * being called.
         *
         * The action can also be configured to run after normal handlers, but before any "after"
         * handlers (as specified using the `order` event option) by passing `'after'`
         * as the `order` parameter.  This configuration gives any event handlers except
         * for "after" handlers the opportunity to cancel the event and prevent the action
         * function from being called.
         *
         * @param {String} eventName The name of the event to fire.
         * @param {Array} args Arguments to pass to handlers and to the action function.
         * @param {Function} fn The action function.
         * @param {Object} [scope] The scope (`this` reference) in which the handler function is
         * executed. **If omitted, defaults to the object which fired the event.**
         * @param {Object} [options] Event options for the action function.  Accepts any
         * of the options of `{@link Ext.util.Observable#addListener addListener}`
         * @param {String} [order='before'] The order to call the action function relative
         * too the event handlers (`'before'` or `'after'`).  Note that this option is
         * simply used to sort the action function relative to the event handlers by "priority".
         * An order of `'before'` is equivalent to a priority of `99.5`, while an order of
         * `'after'` is equivalent to a priority of `-99.5`.  See the `priority` option
         * of `{@link Ext.util.Observable#addListener addListener}` for more details.
         * @deprecated 5.5 Use {@link #fireEventedAction} instead.
         */
        fireAction: function(eventName, args, fn, scope, options, order) {
            // The historical behaviour has been to default the scope to `this`.
            if (typeof fn === 'string' && !scope) {
                fn = this[fn];
            }
            
            // chain options to avoid mutating the user's options object
            options = options ? Ext.Object.chain(options) : {};
            options.single = true;
            options.priority = ((order === 'after') ? -99.5 : 99.5);
 
            this.doAddListener(eventName, fn, scope, options);
            this.fireEventArgs(eventName, args);
        },
 
        $eventedController: {
            _paused: 1,
 
            pause: function() {
                ++this._paused;
            },
 
            resume: function() {
                var me = this,
                    fn = me.fn,
                    scope = me.scope,
                    fnArgs = me.fnArgs,
                    args, ret;
 
                if (! --me._paused) {
                    if (fn) {
                        args = Ext.Array.slice(fnArgs || me.args);
                        
                        if (fnArgs === false) {
                            // Passing false will remove the first item (typically the owner)
                            args.shift();
                        }
 
                        me.fn = null; // only call fn once
                        args.push(me);
 
                        if (Ext.isFunction(fn)) {
                            ret = fn.apply(scope, args);
                        }
                        else if (scope && Ext.isString(fn) && Ext.isFunction(scope[fn])) {
                            ret = scope[fn].apply(scope, args);
                        }
 
                        if (ret === false) {
                            return false;
                        }
                    }
 
                    if (!me._paused) {  // fn could have paused us
                        return me.owner.fireEventArgs(me.eventName, me.args);
                    }
                }
            }
        },
 
        /**
         * Fires the specified event with the passed parameters and executes a function (action).
         * Evented Actions will automatically dispatch a 'before' event passing. This event will
         * be given a special controller that allows for pausing/resuming of the event flow.
         *
         * By pausing the controller the updater and events will not run until resumed. Pausing,
         * however, will not stop the processing of any other before events.
         *
         * @param {String} eventName The name of the event to fire.
         * @param {Array} args Arguments to pass to handlers and to the action function.
         * @param {Function/String} fn The action function.
         * @param {Object} [scope] The scope (`this` reference) in which the handler function is
         * executed. **If omitted, defaults to the object which fired the event.**
         * @param {Array/Boolean} [fnArgs] Optional arguments for the action `fn`. If not
         * given, the normal `args` will be used to call `fn`. If `false` is passed, the
         * `args` are used but if the first argument is this instance it will be removed
         * from the args passed to the action function.
         */
        fireEventedAction: function(eventName, args, fn, scope, fnArgs) {
            var me = this,
                eventedBeforeEventNames = me.eventedBeforeEventNames,
                beforeEventName = eventedBeforeEventNames[eventName] ||
                        (eventedBeforeEventNames[eventName] = 'before' + eventName),
                controller = Ext.apply({
                    owner: me,
                    eventName: eventName,
                    fn: fn,
                    scope: scope,
                    fnArgs: fnArgs,
                    args: args
                }, me.$eventedController),
                value;
 
            args.push(controller);
            value = me.fireEventArgs(beforeEventName, args);
            args.pop();
 
            if (value === false) {
                return false;
            }
 
            return controller.resume();
        },
 
        /**
         * Continue to fire event.
         * @private
         *
         * @param {String} eventName 
         * @param {Array} args 
         * @param {Boolean} bubbles 
         */
        doFireEvent: function(eventName, args, bubbles) {
            var target = this,
                queue, event,
                ret = true;
 
            do {
                if (target.eventsSuspended) {
                    if ((queue = target.eventQueue)) {
                        queue.push([eventName, args]);
                    }
                    
                    return ret;
                }
                else {
                    event = target.events && target.events[eventName];
                    
                    // Continue bubbling if event exists and it is `true` or the handler
                    // didn't returns false and it configure to bubble.
                    if (event && event !== true) {
                        if ((ret = event.fire.apply(event, args)) === false) {
                            break;
                        }
                    }
                }
            } while (bubbles && (target = target.getBubbleParent()));
            
            return ret;
        },
 
        /**
         * Gets the bubbling parent for an Observable
         * @private
         * @return {Ext.util.Observable} The bubble parent. null is returned if
         * no bubble target exists
         */
        getBubbleParent: function() {
            var me = this,
                parent = me.getBubbleTarget && me.getBubbleTarget();
            
            if (parent && parent.isObservable) {
                return parent;
            }
            
            return null;
        },
 
        /**
         * The {@link #on} method is shorthand for 
         * {@link Ext.util.Observable#addListener addListener}.
         *
         * Appends an event handler to this object.  For example:
         *
         *     myGridPanel.on("itemclick", this.onItemClick, this);
         *
         * The method also allows for a single argument to be passed which is a config object
         * containing properties which specify multiple events. For example:
         *
         *     myGridPanel.on({
         *         cellclick: this.onCellClick,
         *         select: this.onSelect,
         *         viewready: this.onViewReady,
         *         scope: this // Important. Ensure "this" is correct during handler execution
         *     });
         *
         * One can also specify options for each event handler separately:
         *
         *     myGridPanel.on({
         *         cellclick: {fn: this.onCellClick, scope: this, single: true},
         *         viewready: {fn: panel.onViewReady, scope: panel}
         *     });
         *
         * *Names* of methods in a specified scope may also be used:
         *
         *     myGridPanel.on({
         *         cellclick: {fn: 'onCellClick', scope: this, single: true},
         *         viewready: {fn: 'onViewReady', scope: panel}
         *     });
         *
         * @param {String/Object} eventName The name of the event to listen for.
         * May also be an object who's property names are event names.
         *
         * @param {Function/String} [fn] The method the event invokes or the *name* of 
         * the method within the specified `scope`.  Will be called with arguments
         * given to {@link Ext.util.Observable#fireEvent} plus the `options` parameter described 
         * below.
         *
         * @param {Object} [scope] The scope (`this` reference) in which the handler function is
         * executed. **If omitted, defaults to the object which fired the event.**
         *
         * @param {Object} [options] An object containing handler configuration.
         *
         * **Note:** The options object will also be passed as the last argument to every 
         * event handler.
         *
         * This object may contain any of the following properties:
         *
         * @param {Object} options.scope 
         *   The scope (`this` reference) in which the handler function is executed. **If omitted,
         *   defaults to the object which fired the event.**
         *
         * @param {Number} options.delay 
         *   The number of milliseconds to delay the invocation of the handler after the event 
         *   fires.
         *
         * @param {Boolean} options.single 
         *   True to add a handler to handle just the next firing of the event, and then remove 
         *   itself.
         *
         * @param {Number} options.buffer 
         *   Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
         *   by the specified number of milliseconds. If the event fires again within that time,
         *   the original handler is _not_ invoked, but the new handler is scheduled in its place.
         *
         * @param {Number} options.onFrame 
         *   Causes the handler to be scheduled to run at the next 
         *   {@link Ext.Function#requestAnimationFrame animation frame event}. If the
         *   event fires again before that time, the handler is not rescheduled - the handler
         *   will only be called once when the next animation frame is fired, with the last set
         *   of arguments passed.
         *
         * @param {Ext.util.Observable} options.target 
         *   Only call the handler if the event was fired on the target Observable, _not_ if the 
         *   event was bubbled up from a child Observable.
         *
         * @param {String} options.element 
         *   **This option is only valid for listeners bound to {@link Ext.Component Components}.**
         *   The name of a Component property which references an {@link Ext.dom.Element element} 
         *   to add a listener to.
         *
         *   This option is useful during Component construction to add DOM event listeners to 
         *   elements of {@link Ext.Component Components} which will exist only after the 
         *   Component is rendered.
         *   
         *   For example, to add a click listener to a Panel's body:
         *
         *       var panel = new Ext.panel.Panel({
         *           title: 'The title',
         *           listeners: {
         *               click: this.handlePanelClick,
         *               element: 'body'
         *           }
         *       });
         *       
         * In order to remove listeners attached using the element, you'll need to reference
         * the element itself as seen below.  
         *
         *      panel.body.un(...)
         * 
         * @param {String} [options.delegate] 
         *   A simple selector to filter the event target or look for a descendant of the target.  
         *   
         *   The "delegate" option is only available on Ext.dom.Element instances (or 
         *   when attaching a listener to a Ext.dom.Element via a Component using the 
         *   element option). 
         *   
         *   See the *delegate* example below.
         *
         * @param {Boolean} [options.capture] 
         *  When set to `true`, the listener is fired in the capture phase of the event propagation
         *  sequence, instead of the default bubble phase.
         *   
         *   The `capture` option is only available on Ext.dom.Element instances (or 
         *   when attaching a listener to a Ext.dom.Element via a Component using the 
         *   element option). 
         *
         * @param {Boolean} [options.stopPropagation] 
         *   **This option is only valid for listeners bound to {@link Ext.dom.Element Elements}.**
         *   `true` to call {@link Ext.event.Event#stopPropagation stopPropagation} on the event
         *   object before firing the handler.
         *
         * @param {Boolean} [options.preventDefault] 
         *   **This option is only valid for listeners bound to {@link Ext.dom.Element Elements}.**
         *   `true` to call {@link Ext.event.Event#preventDefault preventDefault} on the event
         *   object before firing the handler.
         *
         * @param {Boolean} [options.stopEvent] 
         *   **This option is only valid for listeners bound to {@link Ext.dom.Element Elements}.**
         *   `true` to call {@link Ext.event.Event#stopEvent stopEvent} on the event object
         *   before firing the handler.
         *
         * @param {Array} [options.args] 
         *
         * Optional set of arguments to pass to the handler function before the actual
         * fired event arguments. For example, if `args` is set to `['foo', 42]`,
         * the event handler function will be called with an arguments list like this:
         *
         *      handler('foo', 42, <actual event arguments>...);
         *
         * @param {Boolean} [options.destroyable=false] 
         *   When specified as `true`, the function returns a `destroyable` object. An object 
         *   which implements the `destroy` method which removes all listeners added in this call.
         *   This syntax can be a helpful shortcut to using {@link #un}; particularly when 
         *   removing multiple listeners.  *NOTE* - not compatible when using the _element_
         *   option.  See {@link #un} for the proper syntax for removing listeners added using the
         *   _element_ config.
         *   
         * @param {Number} [options.priority] 
         *   An optional numeric priority that determines the order in which event handlers
         *   are run. Event handlers with no priority will be run as if they had a priority
         *   of 0. Handlers with a higher priority will be prioritized to run sooner than
         *   those with a lower priority.  Negative numbers can be used to set a priority
         *   lower than the default. Internally, the framework uses a range of 1000 or
         *   greater, and -1000 or lesser for handlers that are intended to run before or
         *   after all others, so it is recommended to stay within the range of -999 to 999
         *   when setting the priority of event handlers in application-level code.
         *   A priority must be an integer to be valid.  Fractional values are reserved for
         *   internal framework use.
         *
         * @param {String} [options.order='current'] 
         *   A legacy option that is provided for backward compatibility.
         *   It is recommended to use the `priority` option instead.  Available options are:
         *
         *   - `'before'`: equal to a priority of `100`
         *   - `'current'`: equal to a priority of `0` or default priority
         *   - `'after'`: equal to a priority of `-100`
         *
         * @param {String} [order='current'] 
         *   A shortcut for the `order` event option.  Provided for backward compatibility.
         *   Please use the `priority` event option instead.
         *
         * @param caller (private)
         *
         * **Combining Options**
         *
         * Using the options argument, it is possible to combine different types of listeners:
         *
         * A delayed, one-time listener.
         *
         *     myPanel.on('hide', this.handleClick, this, {
         *         single: true,
         *         delay: 100
         *     });
         *
         * **Attaching multiple handlers in 1 call**
         *
         * The method also allows for a single argument to be passed which is a config object 
         * containing properties which specify multiple handlers and handler configs.
         *
         *     grid.on({
         *         itemclick: 'onItemClick',
         *         itemcontextmenu: grid.onItemContextmenu,
         *         destroy: {
         *             fn: function () {
         *                 // function called within the 'altCmp' scope instead of grid
         *             },
         *             scope: altCmp // unique scope for the destroy handler
         *         },
         *         scope: grid       // default scope - provided for example clarity
         *     });
         *
         * **Delegate**
         *
         * This is a configuration option that you can pass along when registering a handler for 
         * an event to assist with event delegation. By setting this configuration option 
         * to a simple selector, the target element will be filtered to look for a 
         * descendant of the target. For example:
         *
         *     var panel = Ext.create({
         *         xtype: 'panel',
         *         renderTo: document.body,
         *         title: 'Delegate Handler Example',
         *         frame: true,
         *         height: 220,
         *         width: 220,
         *         html: '<h1 class="myTitle">BODY TITLE</h1>Body content'
         *     });
         *
         *     // The click handler will only be called when the click occurs on the
         *     // delegate: h1.myTitle ("h1" tag with class "myTitle")
         *     panel.on({
         *         click: function (e) {
         *             console.log(e.getTarget().innerHTML);
         *         },
         *         element: 'body',
         *         delegate: 'h1.myTitle'
         *      });
         *
         * @return {Object} **Only when the `destroyable` option is specified.**
         *
         *  A `Destroyable` object. An object which implements the `destroy` method which removes 
         *  all listeners added in this call. For example:
         *
         *     this.btnListeners =  = myButton.on({
         *         destroyable: true
         *         mouseover:   function() { console.log('mouseover'); },
         *         mouseout:    function() { console.log('mouseout'); },
         *         click:       function() { console.log('click'); }
         *     });
         *
         * And when those listeners need to be removed:
         *
         *     Ext.destroy(this.btnListeners);
         *
         * or
         *
         *     this.btnListeners.destroy();
         */  
        addListener: function(eventName, fn, scope, options, order, caller) {
            var me = this,
                namedScopes = Ext._namedScopes,
                config, namedScope, isClassListener, innerScope, eventOptions;
 
            // Object listener hash passed
            if (typeof eventName !== 'string') {
                options = eventName;
                scope = options.scope;
                namedScope = scope && namedScopes[scope];
                isClassListener = namedScope && namedScope.isSelf;
                // give subclasses the opportunity to switch the valid eventOptions
                // (Ext.Component uses this when the "element" option is used)
                eventOptions = ((me.isComponent || me.isWidget) && options.element)
                    ? me.$elementEventOptions
                    : me.$eventOptions;
 
                for (eventName in options) {
                    config = options[eventName];
                    
                    if (!eventOptions[eventName]) {
                        /* This would be an API change so check removed until https://sencha.jira.com/browse/EXTJSIV-7183 is fully implemented in 4.2
                        // Test must go here as well as in the simple form because of
                        // the attempted property access here on the config object.
                        //<debug>
                        if (!config || (typeof config !== 'function' && !config.fn)) {
                            Ext.raise('No function passed for event ' + me.$className + '.' +
                                      ename);
                        }
                        //</debug>
                        */
 
                        innerScope = config.scope;
                        
                        // for proper scope resolution, scope:'controller' specified on an
                        // inner object, must be translated to 'self.controller' if the
                        // listeners object was declared on the class body.
                        // see also Ext.util.Observable#prepareClass and
                        // Ext.mixin.Inheritable#resolveListenerScope
                        if (innerScope && isClassListener) {
                            namedScope = namedScopes[innerScope];
                            
                            if (namedScope && namedScope.isController) {
                                innerScope = 'self.controller';
                            }
                        }
 
                        me.doAddListener(eventName, config.fn || config, innerScope || scope,
                                         config.fn ? config : options, order, caller);
                    }
 
                }
                
                if (options && options.destroyable) {
                    return new ListenerRemover(me, options);
                }
            }
            else {
                me.doAddListener(eventName, fn, scope, options, order, caller);
 
                if (options && options.destroyable) {
                    return new ListenerRemover(me, eventName, fn, scope, options);
                }
            }
            
            return me;
        },
 
        /**
         * Removes an event handler.
         *
         * @param {String} eventName The type of event the handler was associated with.
         * @param {Function} fn The handler to remove. **This must be a reference to the function 
         * passed into the
         * {@link Ext.util.Observable#addListener addListener} call.**
         * @param {Object} scope (optional) The scope originally specified for the handler. It 
         * must be the same as the scope argument specified in the original call to 
         * {@link Ext.util.Observable#addListener} or the listener will not be removed.
         * @param eventOptions (private)
         *
         * **Convenience Syntax**
         *
         * You can use the {@link Ext.util.Observable#addListener addListener} 
         * `destroyable: true` config option in place of calling un().  For example:
         *
         *     var listeners = cmp.on({
         *         scope: cmp,
         *         afterrender: cmp.onAfterrender,
         *         beforehide: cmp.onBeforeHide,
         *         destroyable: true
         *     });
         *
         *     // Remove listeners
         *     listeners.destroy();
         *     // or
         *     cmp.un(
         *         scope: cmp,
         *         afterrender: cmp.onAfterrender,
         *         beforehide: cmp.onBeforeHide
         *     );
         *
         * **Exception - DOM event handlers using the element config option**
         *
         * You must go directly through the element to detach an event handler attached using
         * the {@link Ext.util.Observable#addListener addListener} _element_ option.
         *
         *     panel.on({
         *         element: 'body',
         *         click: 'onBodyCLick'
         *     });
         *
         *     panel.body.un({
         *         click: 'onBodyCLick'
         *     });
         */
        removeListener: function(eventName, fn, scope, eventOptions) {
            var me = this,
                config, options;
 
            if (typeof eventName !== 'string') {
                options = eventName;
                
                // give subclasses the opportunity to switch the valid eventOptions
                // (Ext.Component uses this when the "element" option is used)
                eventOptions = eventOptions || me.$eventOptions;
                
                for (eventName in options) {
                    if (options.hasOwnProperty(eventName)) {
                        config = options[eventName];
                        
                        if (!me.$eventOptions[eventName]) {
                            me.doRemoveListener(eventName, config.fn || config,
                                                config.scope || options.scope);
                        }
                    }
                }
            }
            else {
                me.doRemoveListener(eventName, fn, scope);
            }
 
            return me;
        },
 
        /**
         * Appends a before-event handler.  Returning `false` from the handler will stop the event.
         *
         * Same as {@link Ext.util.Observable#addListener addListener} with `order` set 
         * to `'before'`.
         *
         * @param {String/String[]/Object} eventName The name of the event to listen for.
         * @param {Function/String} fn The method the event invokes.
         * @param {Object} [scope] The scope for `fn`.
         * @param {Object} [options] An object containing handler configuration.
         */
        onBefore: function(eventName, fn, scope, options) {
            return this.addListener(eventName, fn, scope, options, 'before');
        },
 
        /**
         * Appends an after-event handler.
         *
         * Same as {@link Ext.util.Observable#addListener addListener} with `order` set 
         * to `'after'`.
         *
         * @param {String/String[]/Object} eventName The name of the event to listen for.
         * @param {Function/String} fn The method the event invokes.
         * @param {Object} [scope] The scope for `fn`.
         * @param {Object} [options] An object containing handler configuration.
         */
        onAfter: function(eventName, fn, scope, options) {
            return this.addListener(eventName, fn, scope, options, 'after');
        },
 
        /**
         * Removes a before-event handler.
         *
         * Same as {@link #removeListener} with `order` set to `'before'`.
         *
         * @param {String/String[]/Object} eventName The name of the event the handler
         * was associated with.
         * @param {Function/String} fn The handler to remove.
         * @param {Object} [scope] The scope originally specified for `fn`.
         * @param {Object} [options] Extra options object.
         */
        unBefore: function(eventName, fn, scope, options) {
            return this.removeListener(eventName, fn, scope, options, 'before');
        },
 
        /**
         * Removes a before-event handler.
         *
         * Same as {@link #removeListener} with `order` set to `'after'`.
         *
         * @param {String/String[]/Object} eventName The name of the event the handler
         * was associated with.
         * @param {Function/String} fn The handler to remove.
         * @param {Object} [scope] The scope originally specified for `fn`.
         * @param {Object} [options] Extra options object.
         */
        unAfter: function(eventName, fn, scope, options) {
            return this.removeListener(eventName, fn, scope, options, 'after');
        },
 
        /**
         * Alias for {@link #onBefore}.
         */
        addBeforeListener: function() {
            return this.onBefore.apply(this, arguments);
        },
        
        /**
         * Alias for {@link #onAfter}.
         */
        addAfterListener: function() {
            return this.onAfter.apply(this, arguments);
        },
        
        /**
         * Alias for {@link #unBefore}.
         */
        removeBeforeListener: function() {
            return this.unBefore.apply(this, arguments);
        },
        
        /**
         * Alias for {@link #unAfter}.
         */
        removeAfterListener: function() {
            return this.unAfter.apply(this, arguments);
        },
 
        /**
         * Removes all listeners for this object including the managed listeners
         */
        clearListeners: function() {
            var me = this,
                events = me.events,
                hasListeners = me.hasListeners,
                event,
                key;
 
            if (events) {
                for (key in events) {
                    if (events.hasOwnProperty(key)) {
                        event = events[key];
                        
                        if (event.isEvent) {
                            delete hasListeners[key];
                            event.clearListeners();
                        }
                    }
                }
                
                me.events = null;
            }
 
            me.clearManagedListeners();
        },
 
        //<debug>
        purgeListeners: function() {
            if (Ext.global.console) {
                Ext.global.console.warn('Observable: purgeListeners has been deprecated. ' +
                                        'Please use clearListeners.');
            }
            
            return this.clearListeners.apply(this, arguments);
        },
        //</debug>
 
        /**
         * Removes all managed listeners for this object.
         */
        clearManagedListeners: function() {
            var me = this,
                managedListeners = me.managedListeners,
                i, len;
 
            if (managedListeners) {
                // So that Event#removeListener doesn't find a managedListeners array from which
                // to remove the listener it is removing. It iterates the array to find a match,
                // and splices it.
                me.managedListeners = null;
                
                for (= 0, len = managedListeners.length; i < len; i++) {
                    me.removeManagedListenerItem(true, managedListeners[i]);
                }
                
                managedListeners.length = 0;
            }
 
            me.managedListeners = managedListeners;
        },
 
        /**
         * Remove a single managed listener item
         * @private
         * @param {Boolean} isClear True if this is being called during a clear
         * @param {Object} managedListener The managed listener item
         * @param {Object} item 
         * @param {String} ename 
         * @param {Function} fn 
         * @param {Object} scope 
         * See removeManagedListener for other args
         */
        removeManagedListenerItem: function(isClear, managedListener, item, ename, fn, scope) {
            if (isClear || (managedListener.item === item && managedListener.ename === ename &&
                (!fn || managedListener.fn === fn) &&
                (!scope || managedListener.scope === scope))) {
                // Pass along the options for mixin.Observable, for example if using delegate.
                // If the item has already been destroyed, its listeners were already cleared.
                if (!managedListener.item.destroyed) {
                    managedListener.item.doRemoveListener(managedListener.ename, managedListener.fn,
                                                          managedListener.scope,
                                                          managedListener.options);
                }
                
                if (!isClear) {
                    Ext.Array.remove(this.managedListeners, managedListener);
                }
            }
        },
 
        //<debug>
        purgeManagedListeners: function() {
            if (Ext.global.console) {
                Ext.global.console.warn('Observable: purgeManagedListeners has been deprecated. ' +
                                        'Please use clearManagedListeners.');
            }
            
            return this.clearManagedListeners.apply(this, arguments);
        },
        //</debug>
        
        /**
         * Checks to see if this object has any listeners for a specified event, or whether
         * the event bubbles. The answer indicates whether the event needs firing or not.
         *
         * @param {String} eventName The name of the event to check for
         * @return {Boolean} `true` if the event is being listened for or bubbles, else `false`
         */
        hasListener: function(eventName) {
            eventName = Ext.canonicalEventName(eventName);
            
            return !!this.hasListeners[eventName];
        },
        
        /**
         * Checks if all events, or a specific event, is suspended.
         * @param {String} [event] The name of the specific event to check
         * @return {Boolean} `true` if events are suspended
         */
        isSuspended: function(event) {
            var suspended = this.eventsSuspended > 0,
                events = this.events;
                
            if (!suspended && event && events) {
                event = events[event];
                
                if (event && event.isEvent) {
                    return event.isSuspended();
                }
            }
            
            return suspended;
        },
 
        /**
         * Suspends the firing of all events. (see {@link #resumeEvents})
         *
         * @param {Boolean} queueSuspended `true` to queue up suspended events to be fired
         * after the {@link #resumeEvents} call instead of discarding all suspended events.
         */
        suspendEvents: function(queueSuspended) {
            ++this.eventsSuspended;
            
            if (queueSuspended && !this.eventQueue) {
                this.eventQueue = [];
            }
        },
 
        /**
         * Suspends firing of the named event(s).
         *
         * After calling this method to suspend events, the events will no longer fire when
         * requested to fire.
         *
         * **Note that if this is called multiple times for a certain event, the converse method
         * {@link #resumeEvent} will have to be called the same number of times for it to resume
         * firing.**
         *
         * @param  {String...} eventName Multiple event names to suspend.
         */
        suspendEvent: function() {
            var me = this,
                events = me.events,
                len = arguments.length,
                i, event, ename;
 
            for (= 0; i < len; i++) {
                ename = arguments[i];
                ename = Ext.canonicalEventName(ename);
                event = events[ename];
                
                // we need to spin up the Event instance so it can hold the suspend count
                if (!event || !event.isEvent) {
                    event = me._initEvent(ename);
                }
                
                event.suspend();
            }
        },
 
        /**
         * Resumes firing of the named event(s).
         *
         * After calling this method to resume events, the events will fire when requested to fire.
         *
         * **Note that if the {@link #suspendEvent} method is called multiple times for a certain
         * event, this converse method will have to be called the same number of times for it
         * to resume firing.**
         *
         * @param {String...} eventName Multiple event names to resume.
         */
        resumeEvent: function() {
            var events = this.events || 0,
                len = events && arguments.length,
                i, event, ename;
 
            for (= 0; i < len; i++) {
                ename = Ext.canonicalEventName(arguments[i]);
                event = events[ename];
                
                // If it exists, and is an Event object (not still a boolean placeholder), resume it
                if (event && event.resume) {
                    event.resume();
                }
            }
        },
 
        /**
        * Resumes firing events (see {@link #suspendEvents}).
        *
        * If events were suspended using the `queueSuspended` parameter, then all events fired
        * during event suspension will be sent to any listeners now.
        * 
        * @param {Boolean} [discardQueue] `true` to prevent any previously queued events from firing
        * while we were suspended. See {@link #suspendEvents}.
        */
        resumeEvents: function(discardQueue) {
            var me = this,
                queued = me.eventQueue,
                qLen, q;
 
            if (me.eventsSuspended && ! --me.eventsSuspended) {
                delete me.eventQueue;
 
                if (!discardQueue && queued) {
                    qLen = queued.length;
                    
                    for (= 0; q < qLen; q++) {
                        // Important to call fireEventArgs here so MVC can hook in
                        me.fireEventArgs.apply(me, queued[q]);
                    }
                }
            }
        },
 
        /**
         * Relays selected events from the specified Observable as if the events were fired
         * by `this`.
         *
         * For example if you are extending Grid, you might decide to forward some events from
         * store. So you can do this inside your initComponent:
         *
         *     this.relayEvents(this.getStore(), ['load']);
         *
         * The grid instance will then have an observable 'load' event which will be passed 
         * the parameters of the store's load event and any function fired with the grid's 
         * load event would have access to the grid using the this keyword (unless the event 
         * is handled by a controller's control/listen event listener in which case 'this' 
         * will be the controller rather than the grid).
         *
         * @param {Object} origin The Observable whose events this object is to relay.
         * @param {String[]/Object} events Array of event names to relay or an Object with key/value
         * pairs translating to ActualEventName/NewEventName respectively. For example:
         *     this.relayEvents(this, {add:'push', remove:'pop'});
         *
         * Would now redispatch the add event of this as a push event and the remove event
         * as a pop event.
         *
         * @param {String} [prefix] A common prefix to prepend to the event names. For example:
         *
         *     this.relayEvents(this.getStore(), ['load', 'clear'], 'store');
         *
         * Now the grid will forward 'load' and 'clear' events of store as 'storeload' and
         * 'storeclear'.
         *
         * @return {Object} A `Destroyable` object. An object which implements the `destroy` method
         * which, when destroyed, removes all relayers. For example:
         *
         *     this.storeRelayers = this.relayEvents(this.getStore(), ['load', 'clear'], 'store');
         *
         * Can be undone by calling
         *
         *     Ext.destroy(this.storeRelayers);
         *
         * or
         *     this.store.relayers.destroy();
         */
        relayEvents: function(origin, events, prefix) {
            var me = this,
                len = events.length,
                i = 0,
                oldName, newName,
                relayers = {};
 
            if (Ext.isObject(events)) {
                for (in events) {
                    newName = events[i];
                    relayers[i] = me.createRelayer(newName);
                }
            }
            else {
                for (; i < len; i++) {
                    oldName = events[i];
 
                    // Build up the listener hash.
                    relayers[oldName] = me.createRelayer(prefix ? prefix + oldName : oldName);
                }
            }
            
            // Add the relaying listeners as ManagedListeners so that they are removed when
            // this.clearListeners is called (usually when _this_ is destroyed)
            // Explicitly pass options as undefined so that the listener does not get
            // an extra options param which then has to be sliced off in the relayer.
            me.mon(origin, relayers, null, null, undefined);
 
            // relayed events are always destroyable.
            return new ListenerRemover(me, origin, relayers);
        },
 
        /**
         * @private
         * Creates an event handling function which re-fires the event from this object
         * as the passed event name.
         * @param {String} newName The name under which to re-fire the passed parameters.
         * @param {Array} beginEnd (optional) The caller can specify on which indices to slice.
         * @return {Function} 
         */
        createRelayer: function(newName, beginEnd) {
            var me = this;
            
            return function() {
                // eslint-disable-next-line max-len
                return me.fireEventArgs.call(me, newName, beginEnd ? arraySlice.apply(arguments, beginEnd) : arguments);
            };
        },
 
        /**
         * Enables events fired by this Observable to bubble up an owner hierarchy by calling
         * `this.getBubbleTarget()` if present. There is no implementation in the Observable
         * base class.
         *
         * This is commonly used by Ext.Components to bubble events to owner Containers.
         * See {@link Ext.Component#getBubbleTarget}. The default implementation in Ext.Component
         * returns the Component's immediate owner. But if a known target is required, this can be
         * overridden to access the required target more quickly.
         *
         * Example:
         *
         *     Ext.define('Ext.overrides.form.field.Base', {
         *         override: 'Ext.form.field.Base',
         *
         *         //  Add functionality to Field's initComponent to enable
         *         // the change event to bubble
         *         initComponent: function () {
         *             this.callParent();
         *             this.enableBubble('change');
         *         }
         *     });
         *
         *     var myForm = Ext.create('Ext.form.Panel', {
         *         title: 'User Details',
         *         items: [{
         *             ...
         *         }],
         *         listeners: {
         *             change: function() {
         *                 // Title goes red if form has been modified.
         *                 myForm.header.setStyle('color', 'red');
         *             }
         *         }
         *     });
         *
         * @param {String/String[]} eventNames The event name to bubble, or an Array of event names.
         */
        enableBubble: function(eventNames) {
            if (eventNames) {
                // eslint-disable-next-line vars-on-top
                var me = this,
                    names = (typeof eventNames === 'string') ? arguments : eventNames,
                    // we must create events now if we have not yet
                    events = me.events,
                    length = events && names.length,
                    ename, event, i;
 
                for (= 0; i < length; ++i) {
                    ename = names[i];
                    ename = Ext.canonicalEventName(ename);
                    event = events[ename];
 
                    if (!event || !event.isEvent) {
                        event = me._initEvent(ename);
                    }
 
                    // Event must fire if it bubbles (We don't know if anyone up the
                    // bubble hierarchy has listeners added)
                    me.hasListeners._incr_(ename);
 
                    event.bubble = true;
                }
            }
        },
        
        /**
         * @private
         * Destructor for classes that extend Observable.
         */
        destroy: function() {
            this.clearListeners();
            this.callParent();
            this.destroyObservable(true);
        },
 
        destroyObservable: function(skipClearListeners) {
            var me = this,
                clearPropertiesOnDestroy = me.clearPropertiesOnDestroy;
            
            if (me.$observableDestroyed) {
                return;
            }
            
            if (!skipClearListeners) {
                me.clearListeners();
            }
            
            // This method is called after the Base destructor, and most of the instances
            // should be already destroyed at this point. However Classic Components are
            // conditionally destructible and so can possibly *not* be destroyed before
            // our mixed-in destructor is called. Component's destructor will take care
            // of that by calling this method explicitly.
            if (me.destroyed) {
                if (clearPropertiesOnDestroy) {
                    if (clearPropertiesOnDestroy === true && !me.$nulled) {
                        me.$reap();
                    }
                    
                    // At this point we can safely assume that the instance is completely
                    // destroyed and should not be able to fire events anymore. We don't
                    // want to do this when the prototype is going to be cleared below,
                    // because having these emptyFns on the object instance will defy
                    // the purpose of prototype clearing.
                    if (!me.clearPrototypeOnDestroy) {
                        me.fireEvent = me.fireEventArgs = me.fireAction = me.fireEventedAction =
                            Ext.emptyFn;
                    }
                    
                    // We do not null hasListeners reference since it's a) very special,
                    // and b) can't possibly lead to significant leaks. (In theory, right).
                    me.events = me.managedListeners = me.eventedBeforeEventNames = null;
                    
                    me.$observableDestroyed = true;
                }
                
                //<debug>
                // Due to the way Observable mixin installs the after handler,
                // this can be called twice in a row. Doing that the second time
                // will most probably blow up on some method call -- and that is
                // totally what we are about, except in this particular case.
                if (me.clearPrototypeOnDestroy && Object.setPrototypeOf && !me.$alreadyNulled) {
                    Object.setPrototypeOf(me, null);
                    me.$alreadyNulled = true;
                }
                //</debug>
            }
        },
 
        privates: {
            doAddListener: function(ename, fn, scope, options, order, caller, manager) {
                var me = this,
                    ret = false,
                    event, priority;
 
                order = order || (options && 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];
                    }
                }
 
                ename = Ext.canonicalEventName(ename);
 
                //<debug>
                if (!fn) {
                    Ext.raise("Cannot add '" + ename + "' listener to " + me.$className +
                        " instance.  No function specified.");
                }
                //</debug>
 
                event = (me.events || (me.events = {}))[ename];
                
                if (!event || !event.isEvent) {
                    event = me._initEvent(ename);
                }
 
                if (fn !== emptyFn) {
                    // Check whether the listener should be managed.
                    // Event#addListener will add it to the manager's managedListeners stack 
                    // upon successful add of the listener to the event.
                    if (!manager && (scope && scope.isObservable && (scope !== me))) {
                        manager = scope;
                    }
                    
                    if (event.addListener(fn, scope, options, caller, manager)) {
                        // If a new listener has been added (Event.addListener rejects
                        // duplicates of the same fn+scope)
                        // then increment the hasListeners counter
                        me.hasListeners._incr_(ename);
                        ret = true;
                    }
                }
 
                return ret;
            },
 
            doRemoveListener: function(ename, fn, scope) {
                var me = this,
                    ret = false,
                    events = me.events,
                    event;
 
                ename = Ext.canonicalEventName(ename);
                event = events && events[ename];
 
                //<debug>
                if (!fn) {
                    Ext.raise("Cannot remove '" + ename + "' listener to " + me.$className +
                        " instance.  No function specified.");
                }
                //</debug>
 
                if (event && event.isEvent) {
                    if (event.removeListener(fn, scope)) {
                        me.hasListeners._decr_(ename);
                        ret = true;
                    }
                }
 
                return ret;
            },
 
            _initEvent: function(eventName) {
                return (this.events[eventName] = new Ext.util.Event(this, eventName));
            }
        },
 
        deprecated: {
            '5.0': {
                methods: {
                    addEvents: null
                }
            }
        }
    };
}, function() {
    var Observable = this,
        proto = Observable.prototype,
        HasListeners = function() {},
        prepareMixin = function(T) {
            var proto = T.prototype;
            
            if (!T.HasListeners) {
                // Keep track of whether we were added via a mixin or not, this becomes
                // important later when discovering merged listeners on the class.
                proto.$observableMixedIn = 1;
                // Classes that use us as a mixin (best practice) need to be prepared.
                Observable.prepareClass(T, this);
 
                // Now that we are mixed in to class T, we need to watch T for derivations
                // and prepare them also.
                T.onExtended(function(U, data) {
                    //<debug>
                    Ext.classSystemMonitor && Ext.classSystemMonitor('extend mixin', arguments);
                    //</debug>
 
                    Observable.prepareClass(U, null, data);
                });
 
                // Also, if a class uses us as a mixin and that class is then used as
                // a mixin, we need to be notified of that as well.
                if (proto.onClassMixedIn) {
                    // play nice with other potential overrides...
                    Ext.override(T, {
                        onClassMixedIn: function(U) {
                            prepareMixin.call(this, U);
                            this.callParent(arguments);
                        }
                    });
                }
                else {
                    // just us chickens, so add the method...
                    proto.onClassMixedIn = function(U) {
                        prepareMixin.call(this, U);
                    };
                }
            }
 
            superOnClassMixedIn.call(this, T);
        },
        // We are overriding the onClassMixedIn of Ext.Mixin. Save a reference to it
        // so we can call it after our onClassMixedIn.
        superOnClassMixedIn = proto.onClassMixedIn;
 
    HasListeners.prototype = {
        // $$: 42  // to make sure we have a proper prototype
        _decr_: function(ev, count) {
            // count is optionally passed when clearing listeners in bulk
            // e.g. when clearListeners is called on a component that has listeners that
            // were attached using the "delegate" option
            if (count == null) {
                count = 1;
            }
            
            if (!(this[ev] -= count)) {
                // Delete this entry, since 0 does not mean no one is listening, just
                // that no one is *directly* listening. This allows the eventBus or
                // class observers to "poke" through and expose their presence.
                delete this[ev];
            }
        },
        _incr_: function(ev) {
            if (this.hasOwnProperty(ev)) {
                // if we already have listeners at this level, just increment the count...
                ++this[ev];
            }
            
            else {
                // otherwise, start the count at 1 (which hides whatever is in our prototype
                // chain)...
                this[ev] = 1;
            }
        }
    };
 
    proto.HasListeners = Observable.HasListeners = HasListeners;
 
    Observable.createAlias({
        /**
         * @method on
         * @inheritdoc Ext.util.Observable#method-addListener
         */
        on: 'addListener',
        /**
         * @method un
         * Shorthand for {@link #removeListener}.
         * @inheritdoc Ext.util.Observable#method-removeListener
         */
        un: 'removeListener',
        /**
         * @method mon
         * Shorthand for {@link #addManagedListener}.
         * @inheritdoc Ext.util.Observable#method-addManagedListener
         */
        mon: 'addManagedListener',
        /**
         * @method mun
         * Shorthand for {@link #removeManagedListener}.
         * @inheritdoc Ext.util.Observable#method-removeManagedListener
         */
        mun: 'removeManagedListener',
        /**
         * @method
         * An alias for {@link Ext.util.Observable#addListener addListener}.  In 
         * versions prior to 5.1, {@link #listeners} had a generated setter which could 
         * be called to add listeners.  In 5.1 the listeners config is not processed 
         * using the config system and has no generated setter, so this method is 
         * provided for backward compatibility.  The preferred way of adding listeners 
         * is to use the {@link #on} method.
         * @param {Object} listeners The listeners
         */
        setListeners: 'addListener'
    });
 
    // deprecated, will be removed in 5.0
    Observable.observeClass = Observable.observe;
 
    // Used by Ext.mixin.Hookable to create sequences.
    function getMethodEvent(method) {
        var event = (this.methodEvents = this.methodEvents || {})[method],
            returnValue,
            v,
            cancel,
            me = this,
            makeCall;
 
        if (!event) {
            me.methodEvents[method] = event = {};
            event.originalFn = me[method];
            event.methodName = method;
            event.before = [];
            event.after = [];
 
            makeCall = function(fn, scope, args) {
                scope = scope || me;
                
                if (typeof fn === 'string') {
                    fn = scope[fn];
                }
                
                if ((= fn.apply(scope, args)) !== undefined) {
                    if (typeof v === 'object') {
                        if (v.returnValue !== undefined) {
                            returnValue = v.returnValue;
                        }
                        else {
                            returnValue = v;
                        }
                        
                        cancel = !!v.cancel;
                    }
                    else if (=== false) {
                        cancel = true;
                    }
                    else {
                        returnValue = v;
                    }
                }
            };
 
            me[method] = function() {
                var args = Array.prototype.slice.call(arguments, 0),
                    argsLen = args.length,
                    b, i, len;
                
                returnValue = v = undefined;
                cancel = false;
 
                for (= 0, len = event.before.length; i < len; i++) {
                    b = event.before[i];
                    
                    if (b.extraArgs) {
                        args.push.apply(args, b.extraArgs);
                    }
 
                    makeCall(b.fn, b.scope, args);
                    args.length = argsLen;
                    
                    if (cancel || b.preventDefault) {
                        return returnValue;
                    }
                }
 
                if ((= event.originalFn.apply(me, args)) !== undefined) {
                    returnValue = v;
                }
 
                for (= 0, len = event.after.length; i < len; i++) {
                    b = event.after[i];
                    
                    if (b.extraArgs) {
                        args.push.apply(args, b.extraArgs);
                    }
 
                    makeCall(b.fn, b.scope, args);
                    args.length = argsLen;
                    
                    if (cancel || b.preventDefault) {
                        return returnValue;
                    }
                }
                
                return returnValue;
            };
        }
        
        return event;
    }
 
    Ext.apply(proto, {
        onClassMixedIn: prepareMixin,
 
        // adds an 'interceptor' called before the original method
        beforeMethod: function(method, fn, scope, preventDefault, extraArgs) {
            getMethodEvent.call(this, method).before.push({
                fn: fn,
                scope: scope,
                extraArgs: extraArgs,
                preventDefault: preventDefault
            });
        },
 
        // adds a 'sequence' called after the original method
        afterMethod: function(method, fn, scope, preventDefault, extraArgs) {
            getMethodEvent.call(this, method).after.push({
                fn: fn,
                scope: scope,
                extraArgs: extraArgs,
                preventDefault: preventDefault
            });
        },
 
        removeMethodListener: function(method, fn, scope) {
            var e = getMethodEvent.call(this, method),
                i, len;
            
            for (= 0, len = e.before.length; i < len; i++) {
                // eslint-disable-next-line eqeqeq
                if (e.before[i].fn == fn && e.before[i].scope == scope) {
                    Ext.Array.erase(e.before, i, 1);
                    
                    return;
                }
            }
            
            for (= 0, len = e.after.length; i < len; i++) {
                // eslint-disable-next-line eqeqeq
                if (e.after[i].fn == fn && e.after[i].scope == scope) {
                    Ext.Array.erase(e.after, i, 1);
                    
                    return;
                }
            }
        },
 
        toggleEventLogging: function(toggle) {
            Ext.util.Observable[toggle ? 'capture' : 'releaseCapture'](this, function(en) {
                if (Ext.isDefined(Ext.global.console)) {
                    Ext.global.console.log(en, arguments);
                }
            });
        }
    });
});