Lithium

API Docs for: 0.8.0
Show:

File: src\lithium.pubsub.js

/**
 * Publisher-subscriber pattern utility class
 * @module pubsub
 * @requires core
 * @title Lithium Publisher-Subscriber
 */
(function (factory) {
    if (typeof define === "function" && define.amd) {
        define(['./lithium', 'jquery'], factory);
    } else { //global
        factory(window.Li, jQuery);
    }
}(function (Li, $) {
    /**
     * Base class for Publishers.<br/>
     * This class helps you to achieve the Observer (also known as publisher-subscriber) design pattern.<br/>
     *
     * Your class prototype may have a property named 'eventTypes' which is a list (i.e array) of eventTypes
     * that the class as a publisher would/can trigger.
     *
     * @namespace Li
     * @class Publisher
     */
    Li.Publisher = Li.extend(Object, {
        /**
         * Call all events listeners for the given event name.<br/>
         * @param {String} eventType
         * @param {Any} ... n number of arguments. These shall be directly passed onto the event listeners.
         * @method trigger
         */
        trigger: function (eventType) {
            eventType = eventType.toLowerCase();
            this._eventMap_ = this._eventMap_ || {};
            if ((!this._eventTypes_ || !this._eventTypes_[eventType]) && Li.warnings) {
                console.warn(eventType + "? This event type has not been registered.");
            }
            if (!this._suspendEvents_ && this._eventMap_[eventType]) {
                var i, len,
                    events = this._eventMap_[eventType];
                for (i = 0, len = events.length; i < len; i += 1) {
                    events[i].fn.apply(events[i].scope, Li.slice(arguments, 1));
                }
            }
        },

        /**
         * Adds a listener.
         * If no parameters are specified, then this would re-enable events that were switched off by publisher.unsubscribe();
         * @param {String|Object} object The event type that you want to listen to as string.
         * Or an object with event types and handlers as key-value pairs (with event type as the keys).
         * You can also subscribe for an event that has not yet been registered as an event. Hence the order of registeration is not a concern.
         * @param {Function} handler Function that gets notfied when a event of 'eventType' gets fired. This param is used only when eventType is a string.
         * @param {Object} scope The context in which the function should be called.
         * @method subscribe
         * @return A UUID which can be used to remove the event when required.
         */
        //TODO: Add option to bind arguments
        subscribe: (function () {
            var uuidGen = 1;
            //TODO: Also set config like onetime = true etc
            return function (eventType, handler, scope) {
                if (!Li.isDefined(eventType)) {
                    this._suspendEvents_ = false;
                } else if (Li.isObject(eventType)) {
                    var ret = {};
                    Li.forEach(eventType, function (handler, type) {
                        ret[type] = this.subscribe(type, handler, scope);
                    }, this);
                    return ret;
                } else {
                    this._eventMap_ = this._eventMap_ || {};
                    var events = this._eventMap_,
                        id = 'ls' + (uuidGen++);
                    events[eventType] = events[eventType] || [];
                    events[eventType].push({
                        uuid: id,
                        fn: handler,
                        scope: scope
                    });
                    return id;
                }
            };
        }()),

        /**
         * This function listens to a given publisher on the given event types,
         * and refires the events from itself (scope of the event fired would be this object).
         * This useful for chaining events.
         * @param {Li.Publisher} publisher A publisher instance.
         * @param {Array|null} eventTypes Event types to listen on. If eventType is null, it listens to all events of the publisher.
         * @method relayEvents
         */
        relayEvents: (function () {
            var relayThis = function (eventType) {
                var args = Li.slice(arguments, 1);
                this.trigger.apply(this, ([eventType]).join(args));
            };
            return function (publisher, eventTypes) {
                if (!publisher._eventTypes_) {
                    throw new Error('Object passed is not a publisher');
                }
                eventTypes = eventTypes || Object.keys(publisher._eventTypes_);
                var i, len = eventTypes.length, eventType;
                for (i = 0; i < len; i += 1) {
                    eventType = eventTypes[i];
                    publisher.subscribe(eventType, Li.bind(relayThis, null, false, eventType), this);
                }
            };
        }()),

        /**
         * Remove an event listener.
         * If no parameters are specified, then all events are switched off till you call publisher.subscribe().
         * @param {String|Function} uuidORfunc Can be the event listener as a Function object,
         * OR the UUID returned by 'subscribe' function can also be used.
         * @return {Boolean} Returns true if listener was successfully removed.
         * @method unsubscribe
         */
        unsubscribe: function (eventType, uuidORfunc) {
            if (!Li.isDefined(eventType)) {
                this._suspendEvents_ = true;
                return;
            }
            eventType = eventType.toLowerCase();
            var found = false;
            if (this._eventMap_) {
                var events = this._eventMap_[eventType], i, len,
                    type = Li.isString(uuidORfunc) ? "uuid" : "fn",
                    value = uuidORfunc;
                if (events) {
                    for (i = 0, len = events.length; i < len; i++) {
                        if (events[i][type] === value) {
                            events.splice(i, 1);
                            found = true;
                            break;
                        }
                    }
                }
            }
            return found;
        }
    });

    /**
     * Given a class, it inherits event types from base class.
     * @private
     */
    Li.inheritEvents = function (func) {
        //No need to do anything if class hasn't added any new event types.
        var proto = func.prototype;
        if (proto.eventTypes) {
            var eventTypes = proto.eventTypes || [],
                types = proto._eventTypes_; //This should recursively go up prototype
                                                  //chain and find the first _eventTypes_
            proto._eventTypes_ = $.extend({}, types);
            eventTypes.forEach(function (eventType) {
                proto._eventTypes_[eventType.toLowerCase()] = true;
            });
        }
    };

    return Li;
}));