How are custom broadcast events implemented in JavaScript (or jQuery)?
Asked Answered
M

4

12

I want to implement a custom event that can be "broadcast", rather than sent to specific targets. Only those elements that have registered themselves as listeners for such events will receive them.

What I have in mind would look as follows.

First, in various places of the code, there would be statements of the form

some_subscriber.on_signal( 'some_signal', some_handler );

I'm using the term signal as shorthand for "broadcast event". In the expression above, some_subscriber registers itself as a listener of one type (called 'some_signal') of such signals, by providing a handler for it.

Elsewhere in the code, there would be statements of the form

publisher.signal_types[ 'some_signal' ].broadcast( event_data );

When statements like these get executed, a new event is generated and "broadcast". By this I mean that the code that calls the broadcast method has no direct information about the listeners for the signal it is issuing.


I have implemented a sketch of this idea in this jsFiddle, mostly in order to illustrate what I described in words above1. (It's certainly not production-grade, and I'm not particularly confident that it could be made so.)

The key elements of this implementation are the following. First, publisher objects do not keep track of their subscribers, as can be seen in the implementation of a factory method for such a publisher, shown below:

function make_publisher ( signal_types ) {

    // ...

    var _
    ,   signal = {}
    ,   ping = function ( type ) {
                   signal[ type ].broadcast( ... );
               }
    ;

    signal_types.forEach( function ( type ) {
                             signal[ type ] = $.register_signal_type( type );
                         } );

    return { signal_types: signal_types, ping: ping };
}

This publisher object exposes only two items: the types of signals it broadcasts (in signal_types), and a ping method. When its ping method is invoked, the publisher responds by broadcasting a signal:

signal[ type ].broadcast( ... )

The ultimate recipients of this broadcast are nowhere to be seen in this code.

Second, elsewhere in the code, subscribers register themselves as listeners of these broadcast signals, like so

    $( some_selector ).on_signal( signal_type, some_handler );

Note: It is basically impossible to illustrate the rationale for this scheme using an example that is both small and realistic. The reason for this is that the strength of this scheme is that it supports very loose coupling between the publisher code and subscriber code, and this is a feature that is never necessary in a small example. On the contrary, in a small example, code that implements such loose coupling invariably comes across as unnecessarily complex. It is therefore important to keep in mind that this apparent excess complexity is an artifact of the context. Loose coupling is very useful in larger projects. In particular, loose coupling via a publisher/subscriber-type pattern is one of the essential features of MVC.


My question is: is there a better (or at least more standard) way to achieve this effect of "broadcasting" custom events?

(I'm interested in both jQuery-based answers as well as "pure JS" ones.)


1An earlier, ill-fated version of this post was met with almost universal incomprehension, and (of course) the all-too-typical down-voting. With one exception, all the comments I got challenged the very premises of the post, and one directly questioned my grasp of the basics of event-driven programming, etc. I'm hoping that by presenting a working example of what I mean at least it won't come across as utterly inconceivable as it did when I described it in words alone. Luckily, the one helpful comment I did get on that earlier post informed me of the function jQuery.Callbacks. This was indeed a useful tip; the sketch implementation mentioned in the post is based on jQuery.Callbacks.

Microcircuit answered 4/7, 2015 at 6:1 Comment(8)
One general custom events library........ github.com/mmikowski/jquery.event.gevent.Fachanan
I am curious to know if you were aware of the native dispatchEvent & addEventListener methods especially when both can be used on document object hence, the only dependancy for both broadcasting and listening becomes the global document object for publishing and subscribing respectively. I am also interested in knowing that if you were aware of them, what shortcomings you noticed using these native methods that encouraged you to come up with your own solution. Thanks.Filibeg
@TahirAhmed: No, I was not aware of those methods. I will read more about them. Thanks. (That said, I tend to avoid native methods, in favor of jQuery, for the sake of cross-browser compatibility.)Microcircuit
What you want is publish/subscribe, not events. Events in Javascript are associated with a specific object that you listen for the event on. So, like I said in a comment on your previous post, unless you want to make a specific object (like the document object) be your proxy object, you are just barking up the wrong tree with JS events. You listen for an event on a specific object and the event occurs on that object. Events in JS simply don't work the way you are describing. You can find an external publish/subscribe library if you want a different model, but that is not built into JS.Geometrician
So, since you are aware of jQuery.Callbacks, what is missing from that for your question? It is a form of publish/subscribe. Why won't it do the trick?Geometrician
@kjo: If I understand it correctly, at the core of it, you want de-coupling of publisher and subscriber and document.dispatchEvent() and document.addEventListener should provide you just that. This way, your document object becomes a kind of a channel for all your events, a proxy. And there are ways you can use them in a cross-browser way as well.Filibeg
The jQuery.Callbacks example should do everything you want except ping. Adding a ping method will probably cause the code to lose its simplicity but it depends on exactly what .ping() is intended to do.Pastypat
@TahirAhmed: please post your comment as an answer, so I can accept it.Microcircuit
F
24

All right.

So I think what you can do is use the native dispatchEvent and addEventListener methods and use document as the only element for both publishing and subscribing to those events. Something like:

var myCustomEvent = new Event('someEvent');
document.dispatchEvent(myCustomEvent);
...
document.addEventListener('someEvent', doSomething, false);

And to make cross-browser, you could:

var myCustomEvent = new Event('someEvent');
document.dispatchEvent(myCustomEvent);
...
if (document.addEventListener) {
    document.addEventListener('someEvent', doSomething, false);
} else {
    document.attachEvent('someEvent', doSomething);
}

You can read more on the subject here and here. Hope this helps.

Filibeg answered 4/7, 2015 at 16:13 Comment(0)
G
2

My question is: is there a better (or at least more standard) way to achieve this effect of "broadcasting" custom events?

No, there is not a more standard way of doing publish/subscribe in Javascript. It is not directly built into the language or the browser and there are no platform standards for it that I'm aware of.

You have several options (most of which you seem aware of) to put your own system together.

You could pick a specific object such as the document object or the window object or a new object you create and use jQuery's .on() and .trigger() with that object as a central clearing house to cobble together a publish/subscribe-like model. You could even hide the existence of that object from your actual use by just coding it into a few utility functions if you want.

Or, as you seem to already know, you could use the jQuery.Callbacks functionality. There's even publish/subscribe sample code in the jQuery doc.

Or, you can find a third party library that offers a somewhat traditional publish/subscribe model.

Or, you can build your own from scratch which really just involves keeping a list of callback functions that are associated with a specific event so when that event is triggered, you can call each callback function.

Geometrician answered 4/7, 2015 at 6:38 Comment(0)
F
0

If you came here looking for the jQuery way of doing this, here you go:

Add the event broadcast/dispatch code:

Syntax:
$(<element-name>).trigger(<event-name>);.

Example:

$.ajax({
    ...
    complete: function () {
        // signal to registered listeners that event has occured
        $(document).trigger("build_complete");
        ...
    }
});

Register a listener for the event:

Syntax:
$(<element-name>).on(<event-name>, function() {...});

Example:

$(document).on("build_complete", function () {
    NextTask.Init();
});

Note: Doing it this way: $(document).build_complete(function() {...}); leads to an error: Uncaught TypeError: $(...).build_complete is not a function.

Floranceflore answered 17/4, 2018 at 7:25 Comment(0)
D
0

I know this has been marked as answered back in 2015 -- but a solution that is also elegant and simple could be to use Redux

Dambrosio answered 18/6, 2020 at 16:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.