/*
Copyright (c) 2010,2011,2012,2013 Morgan Roderick http://roderick.dk
License: MIT - http://mrgnrdrck.mit-license.org
Copyright (c) 2014 Visisoft OHG http://visisoft.de
https://github.com/semmel/PubSubJS
see Crockford: JavaScript: The Good Parts, section "Parts"
*/
/*jslint white:true, plusplus:true, stupid:true*/
/*global
setTimeout,
module,
exports,
define,
require,
window
*/
(function(root, factory){
'use strict';
// CommonJS
if (typeof exports === 'object' && module)
{
module.exports = factory();
// AMD
}
else if (typeof define === 'function' && define.amd)
{
define(factory);
// Browser
}
else
{
root.createPublisher = factory();
}
}( ( typeof window === 'object' && window ) || this
, function()
{
function hasKeys(obj){
var key;
for (key in obj){
if ( obj.hasOwnProperty(key) ){
return true;
}
}
return false;
}
/**
* Returns a function that throws the passed exception, for use as argument for setTimeout
* @param { Object } ex An Error object
*/
function throwException( ex ){
return function reThrowException(){
throw ex;
};
}
/**
* @file An Open Source PubSub Implementation
* @see {@link https://github.com/semmel/PubSubJS|Source Code on GitHub}
* @module lib/pubsub
* @version 1.4.0
* @example
* // create a function to receive messages
* var mySubscriber = function( msg, data ){
* console.log( msg, data );
* };
*
* require(['lib/pubsub'], function(createPublisher){
* // create a publisher using the factory method
* var publisher = createPublisher();
*
* // add the function to the list of subscribers for a particular message
* var subscription = publisher.subscribe( 'MY MESSAGE', mySubscriber );
*
* // publish a message asyncronously
* publisher.publish( 'MY MESSAGE', 'hello world!' );
* // logs 'MY MESSAGE', 'hello world!' to the console
*
* // unsubscribe from further messages
* subscription.dispose();
* });
*/
/**
* @class Publisher
*/
/**
* Factory method to create a new Publisher or for adding PubSub functionality to the given object.
* I.e. augments <tt>publish, publishSync, subscribe</tt> and <tt>unsubscribe</tt> methods.
* @function module:lib/pubsub.createPublisher
* @constructs Publisher
* @param {Object=} subject If not given a plain Publisher object is returned
* @returns {module:lib/pubsub~Publisher}
*/
function createPublisher(subject)
{
var
self = subject || {},
messages = {},
lastUid = -1;
/**
* adapt the parameters to the handlers function signature
* @memberof module:lib/pubsub~Publisher#
* @member {function} handlerInvoker
* @param {Function} handler the registered subscriber for the particular topic
* @param {String} message the topic
* @param {*} data the event payload
* @example
* // the new handler function receives the event's payload
* // in it's single argument
* var mySubscriber = function( data ){
* console.log( data );
* };
* // create a publisher using the factory method
* var publisher = createPublisher({
* handlerInvoker: function(handler, message, payload){
* handler(payload);
* }
* });
* // register the handler
* var subscription = publisher.subscribe( 'topic', mySubscriber );
*
* publisher.publish( 'topic', 'hello world!' );
* // logs 'hello world!' to the console
*/
self.handlerInvoker = self.handlerInvoker || function handlerInvoker (handler, message, data)
{
handler.call(undefined, message, data);
};
function callSubscriberWithDelayedExceptions( subscriber, message, data ){
try {
self.handlerInvoker(subscriber, message, data );
} catch( ex ){
setTimeout( throwException( ex ), 0);
}
}
function callSubscriberWithImmediateExceptions( subscriber, message, data ){
self.handlerInvoker(subscriber, message, data );
}
function deliverMessage( originalMessage, matchedMessage, data, immediateExceptions )
{
var
subscribers = messages[matchedMessage],
callSubscriber = immediateExceptions ? callSubscriberWithImmediateExceptions
: callSubscriberWithDelayedExceptions,
s;
if ( !messages.hasOwnProperty( matchedMessage ) ) {
return;
}
for (s in subscribers){
if ( subscribers.hasOwnProperty(s)){
callSubscriber( subscribers[s], originalMessage, data );
}
}
}
function createDeliveryFunction( message, data, immediateExceptions ){
return function deliverNamespaced(){
var topic = String( message ),
position = topic.lastIndexOf( '.' );
// deliver the message as it is now
deliverMessage(message, message, data, immediateExceptions);
// trim the hierarchy and deliver message to each level
while( position !== -1 ){
topic = topic.substr( 0, position );
position = topic.lastIndexOf('.');
deliverMessage( message, topic, data );
}
};
}
function messageHasSubscribers( message ){
var topic = String( message ),
found = Boolean(messages.hasOwnProperty( topic ) && hasKeys(messages[topic])),
position = topic.lastIndexOf( '.' );
while ( !found && position !== -1 ){
topic = topic.substr( 0, position );
position = topic.lastIndexOf('.');
found = Boolean(messages.hasOwnProperty( topic ) && hasKeys(messages[topic]));
}
return found;
}
function publish_( message, data, sync, immediateExceptions ){
var deliver = createDeliveryFunction( message, data, immediateExceptions ),
hasSubscribers = messageHasSubscribers( message );
if ( !hasSubscribers ){
return false;
}
if ( sync === true ){
deliver();
} else {
setTimeout( deliver, 0 );
}
return true;
}
/**
* Publishes the the message, passing the data to it's subscribers
* @memberof module:lib/pubsub~Publisher#
* @method publish
* @param {String} message The message to publish
* @param {*} data The data to pass to subscribers
* @returns {boolean}
*
**/
self.publish = function ( message, data ){
return publish_( message, data, false, self.immediateExceptions );
};
/**
* Publishes the the message synchronously, passing the data to it's subscribers
* @memberof module:lib/pubsub~Publisher#
* @method publishSync
* @param {String} message The message to publish
* @param {*} data The data to pass to subscribers
* @returns {boolean}
**/
self.publishSync = function( message, data ){
return publish_( message, data, true, self.immediateExceptions );
};
/**
* Subscribes the passed function to the passed message.
* @memberof module:lib/pubsub~Publisher#
* @method subscribe
* @param {String} message The message to subscribe to
* @param {module:lib/pubsub~Publisher~eventCallback} callback The function to call when
* a new message is published
* @returns {Object} a subscription object with a single method <tt>dispose</tt>
* you need to call to unsubscribe
**/
self.subscribe = function( message, callback ){
if ( typeof callback !== 'function'){
return false;
}
// message is not registered yet
if ( !messages.hasOwnProperty( message ) ){
messages[message] = {};
}
// forcing token as String, to allow for future expansions without breaking usage
// and allow for easy use as key names for the 'messages' object
var token = 'uid_' + String(++lastUid);
messages[message][token] = callback;
// return token for unsubscribing
//return token;
// return a subscription object with a dispose method
return { dispose: self.unsubscribe.bind(self, token) };
};
/**
* @callback module:lib/pubsub~Publisher~eventCallback
* @param {string} message the topic
* @param {*=} data the payload
*/
/**
* PubSub.unsubscribe( tokenOrFunction ) -> String | Boolean
* - tokenOrFunction (String|Function): The token of the function to unsubscribe or func passed in on subscribe
* Unsubscribes a specific subscriber from a specific message using the unique token
* or if using Function as argument, it will remove all subscriptions with that function
**/
self.unsubscribe = function( tokenOrFunction ){
var isToken = typeof tokenOrFunction === 'string',
result = false,
m, message, t, token;
for ( m in messages ){
if ( messages.hasOwnProperty( m ) ){
message = messages[m];
if ( isToken && message[tokenOrFunction] ){
delete message[tokenOrFunction];
result = tokenOrFunction;
// tokens are unique, so we can just stop here
break;
} else if (!isToken){
for ( t in message ){
if (message.hasOwnProperty(t) && message[t] === tokenOrFunction){
delete message[t];
result = true;
}
}
}
}
}
return result;
};
/**
* remove all references to the subscribers. Is useful if the publisher is destroyed.
* @memberof module:lib/pubsub~Publisher#
* @method unsubscribeAll
*/
self.unsubscribeAll = function()
{
messages = {};
};
return self;
}
return createPublisher;
}));