macoslinuxwindowsinboxwhatsappicloudtweetdeckhipchattelegramhangoutsslackgmailskypefacebook-workplaceoutlookemailmicrosoft-teamsdiscordmessengercustom-services
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1807 lines
77 KiB
1807 lines
77 KiB
// @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, |
|
// Private 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); |
|
}; |
|
|
|
ListenerRemover.prototype.destroy = function() { |
|
this.destroy = Ext.emptyFn; |
|
var observable = this.observable; |
|
observable[this.managedListeners ? 'mun' : 'un'].apply(observable, this.args); |
|
}; |
|
|
|
return { |
|
extend: 'Ext.Mixin', |
|
mixinConfig: { |
|
id: 'observable', |
|
after: { |
|
destroy: 'clearListeners' |
|
} |
|
}, |
|
|
|
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); |
|
}, |
|
|
|
/** |
|
* @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} c The class constructor to make observable. |
|
* @param {Object} listeners An object containing a series of listeners to add. See {@link #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; |
|
}, |
|
|
|
/** |
|
* 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, |
|
name, scope, namedScope; |
|
|
|
// 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). |
|
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(); |
|
} |
|
} |
|
}, |
|
|
|
/* 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 #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 |
|
* 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; |
|
|
|
me.hasListeners = new me.HasListeners(); |
|
|
|
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 (i = 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 |
|
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.SomeGlobalSharedMenu, '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 #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. |
|
* @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, /* private */ noDestroy) { |
|
var me = this, |
|
managedListeners = me.managedListeners = me.managedListeners || [], |
|
config, passedOptions; |
|
|
|
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 (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 (i = 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 #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.Error.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) { |
|
eventName = Ext.canonicalEventName(eventName); |
|
var me = this, |
|
// no need to make events since we need an Event with listeners |
|
events = me.events, |
|
event = events && events[eventName], |
|
ret = true; |
|
|
|
// 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 #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 #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 #addListener}` for more details. |
|
*/ |
|
fireAction: function(eventName, args, fn, scope, options, order) { |
|
// 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); |
|
}, |
|
|
|
/** |
|
* 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 #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.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 arguments to pass to the handler function. Any additional arguments |
|
* passed to {@link #fireEvent} will be appended to these 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. |
|
* |
|
* **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(ename, fn, scope, options, order, /* private */ caller) { |
|
var me = this, |
|
namedScopes = Ext._namedScopes, |
|
config, namedScope, isClassListener, innerScope, eventOptions; |
|
|
|
// Object listener hash passed |
|
if (typeof ename !== 'string') { |
|
options = ename; |
|
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 (ename in options) { |
|
config = options[ename]; |
|
if (!eventOptions[ename]) { |
|
/* 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.Error.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(ename, config.fn || config, innerScope || scope, config.fn ? config : options, order, caller); |
|
} |
|
|
|
} |
|
if (options && options.destroyable) { |
|
return new ListenerRemover(me, options); |
|
} |
|
} else { |
|
me.doAddListener(ename, fn, scope, options, order, caller); |
|
|
|
if (options && options.destroyable) { |
|
return new ListenerRemover(me, ename, 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 #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. |
|
* |
|
* **Convenience Syntax** |
|
* |
|
* You can use the {link #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 #addListener} _element_ option. |
|
* |
|
* panel.on({ |
|
* element: 'body', |
|
* click: 'onBodyCLick' |
|
* }); |
|
* |
|
* panel.body.un({ |
|
* click: 'onBodyCLick' |
|
* }); |
|
*/ |
|
removeListener: function(ename, fn, scope, /* private */ eventOptions) { |
|
var me = this, |
|
config, options; |
|
|
|
if (typeof ename !== 'string') { |
|
options = ename; |
|
// give subclasses the opportunity to switch the valid eventOptions |
|
// (Ext.Component uses this when the "element" option is used) |
|
eventOptions = eventOptions || me.$eventOptions; |
|
for (ename in options) { |
|
if (options.hasOwnProperty(ename)) { |
|
config = options[ename]; |
|
if (!me.$eventOptions[ename]) { |
|
me.doRemoveListener(ename, config.fn || config, config.scope || options.scope); |
|
} |
|
} |
|
} |
|
} else { |
|
me.doRemoveListener(ename, fn, scope); |
|
} |
|
|
|
return me; |
|
}, |
|
|
|
/** |
|
* 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.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 ? me.managedListeners.slice() : [], |
|
i = 0, |
|
len = managedListeners.length; |
|
|
|
for (; i < len; i++) { |
|
me.removeManagedListenerItem(true, managedListeners[i]); |
|
} |
|
|
|
me.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 |
|
* 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 |
|
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(ename) { |
|
ename = Ext.canonicalEventName(ename); |
|
return !!this.hasListeners[ename]; |
|
}, |
|
|
|
/** |
|
* 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 (i = 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; |
|
|
|
for (i = 0; i < len; i++) { |
|
// If it exists, and is an Event object (not still a boolean placeholder), resume it |
|
event = events[arguments[i]]; |
|
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 (q = 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[]} events Array of event names to relay. |
|
* @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, |
|
relayers = {}; |
|
|
|
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() { |
|
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) { |
|
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 (i = 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; |
|
} |
|
} |
|
}, |
|
|
|
destroy: function() { |
|
this.clearListeners(); |
|
this.callParent(); |
|
}, |
|
|
|
privates: { |
|
doAddListener: function(ename, fn, scope, options, order, caller, manager) { |
|
var me = this, |
|
event, managedListeners, 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.Error.raise("Cannot add '" + ename + "' listener to " + me.$className + |
|
" instance. No function specified."); |
|
} |
|
//</debug> |
|
|
|
if (!manager && (scope && scope.isObservable && (scope !== me))) { |
|
manager = scope; |
|
} |
|
|
|
if (manager) { |
|
// if scope is an observable, the listener will be automatically managed |
|
// this eliminates the need to call mon() in a majority of cases |
|
managedListeners = manager.managedListeners = manager.managedListeners || []; |
|
|
|
managedListeners.push({ |
|
item: me, |
|
ename: ename, |
|
fn: fn, |
|
scope: scope, |
|
options: options |
|
}); |
|
} |
|
|
|
event = (me.events || (me.events = {}))[ename]; |
|
if (!event || !event.isEvent) { |
|
event = me._initEvent(ename); |
|
} |
|
|
|
if (fn !== emptyFn) { |
|
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); |
|
} |
|
} |
|
}, |
|
|
|
doRemoveListener: function(ename, fn, scope) { |
|
var me = this, |
|
events = me.events, |
|
event; |
|
|
|
ename = Ext.canonicalEventName(ename); |
|
event = events && events[ename]; |
|
|
|
//<debug> |
|
if (!fn) { |
|
Ext.Error.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); |
|
} |
|
} |
|
}, |
|
|
|
_initEvent: function(eventName) { |
|
return (this.events[eventName] = new Ext.util.Event(this, eventName)); |
|
} |
|
}, |
|
|
|
deprecated: { |
|
'5.0': { |
|
methods: { |
|
addEvents: null |
|
} |
|
}, |
|
'5.1': { |
|
methods: { |
|
/** |
|
* Appends a before-event handler. Returning `false` from the handler will stop the event. |
|
* |
|
* Same as {@link #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. |
|
* @deprecated 5.1 Use {@link #addListener} with the `priority` option instead. |
|
*/ |
|
addBeforeListener: function(eventName, fn, scope, options) { |
|
return this.addListener(eventName, fn, scope, options, 'before'); |
|
}, |
|
|
|
/** |
|
* Appends an after-event handler. |
|
* |
|
* Same as {@link #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. |
|
* @deprecated 5.1 Use {@link #addListener} with the `priority` option instead. |
|
*/ |
|
addAfterListener: 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. |
|
* @deprecated 5.1 Use {@link #removeListener} instead. |
|
*/ |
|
removeBeforeListener: 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. |
|
* @deprecated 5.1 Use {@link #removeListener} instead. |
|
*/ |
|
removeAfterListener: function(eventName, fn, scope, options) { |
|
return this.removeListener(eventName, fn, scope, options, 'after'); |
|
}, |
|
|
|
/** |
|
* @method |
|
* Alias for {@link #addBeforeListener}. |
|
* @inheritdoc Ext.mixin.Observable#addBeforeListener |
|
* @deprecated 5.1 Use {@link #on} instead. |
|
*/ |
|
onBefore: 'addBeforeListener', |
|
/** |
|
* @method |
|
* Alias for {@link #addAfterListener}. |
|
* @inheritdoc Ext.mixin.Observable#addAfterListener |
|
* @deprecated 5.1 Use {@link #on} instead. |
|
*/ |
|
onAfter: 'addAfterListener', |
|
/** |
|
* @method |
|
* Alias for {@link #removeBeforeListener}. |
|
* @inheritdoc Ext.mixin.Observable#removeBeforeListener |
|
* @deprecated 5.1 Use {@link #un} instead. |
|
*/ |
|
unBefore: 'removeBeforeListener', |
|
/** |
|
* @method |
|
* Alias for {@link #removeAfterListener}. |
|
* @inheritdoc Ext.mixin.Observable#removeAfterListener |
|
* @deprecated 5.1 Use {@link #un} instead. |
|
*/ |
|
unAfter: 'removeAfterListener' |
|
} |
|
} |
|
} |
|
}; |
|
}, function() { |
|
var Observable = this, |
|
proto = Observable.prototype, |
|
HasListeners = function () {}, |
|
prepareMixin = function (T) { |
|
if (!T.HasListeners) { |
|
var proto = T.prototype; |
|
// 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) { |
|
if (! --this[ev]) { |
|
// 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 |
|
* @inheritdoc Ext.util.Observable#addListener |
|
*/ |
|
on: 'addListener', |
|
/** |
|
* @method |
|
* Shorthand for {@link #removeListener}. |
|
* @inheritdoc Ext.util.Observable#removeListener |
|
*/ |
|
un: 'removeListener', |
|
/** |
|
* @method |
|
* Shorthand for {@link #addManagedListener}. |
|
* @inheritdoc Ext.util.Observable#addManagedListener |
|
*/ |
|
mon: 'addManagedListener', |
|
/** |
|
* @method |
|
* Shorthand for {@link #removeManagedListener}. |
|
* @inheritdoc Ext.util.Observable#removeManagedListener |
|
*/ |
|
mun: 'removeManagedListener', |
|
/** |
|
* @method |
|
* An alias for {@link #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; |
|
|
|
// this is considered experimental (along with beforeMethod, afterMethod, removeMethodListener?) |
|
// allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call |
|
// private |
|
function getMethodEvent(method){ |
|
var e = (this.methodEvents = this.methodEvents || {})[method], |
|
returnValue, |
|
v, |
|
cancel, |
|
obj = this, |
|
makeCall; |
|
|
|
if (!e) { |
|
this.methodEvents[method] = e = {}; |
|
e.originalFn = this[method]; |
|
e.methodName = method; |
|
e.before = []; |
|
e.after = []; |
|
|
|
makeCall = function(fn, scope, args){ |
|
if((v = fn.apply(scope || obj, args)) !== undefined){ |
|
if (typeof v == 'object') { |
|
if(v.returnValue !== undefined){ |
|
returnValue = v.returnValue; |
|
}else{ |
|
returnValue = v; |
|
} |
|
cancel = !!v.cancel; |
|
} |
|
else |
|
if (v === false) { |
|
cancel = true; |
|
} |
|
else { |
|
returnValue = v; |
|
} |
|
} |
|
}; |
|
|
|
this[method] = function(){ |
|
var args = Array.prototype.slice.call(arguments, 0), |
|
b, i, len; |
|
returnValue = v = undefined; |
|
cancel = false; |
|
|
|
for(i = 0, len = e.before.length; i < len; i++){ |
|
b = e.before[i]; |
|
makeCall(b.fn, b.scope, args); |
|
if (cancel) { |
|
return returnValue; |
|
} |
|
} |
|
|
|
if((v = e.originalFn.apply(obj, args)) !== undefined){ |
|
returnValue = v; |
|
} |
|
|
|
for(i = 0, len = e.after.length; i < len; i++){ |
|
b = e.after[i]; |
|
makeCall(b.fn, b.scope, args); |
|
if (cancel) { |
|
return returnValue; |
|
} |
|
} |
|
return returnValue; |
|
}; |
|
} |
|
return e; |
|
} |
|
|
|
Ext.apply(proto, { |
|
onClassMixedIn: prepareMixin, |
|
|
|
// these are considered experimental |
|
// allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call |
|
// adds an 'interceptor' called before the original method |
|
beforeMethod : function(method, fn, scope){ |
|
getMethodEvent.call(this, method).before.push({ |
|
fn: fn, |
|
scope: scope |
|
}); |
|
}, |
|
|
|
// adds a 'sequence' called after the original method |
|
afterMethod : function(method, fn, scope){ |
|
getMethodEvent.call(this, method).after.push({ |
|
fn: fn, |
|
scope: scope |
|
}); |
|
}, |
|
|
|
removeMethodListener: function(method, fn, scope){ |
|
var e = this.getMethodEvent(method), |
|
i, len; |
|
for(i = 0, len = e.before.length; i < len; i++){ |
|
if(e.before[i].fn == fn && e.before[i].scope == scope){ |
|
Ext.Array.erase(e.before, i, 1); |
|
return; |
|
} |
|
} |
|
for(i = 0, len = e.after.length; i < len; i++){ |
|
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); |
|
} |
|
}); |
|
} |
|
}); |
|
});
|
|
|