UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

413 lines (369 loc) 13.4 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2007 Christian Boulanger License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * Christian Boulanger (cboulanger) * Sebastian Werner (wpbasti) * Christian Hagendorn (chris_schmidt) * Derrell Lipman (derrell) ************************************************************************ */ /** * A simple message bus singleton. * The message bus registers subscriptions to topics and notifies subscribers when * a matching message is dispatched. A topic acts as a filter to select only those * messages which match the filter. It can be the name of a message, which can * terminated with a trailing `*` as a wildcard, or a regular expression. */ qx.Class.define("qx.event.message.Bus", { type: "singleton", extend: qx.core.Object, statics: { /** * Shorthand method for {@link qx.event.message.Bus.getSubscription} * @return {Object} */ getSubscriptions() { return this.getInstance().getSubscriptions(); }, /** * Shorthand method for {@link qx.event.message.Bus.subscribe} * @param topic {String|RegExp} * @param subscriber {Function} Message handler function * @param context {Object} The execution context of the callback (i.e. "this") * @return {Boolean} Success */ subscribe(topic, subscriber, context) { return this.getInstance().subscribe.apply(this.getInstance(), arguments); }, /** * Shorthand method for {@link qx.event.message.Bus.subscribeOnce} * @param topic {String|RegExp} * @param subscriber {Function} Message handler function * @param context {Object} The execution context of the callback (i.e. "this") * @return {Boolean} Success */ subscribeOnce(topic, subscriber, context) { return this.getInstance().subscribeOnce.apply( this.getInstance(), arguments ); }, /** * Shorthand method for {@link qx.event.message.Bus.checkSubscription} * @param topic {String|RegExp} The topic that has been used when subscribing * @param subscriber {Function} Message handler function * @param context {Object} The execution context of the callback (i.e. "this") * @return {Boolean} Whether a subscription was removed */ checkSubscription(topic, subscriber, context) { return this.getInstance().checkSubscription.apply( this.getInstance(), arguments ); }, /** * Shorthand method for {@link qx.event.message.Bus.unsubscribe} * @param topic {String|RegExp} The topic that has been used when subscribing * @param subscriber {Function} Message handler function * @param context {Object} The execution context of the callback (i.e. "this") * @return {Boolean} Whether a subscription was removed */ unsubscribe(topic, subscriber, context) { return this.getInstance().unsubscribe.apply( this.getInstance(), arguments ); }, /** * Shorthand method for {@link qx.event.message.Bus.dispatch} * @param message {qx.event.message.Message} Message which is being dispatched * @return {Boolean} If the message could be dispatched */ dispatch(message) { return this.getInstance().dispatch.apply(this.getInstance(), arguments); }, /** * Shorthand method for {@link qx.event.message.Bus.dispatchByName} * @param name {String} name of the message * @param data {var} Any type of data to attach * @return {Boolean} If the message was dispatched */ dispatchByName(name, data) { return this.getInstance().dispatchByName.apply( this.getInstance(), arguments ); } }, /** * constructor */ construct() { super(); this.__subscriptions = {}; }, members: { /** * Subscriptions cache * @var {Object} */ __subscriptions: null, /** * Returns the map of message subscriptions with registered subscriptions. The key is * the topic and the value is a map with <code>{subscriber: * {Function}, context: {Object|null}}</code>. * * @return {Object} */ getSubscriptions() { return this.__subscriptions; }, /** * Subscribes to a topic * * @param topic {String|RegExp} Either a string, which can be * terminated with a trailing `*` as a wildcard to match all message * names that start with the prefix, or a regular expression * object, which the message name has to match. If you use regular * expressions, you cannot use message names that start and end * with a slash ("/") at the same time, because regular expressions * are converted to their string representation when stored. * @param subscriber {Function} Message handler function * @param context {Object} The execution context of the callback (i.e. "this") * @return {Boolean} Success */ subscribe(topic, subscriber, context) { if (!topic || typeof subscriber != "function") { throw new Error("Invalid parameters! " + [topic, subscriber, context]); // since v6.0.0 } // handle regexes var regex = topic instanceof RegExp ? topic : null; topic = topic.toString(); var sub = this.getSubscriptions(); if (this.checkSubscription(topic)) { if (this.checkSubscription(topic, subscriber, context)) { this.warn("Object method already subscribed to " + topic); return false; } // add a subscription sub[topic].push({ regex: regex, subscriber: subscriber, context: context || null }); return true; } else { // create a subscription sub[topic] = [ { regex: regex, subscriber: subscriber, context: context || null } ]; return true; } }, /** * Subscribes to a topic just for one dispatch and automatically unsubscribes * after executing the message handler. This subscription cannot be unsubscribed * from after it has been registered. * * @param topic {String|RegExp} Topic to subscribe to. see {@link qx.event.message.Bus#subscribe} * for details * @param subscriber {Function} Message handler function * @param context {Object} The execution context of the callback (i.e. "this") * @return {Boolean} Success */ subscribeOnce(topic, subscriber, context) { var that = this; var modified_subscriber = function (message) { subscriber.call(context, message); that.unsubscribe(topic, modified_subscriber, context); }; return this.subscribe(topic, modified_subscriber, context); }, /** * Checks if subscription is already present. If you supply * the message handler function, match only this exact subscription, * otherwise any topic subscription will match. * * @param topic {String|RegExp} Either a string, which can be truncated by `*` * to match all message names that start with the prefix, or a regular expression * object, which the message name has to match. * @param subscriber {Function} Message handler function * @param context {Object} The execution context of the callback (i.e. "this") * @return {Boolean} Whether a subscription exists for the topic */ checkSubscription(topic, subscriber, context) { var topic = topic.toString(); var sub = this.getSubscriptions(); if (!sub[topic] || sub[topic].length === 0) { return false; } if (subscriber) { for (var i = 0; i < sub[topic].length; i++) { if ( sub[topic][i].subscriber === subscriber && sub[topic][i].context === (context || null) ) { return true; } } return false; } return true; }, /** * Unsubscribe from a topic. * * If a "wildcard" topic was subscribed to with a trailing asterisk, * because the subscriber wanted to receive messages for any topic * with the given prefix, that same "wildcard" topic should be used to * unsubscribe. It is not possible to unsubscribe using any topic other * than one exactly matching one that has previously been subscribed to. * * If you supply the callback function and execution context, only this * exact subscription is removed. Otherwise, all subscriptions to this topic * will be removed. * * @param topic {String|RegExp} The topic that has been used when subscribing * @param subscriber {Function} Message handler function * @param context {Object} The execution context of the callback (i.e. "this") * @return {Boolean} Whether a subscription was removed */ unsubscribe(topic, subscriber, context) { var topic = topic.toString(); var sub = this.getSubscriptions(); var subscrList = sub[topic]; if (subscrList) { if (!subscriber) { sub[topic] = null; delete sub[topic]; return true; } else { if (!context) { context = null; } var i = subscrList.length; var subscription; do { subscription = subscrList[--i]; if ( subscription.subscriber === subscriber && subscription.context === context ) { subscrList.splice(i, 1); if (subscrList.length === 0) { sub[topic] = null; delete sub[topic]; } return true; } } while (i); } } return false; }, /** * Dispatch message, which calls subscribers * * @param message {qx.event.message.Message} Message which is being dispatched * @return {Boolean} If the message could be dispatched, i.e. if subscribers * exist which have received the message */ dispatch(message) { var sub = this.getSubscriptions(); var msgName = message.getName(); var dispatched = false; for (var topic in sub) { var len = topic.length; if (topic[len - 1] === "*") { // use of wildcard, only allowed as "*" or at the end of the topic if ( len === 1 || topic.substr(0, len - 2) === msgName.substr(0, len - 2) ) { this.__callSubscribers(sub[topic], message); dispatched = true; } } else if (sub[topic][0].regex) { // regular expression if (message.getName().match(sub[topic][0].regex)) { this.__callSubscribers(sub[topic], message); dispatched = true; } } else if (topic === msgName) { // exact match this.__callSubscribers(sub[topic], message); dispatched = true; } } return dispatched; }, /** * Dispatches a new message by supplying the name of the * message and its data. * * @param name {String} name of the message * @param data {var} Any type of data to attach * @return {Boolean} If the message was dispatched */ dispatchByName(name, data) { var message = new qx.event.message.Message(name, data); // Dispatch the message var ret = this.dispatch(message); // We instantiated this message, so it's our responsibility to dispose it. message.dispose(); message = null; // Let 'em know whether this message was dispatched to any subscribers. return ret; }, /** * Removes all subscriptions */ removeAllSubscriptions() { var subscriptions = this.getSubscriptions(); for (var key in subscriptions) { delete subscriptions[key]; } }, /** * Call subscribers with passed message. * * Each currently-subscribed subscriber function will be called in * turn. Any requests to unsubscribe a subscriber from the list, while * processing the currently-subscribed subscriber functions, will take * effect after all currently-subscribed subscriber functions have been * processed. * * @param subscribers {Array} subscribers to call * @param message {qx.event.message.Message} message for subscribers */ __callSubscribers(subscribers, message) { // (Shallow) clone the subscribers array in case one of them alters the // list, e.g., by unsubscribing subscribers = subscribers.slice(); for (var i = 0; i < subscribers.length; i++) { var subscriber = subscribers[i].subscriber; var context = subscribers[i].context; // call topic subscriber if (context && context.isDisposed) { if (context.isDisposed()) { subscribers.splice(i, 1); i--; } else { subscriber.call(context, message); } } else { subscriber.call(context, message); } } } } });