var mediator = (function(){
// Storage for topics that can be broadcast or listened to
var topics = {};
// Subscribe to a topic, supply a callback to be executed
// when that topic is broadcast to
var subscribe = function( topic, fn ){
if ( !topics[topic] ){
topics[topic] = [];
}
topics[topic].push( { context: this, callback: fn } );
return this;
};
// Publish/broadcast an event to the rest of the application
var publish = function( topic ){
var args;
if ( !topics[topic] ){
return false;
}
args = Array.prototype.slice.call( arguments, 1 );
for ( var i = 0, l = topics[topic].length; i < l; i++ ) {
var subscription = topics[topic][i];
subscription.callback.apply( subscription.context, args );
}
return this;
};
return {
publish: publish,
subscribe: subscribe,
installTo: function( obj ){
obj.subscribe = subscribe;
obj.publish = publish;
}
};
}());
// Pass in a context to attach our Mediator to.
// By default this will be the window object
(function( root ){
function guidGenerator() { /*..*/}
// Our Subscriber constructor
function Subscriber( fn, options, context ){
if ( !(this instanceof Subscriber) ) {
return new Subscriber( fn, context, options );
}else{
// guidGenerator() is a function that generates
// GUIDs for instances of our Mediators Subscribers so
// we can easily reference them later on. We're going
// to skip its implementation for brevity
this.id = guidGenerator();
this.fn = fn;
this.options = options;
this.context = context;
this.topic = null;
}
}
})();
// Let's model the Topic.
// JavaScript lets us use a Function object as a
// conjunction of a prototype for use with the new
// object and a constructor function to be invoked.
function Topic( namespace ){
if ( !(this instanceof Topic) ) {
return new Topic( namespace );
}else{
this.namespace = namespace || "";
this._callbacks = [];
this._topics = [];
this.stopped = false;
}
}
// Define the prototype for our topic, including ways to
// add new subscribers or retrieve existing ones.
Topic.prototype = {
// Add a new subscriber
AddSubscriber: function( fn, options, context ){
var callback = new Subscriber( fn, options, context );
this._callbacks.push( callback );
callback.topic = this;
return callback;
},
...
StopPropagation: function(){
this.stopped = true;
},
GetSubscriber: function( identifier ){
for(var x = 0, y = this._callbacks.length; x < y; x++ ){
if( this._callbacks[x].id == identifier || this._callbacks[x].fn == identifier ){
return this._callbacks[x];
}
}
for( var z in this._topics ){
if( this._topics.hasOwnProperty( z ) ){
var sub = this._topics[z].GetSubscriber( identifier );
if( sub !== undefined ){
return sub;
}
}
}
},
AddTopic: function( topic ){
this._topics[topic] = new Topic( (this.namespace ? this.namespace + ":" : "") + topic );
},
HasTopic: function( topic ){
return this._topics.hasOwnProperty( topic );
},
ReturnTopic: function( topic ){
return this._topics[topic];
},
RemoveSubscriber: function( identifier ){
if( !identifier ){
this._callbacks = [];
for( var z in this._topics ){
if( this._topics.hasOwnProperty(z) ){
this._topics[z].RemoveSubscriber( identifier );
}
}
}
for( var y = 0, x = this._callbacks.length; y < x; y++ ) {
if( this._callbacks[y].fn == identifier || this._callbacks[y].id == identifier ){
this._callbacks[y].topic = null;
this._callbacks.splice( y,1 );
x--; y--;
}
}
},
Publish: function( data ){
for( var y = 0, x = this._callbacks.length; y < x; y++ ) {
var callback = this._callbacks[y], l;
callback.fn.apply( callback.context, data );
l = this._callbacks.length;
if( l < x ){
y--;
x = l;
}
}
for( var x in this._topics ){
if( !this.stopped ){
if( this._topics.hasOwnProperty( x ) ){
this._topics[x].Publish( data );
}
}
}
this.stopped = false;
}
};
function Mediator() {
if ( !(this instanceof Mediator) ) {
return new Mediator();
}else{
this._topics = new Topic( "" );
}
};
Mediator.prototype = {
GetTopic: function( namespace ){
var topic = this._topics,
namespaceHierarchy = namespace.split( ":" );
if( namespace === "" ){
return topic;
}
if( namespaceHierarchy.length > 0 ){
for( var i = 0, j = namespaceHierarchy.length; i < j; i++ ){
if( !topic.HasTopic( namespaceHierarchy[i]) ){
topic.AddTopic( namespaceHierarchy[i] );
}
topic = topic.ReturnTopic( namespaceHierarchy[i] );
}
}
return topic;
},
Subscribe: function( topiclName, fn, options, context ){
var options = options || {},
context = context || {},
topic = this.GetTopic( topicName ),
sub = topic.AddSubscriber( fn, options, context );
return sub;
},
// Returns a subscriber for a given subscriber id / named function and topic namespace
GetSubscriber: function( identifier, topic ){
return this.GetTopic( topic || "" ).GetSubscriber( identifier );
},
// Remove a subscriber from a given topic namespace recursively based on
// a provided subscriber id or named function.
Remove: function( topicName, identifier ){
this.GetTopic( topicName ).RemoveSubscriber( identifier );
},
Publish: function( topicName ){
var args = Array.prototype.slice.call( arguments, 1),
topic = this.GetTopic( topicName );
args.push( topic );
this.GetTopic( topicName ).Publish( args );
}
};
root.Mediator = Mediator;
Mediator.Topic = Topic;
Mediator.Subscriber = Subscriber;
// Remember we can pass anything in here. I've passed inwindowto
// attach the Mediator to, but we can just as easily attach it to another
// object if desired.
})( window );
<h1>Chat</h1>
<form id="chatForm">
<label for="fromBox">Your Name:</label>
<input id="fromBox" type="text"/>
<br />
<label for="toBox">Send to:</label>
<input id="toBox" type="text"/>
<br />
<label for="chatBox">Message:</label>
<input id="chatBox" type="text"/>
<button type="submit">Chat</button>
</form>
<div id="chatResult"></div>
$( "#chatForm" ).on( "submit", function(e) {
e.preventDefault();
// Collect the details of the chat from our UI
var text = $( "#chatBox" ).val(),
from = $( "#fromBox" ).val(),
to = $( "#toBox" ).val();
// Publish data from the chat to the newMessage topic
mediator.publish( "newMessage" , { message: text, from: from, to: to } );
});
// Append new messages as they come through
function displayChat( data ) {
var date = new Date(),
msg = data.from + " said \"" + data.message + "\" to " + data.to;
$( "#chatResult" ).prepend("<p>" + msg + " (" + date.toLocaleTimeString() + ")</p>");
}
// Log messages
function logChat( data ) {
if ( window.console ) {
console.log( data );
}
}
// Subscribe to new chat messages being submitted
// via the mediator
mediator.subscribe( "newMessage", displayChat );
mediator.subscribe( "newMessage", logChat );
// The following will however only work with the more advanced implementation:
function amITalkingToMyself( data ) {
return data.from === data.to;
}
function iAmClearlyCrazy( data ) {
$( "#chatResult" ).prepend("<p>" + data.from + " is talking to himself.</p>");
}
mediator.Subscribe( amITalkingToMyself, iAmClearlyCrazy );