/** * Facility for coordinating communication between components in an event-based * manner. * *

* Communication is modeled by using signals. * * A Signal consist of a category and a payload (data). Components can publish * signals or subscribe to signals of a certain category. When a signal is * published, all components subscribed for the signal's category are notified. *

*/ function EventBus(config) { var log = getLogger("EventBus"); /* * A No-Operation function that is used to replace * handlers when a subscription is cancelled. */ var noop = function() {}; /** * key: signal category value: list of handlers subscribed for this category. */ var subscribers = {}; /** * Registers a listener for a certain signal. * * @param category * The signal type * @param handler * The function to be called when a signal is received. * * @return A Subscription object that can be used * to unsubscribe from an event. */ this.subscribe = function(category, handler) { //log.info("subscribing", handler, "for", category); var handlerList = null; if (category in subscribers) { handlerList = subscribers[category]; } else { handlerList = []; subscribers[category] = handlerList; } handlerList.push(handler); return new Subscription(category, handler); }; /** * Publishes a signal by notifying the handlers * registered for the category. * * @return the number of subscribers the signal was sent to. */ this.publish = function(category, data) { if (config.debug) { log.info("publishing", category, data); } if (category in subscribers) { var subscribersForCategory = subscribers[category]; for (var i = 0; i < subscribersForCategory.length; i++) { subscribersForCategory[i](data); } return subscribersForCategory.length; } return 0; }; /** *

* The subscription can be stored und used later by a client to * unsubscribe a handler from an event. *

* * For convenience, subscriptions can be "chained" by calling the * add-method. * * @returns {Subscription} */ function Subscription(category, handler) { var cancelled = false; var that = this; this.cancel = function() { if (cancelled) { return; } log.info("unsubscribing from " + category); var handlerList = subscribers[category]; if (handlerList) { for (var i = 0; i < handlerList.length; i++) { if (handlerList[i] === handler) { handlerList[i] = noop; cancelled = true; handler = null; return; } } } // for safety. Make sure the subscription was found and removed. throw "handler " + handler + " not subscribed to " + category; }; /** * Adds another subscription to this subscription. * This is handy, when a client subscribes to multiple categories, * and does not want to declare a separate variable for * each subscription. * *

* If the cancel() is invoked, all "nested" subscriptions are * cancelled as well. *

*/ this.add = function(otherSubscription) { var original = that.cancel; that.cancel = function() { otherSubscription.cancel(); original.cancel(); }; }; } /** * A facility, that can collect other subscriptions which can than * be canceled all-at-once with one call. *

* This is handy, if client code subscribes to multiple categories, * or if client code eventually subscribes to a category and does not * want to check if the subscription actually did happen upon unsubscribe. *

* Example: * *
	 * var subscriptions = eventBus.newSubscriptionList();
	 * 
	 * subscriptions.add(eventBus.subscribe('category1', ...);
	 * subscriptions.add(eventBus.subscribe('category2', ...);
	 * 
	 * [...]
	 * 
	 * subscriptions.cancel();
	 * 
	 * 
* */ this.nullSubscription = function() { var nullSubscription = { // do nothing cancel: function() {}, add: function(otherSubscription) { /* * As soon as the first "real" subscription is * added, replace our own cancel and add-implementation * with the real one. * All subsequent calls to add() will then be delegated to the * real implementation. */ nullSubscription.cancel = otherSubscription.cancel; nullSubscription.add = otherSubscription.add; } }; return nullSubscription; }; }