The source code

    /*!
     * Ext JS Library 3.4.0
     * Copyright(c) 2006-2011 Sencha Inc.
     * licensing@sencha.com
     * http://www.sencha.com/license
     */
    (function(){

    var EXTUTIL = Ext.util,
        EACH = Ext.each,
        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(this, 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 = {
        // private
        filterOptRe : /^(?:scope|delay|buffer|single)$/,

        /**
         * 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 = Array.prototype.slice.call(arguments, 0),
                ename = a[0].toLowerCase(),
                me = this,
                ret = TRUE,
                ce = me.events[ename],
                cc,
                q,
                c;
            if (me.eventsSuspended === TRUE) {
                if (q = me.eventQueue) {
                    q.push(a);
                }
            }
            else if(typeof ce == 'object') {
                if (ce.bubble){
                    if(ce.fire.apply(ce, a.slice(1)) === FALSE) {
                        return FALSE;
                    }
                    c = me.getBubbleTarget && me.getBubbleTarget();
                    if(c && c.enableBubble) {
                        cc = c.events[ename];
                        if(!cc || typeof cc != 'object' || !cc.bubble) {
                            c.enableBubble(ename);
                        }
                        return c.fireEvent.apply(c, a);
                    }
                }
                else {
                    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:
         * scope : ObjectThe scope (this reference) in which the handler function is executed.
         * If omitted, defaults to the object which fired the event.
         * delay : NumberThe number of milliseconds to delay the invocation of the handler after the event fires.
         * single : BooleanTrue to add a handler to handle just the next firing of the event, and then remove itself.
         * buffer : NumberCauses 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.
         * target : ObservableOnly 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 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,
                ce;

            if (typeof eventName == 'object') {
                o = eventName;
                for (e in o) {
                    oe = o[e];
                    if (!me.filterOptRe.test(e)) {
                        me.addListener(e, oe.fn || oe, oe.scope || o.scope, oe.fn ? oe : o);
                    }
                }
            } else {
                eventName = eventName.toLowerCase();
                ce = me.events[eventName] || TRUE;
                if (typeof ce == 'boolean') {
                    me.events[eventName] = ce = new EXTUTIL.Event(me, eventName);
                }
                ce.addListener(fn, scope, typeof o == 'object' ? 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[eventName.toLowerCase()];
            if (typeof ce == 'object') {
                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(typeof evt == 'object'){
                    evt.clearListeners();
                }
            }
        },

        /**
         * Adds the specified events to the list of events which this Observable may fire.
         * @param {Object|String} o Either an object with event names as properties with a value of true
         * or the first event name string if multiple event names are being passed as separate parameters.
         * @param {string} Optional. Event name if multiple event names are being passed as separate parameters.
         * Usage:
    this.addEvents('storeloaded', 'storecleared');

         */
        addEvents : function(o){
            var me = this;
            me.events = me.events || {};
            if (typeof o == 'string') {
                var a = arguments,
                    i = a.length;
                while(i--) {
                    me.events[a[i]] = me.events[a[i]] || 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.toLowerCase()];
            return typeof e == 'object' && 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.eventQueue){
                this.eventQueue = [];
            }
        },

        /**
         * 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,
                queued = me.eventQueue || [];
            me.eventsSuspended = FALSE;
            delete me.eventQueue;
            EACH(queued, 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, Array.prototype.slice.call(arguments, 0));
            }
        };
    };

    function createBuffered(h, o, l, scope){
        l.task = new EXTUTIL.DelayedTask();
        return function(){
            l.task.delay(o.buffer, h, scope, Array.prototype.slice.call(arguments, 0));
        };
    };

    function createSingle(h, e, fn, scope){
        return function(){
            e.removeListener(fn, scope);
            return h.apply(scope, arguments);
        };
    };

    function createDelayed(h, o, l, scope){
        return function(){
            var task = new EXTUTIL.DelayedTask(),
                args = Array.prototype.slice.call(arguments, 0);
            if(!l.tasks) {
                l.tasks = [];
            }
            l.tasks.push(task);
            task.delay(o.delay || 10, function(){
                l.tasks.remove(task);
                h.apply(scope, args);
            }, scope);
        };
    };

    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, l, scope);
            }
            if(o.single){
                h = createSingle(h, this, fn, scope);
            }
            if(o.buffer){
                h = createBuffered(h, o, l, scope);
            }
            l.fireFn = h;
            return l;
        },

        findListener : function(fn, scope){
            var list = this.listeners,
                i = list.length,
                l;

            scope = scope || this.obj;
            while(i--){
                l = list[i];
                if(l){
                    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,
                l,
                k,
                me = this,
                ret = FALSE;
            if((index = me.findListener(fn, scope)) != -1){
                if (me.firing) {
                    me.listeners = me.listeners.slice(0);
                }
                l = me.listeners[index];
                if(l.task) {
                    l.task.cancel();
                    delete l.task;
                }
                k = l.tasks && l.tasks.length;
                if(k) {
                    while(k--) {
                        l.tasks[k].cancel();
                    }
                    delete l.tasks;
                }
                me.listeners.splice(index, 1);
                ret = TRUE;
            }
            return ret;
        },

        // Iterate to stop any buffered/delayed events
        clearListeners : function(){
            var me = this,
                l = me.listeners,
                i = l.length;
            while(i--) {
                me.removeListener(l[i].fn, l[i].scope);
            }
        },

        fire : function(){
            var me = this,
                listeners = me.listeners,
                len = listeners.length,
                i = 0,
                l;

            if(len > 0){
                me.firing = TRUE;
                var args = Array.prototype.slice.call(arguments, 0);
                for (; i < len; i++) {
                    l = listeners[i];
                    if(l && l.fireFn.apply(l.scope || me.obj || window, args) === FALSE) {
                        return (me.firing = FALSE);
                    }
                }
            }
            me.firing = FALSE;
            return TRUE;
        }

    };
    })();