/*
* Ext JS Library 2.0 Copyright(c) 2006-2007, 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 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. *
*
* 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();
}
}
},
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]]) {
o[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;
}
};
})();