# Yahoo! UI Library

### Event Utility 2.5.0

Yahoo! UI Library > event > CustomEvent.js (source view)

Show Private Show Protected

    /**
     * The CustomEvent class lets you define events for your application
     * that can be subscribed to by one or more independent component.
     *
     * @param {String}  type The type of event, which is passed to the callback
     *                  when the event fires
     * @param {Object}  oScope The context the event will fire from.  "this" will
     *                  refer to this object in the callback.  Default value:
     *                  the window object.  The listener can override this.
     * @param {boolean} silent pass true to prevent the event from writing to
     *                  the debugsystem
     * @param {int}     signature the signature that the custom event subscriber
     *                  will receive. YAHOO.util.CustomEvent.LIST or
     *                  YAHOO.util.CustomEvent.FLAT.  The default is
     *                  YAHOO.util.CustomEvent.LIST.
     * @namespace YAHOO.util
     * @class CustomEvent
     * @constructor
     */
    YAHOO.util.CustomEvent = function(type, oScope, silent, signature) {

        /**
         * The type of event, returned to subscribers when the event fires
         * @property type
         * @type string
         */
        this.type = type;

        /**
         * The scope the the event will fire from by default.  Defaults to the window
         * obj
         * @property scope
         * @type object
         */
        this.scope = oScope || window;

        /**
         * By default all custom events are logged in the debug build, set silent
         * to true to disable debug outpu for this event.
         * @property silent
         * @type boolean
         */
        this.silent = silent;

        /**
         * Custom events support two styles of arguments provided to the event
         * subscribers.
         * <ul>
         * <li>YAHOO.util.CustomEvent.LIST:
         *   <ul>
         *   <li>param1: event name</li>
         *   <li>param2: array of arguments sent to fire</li>
         *   <li>param3: <optional> a custom object supplied by the subscriber</li>
         *   </ul>
         * </li>
         * <li>YAHOO.util.CustomEvent.FLAT
         *   <ul>
         *   <li>param1: the first argument passed to fire.  If you need to
         *           pass multiple parameters, use and array or object literal</li>
         *   <li>param2: <optional> a custom object supplied by the subscriber</li>
         *   </ul>
         * </li>
         * </ul>
         *   @property signature
         *   @type int
         */
        this.signature = signature || YAHOO.util.CustomEvent.LIST;

        /**
         * The subscribers to this event
         * @property subscribers
         * @type Subscriber[]
         */
        this.subscribers = [];

        if (!this.silent) {
            YAHOO.log( "Creating " + this, "info", "Event" );
        }

        var onsubscribeType = "_YUICEOnSubscribe";

        // Only add subscribe events for events that are not generated by
        // CustomEvent
        if (type !== onsubscribeType) {

            /**
             * Custom events provide a custom event that fires whenever there is
             * a new subscriber to the event.  This provides an opportunity to
             * handle the case where there is a non-repeating event that has
             * already fired has a new subscriber.
             *
             * @event subscribeEvent
             * @type YAHOO.util.CustomEvent
             * @param {Function} fn The function to execute
             * @param {Object}   obj An object to be passed along when the event
             *                       fires
             * @param {boolean|Object}  override If true, the obj passed in becomes
             *                                   the execution scope of the listener.
             *                                   if an object, that object becomes the
             *                                   the execution scope.
             */
            this.subscribeEvent =
                    new YAHOO.util.CustomEvent(onsubscribeType, this, true);

        }

        /**
         * In order to make it possible to execute the rest of the subscriber
         * stack when one thows an exception, the subscribers exceptions are
         * caught.  The most recent exception is stored in this property
         * @property lastError
         * @type Error
         */
        this.lastError = null;
    };

    /**
     * Subscriber listener sigature constant.  The LIST type returns three
     * parameters: the event type, the array of args passed to fire, and
     * the optional custom object
     * @property YAHOO.util.CustomEvent.LIST
     * @static
     * @type int
     */
    YAHOO.util.CustomEvent.LIST = 0;

    /**
     * Subscriber listener sigature constant.  The FLAT type returns two
     * parameters: the first argument passed to fire and the optional
     * custom object
     * @property YAHOO.util.CustomEvent.FLAT
     * @static
     * @type int
     */
    YAHOO.util.CustomEvent.FLAT = 1;

    YAHOO.util.CustomEvent.prototype = {

        /**
         * Subscribes the caller to this event
         * @method subscribe
         * @param {Function} fn        The function to execute
         * @param {Object}   obj       An object to be passed along when the event
         *                             fires
         * @param {boolean|Object}  override If true, the obj passed in becomes
         *                                   the execution scope of the listener.
         *                                   if an object, that object becomes the
         *                                   the execution scope.
         */
        subscribe: function(fn, obj, override) {

            if (!fn) {
    throw new Error("Invalid callback for subscriber to '" + this.type + "'");
            }

            if (this.subscribeEvent) {
                this.subscribeEvent.fire(fn, obj, override);
            }

            this.subscribers.push( new YAHOO.util.Subscriber(fn, obj, override) );
        },

        /**
         * Unsubscribes subscribers.
         * @method unsubscribe
         * @param {Function} fn  The subscribed function to remove, if not supplied
         *                       all will be removed
         * @param {Object}   obj  The custom object passed to subscribe.  This is
         *                        optional, but if supplied will be used to
         *                        disambiguate multiple listeners that are the same
         *                        (e.g., you subscribe many object using a function
         *                        that lives on the prototype)
         * @return {boolean} True if the subscriber was found and detached.
         */
        unsubscribe: function(fn, obj) {

            if (!fn) {
                return this.unsubscribeAll();
            }

            var found = false;
            for (var i=0, len=this.subscribers.length; i<len; ++i) {
                var s = this.subscribers[i];
                if (s && s.contains(fn, obj)) {
                    this._delete(i);
                    found = true;
                }
            }

            return found;
        },

        /**
         * Notifies the subscribers.  The callback functions will be executed
         * from the scope specified when the event was created, and with the
         * following parameters:
         *   <ul>
         *   <li>The type of event</li>
         *   <li>All of the arguments fire() was executed with as an array</li>
         *   <li>The custom object (if any) that was passed into the subscribe()
         *       method</li>
         *   </ul>
         * @method fire
         * @param {Object*} arguments an arbitrary set of parameters to pass to
         *                            the handler.
         * @return {boolean} false if one of the subscribers returned false,
         *                   true otherwise
         */
        fire: function() {
            var len=this.subscribers.length;
            if (!len && this.silent) {
                return true;
            }

            var args=[], ret=true, i, rebuild=false;

            for (i=0; i<arguments.length; ++i) {
                args.push(arguments[i]);
            }

            if (!this.silent) {
                YAHOO.log( "Firing "       + this  + ", " +
                           "args: "        + args  + ", " +
                           "subscribers: " + len,
                           "info", "Event"                  );
            }

            for (i=0; i<len; ++i) {
                var s = this.subscribers[i];
                if (!s) {
                    rebuild=true;
                } else {
                    if (!this.silent) {
                        YAHOO.log( this.type + "->" + (i+1) + ": " +  s,
                                    "info", "Event" );
                    }

                    var scope = s.getScope(this.scope);

                    if (this.signature == YAHOO.util.CustomEvent.FLAT) {
                        var param = null;
                        if (args.length > 0) {
                            param = args[0];
                        }

                        try {
                            ret = s.fn.call(scope, param, s.obj);
                        } catch(e) {
                            this.lastError = e;
                            YAHOO.log(this + " subscriber exception: " + e,
                                      "error", "Event");
                        }
                    } else {
                        try {
                            ret = s.fn.call(scope, this.type, args, s.obj);
                        } catch(ex) {
                            this.lastError = ex;
                            YAHOO.log(this + " subscriber exception: " + ex,
                                      "error", "Event");
                        }
                    }
                    if (false === ret) {
                        if (!this.silent) {
                            YAHOO.log("Event cancelled, subscriber " + i +
                                      " of " + len, "info", "Event");
                        }

                        //break;
                        return false;
                    }
                }
            }

            if (rebuild) {
                var newlist=[],subs=this.subscribers;
                for (i=0,len=subs.length; i<len; i=i+1) {
                    newlist.push(subs[i]);
                }

                this.subscribers=newlist;
            }

            return true;
        },

        /**
         * Removes all listeners
         * @method unsubscribeAll
         * @return {int} The number of listeners unsubscribed
         */
        unsubscribeAll: function() {
            for (var i=0, len=this.subscribers.length; i<len; ++i) {
                this._delete(len - 1 - i);
            }

            this.subscribers=[];

            return i;
        },

        /**
         * @method _delete
         * @private
         */
        _delete: function(index) {
            var s = this.subscribers[index];
            if (s) {
                delete s.fn;
                delete s.obj;
            }

            this.subscribers[index]=null;
        },

        /**
         * @method toString
         */
        toString: function() {
             return "CustomEvent: " + "'" + this.type  + "', " +
                 "scope: " + this.scope;

        }
    };

    /////////////////////////////////////////////////////////////////////

    /**
     * Stores the subscriber information to be used when the event fires.
     * @param {Function} fn       The function to execute
     * @param {Object}   obj      An object to be passed along when the event fires
     * @param {boolean}  override If true, the obj passed in becomes the execution
     *                            scope of the listener
     * @class Subscriber
     * @constructor
     */
    YAHOO.util.Subscriber = function(fn, obj, override) {

        /**
         * The callback that will be execute when the event fires
         * @property fn
         * @type function
         */
        this.fn = fn;

        /**
         * An optional custom object that will passed to the callback when
         * the event fires
         * @property obj
         * @type object
         */
        this.obj = YAHOO.lang.isUndefined(obj) ? null : obj;

        /**
         * The default execution scope for the event listener is defined when the
         * event is created (usually the object which contains the event).
         * By setting override to true, the execution scope becomes the custom
         * object passed in by the subscriber.  If override is an object, that
         * object becomes the scope.
         * @property override
         * @type boolean|object
         */
        this.override = override;

    };

    /**
     * Returns the execution scope for this listener.  If override was set to true
     * the custom obj will be the scope.  If override is an object, that is the
     * scope, otherwise the default scope will be used.
     * @method getScope
     * @param {Object} defaultScope the scope to use if this listener does not
     *                              override it.
     */
    YAHOO.util.Subscriber.prototype.getScope = function(defaultScope) {
        if (this.override) {
            if (this.override === true) {
                return this.obj;
            } else {
                return this.override;
            }
        }
        return defaultScope;
    };

    /**
     * Returns true if the fn and obj match this objects properties.
     * Used by the unsubscribe method to match the right subscriber.
     *
     * @method contains
     * @param {Function} fn the function to execute
     * @param {Object} obj an object to be passed along when the event fires
     * @return {boolean} true if the supplied arguments match this
     *                   subscriber's signature.
     */
    YAHOO.util.Subscriber.prototype.contains = function(fn, obj) {
        if (obj) {
            return (this.fn == fn && this.obj == obj);
        } else {
            return (this.fn == fn);
        }
    };

    /**
     * @method toString
     */
    YAHOO.util.Subscriber.prototype.toString = function() {
        return "Subscriber { obj: " + this.obj  +
               ", override: " +  (this.override || "no") + " }";
    };

Copyright © 2007 Yahoo! Inc. All rights reserved.