Publisher Subscriber Pattern

I've been experimenting with the publisher/subscriber pattern in JavaScript. Admittely, this blog post is not polished, but I wanted to get it up so that I wouldn't forget about it. Hopefully it won't remain neglected for long.

Note that this pattern is similar to the Observer pattern, but may be a little more flexible because the Observer pattern requires each listener to implement an update() method. With pub/sub you can pass in a callback that gets triggered when an event is published.

Here's the HTML:

<!doctype html>
<html lang="en-US">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Widget w/PubSub that can be subclassed</title>
<style type="text/css">
#widgetList li{list-style:none; background-color:#ccc; border:1px solid black; padding:6px 10px; cursor: pointer; }
#widgetList li:hover{background-color:#fff; }
</style>
</head>
<body>
<div id="widgetList"></div>
</body>
</html>

 

Here's the publisher base class:

// set up namespace
rem = {};

rem.Publisher = function(){

this.eventTypes = {};
// eventType's key is an event name and the value is an array of objects
// each object in the array has 'subsriber' property and a 'callback' property
// for ex: eventTypes['click'] = [{subscriber: someSubsrciber, callback: someFunction},...]
};


rem.Publisher.prototype.subscribe = function(type, subscriber, callback){

// get all subcribers who are already registered for this event type
var subscribers = this.eventTypes[type];

if(subscribers){
// check to see if the subscriber is already in the array
if(this.getSubscriberIndex(subscribers, subscriber) != -1){
// the subscriber is already registered for this message
return;
}
}else{
// there are no subscribers for this event type so create an array
subscribers = [];
this.eventTypes[type] = subscribers;
}

// add the subscriber
subscribers.push({subscriber: subscriber, callback: callback});
};


rem.Publisher.prototype.unsubscribe = function(type, subscriber){
if(subscriber){
var subscribers = this.eventTypes[type];

if(subscribers){
var i = this.getSubscriberIndex(subscribers, subscriber);
if(i != -1){
this.eventTypes[type].splice(i,1);
}
}
}else{
delete this.eventTypes[type];
}
}


rem.Publisher.prototype.publish = function(type){
// get all the subscribers for this event type
var subscribers = this.eventTypes[type];

if(subscribers){
// loop through them and trigger the callback
for(var i=0; i < subscribers.length; i++){

//get the args for the callback (so they can be used in the .apply() call)
var args = [];
for(var j=0; j < arguments.length - 1; j++){
args.push(arguments[j+1]);
}

subscribers[i].callback.apply(subscribers[i].subscriber, args);
}
}
}


rem.Publisher.prototype.getSubscriberIndex = function(subscribers, subscriber){
for(var i=0; i < subscribers.length; i++){
if(subscribers[i].subscriber == subscriber){
return i;
}
}
return -1;
}

 

Here's a concrete object that sub classes Publisher:

//WidgetList class
rem.WidgetList = function(){
//call to super...
rem.Publisher.call(this);

this.target = null;
this.widgets = [];
}

// 'inherit' the Publisher prototype
rem.WidgetList.prototype = new rem.Publisher;

rem.WidgetList.prototype.init = function(options){

// verify that options are passed in...
if(!options){
alert("no param passed into Widget.init()");
}
// verify that target element and widgets array are passed in as part of the options object...
this.target = options.target || alert("no 'target' passed in param for WidgetList.init()");
this.widgets = options.widgets || alert("no 'widget' passed in param for WidgetList.init()");

// create a UL and loop through widgets array, adding an LI for each widget...
var list = document.createElement("ul");
for(var x=0; x < widgets.length; x++){
var listItem = document.createElement("li");
listItem.innerHTML = widgets[x].name;
listItem.setAttribute("data-widget-id", widgets[x].id);
list.appendChild(listItem);
}
// append the UL to the target element...
target.appendChild(list);

// set up event handling on the UL...
var thisWidget = this;
function handleListItemClick(event){
var target = (event.target ? event.target : event.srcElement);
var widgetID;
if(widgetID = target.getAttribute("data-widget-id")){
var widgetName = target.innerHTML;
// here's the kicker, note that this particular sub class of Publisher calls publish() with the widgetName, widgetId params after the event
// type. These two params will get passed into the callback that is used when the subscription is set up (see below)
thisWidget.publish("widget_selected", widgetName, widgetID);
}
}

if(list.addEventListener){
list.addEventListener('click',handleListItemClick,false);
}else{
list.attachEvent('x', handleListItemClick);
}

}

 

Put it all together:

var myWidgetList = new rem.WidgetList();
var target = document.getElementById("widgetList");
var widgets = [{name:"Widget 1",id:1}, {name:"Widget 2",id:2},{name:"Widget 3",id:3}];

myWidgetList.init({target:target, widgets:widgets});

// in this case the subscriber is just a plain old object, but in a real project it might be something like a controller
var subscriber = {
// note that when the publisher calls publish(), it will pass 'widgetName' and 'widg'
callback: function(widgetName, widgetID){
alert("widget was clicked: " + widgetName + " ID: " + widgetID);
}
};
myWidgetList.subscribe("widget_selected", subscriber, subscriber.callback);

Add a Comment...

 

Comments

hello!,I like your writing so a lot! share we communicate extrfa about your article on AOL? I need a specialist on this space to solve my problem. May be that is you! Having a look ahead to see you. - 9/26/2016

I check this out paragraph fully regarding the resemblance of latest and earlier technologies, it's remarkable article. - 9/23/2016

Oh my goodness! Incredible article dude! Thank you so much, However I am just encountering troubles with your RSS. I don't know the main reason why I can't subscribe to it. Could there be anybody else having identical RSS issues? Anyone who knows the best solution is it possible to kindly respond? Thanx!! - 9/17/2016

Howdy great website! Does running a blog such as this take a massive amount work? I've no expertise in computer programming but I had been hoping to start my own blog in the near future. Anyway, if you have any suggestions or techniques for new blog owners please share. I understand this is off subject nevertheless I just had to ask. Appreciate it! - 9/17/2016

Hi there, I read your blogs regularly. Your story-telling style is awesome, keep doing what you're doing! - 9/06/2016

Wonderful website. A great deal of useful information here. I'm sending it to some friends ans also sharing in delicious. And obviously, thank you for your sweat! - 9/02/2016

I've bbeen surding on-line more than three houes as of late, but I never discovered any interesting article like yours. It is lovely value sufficient for me. In my view, iff all site owners and bloggers made excellent content as you probably did, thhe nnet caan be much more helpful than ever before. - 5/19/2017

death-clan pmwiki facetraveler kotorisha integra-scs anavitor viveibague iae texmechs events-24h - 5/09/2017

Way cool! Some very valid points! I appreciate you penning this article plus the rest of the website is also really good. fotbollstrøjer - 4/26/2017

Hmm it looks like your blog ate my first comment (it was super long) so I guess I'll just sum it up what I submitted and say, I'm thoroughly enjoying your blog. I as well am an aspiring blog writer but I'm still new to the whole thing. Do you have any tips for inexperienced blog writers? I'd definitely appreciate it. - 4/24/2017

Thanks for finally talking about >Publisher Subscriber Pattern - 2/28/2017

This is a topic that's near to my heart... Cheers! Where are your contact details though? - 2/12/2017

I have Ьeеn browsing online moге than 3 houÐ³Ñ todaÊ, yet I nevеr found any Ñnteresting article like yours. It's pretty worth ᥱnough for me. Personally, if alâ¼ web owners аnd bloggers mаÔе gooâ¾ content as yÖu did, the web will be much more Õ½seful tɦan evеr beforе.| I câ²uld not refrain frÖm commenting. Vᥱry wеll writtеn!| â 'll rigÒ»t awаy grasp yοur rss feed És I Ñan not find your email subscription hyperlink оr e-newsletter service. á o you have an - 12/23/2016

At this time it appears liie Movable Type is the top blogging platform available right now. (from what I've read) Is that what you are using on your blog? - 11/04/2016

Hey I am so happy I found your site, I really found you by accident, while I was looking on Bing for something else, Nonetheless I am here now and would just like to say thanks a lot for a remarkable post and a all round interesting blog (I also love the theme/design), I don't have time to go through it all at the minute but I have bookmarked it and also added your RSS feeds, so when I have time I will be back to read a great deal more, Please do keep up the excellent job. - 10/12/2016

I'd like to find out more? I'd want to find out some additional information. - 10/12/2016

I'm gone to convey my little brother, that he should also pay a visit this webpage on regular basis to get updated from most up-to-date reports. - 10/08/2016

Do not lean at the waist; instead bend with the knees and lift together with your legs, keeping your back straight. The best solution with this issue is to help keep the purse within your tote bag. The only one that could decide how much you must dedicate to a sleeping bag is you. - 1/07/2017

Portable speakers can be quite a couple of speakers or just one device with a single or dual output. He allowed our other suppliers to lower their pallets at his dock in lieu of drive up the haul road. They have a perfect casual look which doesn't look sloppy or sneakerish. - 1/05/2017