191

I'm looking for some input on how to implement custom eventhandling in jquery the best way. I know how to hook up events from the dom elements like 'click' etc, but I'm building a tiny javascript library/plugin to handle some preview functionality.

I've got a script running to update some text in a dom element from a set of rules and data/user input I got, but now I need that same text shown in other elements that this script can't possibly know of. What I need is a good pattern to somehow observe this script producing the needed text.

So how do I do this? Did I overlook some builtin functionality in jquery to raise/handle user events or do I need some jquery plugin to do it? What do you think is the best way/plugin to handle this?

Ufuk Hacıoğulları
  • 37,978
  • 12
  • 114
  • 156
Per Hornshøj-Schierbeck
  • 15,097
  • 21
  • 80
  • 101

6 Answers6

135

The link provided in the accepted answer shows a nice way to implement the pub/sub system using jQuery, but I found the code somewhat difficult to read, so here is my simplified version of the code:

http://jsfiddle.net/tFw89/5/

$(document).on('testEvent', function(e, eventInfo) { 
  subscribers = $('.subscribers-testEvent');
  subscribers.trigger('testEventHandler', [eventInfo]);
});

$('#myButton').on('click', function() {
  $(document).trigger('testEvent', [1011]);
});

$('#notifier1').on('testEventHandler', function(e, eventInfo) { 
  alert('(notifier1)The value of eventInfo is: ' + eventInfo);
});

$('#notifier2').on('testEventHandler', function(e, eventInfo) { 
  alert('(notifier2)The value of eventInfo is: ' + eventInfo);
});
Manuel Navarro
  • 1,789
  • 2
  • 16
  • 18
  • 11
    I really like this answer. Anyone can read the docs on trigger() bind() (and on()), but this answer provides a concrete example of creating an event bus (pub/sub system) using jquery. – lostdorje Mar 28 '13 at 07:23
  • 1
    Bravo. Everything I needed to know. – Patrick Fisher Jul 10 '13 at 05:01
  • I have found that if you pass an array (where length > 1) when you trigger a custom event, then the listener will get all the array items in it's arguments, so you will end up with `n` number of arguments. – vsync Mar 29 '14 at 23:51
  • solution to the multiple arguments handling in the listener: `var eventData = [].slice.call(arguments).splice(1)` – vsync Mar 29 '14 at 23:57
  • 2
    Is there a way to do this without having the #notifier dummy elements? If I had a javascript library and wanted an object to expose an event, I can't be sure what elements exist on the page. – AaronLS May 15 '14 at 19:13
107

Take a look at this:

(reprinted from the expired blog page http://jamiethompson.co.uk/web/2008/06/17/publish-subscribe-with-jquery/ based on the archived version at http://web.archive.org/web/20130120010146/http://jamiethompson.co.uk/web/2008/06/17/publish-subscribe-with-jquery/)


Publish / Subscribe With jQuery

June 17th, 2008

With a view to writing a jQuery UI integrated with the offline functionality of Google Gears i’ve been toying with some code to poll for network connection status using jQuery.

The Network Detection Object

The basic premise is very simple. We create an instance of a network detection object which will poll a URL at regular intervals. Should these HTTP requests fail we can assume that network connectivity has been lost, or the server is simply unreachable at the current time.

$.networkDetection = function(url,interval){
    var url = url;
    var interval = interval;
    online = false;
    this.StartPolling = function(){
        this.StopPolling();
        this.timer = setInterval(poll, interval);
    };
    this.StopPolling = function(){
        clearInterval(this.timer);
    };
    this.setPollInterval= function(i) {
        interval = i;
    };
    this.getOnlineStatus = function(){
        return online;
    };
    function poll() {
        $.ajax({
            type: "POST",
            url: url,
            dataType: "text",
            error: function(){
                online = false;
                $(document).trigger('status.networkDetection',[false]);
            },
            success: function(){
                online = true;
                $(document).trigger('status.networkDetection',[true]);
            }
        });
    };
};

You can view the demo here. Set your browser to work offline and see what happens…. no, it’s not very exciting.

Trigger and Bind

What is exciting though (or at least what is exciting me) is the method by which the status gets relayed through the application. I’ve stumbled upon a largely un-discussed method of implementing a pub/sub system using jQuery’s trigger and bind methods.

The demo code is more obtuse than it need to be. The network detection object publishes ’status ‘events to the document which actively listens for them and in turn publishes ‘notify’ events to all subscribers (more on those later). The reasoning behind this is that in a real world application there would probably be some more logic controlling when and how the ‘notify’ events are published.

$(document).bind("status.networkDetection", function(e, status){
    // subscribers can be namespaced with multiple classes
    subscribers = $('.subscriber.networkDetection');
    // publish notify.networkDetection even to subscribers
    subscribers.trigger("notify.networkDetection", [status])
    /*
    other logic based on network connectivity could go here
    use google gears offline storage etc
    maybe trigger some other events
    */
});

Because of jQuery’s DOM centric approach events are published to (triggered on) DOM elements. This can be the window or document object for general events or you can generate a jQuery object using a selector. The approach i’ve taken with the demo is to create an almost namespaced approach to defining subscribers.

DOM elements which are to be subscribers are classed simply with “subscriber” and “networkDetection”. We can then publish events only to these elements (of which there is only one in the demo) by triggering a notify event on $(“.subscriber.networkDetection”)

The #notifier div which is part of the .subscriber.networkDetection group of subscribers then has an anonymous function bound to it, effectively acting as a listener.

$('#notifier').bind("notify.networkDetection",function(e, online){
    // the following simply demonstrates
    notifier = $(this);
    if(online){
        if (!notifier.hasClass("online")){
            $(this)
                .addClass("online")
                .removeClass("offline")
                .text("ONLINE");
        }
    }else{
        if (!notifier.hasClass("offline")){
            $(this)
                .addClass("offline")
                .removeClass("online")
                .text("OFFLINE");
        }
    };
});

So, there you go. It’s all pretty verbose and my example isn’t at all exciting. It also doesn’t showcase anything interesting you could do with these methods, but if anyone’s at all interested to dig through the source feel free. All the code is inline in the head of the demo page

saluce
  • 13,035
  • 3
  • 50
  • 67
Vitor Silva
  • 17,114
  • 8
  • 33
  • 27
  • Excatly what i was looking for. He is probably right that it's the way to go, but i can't help thinking jquery need either direct support or a plugin to handle it more gracefully. I'll wait a bit and see if there are other takes on the problem before i flag an answer :) – Per Hornshøj-Schierbeck Dec 30 '08 at 12:09
  • 6
    Is this a quote from somewhere? If so, please cite your source, and provide a link, if it still exists. – Aryeh Leib Taurog Mar 03 '13 at 10:31
  • 1
    Yes it's quote, in edit history is [this link](http://jamiethompson.co.uk/web/2008/06/17/publish-subscribe-with-jquery/), currently it's inaccessible. [Archived page is here](http://web.archive.org/web/20130120010146/http://jamiethompson.co.uk/web/2008/06/17/publish-subscribe-with-jquery/). – Stano Jul 05 '13 at 19:22
  • 1
    [Ben Alman](http://www.benalman.com) wrote a tiny (tiny tiny tiny) [jQuery pub sub plugin](https://github.com/cowboy/jquery-tiny-pubsub). It's extremely elegant. – Barney Sep 24 '13 at 09:19
21

I think so.. it's possible to 'bind' custom events, like(from: http://docs.jquery.com/Events/bind#typedatafn):

 $("p").bind("myCustomEvent", function(e, myName, myValue){
      $(this).text(myName + ", hi there!");
      $("span").stop().css("opacity", 1)
               .text("myName = " + myName)
               .fadeIn(30).fadeOut(1000);
    });
    $("button").click(function () {
      $("p").trigger("myCustomEvent", [ "John" ]);
    });
Tuxified
  • 675
  • 6
  • 11
15

I had a similar question, but was actually looking for a different answer; I'm looking to create a custom event. For example instead of always saying this:

$('#myInput').keydown(function(ev) {
    if (ev.which == 13) {
        ev.preventDefault();
        // Do some stuff that handles the enter key
    }
});

I want to abbreviate it to this:

$('#myInput').enterKey(function() {
    // Do some stuff that handles the enter key
});

trigger and bind don't tell the whole story - this is a JQuery plugin. http://docs.jquery.com/Plugins/Authoring

The "enterKey" function gets attached as a property to jQuery.fn - this is the code required:

(function($){
    $('body').on('keydown', 'input', function(ev) {
        if (ev.which == 13) {
            var enterEv = $.extend({}, ev, { type: 'enterKey' });
            return $(ev.target).trigger(enterEv);
        }
    });

    $.fn.enterKey = function(selector, data, fn) {
        return this.on('enterKey', selector, data, fn);
    };
})(jQuery);

http://jsfiddle.net/b9chris/CkvuJ/4/

A nicety of the above is you can handle keyboard input gracefully on link listeners like:

$('a.button').on('click enterKey', function(ev) {
    ev.preventDefault();
    ...
});

Edits: Updated to properly pass the right this context to the handler, and to return any return value back from the handler to jQuery (for example in case you were looking to cancel the event and bubbling). Updated to pass a proper jQuery event object to handlers, including key code and ability to cancel event.

Old jsfiddle: http://jsfiddle.net/b9chris/VwEb9/24/

Chris Moschini
  • 36,764
  • 19
  • 160
  • 190
  • Chris - The function worked perfectly.. the only thing thats not working is accessing this variable.. i am able to access it using e.currentTarget.. but was wondering if there is a more efficient way. – Addy Sep 02 '12 at 22:42
  • Good catch on that bug. Yes, if you change line 5 from handler(ev); to: handler.call(this, ev); then the this argument will be as it normally is in standard jquery event handlers. http://jsfiddle.net/b9chris/VwEb9/ – Chris Moschini Sep 04 '12 at 23:04
  • Do you also know of a way to implement this the other way around? I'm trying to use this example but with `scroll`, which doesn't propagate. I think that instead of having the listeners hardcoded to the body, I need the elements that have the `on`-event bound to them to fire the event themselves. – Gust van de Wal Sep 13 '18 at 00:23
  • Gust I'm unsure of the scenario you're asking about; might be best asked as a Question with code sample? – Chris Moschini Oct 01 '18 at 09:11
10

It is an old post, but I will try to update it with a new information.

To use custom events you need to bind it to some DOM element and to trigger it. So you need to use

.on() method takes an event type and an event handling function as arguments. Optionally, it can also receive event-related data as its second argument, pushing the event handling function to the third argument. Any data that is passed will be available to the event handling function in the data property of the event object. The event handling function always receives the event object as its first argument.

and

.trigger() method takes an event type as its argument. Optionally, it can also take an array of values. These values will be passed to the event handling function as arguments after the event object.

The code looks like this:

$(document).on("getMsg", {
    msg: "Hello to everyone",
    time: new Date()
}, function(e, param) {
    console.log( e.data.msg );
    console.log( e.data.time );
    console.log( param );
});

$( document ).trigger("getMsg", [ "Hello guys"] );

Nice explanation can be found here and here. Why exactly this can be useful? I found how to use it in this excellent explanation from twitter engineer.

P.S. In plain javascript you can do this with new CustomEvent, but beware of IE and Safari problems.

Salvador Dali
  • 214,103
  • 147
  • 703
  • 753
8

Here is how I author custom events:

var event = jQuery.Event('customEventName');
$(element).trigger(event);

Granted, you could simply do

$(element).trigger('eventname');

But the way I wrote allows you to detect whether the user has prevented default or not by doing

var prevented = event.isDefaultPrevented();

This allows you to listen to your end-user's request to stop processing a particular event, such as if you click a button element in a form but do not want to the form to post if there is an error.


I then usually listen to events like so

$(element).off('eventname.namespace').on('eventname.namespace', function () {
    ...
});

Once again, you could just do

$(element).on('eventname', function () {
    ...
});

But I've always found this somewhat unsafe, especially if you're working in a team.

There is nothing wrong with the following:

$(element).on('eventname', function () {});

However, assume that I need to unbind this event for whatever reason (imagine a disabled button). I would then have to do

$(element).off('eventname', function () {});

This will remove all eventname events from $(element). You cannot know whether someone in the future will also bind an event to that element, and you'd be inadvertently unbinding that event as well

The safe way to avoid this is to namespace your events by doing

$(element).on('eventname.namespace', function () {});

Lastly, you may have noticed that the first line was

$(element).off('eventname.namespace').on('eventname.namespace', ...)

I personally always unbind an event before binding it just to make sure that the same event handler never gets called multiple times (imagine this was the submit button on a payment form and the event had been bound 5 times)

Ryan M
  • 18,333
  • 31
  • 67
  • 74
Luke Madhanga
  • 6,871
  • 2
  • 43
  • 47