/** * @class Ext.util.Observable * Base class that provides a common interface for publishing events. Subclasses are expected to * to have a property "events" with all the events defined, and, optionally, a property "listeners" * with configured listeners defined.
* For example: ** This could then be used like this:Employee = Ext.extend(Ext.util.Observable, { constructor: function(config){ this.name = config.name; this.addEvents({ "fired" : true, "quit" : true }); // Copy configured listeners into *this* object so that the base class's // constructor will add them. this.listeners = config.listeners; // Call our superclass constructor to complete construction process. Employee.superclass.constructor.call(this, config) } });
*/ Ext.util.Observable = Ext.extend(Object, { /** * @cfg {Object} listeners (optional)var newEmployee = new Employee({ name: employeeName, listeners: { quit: function() { // By default, "this" will be the object that fired the event. alert(this.name + " has quit!"); } } });
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 ExtJs {@link Ext.Component Components}
*While some ExtJs 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.DataView DataView}'s *
{@link Ext.DataView#click click}
event passing the node clicked on. To access DOM * events directly from a a child element of a Component, we need to specify this explicitly: ** */ // @private isObservable: true, constructor: function(config) { var me = this; Ext.apply(me, config); if (me.listeners) { me.on(me.listeners); delete me.listeners; } me.events = me.events || {}; if (this.bubbleEvents) { this.enableBubble(this.bubbleEvents); } }, // @private eventOptionsRe : /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate|element|vertical|horizontal)$/, /** *new Ext.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'); } } } });
Adds listeners to any Observable object (or Element) which are automatically removed when this Component * is destroyed. * @param {Observable|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} fn Optional. If the
/** * Removes listeners that were added by the {@link #mon} method. * @param {Observable|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 theename
parameter was an event name, this * is the handler function. * @param {Object} scope Optional. If theename
parameter was an event name, this * is the scope (this
reference) in which the handler function is executed. * @param {Object} opt Optional. If theename
parameter was an event name, this * is the {@link Ext.util.Observable#addListener addListener} options. */ addManagedListener : function(item, ename, fn, scope, options) { var me = this, managedListeners = me.managedListeners = me.managedListeners || [], config; if (Ext.isObject(ename)) { options = ename; for (ename in options) { if (!options.hasOwnProperty(ename)) { continue; } config = options[ename]; if (!me.eventOptionsRe.test(ename)) { me.addManagedListener(item, ename, config.fn || config, config.scope || options.scope, config.fn ? config : options); } } } else { managedListeners.push({ item: item, ename: ename, fn: fn, scope: scope, options: options }); item.on(ename, fn, scope, options); } },ename
parameter was an event name, this * is the handler function. * @param {Object} scope Optional. If theename
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, o, config, managedListeners, managedListener, length, i; if (Ext.isObject(ename)) { o = ename; for (ename in o) { if (!o.hasOwnProperty(ename)) { continue; } config = o[ename]; if (!me.eventOptionsRe.test(ename)) { me.removeManagedListener(item, ename, config.fn || config, config.scope || o.scope); } } } managedListeners = this.managedListeners ? this.managedListeners.slice() : []; length = managedListeners.length; for (i = 0; i < length; i++) { managedListener = managedListeners[i]; if (managedListener.item === item && managedListener.ename === ename && (!fn || managedListener.fn === fn) && (!scope || managedListener.scope === scope)) { this.managedListeners.remove(managedListener); item.un(managedListener.ename, managedListener.fn, managedListener.scope); } } }, /** *Fires the specified event with the passed parameters (minus the event name).
*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() { var me = this, a = Ext.toArray(arguments), ename = a[0].toLowerCase(), ret = true, ev = me.events[ename], queue = me.eventQueue, parent; if (me.eventsSuspended === true) { if (queue) { queue.push(a); } return false; } else if (ev && Ext.isObject(ev) && ev.bubble) { if (ev.fire.apply(ev, a.slice(1)) === false) { return false; } parent = me.getBubbleTarget && me.getBubbleTarget(); if (parent && parent.isObservable) { if (!parent.events[ename] || !Ext.isObject(parent.events[ename]) || !parent.events[ename].bubble) { parent.enableBubble(ename); } return parent.fireEvent.apply(parent, a); } } else if (ev && Ext.isObject(ev)) { a.shift(); ret = ev.fire.apply(ev, a); } return ret; }, /** * Appends an event handler to this object. * @param {String} eventName The name of the event to listen for. * @param {Function} handler The method the event invokes. * @param {Object} scope (optional) The scope (this
reference) in which the handler function is executed. * If omitted, defaults to the object which fired the event. * @param {Object} options (optional) An object containing handler configuration. * properties. This may contain any of the following properties:
this
reference) in which the handler function is executed.
* If omitted, defaults to the object which fired the event.
* Combining Options
* Using the options argument, it is possible to combine different types of listeners:
*
* A delayed, one-time listener.
*
myPanel.on('hide', this.onClick, 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.
*
*/ addListener: function(ename, fn, scope, o) { var me = this, config, ev; if (Ext.isObject(ename)) { o = ename; for (ename in o) { if (!o.hasOwnProperty(ename)) { continue; } config = o[ename]; if (!me.eventOptionsRe.test(ename)) { me.addListener(ename, config.fn || config, config.scope || o.scope, config.fn ? config : o); } } } else { ename = ename.toLowerCase(); me.events[ename] = me.events[ename] || true; ev = me.events[ename] || true; if (Ext.isBoolean(ev)) { me.events[ename] = ev = new Ext.util.Event(me, ename); } ev.addListener(fn, scope, Ext.isObject(o) ? o: {}); } },
/** * Removes an event handler. * @param {String} eventName The type of event the handler was associated with. * @param {Function} handler 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. */ removeListener: function(ename, fn, scope) { var me = this, config, ev; if (Ext.isObject(ename)) { var o = ename; for (ename in o) { if (!o.hasOwnProperty(ename)) { continue; } config = o[ename]; if (!me.eventOptionsRe.test(ename)) { me.removeListener(ename, config.fn || config, config.scope || o.scope); } } } else { ename = ename.toLowerCase(); ev = me.events[ename]; if (ev.isEvent) { ev.removeListener(fn, scope); } } }, /** * Removes all listeners for this object including the managed listeners */ clearListeners: function() { var events = this.events, ev, key; for (key in events) { if (!events.hasOwnProperty(key)) { continue; } ev = events[key]; if (ev.isEvent) { ev.clearListeners(); } } this.clearManagedListeners(); }, //true
* or the first event name string if multiple event names are being passed as separate parameters.
* @param {string} Optional. Event name if multiple event names are being passed as separate parameters.
* Usage:
this.addEvents('storeloaded', 'storecleared');
*/
addEvents: function(o) {
var me = this;
me.events = me.events || {};
if (Ext.isString(o)) {
var a = arguments,
i = a.length;
while (i--) {
me.events[a[i]] = me.events[a[i]] || true;
}
} else {
Ext.applyIf(me.events, o);
}
},
/**
* Checks to see if this object has any listeners for a specified event
* @param {String} eventName The name of the event to check for
* @return {Boolean} True if the event is being listened for, else false
*/
hasListener: function(ename) {
var e = this.events[ename];
return e.isEvent === true && e.listeners.length > 0;
},
/**
* Suspend the firing of all events. (see {@link #resumeEvents})
* @param {Boolean} queueSuspended Pass as 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 = true;
if (queueSuspended && !this.eventQueue) {
this.eventQueue = [];
}
},
/**
* Resume 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.
*/
resumeEvents: function() {
var me = this,
queued = me.eventQueue || [];
me.eventsSuspended = false;
delete me.eventQueue;
Ext.each(queued,
function(e) {
me.fireEvent.apply(me, e);
});
},
/**
* Relays selected events from the specified Observable as if the events were fired by this.
* @param {Object} o The Observable whose events this object is to relay.
* @param {Array} events Array of event names to relay.
*/
relayEvents : function(origin, events, prefix) {
prefix = prefix || '';
var me = this,
len = events.length,
i, ename;
function createHandler(ename){
return function(){
return me.fireEvent.apply(me, [prefix + ename].concat(Array.prototype.slice.call(arguments, 0, -1)));
};
}
for(i = 0, len = events.length; i < len; i++){
ename = events[i].substr(prefix.length);
me.events[ename] = me.events[ename] || true;
origin.on(ename, createHandler(ename), me);
}
},
/**
* 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.override(Ext.form.Field, {
// Add functionality to Field's initComponent to enable the change event to bubble
initComponent : Ext.createSequence(Ext.form.Field.prototype.initComponent, function() {
this.enableBubble('change');
}),
// We know that we want Field's events to bubble directly to the FormPanel.
getBubbleTarget : function() {
if (!this.formPanel) {
this.formPanel = this.findParentByType('form');
}
return this.formPanel;
}
});
var myForm = new Ext.formPanel({
title: 'User Details',
items: [{
...
}],
listeners: {
change: function() {
// Title goes red if form has been modified.
myForm.header.setStyle('color', 'red');
}
}
});
* @param {String/Array} events The event name to bubble, or an Array of event names.
*/
enableBubble: function(events) {
var me = this;
if (!Ext.isEmpty(events)) {
events = Ext.isArray(events) ? events: Ext.toArray(arguments);
Ext.each(events,
function(ename) {
ename = ename.toLowerCase();
var ce = me.events[ename] || true;
if (Ext.isBoolean(ce)) {
ce = new Ext.util.Event(me, ename);
me.events[ename] = ce;
}
ce.bubble = true;
});
}
}
});
Ext.override(Ext.util.Observable, {
/**
* Appends an event handler to this object (shorthand for {@link #addListener}.)
* @param {String} eventName The type of event to listen for
* @param {Function} handler The method the event invokes
* @param {Object} scope (optional) The scope (this
reference) in which the handler function is executed.
* If omitted, defaults to the object which fired the event.
* @param {Object} options (optional) An object containing handler configuration.
* @method
*/
on: Ext.util.Observable.prototype.addListener,
/**
* Removes an event handler (shorthand for {@link #removeListener}.)
* @param {String} eventName The type of event the handler was associated with.
* @param {Function} handler 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.
* @method
*/
un: Ext.util.Observable.prototype.removeListener,
mon: Ext.util.Observable.prototype.addManagedListener,
mun: Ext.util.Observable.prototype.removeManagedListener
});
/**
* Removes all added captures from the Observable.
* @param {Observable} o The Observable to release
* @static
*/
Ext.util.Observable.releaseCapture = function(o) {
o.fireEvent = Ext.util.Observable.prototype.fireEvent;
};
/**
* 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 {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
*/
Ext.util.Observable.capture = function(o, fn, scope) {
o.fireEvent = Ext.createInterceptor(o.fireEvent, 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
*/
Ext.util.Observable.observe = function(cls, listeners) {
if (cls) {
if (!cls.isObservable) {
Ext.applyIf(cls, new Ext.util.Observable());
Ext.util.Observable.capture(cls.prototype, cls.fireEvent, cls);
}
if (typeof listeners == 'object') {
cls.on(listeners);
}
return cls;
}
};
//deprecated, will be removed in 5.0
Ext.util.Observable.observeClass = Ext.util.Observable.observe;
Ext.util.Event = Ext.extend(Object, (function() {
function createBuffered(handler, listener, o, scope) {
listener.task = new Ext.util.DelayedTask();
return function() {
listener.task.delay(o.buffer, handler, scope, Ext.toArray(arguments));
};
};
function createDelayed(handler, listener, o, scope) {
return function() {
var task = new Ext.util.DelayedTask();
if (!listener.tasks) {
listener.tasks = [];
}
listener.tasks.push(task);
task.delay(o.delay || 10, handler, scope, Ext.toArray(arguments));
};
};
function createSingle(handler, listener, o, scope) {
return function() {
listener.ev.removeListener(listener.fn, scope);
return handler.apply(scope, arguments);
};
};
return {
isEvent: true,
constructor: function(observable, name) {
this.name = name;
this.observable = observable;
this.listeners = [];
},
addListener: function(fn, scope, options) {
var me = this,
listener;
scope = scope || me.observable;
if (!me.isListening(fn, scope)) {
listener = me.createListener(fn, scope, options);
if (me.firing) {
// if we are currently firing this event, don't disturb the listener loop
me.listeners = me.listeners.slice(0);
}
me.listeners.push(listener);
}
},
createListener: function(fn, scope, o) {
o = o || {};
scope = scope || this.observable;
var listener = {
fn: fn,
scope: scope,
o: o,
ev: this
},
handler = fn;
if (o.delay) {
handler = createDelayed(handler, listener, o, scope);
}
if (o.buffer) {
handler = createBuffered(handler, listener, o, scope);
}
if (o.single) {
handler = createSingle(handler, listener, o, scope);
}
listener.fireFn = handler;
return listener;
},
findListener: function(fn, scope) {
var listeners = this.listeners,
i = listeners.length,
listener,
s;
while (i--) {
listener = listeners[i];
if (listener) {
s = listener.scope;
if (listener.fn == fn && (s == scope || s == this.observable)) {
return i;
}
}
}
return - 1;
},
isListening: function(fn, scope) {
return this.findListener(fn, scope) !== -1;
},
removeListener: function(fn, scope) {
var me = this,
index,
listener,
k;
index = me.findListener(fn, scope);
if (index != -1) {
listener = me.listeners[index];
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
me.listeners.splice(index, 1);
return true;
}
return false;
},
// Iterate to stop any buffered/delayed events
clearListeners: function() {
var listeners = this.listeners,
i = listeners.length;
while (i--) {
this.removeListener(listeners[i].fn, listeners[i].scope);
}
},
fire: function() {
var me = this,
listeners = me.listeners,
count = listeners.length,
i,
args,
listener;
if (count > 0) {
me.firing = true;
for (i = 0; i < count; i++) {
listener = listeners[i];
args = arguments.length ? Array.prototype.slice.call(arguments, 0) : [];
if (listener.o) {
args.push(listener.o);
}
if (listener && listener.fireFn.apply(listener.scope || me.observable, args) === false) {
return (me.firing = false);
}
}
}
me.firing = false;
return true;
}
};
})());