Skip to content

Instantly share code, notes, and snippets.

@addyosmani
Created October 28, 2011 06:49
Star You must be signed in to star a gist
Save addyosmani/1321768 to your computer and use it in GitHub Desktop.
Four ways to do Pub/Sub with jQuery 1.7 and jQuery UI (in the future)

#Four Ways To Do Pub/Sub With jQuery and jQuery UI (in the future)

Between jQuery 1.7 and some of work going into future versions of jQuery UI, there are a ton of hot new ways for you to get your publish/subscribe on. Here are just four of them, three of which are new.

(PS: If you're unfamiliar with pub/sub, read the guide to it that Julian Aubourg and I wrote here http://msdn.microsoft.com/en-us/scriptjunkie/hh201955.aspx)

##Option 1: Using jQuery 1.7's $.Callbacks() feature:

$.Callbacks are a multi-purpose callbacks list object which can be used as a base layer to build new functionality including simple publish/subscribe systems. We haven't yet released the API documentation for this feature just yet, but for more information on it (including lots of examples), see my post on $.Callbacks() here: http://addyosmani.com/blog/jquery-1-7s-callbacks-feature-demystified/.

var topics = {};

jQuery.Topic = function( id ) {
	var callbacks,
		topic = id && topics[ id ];
	if ( !topic ) {
		callbacks = jQuery.Callbacks();
		topic = {
			publish: callbacks.fire,
			subscribe: callbacks.add,
			unsubscribe: callbacks.remove
		};
		if ( id ) {
			topics[ id ] = topic;
		}
	}
	return topic;
};

Usage:

// Subscribers
$.Topic( 'mailArrived' ).subscribe( fn1 );
$.Topic( 'mailArrived' ).subscribe( fn2 );
$.Topic( 'mailSent' ).subscribe( fn1 );

// Publisher
$.Topic( 'mailArrived' ).publish( 'hello world!' );
$.Topic( 'mailSent' ).publish( 'woo! mail!' );

//  Here, 'hello world!' gets pushed to fn1 and fn2
//  when the 'mailArrived' notification is published
//  with 'woo! mail!' also being pushed to fn1 when
//  the 'mailSent' notification is published.
/*
output:
hello world!
fn2 says: hello world!
woo! mail!
*/

##Option 2: Custom events using .on() and .off():

In jQuery 1.7, we updated the events API to support two new methods: .on() and .off(). These methods are meant to simplify the usage of .bind(),.live() and .delegate() such that rather than relying on developers to know which of these options is the best to use, all developers can simply use .on() and .off() and we'll make the best logic decisions for what to use beneath the hood. Note: As of jQuery 1.7 all of these three methods (bind/live/delegate) use .on() and .off() when you call them, so calling these newer methods directly is advised.

Here's Ben Alman's really tiny pub/sub with my (minor) 1.7 updates from https://gist.github.com/1319216. The link to his gist with lots of useful comments is here: https://gist.github.com/661855

/* jQuery Tiny Pub/Sub - v0.7 - 10/27/2011
 * http://benalman.com/
 * Copyright (c) 2011 "Cowboy" Ben Alman; Licensed MIT, GPL */

(function($) {

  var o = $({});

  $.subscribe = function() {
    o.on.apply(o, arguments);
  };

  $.unsubscribe = function() {
    o.off.apply(o, arguments);
  };

  $.publish = function() {
    o.trigger.apply(o, arguments);
  };

}(jQuery));

Usage

// Super-basic example:

function handle(e, a, b, c) {
  // `e` is the event object, you probably don't care about it.
  console.log(a + b + c);
};

$.subscribe("/some/topic", handle);

$.publish("/some/topic", [ "a", "b", "c" ]);
// logs: abc

$.unsubscribe("/some/topic", handle); // Unsubscribe just this handler

// Or:

$.subscribe("/some/topic", function(e, a, b, c) {
  console.log(a + b + c);
});

$.publish("/some/topic", [ "a", "b", "c" ]);
// logs: abc
// Unsubscribe all handlers for this topic
$.unsubscribe("/some/topic"); 

##Option 3: Using jQuery UI $.Observable

Note: $.Observables are currently still evolving and will not be available in jQuery UI until the jQueryUI Grid itself is released (http://wiki.jqueryui.com/w/page/34246941/Grid). The below is provided as a preview of what's being developed. Feel free to play around with the demos and submit any feedback or comments you may have about it.

The basic idea behind observables are that when objects/collections of data are changed or updated, events often need to be triggered to inform any observers of the change. This is a concept you see in a few different frameworks (Backbone's Collections for example). I believe the idea with this is that jQuery UI intend on applying this concept to UI components in particular (which should be very interesting to see).

Demo: http://jsfiddle.net/jUZmM/ Implem: http://view.jqueryui.com/grid/ui/jquery.ui.observable.js More information: http://wiki.jqueryui.com/w/page/47179578/Observable

/*$.observers/$.observables example by @addyosmani*/

// The array we would like observed
var myData = [];

// An 'observable' instance of myData
var observer = $.observer(myData); 

// A simple data logger for when our observable myData changes
function dataChange( data ){
   console.log('New data arrived with ID ' + data[0].id + ' and value ' + data[0].title);   
}

// Bind a callback to be executed when our observable instance of myData changes
$(observer).bind("change", function ( e ) { 
    dataChange( e.target.data );
});

// Insert a new record into the observable myData instance 
$.observable( myData ).insert({
                id: myData.length + 1,
                title: 'test'
            });

##Option 4: Third-party Plugins

A number of jQuery plugins have been written which provide a pub/sub implementation free of reliance on .bind()/.trigger() (and consequently .on()/.off). Should you wish to use one of these solutions, I'm happy to recommend Peter Higgin's plugin available here: https://github.com/phiggins42/bloody-jquery-plugins/blob/master/pubsub.js.

Demo: http://jsfiddle.net/zkwra/

There are of course many, many library agnostic Pub/Sub implementations that have been written (Shameless plug: I've also done one https://github.com/addyosmani/pubsubz/), but this rough guide will be focusing on jQuery for the most part. Here's Peter's implementation and a demo:

;(function(d){
    // the topic/subscription hash
    var cache = {};

    // Publish some data on a named topic.
    d.publish = function(/* String */topic, /* Array? */args){
        // topic: String - The channel to publish on
        // args: Array - The data to publish. Each array item is converted into an ordered
        // arguments on the subscribed functions. 
        cache[topic] && d.each(cache[topic], function(){
            this.apply(d, args || []);
        });
    };

    // Register a callback on a named topic.
    d.subscribe = function(/* String */topic, /* Function */callback){    
        // @topic: String - The channel to subscribe to
        // @callback: Function - The handler event. Anytime something is $.publish'ed on a 
        // subscribed channel, the callback will be called with the published array as 
        // ordered arguments.
        //
        // returns: Array - A handle which can be used to unsubscribe this 
        // particular subscription.

        if(!cache[topic]){
            cache[topic] = [];
        }
        cache[topic].push(callback);
        return [topic, callback]; // Array
    };

    // Disconnect a subscribed function for a topic.
    d.unsubscribe = function(/* Array */handle){    
        // handle: Array - The return value from a $.subscribe call.
        var t = handle[0];
        cache[t] && d.each(cache[t], function(idx){
            if(this == handle[1]){
                cache[t].splice(idx, 1);
            }
        });
    };

})(jQuery);

// Publish stuff on '/some/topic'. Anything subscribed will be called
// with a function signature like: function(a,b,c){ ... }


$.subscribe("/some/topic", function(a, b, c){ 
    console.log(a,b,c);
});


$.publish("/some/topic", ["a","b","c"]);
@addyosmani
Copy link
Author

Niiiice @furf!

@eliperelman
Copy link

Hey @addyosmani, your first code snippet has an unused variable method. Just wanted to pass that along.

@addyosmani
Copy link
Author

Thanks for pointing that out @eliperelman! :) Fixed.

@eliperelman
Copy link

No problem, bud!

@mithun-daa
Copy link

Sweet Gist. Noob question. Why does Peter Higgin's pluggin start with a ; ??

@furf
Copy link

furf commented Feb 24, 2012

to ensure that in the event of js file concatenation, the javascript will not break if the previous file is missing a trailing semi-colon.

@mithun-daa
Copy link

@furf thank you sir. that makes total sense.

@crazy4groovy
Copy link

What about $.Deffered()? Can't those be used in some cases? https://tutsplus.com/lesson/deferreds/

@chaslewis
Copy link

Thanks! This is a really clear and practical overview of an important topic.
I'd make a slight modification to the first implementation to take topics out of the global space using the module pattern:

$.Topic = (function() {
    var topics = {};
    return function( id ) {
        // ... as above
        return topic;
    };
})();

@Salvodif
Copy link

Salvodif commented Feb 7, 2014

nice post

@prologic
Copy link

+1 gives me some ideas :)

@Janking
Copy link

Janking commented Jun 1, 2015

nice!

@murliatdure
Copy link

simple and effective

@gandhirahul
Copy link

Simply Amazing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment