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.
551 lines
21 KiB
551 lines
21 KiB
// @tag core |
|
/** |
|
* Represents single event type that an Observable object listens to. |
|
* All actual listeners are tracked inside here. When the event fires, |
|
* it calls all the registered listener functions. |
|
* |
|
* @private |
|
*/ |
|
Ext.define('Ext.util.Event', function() { |
|
var arraySlice = Array.prototype.slice, |
|
arrayInsert = Ext.Array.insert, |
|
toArray = Ext.Array.toArray, |
|
fireArgs = {}; |
|
|
|
return { |
|
requires: 'Ext.util.DelayedTask', |
|
|
|
/** |
|
* @property {Boolean} isEvent |
|
* `true` in this class to identify an object as an instantiated Event, or subclass thereof. |
|
*/ |
|
isEvent: true, |
|
|
|
// Private. Event suspend count |
|
suspended: 0, |
|
|
|
noOptions: {}, |
|
|
|
constructor: function(observable, name) { |
|
this.name = name; |
|
this.observable = observable; |
|
this.listeners = []; |
|
}, |
|
|
|
addListener: function(fn, scope, options, caller, manager) { |
|
var me = this, |
|
added = false, |
|
observable = me.observable, |
|
eventName = me.name, |
|
listeners, listener, priority, isNegativePriority, highestNegativePriorityIndex, |
|
hasNegativePriorityIndex, length, index, i, listenerPriority; |
|
|
|
//<debug> |
|
if (scope && !Ext._namedScopes[scope] && (typeof fn === 'string') && (typeof scope[fn] !== 'function')) { |
|
Ext.Error.raise("No method named '" + fn + "' found on scope object"); |
|
} |
|
//</debug> |
|
|
|
if (me.findListener(fn, scope) === -1) { |
|
listener = me.createListener(fn, scope, options, caller, manager); |
|
if (me.firing) { |
|
// if we are currently firing this event, don't disturb the listener loop |
|
me.listeners = me.listeners.slice(0); |
|
} |
|
listeners = me.listeners; |
|
index = length = listeners.length; |
|
priority = options && options.priority; |
|
highestNegativePriorityIndex = me._highestNegativePriorityIndex; |
|
hasNegativePriorityIndex = highestNegativePriorityIndex !== undefined; |
|
if (priority) { |
|
// Find the index at which to insert the listener into the listeners array, |
|
// sorted by priority highest to lowest. |
|
isNegativePriority = (priority < 0); |
|
if (!isNegativePriority || hasNegativePriorityIndex) { |
|
// If the priority is a positive number, or if it is a negative number |
|
// and there are other existing negative priority listenrs, then we |
|
// need to calcuate the listeners priority-order index. |
|
// If the priority is a negative number, begin the search for priority |
|
// order index at the index of the highest existing negative priority |
|
// listener, otherwise begin at 0 |
|
for(i = (isNegativePriority ? highestNegativePriorityIndex : 0); i < length; i++) { |
|
// Listeners created without options will have no "o" property |
|
listenerPriority = listeners[i].o ? listeners[i].o.priority||0 : 0; |
|
if (listenerPriority < priority) { |
|
index = i; |
|
break; |
|
} |
|
} |
|
} else { |
|
// if the priority is a negative number, and there are no other negative |
|
// priority listeners, then no calculation is needed - the negative |
|
// priority listener gets appended to the end of the listeners array. |
|
me._highestNegativePriorityIndex = index; |
|
} |
|
} else if (hasNegativePriorityIndex) { |
|
// listeners with a priority of 0 or undefined are appended to the end of |
|
// the listeners array unless there are negative priority listeners in the |
|
// listeners array, then they are inserted before the highest negative |
|
// priority listener. |
|
index = highestNegativePriorityIndex; |
|
} |
|
|
|
if (!isNegativePriority && index <= highestNegativePriorityIndex) { |
|
me._highestNegativePriorityIndex ++; |
|
} |
|
if (index === length) { |
|
listeners[length] = listener; |
|
} else { |
|
arrayInsert(listeners, index, [listener]); |
|
} |
|
|
|
if (observable.isElement) { |
|
// It is the role of Ext.util.Event (vs Ext.Element) to handle subscribe/ |
|
// unsubscribe because it is the lowest level place to intercept the |
|
// listener before it is added/removed. For addListener this could easily |
|
// be done in Ext.Element's doAddListener override, but since there are |
|
// multiple paths for listener removal (un, clearListeners), it is best |
|
// to keep all subscribe/unsubscribe logic here. |
|
observable._getPublisher(eventName).subscribe( |
|
observable, |
|
eventName, |
|
options.delegated !== false, |
|
options.capture |
|
); |
|
} |
|
|
|
added = true; |
|
} |
|
|
|
return added; |
|
}, |
|
|
|
createListener: function(fn, scope, o, caller, manager) { |
|
var me = this, |
|
namedScope = Ext._namedScopes[scope], |
|
listener = { |
|
fn: fn, |
|
scope: scope, |
|
ev: me, |
|
caller: caller, |
|
manager: manager, |
|
namedScope: namedScope, |
|
defaultScope: namedScope ? (scope || me.observable) : undefined, |
|
lateBound: typeof fn === 'string' |
|
}, |
|
handler = fn, |
|
wrapped = false, |
|
type; |
|
|
|
// The order is important. The 'single' wrapper must be wrapped by the 'buffer' and 'delayed' wrapper |
|
// because the event removal that the single listener does destroys the listener's DelayedTask(s) |
|
if (o) { |
|
listener.o = o; |
|
if (o.single) { |
|
handler = me.createSingle(handler, listener, o, scope); |
|
wrapped = true; |
|
} |
|
if (o.target) { |
|
handler = me.createTargeted(handler, listener, o, scope, wrapped); |
|
wrapped = true; |
|
} |
|
if (o.delay) { |
|
handler = me.createDelayed(handler, listener, o, scope, wrapped); |
|
wrapped = true; |
|
} |
|
if (o.buffer) { |
|
handler = me.createBuffered(handler, listener, o, scope, wrapped); |
|
wrapped = true; |
|
} |
|
|
|
if (me.observable.isElement) { |
|
// If the event type was translated, e.g. mousedown -> touchstart, we need to save |
|
// the original type in the listener object so that the Ext.event.Event object can |
|
// reflect the correct type at firing time |
|
type = o.type; |
|
if (type) { |
|
listener.type = type; |
|
} |
|
} |
|
} |
|
|
|
listener.fireFn = handler; |
|
listener.wrapped = wrapped; |
|
return listener; |
|
}, |
|
|
|
findListener: function(fn, scope) { |
|
var listeners = this.listeners, |
|
i = listeners.length, |
|
listener; |
|
|
|
while (i--) { |
|
listener = listeners[i]; |
|
if (listener) { |
|
// use ==, not === for scope comparison, so that undefined and null are equal |
|
if (listener.fn === fn && listener.scope == scope) { |
|
return i; |
|
} |
|
} |
|
} |
|
|
|
return - 1; |
|
}, |
|
|
|
removeListener: function(fn, scope, index) { |
|
var me = this, |
|
removed = false, |
|
observable = me.observable, |
|
eventName = me.name, |
|
listener, highestNegativePriorityIndex, options, |
|
k, manager, managedListeners, managedListener, i; |
|
|
|
index = index || me.findListener(fn, scope); |
|
|
|
if (index != -1) { |
|
listener = me.listeners[index]; |
|
options = listener.o; |
|
highestNegativePriorityIndex = me._highestNegativePriorityIndex; |
|
|
|
if (me.firing) { |
|
me.listeners = me.listeners.slice(0); |
|
} |
|
|
|
// cancel and remove a buffered handler that hasn't fired yet |
|
if (listener.task) { |
|
listener.task.cancel(); |
|
delete listener.task; |
|
} |
|
|
|
// cancel and remove all delayed handlers that haven't fired yet |
|
k = listener.tasks && listener.tasks.length; |
|
if (k) { |
|
while (k--) { |
|
listener.tasks[k].cancel(); |
|
} |
|
delete listener.tasks; |
|
} |
|
|
|
// Remove this listener from the listeners array |
|
// We can use splice directly. The IE8 bug which Ext.Array works around only affects *insertion* |
|
// http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/6e946d03-e09f-4b22-a4dd-cd5e276bf05a/ |
|
me.listeners.splice(index, 1); |
|
|
|
manager = listener.manager; |
|
if (manager) { |
|
// If this is a managed listener we need to remove it from the manager's |
|
// managedListeners array. This ensures that if we listen using mon |
|
// and then remove without using mun, the managedListeners array is updated |
|
// accordingly, for example |
|
// |
|
// manager.on(target, 'foo', fn); |
|
// |
|
// target.un('foo', fn); |
|
managedListeners = manager.managedListeners; |
|
if (managedListeners) { |
|
for (i = managedListeners.length; i--;) { |
|
managedListener = managedListeners[i]; |
|
if (managedListener.item === me.observable && managedListener.ename === eventName && |
|
managedListener.fn === fn && managedListener.scope === scope) { |
|
managedListeners.splice(i, 1); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// if the listeners array contains negative priority listeners, adjust the |
|
// internal index if needed. |
|
if (highestNegativePriorityIndex) { |
|
if (index < highestNegativePriorityIndex) { |
|
me._highestNegativePriorityIndex --; |
|
} else if (index === highestNegativePriorityIndex && index === me.listeners.length) { |
|
delete me._highestNegativePriorityIndex; |
|
} |
|
} |
|
|
|
if (observable.isElement) { |
|
observable._getPublisher(eventName).unsubscribe( |
|
observable, |
|
eventName, |
|
options.delegated !== false, |
|
options.capture |
|
); |
|
} |
|
|
|
removed = true; |
|
} |
|
|
|
return removed; |
|
}, |
|
|
|
// Iterate to stop any buffered/delayed events |
|
clearListeners: function() { |
|
var listeners = this.listeners, |
|
i = listeners.length, |
|
listener; |
|
|
|
while (i--) { |
|
listener = listeners[i]; |
|
this.removeListener(listener.fn, listener.scope); |
|
} |
|
}, |
|
|
|
suspend: function() { |
|
++this.suspended; |
|
}, |
|
|
|
resume: function() { |
|
if (this.suspended) { |
|
--this.suspended; |
|
} |
|
}, |
|
|
|
isSuspended: function() { |
|
return this.suspended > 0; |
|
}, |
|
|
|
fire: function() { |
|
var me = this, |
|
listeners = me.listeners, |
|
count = listeners.length, |
|
isElement = me.observable.isElement, |
|
options, delegate, fireInfo, i, args, listener, len, delegateEl, currentTarget, |
|
type, chained, firingArgs, e; |
|
|
|
if (!me.suspended && count > 0) { |
|
me.firing = true; |
|
args = arguments.length ? arraySlice.call(arguments, 0) : []; |
|
len = args.length; |
|
if (isElement) { |
|
e = args[0]; |
|
} |
|
for (i = 0; i < count; i++) { |
|
listener = listeners[i]; |
|
options = listener.o; |
|
|
|
if (isElement) { |
|
if (currentTarget) { |
|
// restore the previous currentTarget if we changed it last time |
|
// around the loop while processing the delegate option. |
|
e.setCurrentTarget(currentTarget); |
|
} |
|
|
|
// For events that have been translated to provide device compatibility, |
|
// e.g. mousedown -> touchstart, we want the event object to reflect the |
|
// type that was originally listened for, not the type of the actual event |
|
// that fired. The listener's "type" property reflects the original type. |
|
type = listener.type; |
|
|
|
if (type) { |
|
// chain a new object to the event object before changing the type. |
|
// This is more efficient than creating a new event object, and we |
|
// don't want to change the type of the original event because it may |
|
// be used asynchronously by other handlers |
|
chained = e; |
|
e = args[0] = chained.chain({ type: type }); |
|
} |
|
|
|
// In Ext4 Ext.EventObject was a singleton event object that was reused as events |
|
// were fired. Set Ext.EventObject to the last fired event for compatibility. |
|
Ext.EventObject = e; |
|
} |
|
|
|
firingArgs = args; |
|
|
|
if (options) { |
|
if (isElement) { |
|
delegate = options.delegate; |
|
|
|
if (delegate) { |
|
// prepending the currentTarget.id to the delegate selector |
|
// allows us to match selectors such as "> div" |
|
delegateEl = e.getTarget('#' + e.currentTarget.id + ' ' + delegate); |
|
if (delegateEl) { |
|
args[1] = delegateEl; |
|
// save the current target before changing it to the delegateEl |
|
// so that we can restore it next time around |
|
currentTarget = e.currentTarget; |
|
e.setCurrentTarget(delegateEl); |
|
} else { |
|
continue; |
|
} |
|
} |
|
|
|
if (options.preventDefault) { |
|
e.preventDefault(); |
|
} |
|
|
|
if (options.stopPropagation) { |
|
e.stopPropagation(); |
|
} |
|
|
|
if (options.stopEvent) { |
|
e.stopEvent(); |
|
} |
|
} |
|
|
|
args[len] = options; |
|
|
|
if (options.args) { |
|
firingArgs = options.args.concat(args); |
|
} |
|
} |
|
|
|
fireInfo = me.getFireInfo(listener); |
|
if (fireInfo.fn.apply(fireInfo.scope, firingArgs) === false) { |
|
return (me.firing = false); |
|
} |
|
|
|
if (chained) { |
|
// if we chained the event object for type translation we need to |
|
// un-chain it before proceeding to process the next listener, which |
|
// may not be a translated event. |
|
e = args[0] = chained; |
|
chained = null; |
|
} |
|
} |
|
} |
|
me.firing = false; |
|
return true; |
|
}, |
|
|
|
getFireInfo: function(listener, fromWrapped) { |
|
var observable = this.observable, |
|
fireFn = listener.fireFn, |
|
scope = listener.scope, |
|
namedScope = listener.namedScope, |
|
fn; |
|
|
|
// If we are called with a wrapped listener, only attempt to do scope |
|
// resolution if we are explicitly called by the last wrapped function |
|
if (!fromWrapped && listener.wrapped) { |
|
fireArgs.fn = fireFn; |
|
return fireArgs; |
|
} |
|
|
|
fn = fromWrapped ? listener.fn : fireFn; |
|
//<debug> |
|
var name = fn; |
|
|
|
//</debug> |
|
if (listener.lateBound) { |
|
// handler is a function name - need to resolve it to a function reference |
|
if (!scope || namedScope) { |
|
// Only invoke resolveListenerScope if the user did not specify a scope, |
|
// or if the user specified a named scope. Named function handlers that |
|
// use an arbitrary object as the scope just skip this part, and just |
|
// use the given scope object to resolve the method. |
|
scope = (listener.caller || observable).resolveListenerScope(listener.defaultScope); |
|
} |
|
//<debug> |
|
if (!scope) { |
|
Ext.Error.raise('Unable to dynamically resolve scope for "' + listener.ev.name + '" listener on ' + this.observable.id); |
|
} |
|
|
|
if (!Ext.isFunction(scope[fn])) { |
|
Ext.Error.raise('No method named "' + fn + '" on ' + |
|
(scope.$className || 'scope object.')); |
|
} |
|
//</debug> |
|
|
|
fn = scope[fn]; |
|
} else if (namedScope && namedScope.isController) { |
|
// If handler is a function reference and scope:'controller' was requested |
|
// we'll do our best to look up a controller. |
|
scope = (listener.caller || observable).resolveListenerScope(listener.defaultScope); |
|
//<debug> |
|
if (!scope) { |
|
Ext.Error.raise('Unable to dynamically resolve scope for "' + listener.ev.name + '" listener on ' + this.observable.id); |
|
} |
|
//</debug> |
|
} else if (!scope || namedScope) { |
|
// If handler is a function reference we use the observable instance as |
|
// the default scope |
|
scope = observable; |
|
} |
|
|
|
// We can only ever be firing one event at a time, so just keep |
|
// overwriting tghe object we've got in our closure, otherwise we'll be |
|
// creating a whole bunch of garbage objects |
|
fireArgs.fn = fn; |
|
fireArgs.scope = scope; |
|
//<debug> |
|
if (!fn) { |
|
Ext.Error.raise('Unable to dynamically resolve method "' + name + '" on ' + this.observable.$className); |
|
} |
|
//</debug> |
|
return fireArgs; |
|
}, |
|
|
|
createTargeted: function (handler, listener, o, scope, wrapped) { |
|
return function(){ |
|
if (o.target === arguments[0]) { |
|
var fireInfo; |
|
|
|
if (!wrapped) { |
|
fireInfo = listener.ev.getFireInfo(listener, true); |
|
handler = fireInfo.fn; |
|
scope = fireInfo.scope; |
|
} |
|
|
|
return handler.apply(scope, arguments); |
|
} |
|
}; |
|
}, |
|
|
|
createBuffered: function (handler, listener, o, scope, wrapped) { |
|
listener.task = new Ext.util.DelayedTask(); |
|
return function() { |
|
var fireInfo; |
|
|
|
if (!wrapped) { |
|
fireInfo = listener.ev.getFireInfo(listener, true); |
|
handler = fireInfo.fn; |
|
scope = fireInfo.scope; |
|
} |
|
|
|
listener.task.delay(o.buffer, handler, scope, toArray(arguments)); |
|
}; |
|
}, |
|
|
|
createDelayed: function (handler, listener, o, scope, wrapped) { |
|
return function() { |
|
var task = new Ext.util.DelayedTask(), |
|
fireInfo; |
|
|
|
if (!wrapped) { |
|
fireInfo = listener.ev.getFireInfo(listener, true); |
|
handler = fireInfo.fn; |
|
scope = fireInfo.scope; |
|
} |
|
|
|
if (!listener.tasks) { |
|
listener.tasks = []; |
|
} |
|
listener.tasks.push(task); |
|
task.delay(o.delay || 10, handler, scope, toArray(arguments)); |
|
}; |
|
}, |
|
|
|
createSingle: function (handler, listener, o, scope, wrapped) { |
|
return function() { |
|
var event = listener.ev, |
|
fireInfo; |
|
|
|
|
|
if (event.removeListener(listener.fn, scope) && event.observable) { |
|
// Removing from a regular Observable-owned, named event (not an anonymous |
|
// event such as Ext's readyEvent): Decrement the listeners count |
|
event.observable.hasListeners[event.name]--; |
|
} |
|
|
|
if (!wrapped) { |
|
fireInfo = event.getFireInfo(listener, true); |
|
handler = fireInfo.fn; |
|
scope = fireInfo.scope; |
|
} |
|
return handler.apply(scope, arguments); |
|
}; |
|
} |
|
}; |
|
});
|
|
|