Observable.js
authorAlan Knowles <alan@akbkhome.com>
Fri, 30 Apr 2010 04:15:06 +0000 (12:15 +0800)
committerAlan Knowles <alan@akbkhome.com>
Fri, 30 Apr 2010 04:15:06 +0000 (12:15 +0800)
Observable.js

index e69de29..45d9f49 100644 (file)
@@ -0,0 +1,359 @@
+/*
+ * Based on:
+ * Ext JS Library 1.1.1
+ * Copyright(c) 2006-2007, Ext JS, LLC.
+ *
+ * Originally Released Under LGPL - original licence link has changed is not relivant.
+ *
+ * Fork - LGPL
+ * <script type="text/javascript">
+ */
+XObject = imports.XObject.XObject;
+/**
+ * @class 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.<br>
+ * For example:
+ * <pre><code>
+ Employee = XObject.define(
+      function(name){
+        this.name = name;
+        this.addEvents({
+            "fired" : true,
+            "quit" : true
+        });
+     },
+     Observable, {});
+     
+</code></pre>
+ * @param {Object} config properties to use (incuding events / listeners)
+ */
+Observable = XObject.define(
+    function(cfg){
+        
+        cfg = cfg|| {};
+        this.addEvents(cfg.events || {});
+        if (cfg.events) {
+            delete cfg.events; // make sure
+        }
+         
+        XObject.extend(this, cfg);
+        
+        if(this.listeners){
+            this.on(this.listeners);
+            delete this.listeners;
+        }
+    },
+    Object,  
+    {
+        /** 
+         * @cfg {Object} listeners  list of events and functions to call for this object, 
+         * For example :
+         * <pre><code>
+            listeners :  { 
+               'click' : function(e) {
+                   ..... 
+                } ,
+                .... 
+            } 
+          </code></pre>
+         */
+            
+        
+        /**
+         * 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(){
+            var ce = this.events[arguments[0].toLowerCase()];
+            if(typeof ce == "object"){
+                return ce.fire.apply(ce, Array.prototype.slice.call(arguments, 1));
+            }else{
+                return true;
+            }
+        },
+
+        // private - blcoks these types of signals?
+        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:<ul>
+         * <li>scope {Object} The scope in which to execute the handler function. The handler function's "this" context.</li>
+         * <li>delay {Number} The number of milliseconds to delay the invocation of the handler after te event fires.</li>
+         * <li>single {Boolean} True to add a handler to handle just the next firing of the event, and then remove itself.</li>
+         * <li>buffer {Number} Causes the handler to be scheduled to run in an {@link Roo.DelayedTask} delayed
+         * by the specified number of milliseconds. If the event fires again within that time, the original
+         * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</li>
+         * </ul><br>
+         * <p>
+         * <b>Combining Options</b><br>
+         * Using the options argument, it is possible to combine different types of listeners:<br>
+         * <br>
+         * A normalized, delayed, one-time listener that auto stops the event and passes a custom argument (forumId)
+            <pre><code>
+            el.on('click', this.onClick, this, {
+                single: true,
+                delay: 100,
+                forumId: 4
+            });
+            </code></pre>
+         * <p>
+         * <b>Attaching multiple handlers in 1 call</b><br>
+         * The method also allows for a single argument to be passed which is a config object containing properties
+         * which specify multiple handlers.
+         * <pre><code>
+            el.on({
+                'click': {
+                    fn: this.onClick,
+                    scope: this,
+                    delay: 100
+                }, 
+                'mouseover': {
+                    fn: this.onMouseOver,
+                    scope: this
+                },
+                'mouseout': {
+                    fn: this.onMouseOut,
+                    scope: this
+                }
+            });
+            </code></pre>
+         * <p>
+         * Or a shorthand syntax which passes the same scope object to all handlers:
+            <pre><code>
+            el.on({
+                'click': this.onClick,
+                'mouseover': this.onMouseOver,
+                'mouseout': this.onMouseOut,
+                scope: this
+            });
+            </code></pre>
+         */
+        on : 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.on(e, o[e], o.scope,  o);
+                    }else{
+                        // individual options
+                        this.on(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 Roo.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
+         */
+        un : 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, Roo.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 = {};
+            }
+            Roo.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;
+        }
+};
+/**
+ * Starts capture on the specified Observable. All events will be passed
+ * to the supplied function with the event name + standard signature of the event
+ * <b>before</b> 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
+ */
+Observable.capture = function(o, fn, scope){
+    o.fireEvent = o.fireEvent.createInterceptor(fn, scope);
+};
+
+/**
+ * Removes <b>all</b> added captures from the Observable.
+ * @param {Observable} o The Observable to release
+ * @static
+ */
+Observable.releaseCapture = function(o){
+    o.fireEvent = Observable.prototype.fireEvent;
+};
+
+(function(){
+
+    var createBuffered = function(h, o, scope){
+        var task = new Roo.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);
+        };
+    };
+
+    Roo.Event = function(obj, name){
+        this.name = name;
+        this.obj = obj;
+        this.listeners = [];
+    };
+
+    Roo.Event.prototype = {
+        addListener : function(fn, scope, options){
+            var o = options || {};
+            scope = scope || this.obj;
+            if(!this.isListening(fn, scope)){
+                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;
+                if(!this.firing){ // if we are currently firing this event, don't disturb the listener loop
+                    this.listeners.push(l);
+                }else{
+                    this.listeners = this.listeners.slice(0);
+                    this.listeners.push(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;
+        }
+    };
+})();
\ No newline at end of file