/** * PublishSubscribe * A flexible publish-subscribe pattern implementation for PHP, JavaScript, Python * * @version: 2.0.0 * https://github.com/foo123/PublishSubscribe * **/ !function(root, name, factory) { "use strict"; if (('undefined'!==typeof Components)&&('object'===typeof Components.classes)&&('object'===typeof Components.classesByID)&&Components.utils&&('function'===typeof Components.utils['import'])) /* XPCOM */ (root.$deps = root.$deps||{}) && (root.EXPORTED_SYMBOLS = [name]) && (root[name] = root.$deps[name] = factory.call(root)); else if (('object'===typeof module)&&module.exports) /* CommonJS */ (module.$deps = module.$deps||{}) && (module.exports = module.$deps[name] = factory.call(root)); else if (('function'===typeof define)&&define.amd&&('function'===typeof require)&&('function'===typeof require.specified)&&require.specified(name) /*&& !require.defined(name)*/) /* AMD */ define(name,['module'],function(module) {factory.moduleUri = module.uri; return factory.call(root);}); else if (!(name in root)) /* Browser/WebWorker/.. */ (root[name] = factory.call(root)||1)&&('function'===typeof(define))&&define.amd&&define(function() {return root[name];} ); }(/* current root */ 'undefined' !== typeof self ? self : this, /* module name */ "PublishSubscribe", /* module factory */ function ModuleFactory__PublishSubscribe(undef) { "use strict"; var __version__ = "2.0.0", PROTO = 'prototype', HAS = Object[PROTO].hasOwnProperty, TOPIC_SEP = '/', TAG_SEP = '#', NS_SEP = '@', OTOPIC_SEP = '/', OTAG_SEP = '#', ONS_SEP = '@', KEYS = Object.keys, NOW = Date.now ? Date.now : function() {return new Date().getTime();} ; function PublishSubscribeData(props) { if (props) { for (var k in props) { if (HAS.call(props, k)) this[k] = props[k]; } } } PublishSubscribeData[PROTO] = { constructor: PublishSubscribeData, dispose: function(props) { if (props) { for (var k=0; k 0; } function parse_topic(seps, topic) { var nspos, tagspos, tags, namespaces; topic = String(topic); nspos = topic.indexOf(seps[2]); tagspos = topic.indexOf(seps[1]); if (-1 < nspos) { namespaces = topic .slice(nspos) .split(seps[2]) .filter(not_empty) .sort() ; topic = topic.slice(0, nspos); } else { namespaces = []; } if (-1 < tagspos) { tags = topic .slice(tagspos) .split(seps[1]) .filter(not_empty) .sort() ; topic = topic.slice(0, tagspos); } else { tags = []; } topic = topic.split(seps[0]).filter(not_empty); return [topic, tags, namespaces]; } function get_all_topics(seps, topic) { var topics = [], tags = [], namespaces/* = [ ]*/, ttags, tns, l, i, j, jj, tmp, combinations; topic = parse_topic(seps, topic); //tns = topic[2]; namespaces = topic[2]; ttags = topic[1]; topic = topic[0]; l = topic.length; while (l) { topics.push(topic.join(OTOPIC_SEP)); topic.pop(); --l; } l = ttags.length; if (1 < l) { combinations = (1 << l); for (i=combinations-1; i>=1; --i) { tmp = []; for (j=0,jj=1; j=1; --i) { tmp = ]; for (j=0,jj=1; j= pbns[ns])) return false; } return true; } function check_is_subscribed(pubsub, subscribedTopics, topic, tag, namespaces, nl) { var _topic = !!topic ? ('tp_' + topic) : false, _tag = !!tag ? ('tg_' + tag) : false; if (_topic && HAS.call(pubsub.topics, _topic)) { if (_tag && HAS.call(pubsub.topics[_topic].tags, _tag)) { if (pubsub.topics[_topic].tags[_tag].list.length && (nl <= 0 || match_namespace(pubsub.topics[_topic].tags[_tag].namespaces, namespaces, nl))) { subscribedTopics.push([topic, tag, nl > 0, pubsub.topics[_topic].tags[_tag]]); return true; } } else { if (pubsub.topics[_topic].notags.list.length && (nl <= 0 || match_namespace(pubsub.topics[_topic].notags.namespaces, namespaces, nl))) { subscribedTopics.push([topic, null, nl > 0, pubsub.topics[_topic].notags]); return true; } } } else { if (_tag && HAS.call(pubsub.notopics.tags, _tag)) { if (pubsub.notopics.tags[_tag].list.length && (nl <= 0 || match_namespace(pubsub.notopics.tags[_tag].namespaces, namespaces, nl))) { subscribedTopics.push([null, tag, nl > 0, pubsub.notopics.tags[_tag]]); return true; } } else { if (pubsub.notopics.notags.list.length && (nl > 0 && match_namespace(pubsub.notopics.notags.namespaces, namespaces, nl))) { subscribedTopics.push([null, null, true, pubsub.notopics.notags]); return true; } /* else no topics no tags no namespaces, do nothing */ } } return false; } function get_subscribed_topics(seps, pubsub, atopic) { var all = get_all_topics(seps, atopic), l, topic, tag, ns, //_topic, _tag, t, n, tl, nl, topics = all[1], tags = all[2], namespaces = all[3], topTopic = all[0], subscribedTopics = [] ; tl = tags.length; nl = namespaces.length; l = topics.length; if (l) { while (l) { topic = topics[0]; //_topic = 'tp_' + topic; if (HAS.call(pubsub.topics, 'tp_' + topic)) { if (tl > 0) { for (t=0; t 0) { for (t=0; t 0) { for (n=0; n 0) { for (s=sl-1; s>=0; --s) { subscriber = subs[s]; if (subscriber[1] && subscriber[4] > 0) { subs.splice(s, 1); subscribers.oneOffs = subscribers.oneOffs > 0 ? (subscribers.oneOffs-1) : 0; } } } else { subscribers.oneOffs = 0; } } } } function publish(target, seps, pubsub, topic, data) { if (pubsub) { var topics = get_subscribed_topics(seps, pubsub, topic), t, s, tl, sl, subs, subscribers, subscriber, topTopic, subTopic, tags, namespaces, hasNamespace, nl, evt, res = false, pos, nskeys ; topTopic = topics[0]; namespaces = topics[2]; nl = namespaces.length; topics = topics[1]; tl = topics.length; evt = null; if (tl > 0) { evt = new PublishSubscribeEvent(target); evt.data = data; evt.originalTopic = topTopic ? topTopic.split(OTOPIC_SEP) : []; } for (t=0; t 0) continue; // oneoff subscriber already called evt.namespaces = hasNamespace ? subscriber[3].slice(0) : []; subscriber[4] = 1; // subscriber called res = subscriber[0](evt); // stop event propagation if ((false === res) || evt.stopped() || evt.aborted()) break; } // unsubscribeOneOffs unsubscribe_oneoffs(subscribers); // stop event bubble propagation if (evt.aborted() || !evt.propagates()) break; } if (evt) { evt.dispose(); evt = null; } } } function create_pipeline_loop(evt, topics, abort, finish) { var topTopic = topics[0], namespaces = topics[2], topics = topics[1]; evt.state = new PublishSubscribeData({ 't': 0, 's': 0, 'start_topic': true, 'subscribers': null, 'topics': topics, 'namespaces': namespaces, 'hasNamespace': false, 'abort': abort, 'finish': finish }); evt.originalTopic = topTopic ? topTopic.split(OTOPIC_SEP) : []; var pipeline_loop = function pipeline_loop(evt) { if (!evt.state) return; var res, state = evt.state, subTopic, tags, subscriber, done, abort, finish; if (state.t < state.topics.length) { if (state.start_topic) { // unsubscribeOneOffs unsubscribe_oneoffs(state.subscribers); // stop event propagation if (evt.aborted() || !evt.propagates()) { if (evt.aborted() && 'function' === typeof state.abort) { abort = state.abort; state.abort = null; abort(evt); if ('function' === typeof state.finish) { finish = state.finish; state.finish = null; finish(evt); } } return false; } subTopic = state.topics[state.t][0]; tags = state.topics[state.t][1]; evt.topic = subTopic ? subTopic.split(OTOPIC_SEP) : []; evt.tags = tags ? tags.split(OTAG_SEP) : []; state.hasNamespace = state.topics[state.t][2]; state.subscribers = state.topics[state.t][3]; state.s = 0; state.start_topic = false; } //if (state.subscribers) state.sl = state.subscribers.list.length; if (state.s < state.subscribers.list.length) { // stop event propagation if (evt.aborted() || evt.stopped()) { // unsubscribeOneOffs unsubscribe_oneoffs(state.subscribers); if (evt.aborted() && 'function' === typeof state.abort) { abort = state.abort; state.abort = null; abort(evt); if ('function' === typeof state.finish) { finish = state.finish; state.finish = null; finish(evt); } } return false; } done = false; while (state.s < state.subscribers.list.length && !done) { subscriber = state.subscribers.list[state.s]; if ((!subscriber[1] || !subscriber[4]) && (!state.hasNamespace || (subscriber[2] && match_namespace(subscriber[2], state.namespaces, state.namespaces.length))) ) { done = true; } state.s += 1; } if (state.s >= state.subscribers.list.length) { state.t += 1; state.start_topic = true; } if (done) { evt.namespaces = state.hasNamespace ? subscriber[3].slice(0) : []; subscriber[4] = 1; // subscriber called res = subscriber[0](evt); } } else { state.t += 1; state.start_topic = true; } } if (!evt.state) return; if (state.t >= state.topics.length) { // unsubscribeOneOffs unsubscribe_oneoffs(state.subscribers); if ('function' === typeof state.finish) { finish = state.finish; state.finish = null; finish(evt); } if (evt) { /*evt.state.dispose([ 't', 's', 'start_topic', 'subscribers', 'topics', 'namespaces', 'hasNamespace', 'abort', 'finish' ]);*/ evt.state = null; evt.dispose(); evt = null; } } }; return pipeline_loop; } function pipeline(target, seps, pubsub, topic, data, abort, finish) { if (pubsub) { var topics = get_subscribed_topics(seps, pubsub, topic), evt = null, pipeline_loop; if (topics[1].length > 0) { evt = new PublishSubscribeEvent(target); evt.data = data; evt.pipeline(pipeline_loop = create_pipeline_loop(evt, topics, abort, finish)); pipeline_loop(evt); } } } function subscribe(seps, pubsub, topic, subscriber, oneOff, on1) { if (pubsub && "function" === typeof subscriber) { topic = parse_topic(seps, topic); var tags = topic[1].join(OTAG_SEP), tagslen = tags.length, entry, queue, _topic, _tag, namespaces = topic[2], nshash, namespaces_ref, n, nslen = namespaces.length; topic = topic[0].join(OTOPIC_SEP); oneOff = true === oneOff; on1 = true === on1; nshash = {}; if (nslen) { for (n=0; n 0)) { while (--pos >= 0) { if (subscriber === pb.list[pos][0]) { if (nslen && pb.list[pos][2] && match_namespace(pb.list[pos][2], namespaces, nslen)) { nskeys = KEYS(pb.list[pos][2]); remove_namespaces(pb.namespaces, nskeys, nskeys.length); if (pb.list[pos][1]) pb.oneOffs = pb.oneOffs > 0 ? (pb.oneOffs-1) : 0; pb.list.splice(pos, 1); } else if (!nslen) { if (pb.list[pos][2]) { nskeys = KEYS(pb.list[pos][2]); remove_namespaces(pb.namespaces, nskeys, nskeys.length); } if (pb.list[pos][1]) pb.oneOffs = pb.oneOffs > 0 ? (pb.oneOffs-1) : 0; pb.list.splice(pos, 1); } } } } } else if (!hasSubscriber && (nslen > 0) && (pos > 0)) { while (--pos >= 0) { if (pb.list[pos][2] && match_namespace(pb.list[pos][2], namespaces, nslen)) { nskeys = KEYS(pb.list[pos][2]); remove_namespaces(pb.namespaces, nskeys, nskeys.length); if (pb.list[pos][1]) pb.oneOffs = pb.oneOffs > 0 ? (pb.oneOffs-1) : 0; pb.list.splice(pos, 1); } } } else if (!hasSubscriber && (pos > 0)) { pb.list = []; pb.oneOffs = 0; pb.namespaces = {}; } } function unsubscribe(seps, pubsub, topic, subscriber) { if (pubsub) { topic = parse_topic(seps, topic); var t, t2, tags = topic[1].join(OTAG_SEP), namespaces = topic[2], _topic, _tag, tagslen = tags.length, nslen = namespaces.length, topiclen, hasSubscriber ; topic = topic[0].join(OTOPIC_SEP); topiclen = topic.length; _topic = topiclen ? ('tp_' + topic) : false; _tag = tagslen ? ('tg_' + tags) : false; hasSubscriber = "function" === typeof subscriber; if (!hasSubscriber) subscriber = null; if (topiclen && HAS.call(pubsub.topics, _topic)) { if (tagslen && HAS.call(pubsub.topics[_topic].tags, _tag)) { remove_subscriber(pubsub.topics[_topic].tags[_tag], hasSubscriber, subscriber, namespaces, nslen); if (!pubsub.topics[_topic].tags[_tag].list.length) delete pubsub.topics[_topic].tags[_tag]; } else if (!tagslen) { remove_subscriber(pubsub.topics[_topic].notags, hasSubscriber, subscriber, namespaces, nslen); } if (!pubsub.topics[_topic].notags.list.length && !KEYS(pubsub.topics[_topic].tags).length) delete pubsub.topics[_topic]; } else if (!topiclen && (tagslen || nslen)) { if (tagslen) { if (HAS.call(pubsub.notopics.tags, _tag)) { remove_subscriber(pubsub.notopics.tags[_tag], hasSubscriber, subscriber, namespaces, nslen); if (!pubsub.notopics.tags[_tag].list.length) delete pubsub.notopics.tags[_tag]; } // remove from any topics as well for (t in pubsub.topics) { if (HAS.call(pubsub.topics, t) && HAS.call(pubsub.topics[t].tags, _tag)) { remove_subscriber(pubsub.topics[t].tags[_tag], hasSubscriber, subscriber, namespaces, nslen); if (!pubsub.topics[t].tags[_tag].list.length) delete pubsub.topics[t].tags[_tag]; } } } else { remove_subscriber(pubsub.notopics.notags, hasSubscriber, subscriber, namespaces, nslen); // remove from any tags as well for (t2 in pubsub.notopics.tags) { if (HAS.call(pubsub.notopics.tags, t2)) { remove_subscriber(pubsub.notopics.tags[t2], hasSubscriber, subscriber, namespaces, nslen); if (!pubsub.notopics.tags[t2].list.length) delete pubsub.notopics.tags[t2]; } } // remove from any topics and tags as well for (t in pubsub.topics) { if (HAS.call(pubsub.topics, t)) { remove_subscriber(pubsub.topics[t].notags, hasSubscriber, subscriber, namespaces, nslen); for (t2 in pubsub.topics[t].tags) { if (HAS.call(pubsub.topics[t].tags, t2)) { remove_subscriber(pubsub.topics[t].tags[t2], hasSubscriber, subscriber, namespaces, nslen); if (!pubsub.topics[t].tags[t2].list.length) delete pubsub.topics[t].tags[t2]; } } } } } } } } // // PublishSubscribe (Interface) function PublishSubscribe() { var self = this; if (!(self instanceof PublishSubscribe)) return new PublishSubscribe(); self.initPubSub(); } PublishSubscribe.VERSION = __version__; PublishSubscribe.Event = PublishSubscribeEvent; PublishSubscribe.Data = function(props) { return new PublishSubscribeData(props); }; PublishSubscribe[PROTO] = { constructor: PublishSubscribe ,opts: null ,_pubsub: null ,option: function(key, val) { var self = this, nargs = arguments.length; if (1 == nargs) { return HAS.call(self.opts, key) ? self.opts[key] : undef; } else if (1 < nargs) { self.opts[key] = val; } return self; } ,initPubSub: function() { var self = this; self.opts = {}; // defaults self.option('topic_separator', TOPIC_SEP); self.option('tag_separator', TAG_SEP); self.option('namespace_separator', NS_SEP); self._pubsub = get_pubsub(); return self; } ,disposePubSub: function() { var self = this; self.opts = null; self._pubsub = null; return self; } ,emit: function(message, data) { var self = this; if (2 > arguments.length) data = {}; publish(self, [ self.option('topic_separator'), self.option('tag_separator'), self.option('namespace_separator') ], self._pubsub, message, data); return self; } ,pipeline: function(message, data, abort, finish) { var self = this; if (2 > arguments.length) data = {}; pipeline(self, [ self.option('topic_separator'), self.option('tag_separator'), self.option('namespace_separator') ], self._pubsub, message, data, abort || null, finish || null); return self; } ,on: function(message, callback) { var self = this; if ("function" === typeof callback) { subscribe([ self.option('topic_separator'), self.option('tag_separator'), self.option('namespace_separator') ], self._pubsub, message, callback); } return self; } ,one: function(message, callback) { var self = this; if ("function" === typeof callback) { subscribe([ self.option('topic_separator'), self.option('tag_separator'), self.option('namespace_separator') ], self._pubsub, message, callback, true); } return self; } ,on1: function(message, callback) { var self = this; if ("function" === typeof callback) { subscribe([ self.option('topic_separator'), self.option('tag_separator'), self.option('namespace_separator') ], self._pubsub, message, callback, false, true); } return self; } ,one1: function(message, callback) { var self = this; if ("function" === typeof callback) { subscribe([ self.option('topic_separator'), self.option('tag_separator'), self.option('namespace_separator') ], self._pubsub, message, callback, true, true); } return self; } ,off: function(message, callback) { var self = this; unsubscribe([ self.option('topic_separator'), self.option('tag_separator'), self.option('namespace_separator') ], self._pubsub, message, callback || null); return self; } }; // export it return PublishSubscribe; });