# Yahoo! UI Library

### Event Utility 2.8.1

Yahoo! UI Library > event > CustomEvent.js (source view)
Search:
Filters Show Private Show Protected Show Deprecated

    /**
     * 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}  context 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.
     * @param fireOnce {boolean} If configured to fire once, the custom event
     * will only notify subscribers a single time regardless of how many times
     * the event is fired.  In addition, new subscribers will be notified
     * immediately if the event has already been fired.
     * @namespace YAHOO.util
     * @class CustomEvent
     * @constructor
     */
    YAHOO.util.CustomEvent = function(type, context, silent, signature, fireOnce) {

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

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

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

        /**
         * If configured to fire once, the custom event will only notify subscribers
         * a single time regardless of how many times the event is fired.  In addition,
         * new subscribers will be notified immediately if the event has already been
         * fired.
         * @property fireOnce
         * @type boolean
         * @default false
         */
        this.fireOnce = fireOnce;

        /**
         * Indicates whether or not this event has ever been fired.
         * @property fired
         * @type boolean
         * @default false
         */
        this.fired = false;

        /**
         * For fireOnce events the arguments the event was fired with are stored
         * so that new subscribers get the proper payload.
         * @property firedWith
         * @type Array
         */
        this.firedWith = null;

        /**
         * 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 fn {Function} The function to execute
             * @param obj <Object> An object to be passed along when the event fires.
             * Defaults to the custom event.
             * @param override <boolean|Object> If true, the obj passed in becomes the
             * execution context of the listener. If an object, that object becomes
             * the execution context. Defaults to the custom event.
             */
            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.
         * overrideContext <boolean|Object> If true, the obj passed in becomes the execution
         * context of the listener. If an object, that object becomes the execution context.
         */
        subscribe: function(fn, obj, overrideContext) {

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

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

            var s = new YAHOO.util.Subscriber(fn, obj, overrideContext);

            if (this.fireOnce && this.fired) {
                this.notify(s, this.firedWith);
            } else {
                this.subscribers.push(s);
            }
        },

        /**
         * 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 context 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() {

            this.lastError = null;

            var errors = [],
                len=this.subscribers.length;

            var args=[].slice.call(arguments, 0), ret=true, i, rebuild=false;

            if (this.fireOnce) {
                if (this.fired) {
                    YAHOO.log('fireOnce event has already fired: ' + this.type);
                    return true;
                } else {
                    this.firedWith = args;
                }
            }

            this.fired = true;

            if (!len && this.silent) {
                //YAHOO.log('DEBUG no subscribers');
                return true;
            }

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

            // make a copy of the subscribers so that there are
            // no index problems if one subscriber removes another.
            var subs = this.subscribers.slice();

            for (i=0; i<len; ++i) {
                var s = subs[i];
                if (!s) {
                    rebuild=true;
                } else {

                    ret = this.notify(s, args);

                    if (false === ret) {
                        if (!this.silent) {
                            YAHOO.log("Event stopped, sub " + i + " of " + len, "info", "Event");
                        }

                        break;
                    }
                }
            }

            return (ret !== false);
        },

        notify: function(s, args) {

            var ret, param=null, scope = s.getScope(this.scope),
                     throwErrors = YAHOO.util.Event.throwErrors;

            if (!this.silent) {
                YAHOO.log( this.type + "-> " + s, "info", "Event" );
            }

            if (this.signature == YAHOO.util.CustomEvent.FLAT) {

                if (args.length > 0) {
                    param = args[0];
                }

                try {
                    ret = s.fn.call(scope, param, s.obj);
                } catch(e) {
                    this.lastError = e;
                    // errors.push(e);
                    YAHOO.log(this + " subscriber exception: " + e, "error", "Event");
                    if (throwErrors) {
                        throw e;
                    }
                }
            } 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 (throwErrors) {
                        throw ex;
                    }
                }
            }

            return ret;
        },

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

            this.subscribers=[];

            return l;
        },

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

            // this.subscribers[index]=null;
            this.subscribers.splice(index, 1);
        },

        /**
         * @method toString
         */
        toString: function() {
             return "CustomEvent: " + "'" + this.type  + "', " +
                 "context: " + 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}  overrideContext If true, the obj passed in becomes the execution
     *                            context of the listener
     * @class Subscriber
     * @constructor
     */
    YAHOO.util.Subscriber = function(fn, obj, overrideContext) {

        /**
         * 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 context for the event listener is defined when the
         * event is created (usually the object which contains the event).
         * By setting overrideContext to true, the execution context becomes the custom
         * object passed in by the subscriber.  If overrideContext is an object, that
         * object becomes the context.
         * @property overrideContext
         * @type boolean|object
         */
        this.overrideContext = overrideContext;

    };

    /**
     * Returns the execution context for this listener.  If overrideContext was set to true
     * the custom obj will be the context.  If overrideContext is an object, that is the
     * context, otherwise the default context will be used.
     * @method getScope
     * @param {Object} defaultScope the context to use if this listener does not
     *                              override it.
     */
    YAHOO.util.Subscriber.prototype.getScope = function(defaultScope) {
        if (this.overrideContext) {
            if (this.overrideContext === true) {
                return this.obj;
            } else {
                return this.overrideContext;
            }
        }
        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  +
               ", overrideContext: " +  (this.overrideContext || "no") + " }";
    };

Copyright © 2010 Yahoo! Inc. All rights reserved.