(function(){
var EXTUTIL = Ext.util,
TOARRAY = Ext.toArray,
EACH = Ext.each,
ISOBJECT = Ext.isObject
TRUE = true,
FALSE = false;
/**
* @class Ext.util.Observable
* Abstract base class that provides a common interface for publishing events. Subclasses are expected to
* to have a property "events" with all the events defined.
* For example:
*
Employee = function(name){
this.name = name;
this.addEvents({
"fired" : true,
"quit" : true
});
}
Ext.extend(Employee, Ext.util.Observable);
*/
EXTUTIL.Observable = function(){
/**
* @cfg {Object} listeners (optional) 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.
*/
var me = this, e = me.events;
if(me.listeners){
me.on(me.listeners);
delete me.listeners;
}
me.events = e || {};
};
EXTUTIL.Observable.prototype = function(){
var filterOptRe = /^(?:scope|delay|buffer|single)$/, toLower = function(s){
return s.toLowerCase();
};
return {
/**
* 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 = toLower(a[0]),
me = this,
ret = TRUE,
ce = me.events[ename],
q,
c;
if (me.eventsSuspended === TRUE) {
if (q = me.suspendedEventsQueue) {
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) {
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 component
* @param {String} eventName The type of event to listen for
* @param {Function} handler The method the event invokes
* @param {Object} scope (optional) The scope in which to execute the handler
* function. The handler function's "this" context.
* @param {Object} options (optional) An object containing handler configuration
* properties. This may contain any of the following properties:The scope in which to execute the handler function. The handler function's "this" context.
The number of milliseconds to delay the invocation of the handler after the event fires.
True to add a handler to handle just the next firing of the event, and then remove itself.
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.
Only call the handler if the event was fired on the target Observable, not * if the event was bubbled up from a child Observable.
* Combining Options
* Using the options argument, it is possible to combine different types of listeners:
*
* A normalized, delayed, one-time listener that auto stops the event and passes a custom argument (forumId)
*
el.on('click', this.onClick, this, {
single: true,
delay: 100,
forumId: 4
});
*
* 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.
*
*
foo.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:
*
foo.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 (!filterOptRe.test(e)) {
me.addListener(e, oe.fn || oe, oe.scope || o.scope, oe.fn ? oe : o);
}
}
} else {
eventName = toLower(eventName);
ce = me.events[eventName] || TRUE;
if (typeof ce == "boolean") {
me.events[eventName] = ce = new EXTUTIL.Event(me, eventName);
}
ce.addListener(fn, scope, ISOBJECT(o) ? o : {});
}
},
/**
* Removes a listener
* @param {String} eventName The type of event to listen for
* @param {Function} handler The handler to remove
* @param {Object} scope (optional) The scope (this object) for the handler
*/
removeListener : function(eventName, fn, scope){
var ce = this.events[toLower(eventName)];
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();
}
}
},
/**
* Used to define events on this Observable
* @param {Object} object The object with the events defined
*/
addEvents : function(o){
var me = this;
me.events = me.events || {};
if (typeof o == 'string') {
EACH(arguments, function(a) {
me.events[a] = me.events[a] || 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 queueSuspended {Boolean} 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.suspendedEventsQueue = [];
}
},
/**
* 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;
me.eventsSuspended = !delete me.suspendedEventQueue;
EACH(me.suspendedEventsQueue, function(e) {
me.fireEvent.apply(me, e);
});
}
}
}();
var OBSERVABLE = EXTUTIL.Observable.prototype;
/**
* Appends an event handler to this element (shorthand for 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 in which to execute the handler
* function. The handler function's "this" context.
* @param {Object} options (optional)
* @method
*/
OBSERVABLE.on = OBSERVABLE.addListener;
/**
* Removes a listener (shorthand for removeListener)
* @param {String} eventName The type of event to listen for
* @param {Function} handler The handler to remove
* @param {Object} scope (optional) The scope (this object) 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, scope){
var task = new EXTUTIL.DelayedTask();
return function(){
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, scope){
return function(){
var args = TOARRAY(arguments);
(function(){
h.apply(scope, args);
}).defer(o.delay || 10);
};
};
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, scope);
}
if(o.single){
h = createSingle(h, this, fn, scope);
}
if(o.buffer){
h = createBuffered(h, o, scope);
}
l.fireFn = h;
return l;
},
findListener : function(fn, scope){
var s, ret = -1
EACH(this.listeners, function(l, i) {
s = l.scope;
if(l.fn == fn && (s == scope || s == this.obj)){
ret = i;
return FALSE;
}
},
this);
return ret;
},
isListening : function(fn, scope){
return this.findListener(fn, scope) != -1;
},
removeListener : function(fn, scope){
var index,
me = this,
ret = FALSE;
if((index = me.findListener(fn, scope)) != -1){
if (me.firing) {
me.listeners = me.listeners.slice(0);
}
me.listeners.splice(index, 1);
ret = TRUE;
}
return ret;
},
clearListeners : function(){
this.listeners = [];
},
fire : function(){
var me = this,
args = TOARRAY(arguments),
ret = TRUE;
EACH(me.listeners, function(l) {
me.firing = TRUE;
if (l.fireFn.apply(l.scope || me.obj || window, args) === FALSE) {
return ret = me.firing = FALSE;
}
});
me.firing = FALSE;
return ret;
}
};
})();