Skip to content

Instantly share code, notes, and snippets.

@cowboy
Last active March 6, 2024 21:36
Star You must be signed in to star a gist
Save cowboy/661855 to your computer and use it in GitHub Desktop.
jQuery Tiny Pub/Sub: A really, really, REALLY tiny pub/sub implementation for jQuery.
/* 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));
/* jQuery Tiny Pub/Sub - v0.7 - 10/27/2011
* http://benalman.com/
* Copyright (c) 2011 "Cowboy" Ben Alman; Licensed MIT, GPL */
(function(a){var b=a({});a.subscribe=function(){b.on.apply(b,arguments)},a.unsubscribe=function(){b.off.apply(b,arguments)},a.publish=function(){b.trigger.apply(b,arguments)}})(jQuery)
@bentruyman
Copy link

@furf - Bindable is closer traditional event "binding" than actual pub/sub.

The pub/sub pattern consists of two things: topics and messages. Topics are subscribed to by arbitrary listeners, and messages are received when that topic publishes a message. With event systems, events are bound to and fired from contexts (such as HTML elements), like the Bindable example you linked to.

The whole point of pub/sub is that it doesn't assume a context for it's topics/messages. Think of it like an application-wide notification system.

@furf
Copy link

furf commented May 18, 2011

@bentruyman understood. i was just posting as a comparison. personally i use bindable not only on classes for custom events, but also the application level for handling app-wide pub/sub. i prefer it because i often have multiple apps on the same page and i can scope the pub/sub to an individual app instead of the global (which this basic pub/sub does) without need for unnecessarily deep namespacing. think of it as a var for pub/sub.

@sneakyness
Copy link

This rules, thanks

@a-laughlin
Copy link

Nice, Ben. Very elegant. Here's the same with a tiny bit more shaved off.

var o=$({}); $.each( {on:'subscribe',off:'unsubscribe',trigger:'publish'} ,function(k,v){ $[v]=function(){o[k].apply(o,arguments)}; });

@MarcusJT
Copy link

A handy little plugin, thanks Ben! And even littler thanks to a-laughlin, nice work! :)

JSFiddled here: http://jsfiddle.net/HvAJf/46/

@mahemoff
Copy link

a-laughlin's is 132 characters in case anyone else is wondering!

@stof
Copy link

stof commented Dec 14, 2011

@cowboy the minified version is wrong. it uses , instead of ; between the different functions

@cowboy
Copy link
Author

cowboy commented Dec 14, 2011

@stof, no. Read this.

@cowboy
Copy link
Author

cowboy commented Dec 14, 2011

@mahemoff note that @a-laughlin's code, once wrapped in an IIFE (152 bytes minified), is 138 bytes gzipped, while mine (185 bytes minified) is 121 bytes gzipped.

@stof
Copy link

stof commented Dec 14, 2011

@cowboy I figured after commenting that what really broke the plugin was the missing ; at then end, which caused issues when concatenating files together

@cowboy
Copy link
Author

cowboy commented Dec 14, 2011

Your concatenating utility could join JavaScript source files on ";" instead of "".

@drewwells
Copy link

God help me how do I stop getting notifications on this thing?

@paulirish
Copy link

works in IE9+, or with Modernizr 2.5 or standalone Function.prototype.bind polyfill

(function($) {
  var o         = $({});
  $.subscribe   = o.on.bind(o);
  $.unsubscribe = o.off.bind(o);
  $.publish     = o.trigger.bind(o);
}(jQuery));

@eschwartz
Copy link

Does anyone have any thoughts on using this pattern with Zepto? I've found that Zepto doesn't like $({}), though it's fine with $('<b/>'). Is this a significant performance hit to use $('<b/>'), or is there a better way to port this plugin to Zepto?

@rwaldron
Copy link

@eschwartz $({}) is just an empty jQuery object, substitute it for whatever is the equivalent in Zepto

@eschwartz
Copy link

@rwldrn Thanks, but my issue is that I haven't been able to figure out how to create an empty element in Zepto. I suppose I should pose that issue on a Zepto forum, but I thought someone might have a workaround here.

@javascripter
Copy link

Zepto only makes use of the DOM-based event system so there's no way to get a real empty element to work as the global pub/sub object. We might as well use ("").

@kodi
Copy link

kodi commented Oct 12, 2012

I wrapped this into AMD module to use it with require.js

define(function () {

    "use strict";

    /**
     *    Events. Pub/Sub system for Loosely Coupled logic.
     *    Based on Peter Higgins' port from Dojo to jQuery
     *    https://github.com/phiggins42/bloody-jquery-plugins/blob/master/pubsub.js
     *
     *    Re-adapted to vanilla Javascript
     *
     *    ----------------------------------------------------------
     *    And then wrapped to AMD Module by Dragan Bajcic @kodisha
     *
     *    @class Events
     */
    return {
        cache : {},
        /**
         *    Events.publish
         *    e.g.: Events.publish("/Article/added", [article], this);
         *
         *    @class Events
         *    @method publish
         *    @param topic {String}
         *    @param args    {Array}
         *    @param scope {Object=} Optional
         */
        publish : function (topic, args, /** {Object=} */ scope) {

            console.log('publish',topic, args);
            if (this.cache[topic]) {
                var thisTopic = this.cache[topic],
                    i = thisTopic.length - 1;

                for (i ; i >= 0 ; i -= 1) {
                    thisTopic[i].apply(scope || this, args || []);
                }
            }
        },
        /**
         *    Events.subscribe
         *    e.g.: Events.subscribe("/Article/added", Articles.validate)
         *
         *    @class Events
         *    @method subscribe
         *    @param topic {String}
         *    @param callback {Function}
         *    @return Event handler {Array}
         */
        subscribe : function (topic, callback) {

            console.log('subscribe', topic, callback);
            if (!this.cache[topic]) {
                this.cache[topic] = [];
            }
            this.cache[topic].push(callback);
            return [topic, callback];
        },
        /**
         *    Events.unsubscribe
         *    e.g.: var handle = Events.subscribe("/Article/added", Articles.validate);
         *        Events.unsubscribe(handle);
         *
         *    @class Events
         *    @method unsubscribe
         *    @param handle {Array}
         *    @param completly {Boolean}
         *    @return {type description }
         */
        unsubscribe : function (handle, completly) {
            var t = handle[0],
                i = this.cache[t].length - 1;

            if (this.cache[t]) {
                for (i ; i >= 0 ; i -= 1) {
                    if (this.cache[t][i] === handle[1]) {
                        this.cache[t].splice(this.cache[t][i], 1);
                        if (completly) {
                            delete this.cache[t];
                        }
                    }
                }
            }
        }
    };

});

@stsvilik
Copy link

I have only one concern/suggestion to this relatively simple PubSub - it doesn't take into account published events that have already happened (in the past). Why is this important? Assume for a second that I want to subscribe to an event that had already happened, or I dont know that it happened, but still want my new subscriber to be triggered with the last-published values? My suggestion is to add something like this:

(function($) {

var o = $({}), pastEvents = {};

$.subscribe = function() {
var type = arguments.slice(0, 1)[0],
handler = arguments.slice(-1)[0];
//Fire your subscribe handler if event has already happened
if(type in pastEvents) {
pastEvents[type].done(function() {
handler.apply(o, arguments);
});
}
//Subscribe to future events as well
o.on.apply(o, arguments);
};

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

$.publish = function() {
var type = arguments.slice(0, 1)[0],
data = arguments.slice(1)[0];
//Preserve data for future subscribers to this event
pastEvents[type] = $.Deferred().resolve(data).promise();
o.trigger.apply(o, arguments);
};

}(jQuery));

@chrisclarke1977
Copy link

Even smaller just for sillyness

var o=$({}),s='subscribe';
$.each({on:s,off:'un'+s,trigger:'publish'},function(k,v){$[v]=function(){o[k].apply(o,arguments)};});

@zaus
Copy link

zaus commented Feb 11, 2013

If we're talking about relative sizes, why use "publish/subscribe" nomenclature? Just stick with "on/off/trigger" (or my preferred "on/off/do").

UPDATE I just found out why the "pros" stick to trigger instead of do (boo <IE9).

Example: https://gist.github.com/zaus/4756518

/* jQuery Tinier Pub/Sub - v0.9b - "on/off/do version" - 2013-02-11
 * original by http://benalman.com/ 10/27/2011
 * Original Copyright (c) 2011 "Cowboy" Ben Alman; Licensed MIT, GPL */

(function($) {

  // "topic" holder
  var o = $({});

  // attach each alias method
  $.each({on:0,off:0,"go":'trigger'}, function(alias,method) {
    $[alias] = function(topic, callbackOrArgs) {
        o[method || alias].apply(o, arguments);
    }
  });

}(jQuery));

@zaus
Copy link

zaus commented Feb 19, 2013

@connected - what's the default event you're trying to prevent? isn't this limited to the arbitrary hidden topic var o? just curious.

@aghouseh
Copy link

@kodi thanks for the requirejs module implementation!

@mediafreakch
Copy link

How would you add support for wildcards in the topic name? Does it even make sense as jQuery doesn't support wildcards for event names? Or is using a standalone pub/sub library the better approach?

@gmanish
Copy link

gmanish commented Dec 4, 2015

I know some javascript and little of jQuery (been playing with Ember, just for fun). I understand the on off methods, but I do not understand what var o = $({}); does. Can anyone please explain?

As always, google doesn't consider these braces and the $ in its search results.

@shshaw
Copy link

shshaw commented Apr 14, 2016

@gmanish var o = $({}) simply creates a jQuery collection with an empty object {} that becomes the recipient of all the event triggers. As other comments show, the recipient could be anything like $('<b />').

@Kiodaddy
Copy link

I am telling you this is working really good.

@ahmed-musallam
Copy link

@ionutzp
Copy link

ionutzp commented Apr 1, 2023

@cowboy this gist is getting spammed

@doramavip
Copy link

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