Skip to content

Instantly share code, notes, and snippets.

@addyosmani
Created October 28, 2011 06:49
Show Gist options
  • Save addyosmani/1321768 to your computer and use it in GitHub Desktop.
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"]);
@gnarf
Copy link

gnarf commented Oct 28, 2011

It is pretty awesome, just want to put out a healthy reminder that $.observable is still evolving - $.observer is still a very new child, glad to see a demo of using it for something!

@ajpiano
Copy link

ajpiano commented Oct 28, 2011

Yeah, I am pretty sure that the observable stuff won't be available as part of jQuery UI 1.9

@addyosmani
Copy link
Author

Updating the gist to reflect correct version information on $.observable and $.observer being available.

@gnarf
Copy link

gnarf commented Oct 28, 2011

@addyosamani - $.observer was written very recently by @brado23 - I am pretty sure that it is in flux (but a useful helper for observables, right! :) )

I don't think of $.observer as a way to do pub/sub, that whole concept seems very rooted in a very simple string named "topic" structure. $.observer might help you with a keeping track of data in your storage arrays though, and give ways for other components to listen for change events on specific items or arrays.

@addyosmani
Copy link
Author

@gnarf37 that makes sense. Whilst pub/sub is slightly more event driven and $.observer is more data-oriented I consider them both as a part of the same family of observable patterns of development. I should probably come up with a better title for this gist :) Thanks for sharing the extra information about $.observer!. I'm finding out more and more about it as I read and play around with the code.

@pomeh
Copy link

pomeh commented Nov 6, 2011

this is nice :)

I would like to know what do you think about this problem. I've write a simple test case on jsFiddle if you want to see it in action and play with it.

Cheers

@furf
Copy link

furf commented Jan 11, 2012

Here's another variation where jQuery's event methods are "mixed in" to the supplied object or supplied function's prototype. Also includes a wildcard event for listening to all events. https://github.com/furf/jquery-enable/blob/master/src/bindable.js

@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