Using One Object Per Event Type With Publish And Subscribe (Pub/Sub)

Posted October 27, 2011 at 10:27 AM by Ben Nadel

Tags: Javascript / DHTML

Yesterday, I was reading an interesting article by Miller Medeiros that compared different types of publish and subscribe mechanisms. Publish and Subscribe (Pub/Sub) is nothing new - if you use jQuery at all, you've probably been using Pub/Sub a lot (even without thinking about it). In the article, however, Miller described a Pub/Sub approach that I had never seen before: Signal. In the signal approach, each event type is defined by an individual event beacon. So, rather than binding to a generic event dispatcher, a subscriber would bind to a specific object that codes for a single event type.


 
 
 

 
  
 
 
 

In the more generic, flexible approach to event binding, a subscriber would bind to an event beacon, supplying both an event type and an event callback:

  • eventedObject.bind( "someEvent", subscriberCallback );

In this case, the subscriber is binding to the event type, "someEvent," and would like the function, "subscriberCallback" to be invoked when that event is dispatched. In the Signal approach, the event binding is performed on an event beacon that codes for a specific event:

  • eventedObject.someEvent.bind( subscriberCallback );

The main difference here is that in the first approach, "someEvent" is an arbitrary string value; and, in the latter approach, "someEvent" is a property of the evented object.

In theory, both of these approaches are the same - they allow for publishers to expose an event-binding surface; and, they allow subscribers to bind to specific event types. In practice, however, the major difference is that the Signal approach requires a more formalized event architecture; rather than creating just-in-time, string-based event types, each event has to be defined explicitly ahead of time as a property of the evented object before subscribers can even bind to it.

As Miller points out in his pro/cons list, this is probably a good constraint.

Since I had never seen this kind of Pub/Sub architecture before, I thought it would be good to experiment with a little code. And, to make it more fun, I wanted to explore some of the jQuery 1.7 Callbacks information that Addy Osmani discussed in his Demystifying jQuery 1.7's $.Callbacks article. In the following demo, I'm creating a Signal class (which wraps $.Callbacks) and then I'm using it to define several event types on a given object.

  • <!DOCTYPE html>
  • <html>
  • <head>
  • <title>
  • Using One Object Per Event Type With Publish And Subscribe
  • </title>
  •  
  • <script type="text/javascript" src="./jquery-1.7rc1.js"></script>
  • <script type="text/javascript">
  •  
  •  
  • // Define the Signal class. Each signal will be responsible
  • // for a specific event beacon attached to an object.
  • function Signal( context, eventType ){
  •  
  • // Store the context (the object on which this event
  • // is being attached).
  • this.context = context;
  •  
  • // Store the event type - the name of the event.
  • this.eventType = eventType;
  •  
  • // Create a queue of callbacks for this event.
  • this.callbacks = $.Callbacks();
  •  
  • }
  •  
  • // Define the class methods.
  • Signal.prototype = {
  •  
  • // I bind an event handler.
  • bind: function( callback ){
  •  
  • // Add this callback to the queue.
  • this.callbacks.add( callback );
  •  
  • },
  •  
  •  
  • // I dispatch the event with the given parameters.
  • dispatch: function(){
  •  
  • // Invoke the callbacks.
  • this.callbacks.fireWith(
  • this.context,
  • arguments
  • );
  •  
  • },
  •  
  • // I unbind an event handler.
  • unbind: function( callback ){
  •  
  • // Remove the callback from the queue.
  • this.callbacks.remove( callback );
  •  
  • }
  •  
  • };
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Create an instance of Sarah
  • var sarah = {};
  •  
  • // Set the name.
  • sarah.name = "Sarah";
  •  
  • // Create an event collection. In this scenario, each event
  • // is defined by an actual object - not just an event publish
  • // and subscribe entry point.
  • sarah.events = {
  • wakeUp: new Signal( sarah, "wakeup" ),
  • shower: new Signal( sarah, "shower" ),
  • gotoWork: new Signal( sarah, "gotowork" ),
  • gotoHome: new Signal( sarah, "gotohome" ),
  • sleep: new Signal( sarah, "sleep" )
  • };
  •  
  • // Execute one day.
  • sarah.live = function(){
  • this.events.wakeUp.dispatch( "Stretch!" );
  • this.events.shower.dispatch( "Rubba-dub-dub" );
  • this.events.gotoWork.dispatch( "Hmmph" );
  • this.events.gotoHome.dispatch( "Yawwwn" );
  • this.events.shower.dispatch( "Oooooooh" );
  • this.events.sleep.dispatch( "Sleeeepy!" );
  • };
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Bind to shower events.
  • sarah.events.shower.bind(
  • function( metaData ){
  •  
  • // Log event.
  • console.log( this.name, "[Shower]:", metaData );
  •  
  • }
  • );
  •  
  • // Execute one day in the life of Sarah.
  • sarah.live();
  •  
  •  
  • </script>
  • </head>
  • <body>
  • <!-- Left intentionally blank. -->
  • </body>
  • </html>

As you can see, the Signal class represents a single event type. It then exposes two subscriber methods:

  • bind( callback )
  • unbind( callback )

This Signal class is then instantiated for each event type available on the "sarah" object. When we bind to events on sarah, we aren't binding to sarah itself; rather, we are binding to the event surface exposed by sarah.

And, when we run the above code, we get the following console output:

Sarah [Shower]: Rubba-dub-dub
Sarah [Shower]: Oooooooh

While this approach does feel a bit awkward, probably due to its novelty, I like the fact that I have to explicitly define my object's event types. I suspect that this would force me to think more holistically about my object architecture. And, I think there would be improved readability and maintainability in being able to see all the events defined in a single place.




Reader Comments

Oct 27, 2011 at 3:12 PM // reply »
4 Comments

Nice write-up and video, I hope more people understands the benefits of having a concrete object to each event type and not relying on strings.

I'm not sure if it was by coincidence but [DART DOM event system](www.dartlang.org/articles/improving-the-dom) is very similar to Signals, they use a namespace called "on" to store all the event types instead of "events" tho...

[js-signals](http://millermedeiros.github.com/js-signals/) have a couple extra features like disable/enable a signal, remove all the callbacks at once, ability to set default parameters and change the execution context (`this` object) for each handler. It also doesn't require jQuery and works on nodejs as well.

Cheers.


Oct 29, 2011 at 2:42 PM // reply »
2 Comments

This pretty much like the signal/slot system in Qt. Classes have signals and slots and you connect signals to slots. When the signal is emited(fired) it calls all the slots connected to it in an arbitrary order.

It must feel somewhat awkward in javascript since the event mechanism is already somewhat native to language unlike in Qt where it was build to make gui easier to make in c++.


Oct 29, 2011 at 2:49 PM // reply »
2 Comments

[sorry for the double post]

Qt code sample

  • class c : public QObject{
  • Q_OBJECT
  • public:
  • c(){
  • connect(this, SIGNAL(somethingdone()),
  • this, SLOT(dosomethingelse()));
  •  
  • connect(this, SIGNAL(somethingdonewithparam(int x)),
  • this, SLOT(dosomethingelsewithparam(int x)));
  •  
  • }
  •  
  • void dosomething(){
  • emit somethingdone();
  • }
  • void dosomethingwithparam(int x){
  • emit somethingdonewithparam(x);
  • }
  •  
  • signals:
  • void somethingdone();
  • void somethingdonewithparam(int p);
  •  
  • public slots:
  • dosomethingelse(){
  • std::cout << "something something";
  • }
  • dosomethingelsewithparam(int x){
  • std::cout << "something " << x;
  • }
  • };
  •  
  • int main(int argc, char** argv){
  • c obj;
  • obj.dosomething(); /*print something something in console*/
  • obj.dosomethingwithparam(4); /*print something 4 in console*/
  • }


Oct 29, 2011 at 4:37 PM // reply »
4 Comments

Yep Guillaume, it's very similar to Qt, AS3-Signals (js-signals was based on AS3-Signals) was based on Qt signals/slots and C# Events.

ActionScript 3 native Events are very similar to DOM2 events (in fact it was based on DOM3 events), and still a lot of AS3 developers started using Signals instead of the native event model (even for native events), the main reason for that is because it doesn't rely on strings (code completion, live error checking / compiler errors, can be added to interfaces, etc..) [more info about it](https://github.com/robertpenner/as3-signals/wiki/).

In JavaScript it makes sense to create a custom pub/sub system since the language doesn't provide an easy way to dispatch custom events (

  • document.createEvent()

is really awkward and verbose).

Cheers.


Oct 29, 2011 at 6:58 PM // reply »
11,246 Comments

@Miller,

The JS Signal stuff looks pretty interesting. I enjoyed, especially, that you included a Naming convention for the event types. Although naming stuff is supposed to be one of the hardest parts of computer science, I happen to think that naming event-types is especially difficult. I never know if I should to present tense or past tense... or if it should depend on whether or not the event can be cancelled.

@Guillaume,

It is definitely a bit strange, especially since every event type I've looked at up to this point has been string-based with a generic, flexible binding. But, while it is awkward, there is something that I really do like about it :)

@Miller,

Having the code-completion aspect is really nice. I use an Eclipse-based editor which, when dealing with Html / JavaScript, uses Aptana under the cover (or so I'm told); in general, this seems to have really good code completion - so, Im sure it would pick on these object-based events quite easily. Heck yeah :D


Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 25, 2013 at 10:08 AM
Using "//" And ".//" Expressions In XPath XML Search Directives In ColdFusion
@Ben, my question is that i want the current node with its tag and its parent node. i just want only that data. So, give me the solution for that. and remember solution is working on " xpath 1.0 ... read »
May 25, 2013 at 10:01 AM
Using "//" And ".//" Expressions In XPath XML Search Directives In ColdFusion
hey ben, i want get my current node tag and also want the root node tag withing. So, how can i fix it.. ! ... read »
May 24, 2013 at 5:39 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
@Adam Oops! My mistake! I hadn't gotten that far in my testing - I'm still baby stepping my way through the process. ... read »
May 24, 2013 at 5:13 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
Hi Jason, Thanks for checking up on that, but I still stand firm on my position. :) There are actually two listLast()'s in use, and you're right that the one using a space as a delimiter is fine. ... read »
May 24, 2013 at 4:45 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
@Ben I have been lurking your site for quite some time, and haven't stepped up to comment until today. Thanks for all the great info - keep it up! @Adam I believe you are mistaken... as the commen ... read »
May 24, 2013 at 11:21 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@WebManWalking, Ha ha, let's us never speak of justifying "##" notation again :P ... read »
May 24, 2013 at 11:18 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Ben, Ah, so it was indeed how I vaguely remembered it to be: A direct assignment value = users.id[ i ] causes value to retain the sticky datatype of the query column. Although unnecessary in ... read »
May 24, 2013 at 9:11 AM
Preventing Links In Standalone iPhone Applications From Opening In Mobile Safari
@Brandon, Hi, No, I haven't been able to do that. I have just kept it as it is. ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools