(function(){ var EXTUTIL = Ext.util, TOARRAY = Ext.toArray, EACH = Ext.each, ISOBJECT = Ext.isObject, TRUE = true, FALSE = false; /** * @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(config) } });
*/ EXTUTIL.Observable = function(){ /** * @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 Component's HTMLElement, listeners must be added to the {@link Ext.Component#getEl Element} after the Component * has been rendered. A plugin can simplify this step:*/ var me = this, e = me.events; if(me.listeners){ me.on(me.listeners); delete me.listeners; } me.events = e || {}; }; EXTUTIL.Observable.prototype = { // private filterOptRe : /^(?:scope|delay|buffer|single)$/, /** *// Plugin is configured with a listeners config object. // The Component is appended to the argument list of all handler functions. Ext.DomObserver = Ext.extend(Object, { constructor: function(config) { this.listeners = config.listeners ? config.listeners : config; }, // Component passes itself into plugin's init method init: function(c) { var p, l = this.listeners; for (p in l) { if (Ext.isFunction(l[p])) { l[p] = this.createHandler(l[p], c); } else { l[p].fn = this.createHandler(l[p].fn, c); } } // Add the listeners to the Element immediately following the render call c.render = c.render.{@link Function#createSequence createSequence}(function() { var e = c.getEl(); if (e) { e.on(l); } }); }, createHandler: function(fn, c) { return function(e) { fn.call(this, e, c); }; } }); var combo = new Ext.form.ComboBox({ // Collapse combo when its element is clicked on plugins: [ new Ext.DomObserver({ click: function(evt, comp) { comp.collapse(); } })], store: myStore, typeAhead: true, mode: 'local', triggerAction: 'all' }); *
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 a = TOARRAY(arguments), ename = a[0].toLowerCase(), me = this, ret = TRUE, ce = me.events[ename], q, c; if (me.eventsSuspended === TRUE) { if (q = me.eventQueue) { q.push(a); } } else if(ISOBJECT(ce) && ce.bubble){ if(ce.fire.apply(ce, a.slice(1)) === FALSE) { return FALSE; } c = me.getBubbleTarget && me.getBubbleTarget(); if(c && c.enableBubble) { if(!c.events[ename] || !Ext.isObject(c.events[ename]) || !c.events[ename].bubble) { c.enableBubble(ename); } return c.fireEvent.apply(c, a); } } else { if (ISOBJECT(ce)) { a.shift(); ret = ce.fire.apply(ce, 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.
*
myDataView.on('click', 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.
*
*
myGridPanel.on({
'click' : {
fn: this.onClick,
scope: this,
delay: 100
},
'mouseover' : {
fn: this.onMouseOver,
scope: this
},
'mouseout' : {
fn: this.onMouseOut,
scope: this
}
});
*
* Or a shorthand syntax:
*
myGridPanel.on({
'click' : this.onClick,
'mouseover' : this.onMouseOver,
'mouseout' : this.onMouseOut,
scope: this
});
*/
addListener : function(eventName, fn, scope, o){
var me = this,
e,
oe,
isF,
ce;
if (ISOBJECT(eventName)) {
o = eventName;
for (e in o){
oe = o[e];
if (!me.filterOptRe.test(e)) {
me.addListener(e, oe.fn || oe, oe.scope || o.scope, oe.fn ? oe : o);
}
}
} else {
eventName = eventName.toLowerCase();
ce = me.events[eventName] || TRUE;
if (Ext.isBoolean(ce)) {
me.events[eventName] = ce = new EXTUTIL.Event(me, eventName);
}
ce.addListener(fn, scope, 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(eventName, fn, scope){
var ce = this.events[eventName.toLowerCase()];
if (ISOBJECT(ce)) {
ce.removeListener(fn, scope);
}
},
/**
* Removes all listeners for this object
*/
purgeListeners : function(){
var events = this.events,
evt,
key;
for(key in events){
evt = events[key];
if(ISOBJECT(evt)){
evt.clearListeners();
}
}
},
/**
* Adds the specified events to the list of events which this Observable may fire.
* @param {Object|String} o Either an object with event names as properties with a value of 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(eventName){
var e = this.events[eventName];
return ISOBJECT(e) && 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;
EACH(queued, function(e) {
me.fireEvent.apply(me, e);
});
}
};
var OBSERVABLE = EXTUTIL.Observable.prototype;
/**
* 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
*/
OBSERVABLE.on = OBSERVABLE.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
*/
OBSERVABLE.un = OBSERVABLE.removeListener;
/**
* Removes all added captures from the Observable.
* @param {Observable} o The Observable to release
* @static
*/
EXTUTIL.Observable.releaseCapture = function(o){
o.fireEvent = OBSERVABLE.fireEvent;
};
function createTargeted(h, o, scope){
return function(){
if(o.target == arguments[0]){
h.apply(scope, TOARRAY(arguments));
}
};
};
function createBuffered(h, o, l, scope){
l.task = new EXTUTIL.DelayedTask();
return function(){
l.task.delay(o.buffer, h, scope, TOARRAY(arguments));
};
};
function createSingle(h, e, fn, scope){
return function(){
e.removeListener(fn, scope);
return h.apply(scope, arguments);
};
};
function createDelayed(h, o, l, scope){
return function(){
var task = new EXTUTIL.DelayedTask();
if(!l.tasks) {
l.tasks = [];
}
l.tasks.push(task);
task.delay(o.delay || 10, h, scope, TOARRAY(arguments));
};
};
EXTUTIL.Event = function(obj, name){
this.name = name;
this.obj = obj;
this.listeners = [];
};
EXTUTIL.Event.prototype = {
addListener : function(fn, scope, options){
var me = this,
l;
scope = scope || me.obj;
if(!me.isListening(fn, scope)){
l = 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(l);
}
},
createListener: function(fn, scope, o){
o = o || {}, scope = scope || this.obj;
var l = {
fn: fn,
scope: scope,
options: o
}, h = fn;
if(o.target){
h = createTargeted(h, o, scope);
}
if(o.delay){
h = createDelayed(h, o, l, scope);
}
if(o.single){
h = createSingle(h, this, fn, scope);
}
if(o.buffer){
h = createBuffered(h, o, l, scope);
}
l.fireFn = h;
return l;
},
findListener : function(fn, scope){
var list = this.listeners,
i = list.length,
l,
s;
while(i--) {
l = list[i];
if(l) {
s = l.scope;
if(l.fn == fn && (s == scope || s == this.obj)){
return i;
}
}
}
return -1;
},
isListening : function(fn, scope){
return this.findListener(fn, scope) != -1;
},
removeListener : function(fn, scope){
var index,
l,
k,
me = this,
ret = FALSE;
if((index = me.findListener(fn, scope)) != -1){
if (me.firing) {
me.listeners = me.listeners.slice(0);
}
l = me.listeners[index];
if(l.task) {
l.task.cancel();
delete l.task;
}
k = l.tasks && l.tasks.length;
if(k) {
while(k--) {
l.tasks[k].cancel();
}
delete l.tasks;
}
me.listeners.splice(index, 1);
ret = TRUE;
}
return ret;
},
// Iterate to stop any buffered/delayed events
clearListeners : function(){
var me = this,
l = me.listeners,
i = l.length;
while(i--) {
me.removeListener(l[i].fn, l[i].scope);
}
},
fire : function(){
var me = this,
args = TOARRAY(arguments),
listeners = me.listeners,
len = listeners.length,
i = 0,
l;
if(len > 0){
me.firing = TRUE;
for (; i < len; i++) {
l = listeners[i];
if(l && l.fireFn.apply(l.scope || me.obj || window, args) === FALSE) {
return (me.firing = FALSE);
}
}
}
me.firing = FALSE;
return TRUE;
}
};
})();