In the past, I've talked about how awesome jQuery's DOM-based event management is. In fact, I've even played around with using the DOM-based event management to power an object-based bind and trigger mechanism. As you saw in that exploration, however, porting the jQuery event framework onto a non-DOM context requires a good bit of finagling. At this year's jQuery Conference (2010), one of the most frequently discussed topics was that of light weight Pub/Sub (publish and subscribe). This brand of event binding is like jQuery's event binding; but, it circumvents a lot of the processing that a DOM-based event framework needs to perform. Since this seemed to be the direction in which people were moving, I thought it was time to try it out for myself.
As Rebecca Murphey pointed out in her jQCon presentation, there's an existing jQuery plugin that provides pub/sub in six lines of code. But, if you've been following my blog for any amount of time, you'll probably know that I like to learn something by writing 200 lines of code in order to figure out why those six lines of code rock. And, that's exactly what I've done here.
In the following experiment, I've created three extensions to the jQuery namespace:
$.subscribe( eventType, subscriber, callback ) - This subscribes the given subscriber to the given event type. When the event type is triggered, the given callback will be executed in the context of the subscriber (ie. "this" refers to the subscriber within the callback).
$.unsubscribe( eventType, callback ) - This unsubscribes the given callback from the given event type. Since functions are comparable objects, the subscriber is not required - only the callback that it defined.
$.publish( publisher, eventType, data ) - This allows the given publisher to publish the given event type with the given data array (if any). When the publication event is created, the publisher is defined as the "target" of the event.
Unlike traditional jQuery event binding, this light weight publication and subscription mechanism is not associated with any given jQuery collection. As such, I am requiring the publishers and the subscribers to pass themselves along with their related method calls. This is not something that I saw the pub/sub presentation demos doing; however, knowing who fired off a given event just seems kind of necessary to me. It makes the pub/sub API a little less elegant but, I think it will be worth it in the long run.
When an event gets published, an event object gets created with the following properties:
type - The event type (string) that has been published.
target - The object that has published the event.
data - An array of any additional data that has been published along with the event. Each item in this array gets passed to the subscriber callback as an additional invocation argument.
result - This is the return value of the most recently executed subscriber callback (or undefined if no value was returned).
This event object is capable of some very light-weight propagation logic. If you look at the event object that gets created above, you'll see that it stores the result of each subscriber that gets notified of a given event. If any of the subscriber callbacks return false, the $.publish() method will treat this as a request to stop immediate propagation. As such, it will break out of the $.each() iteration method that it uses to invoke all subscribers to the given event.
Furthermore, once all of the subscribers have been notified, the event object is then returned to the event publisher. At that point, the event publisher has the opportunity to alter its default behavior based on the result value of the event. There is nothing in the pub/sub model that requires any particular action to be taken place; however, if the publisher sees that the last known result is "false", then it can chose not to complete the event that it just published.
Now that you have an idea of where I am going with this, let's take a look at some code. In the following demo, I am defining the three pub/sub jQuery extensions - subscribe, unsubscribe, and publish. Then, I am creating two Person objects that automatically subscribe to the global event, "oven.doneBaking." Then, I create an oven object that announces the event, "oven.doneBaking." Naturally, the person objects will react to this event.
As you can see in this code, the Person constructor automatically subscribes to the event, "oven.doneBaking." However, in the callback that it uses to subscribe to the event, the Person class is returning false. As such, the first person to subscribe to the event will stop propagation of the event, thereby preventing the second person from knowing anything about the very very delicious pumpkin pie (this is typically my strategy of choice at Thanksgiving).
Because of this propagation logic, running the code leaves us with the following console output:
Nom nom nom - Joanna is hungry for Pumpkin Pie
The event was NOT propagated!
NOTE: I have not included the logged event object as it is a complex value.
In this demo, I am not making any final use of the event object other than to check the propagation status. However, if my publish request was made inside of a true domain object, I could have used the event.result value to react in different ways (much in the same way that a Form won't submit if its default behavior is prevented).
Want to use code from this post? Check out the license.