X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/ee06f37b0f6f6d94cd05a6ffae556660f7c4a2bc..c930e9176a5a85509c5b0230e2bff5c22a591432:/src/util/core/Observable.js?ds=sidebyside
diff --git a/src/util/core/Observable.js b/src/util/core/Observable.js
new file mode 100644
index 00000000..6d72c662
--- /dev/null
+++ b/src/util/core/Observable.js
@@ -0,0 +1,483 @@
+/*!
+ * Ext JS Library 3.0.0
+ * Copyright(c) 2006-2009 Ext JS, LLC
+ * licensing@extjs.com
+ * http://www.extjs.com/license
+ */
+(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:
+ *
+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)
+ }
+});
+
+ * This could then be used like this:
+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!");
+ }
+ }
+});
+
+ */
+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.
+ *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:
+// 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'
+});
+ *
+ */
+ 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 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 (!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 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[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 {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.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 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, 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;
+ }
+};
+})();
\ No newline at end of file