/*
* Ext JS Library 2.2.1
* Copyright(c) 2006-2009, Ext JS, LLC.
* licensing@extjs.com
*
* http://extjs.com/license
*/
/**
* @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);
*/
Ext.util.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.
*/
if(this.listeners){
this.on(this.listeners);
delete this.listeners;
}
};
Ext.util.Observable.prototype = {
/**
* Fires the specified event with the passed parameters (minus the event name).
* @param {String} eventName
* @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(){
if(this.eventsSuspended !== true){
var ce = this.events[arguments[0].toLowerCase()];
if(typeof ce == "object"){
return ce.fire.apply(ce, Array.prototype.slice.call(arguments, 1));
}
}
return true;
},
// private
filterOptRe : /^(?:scope|delay|buffer|single)$/,
/**
* 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.
* 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){
if(typeof eventName == "object"){
o = eventName;
for(var e in o){
if(this.filterOptRe.test(e)){
continue;
}
if(typeof o[e] == "function"){
// shared options
this.addListener(e, o[e], o.scope, o);
}else{
// individual options
this.addListener(e, o[e].fn, o[e].scope, o[e]);
}
}
return;
}
o = (!o || typeof o == "boolean") ? {} : o;
eventName = eventName.toLowerCase();
var ce = this.events[eventName] || true;
if(typeof ce == "boolean"){
ce = new Ext.util.Event(this, eventName);
this.events[eventName] = ce;
}
ce.addListener(fn, scope, 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[eventName.toLowerCase()];
if(typeof ce == "object"){
ce.removeListener(fn, scope);
}
},
/**
* Removes all listeners for this object
*/
purgeListeners : function(){
for(var evt in this.events){
if(typeof this.events[evt] == "object"){
this.events[evt].clearListeners();
}
}
},
/**
* 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(o, events){
var createHandler = function(ename){
return function(){
return this.fireEvent.apply(this, Ext.combine(ename, Array.prototype.slice.call(arguments, 0)));
};
};
for(var i = 0, len = events.length; i < len; i++){
var ename = events[i];
if(!this.events[ename]){ this.events[ename] = true; };
o.on(ename, createHandler(ename), this);
}
},
/**
* Used to define events on this Observable
* @param {Object} object The object with the events defined
*/
addEvents : function(o){
if(!this.events){
this.events = {};
}
if(typeof o == 'string'){
for(var i = 0, a = arguments, v; v = a[i]; i++){
if(!this.events[a[i]]){
this.events[a[i]] = true;
}
}
}else{
Ext.applyIf(this.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 typeof e == "object" && e.listeners.length > 0;
},
/**
* Suspend the firing of all events. (see {@link #resumeEvents})
*/
suspendEvents : function(){
this.eventsSuspended = true;
},
/**
* Resume firing events. (see {@link #suspendEvents})
*/
resumeEvents : function(){
this.eventsSuspended = false;
},
// these are considered experimental
// allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
// private
getMethodEvent : function(method){
if(!this.methodEvents){
this.methodEvents = {};
}
var e = this.methodEvents[method];
if(!e){
e = {};
this.methodEvents[method] = e;
e.originalFn = this[method];
e.methodName = method;
e.before = [];
e.after = [];
var returnValue, v, cancel;
var obj = this;
var 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;
}
if(v.cancel === true){
cancel = true;
}
}else if(v === false){
cancel = true;
}else {
returnValue = v;
}
}
}
this[method] = function(){
returnValue = v = undefined; cancel = false;
var args = Array.prototype.slice.call(arguments, 0);
for(var i = 0, len = e.before.length; i < len; i++){
makeCall(e.before[i].fn, e.before[i].scope, args);
if(cancel){
return returnValue;
}
}
if((v = e.originalFn.apply(obj, args)) !== undefined){
returnValue = v;
}
for(var i = 0, len = e.after.length; i < len; i++){
makeCall(e.after[i].fn, e.after[i].scope, args);
if(cancel){
return returnValue;
}
}
return returnValue;
};
}
return e;
},
// adds an "interceptor" called before the original method
beforeMethod : function(method, fn, scope){
var e = this.getMethodEvent(method);
e.before.push({fn: fn, scope: scope});
},
// adds a "sequence" called after the original method
afterMethod : function(method, fn, scope){
var e = this.getMethodEvent(method);
e.after.push({fn: fn, scope: scope});
},
removeMethodListener : function(method, fn, scope){
var e = this.getMethodEvent(method);
for(var i = 0, len = e.before.length; i < len; i++){
if(e.before[i].fn == fn && e.before[i].scope == scope){
e.before.splice(i, 1);
return;
}
}
for(var i = 0, len = e.after.length; i < len; i++){
if(e.after[i].fn == fn && e.after[i].scope == scope){
e.after.splice(i, 1);
return;
}
}
}
};
/**
* 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
*/
Ext.util.Observable.prototype.on = Ext.util.Observable.prototype.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
*/
Ext.util.Observable.prototype.un = Ext.util.Observable.prototype.removeListener;
/**
* 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
* @param {Function} fn The function to call
* @param {Object} scope (optional) The scope (this object) for the fn
* @static
*/
Ext.util.Observable.capture = function(o, fn, scope){
o.fireEvent = o.fireEvent.createInterceptor(fn, scope);
};
/**
* 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;
};
(function(){
var createBuffered = function(h, o, scope){
var task = new Ext.util.DelayedTask();
return function(){
task.delay(o.buffer, h, scope, Array.prototype.slice.call(arguments, 0));
};
};
var createSingle = function(h, e, fn, scope){
return function(){
e.removeListener(fn, scope);
return h.apply(scope, arguments);
};
};
var createDelayed = function(h, o, scope){
return function(){
var args = Array.prototype.slice.call(arguments, 0);
setTimeout(function(){
h.apply(scope, args);
}, o.delay || 10);
};
};
Ext.util.Event = function(obj, name){
this.name = name;
this.obj = obj;
this.listeners = [];
};
Ext.util.Event.prototype = {
addListener : function(fn, scope, options){
scope = scope || this.obj;
if(!this.isListening(fn, scope)){
var l = this.createListener(fn, scope, options);
if(!this.firing){
this.listeners.push(l);
}else{ // if we are currently firing this event, don't disturb the listener loop
this.listeners = this.listeners.slice(0);
this.listeners.push(l);
}
}
},
createListener : function(fn, scope, o){
o = o || {};
scope = scope || this.obj;
var l = {fn: fn, scope: scope, options: o};
var h = fn;
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){
scope = scope || this.obj;
var ls = this.listeners;
for(var i = 0, len = ls.length; i < len; i++){
var l = ls[i];
if(l.fn == fn && l.scope == scope){
return i;
}
}
return -1;
},
isListening : function(fn, scope){
return this.findListener(fn, scope) != -1;
},
removeListener : function(fn, scope){
var index;
if((index = this.findListener(fn, scope)) != -1){
if(!this.firing){
this.listeners.splice(index, 1);
}else{
this.listeners = this.listeners.slice(0);
this.listeners.splice(index, 1);
}
return true;
}
return false;
},
clearListeners : function(){
this.listeners = [];
},
fire : function(){
var ls = this.listeners, scope, len = ls.length;
if(len > 0){
this.firing = true;
var args = Array.prototype.slice.call(arguments, 0);
for(var i = 0; i < len; i++){
var l = ls[i];
if(l.fireFn.apply(l.scope||this.obj||window, arguments) === false){
this.firing = false;
return false;
}
}
this.firing = false;
}
return true;
}
};
})();