Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Jay Vanderlyn and Mike Shea

Simple Neural-Net jQuery Pub/Sub Experiment With Propagation Logic

By Ben Nadel on

Yesterday, I experimented with simple publication and subscription (pub/sub) functionality in jQuery for the first time. As part of my pub/sub functionality, I built in the ability for subscribers to be able to stop the immediate propagation of a given event. The publisher, could then, in turn, decide whether or not to halt its activity based on the event result. In the comments to my post, both Rebecca Murphey and Hal Helms argued that the beauty of pub/sub was that events could not be stopped; that is was this listen-only approach that allowed components to stay decoupled.

While I can understand their point of view, I can't help but like the slightly coupled nature of the core jQuery approach, which is what I tried to model in a light-weight way. All of the pros and cons of this approach started flying around in my head. When I went to bed last night, I found myself just laying there, not being able to stop thinking about pub/sub. Then after about an hour of pub-sub-induced insomnia, it hit me! THIS would be a perfect time for some higher-mind event mediation via pub/sub.

 
 
 
 
 
 
 
 
 
 

Warning: What I'm about to say is not based on what you'd call "real" science.

My brain is composed of two major parts: my Lizard brain and my Conscious brain. I don't have much control over my lizard brain - it's an ancient part of my evolved self that worries about fight or flight, survival, food, and erotic topics like publication and subscription. What I found happening last night was that my lizard brain kept firing off provocatively sexy thoughts about pub/sub and my conscious brain had no choice but to listen to them. At about 1AM (I get into bed at 12AM), I realized that I was, at that very moment, experiencing a situation that could have benefited greatly from a publication and subscription architecture that afforded better propagation logic.

The problem I had last night was that my two brains were decoupled to the point were no "bigger picture" could be sustained. But, imagine if I had some sort of "higher mind" that could regulate the way thoughts were bounced around in my head. Rebecca Murphey might call this a "mediator". If I had this brain mediator, I could bring an overarching strategic logic to a collection of independent systems. Such an approach might, for instance, prevent erotic thoughts from being propagated if (and only if), it was time for bed.

To explore this idea, I tried to model this brain mediator in Javascript. In the following demo, I have three classes:

  • BrainController
  • LizardBrainController
  • ConsciousBrainController

The BrainController class is my mediator - it provides the bigger picture understanding of the systems that make up my brain. The LizardBrainController is my ancient lizard brain - it periodically announces scandalous thoughts about publication and subscription functionality. And then the ConsciousBrainController is my human brain - its overwhelming desire for environmental stimulation leaves it powerless to resist the constant barrage of the lizard brain's erotic imagery.

In this setup, the BrainController is responsible for setting up the other two controllers. As such, it has the opportunity to be the first subscriber to events published within the aggregated system. This point is crucial for the exploration because it allows the BrainController to stop the immediate propagation of events that are counter-productive to the aggregated system's long-term success strategy.

In the following demo, the only "bigger picture" concept that the BrainController understands is that it's either time to go to sleep or it's not. If it is time to go to sleep, the BrainController will halt the immediate propagation of erotic-thought events that originate from the LizardBrainController. This should prevent my ConsciousBrainController from getting all hot and bothered, thereby allowing it to fall asleep (our long-term strategy for success).

  • <!DOCTYPE html>
  • <html>
  • <head>
  • <title>Simple jQuery Pub/Sub With Event Propagation Logic</title>
  • <script type="text/javascript" src="./jquery-1.4.3.js"></script>
  • <script type="text/javascript" src="./jquery.pubsub.js"></script>
  • <style type="text/css">
  •  
  • div.brain {
  • background-color: #F8F8F8 ;
  • border: 5px solid #CCCCCC ;
  • height: 400px ;
  • overflow: hidden ;
  • position: relative ;
  • width: 500px ;
  • }
  •  
  • div.lizardBrain {
  • background-color: #DDE684 ;
  • border: 5px solid #CCCC00 ;
  • height: 40px ;
  • left: 50% ;
  • margin: -25px 0px 0px -25px ;
  • overflow: hidden ;
  • position: absolute ;
  • text-indent: -999px ;
  • top: 50% ;
  • width: 40px ;
  • z-index: 100 ;
  • }
  •  
  • div.lizardBrain.active {
  • background-color: #CC9999 ;
  • border-color: #FF0000 ;
  • }
  •  
  • div.consciousBrain {
  • height: 400px ;
  • left: 0px ;
  • overflow: hidden ;
  • position: absolute ;
  • top: 0px ;
  • width: 500px ;
  • z-index: 50 ;
  • }
  •  
  • div.consciousBrain ul.neuralNet {
  • left: 0px ;
  • list-style-type: none ;
  • margin: 0px 0px 0px 0px ;
  • padding: 0px 0px 0px 0px ;
  • position: absolute ;
  • top: 0px ;
  • }
  •  
  • div.consciousBrain ul.neuralNet li {
  • color: #FF0000 ;
  • margin: -10px 0px 0px -20px ;
  • position: absolute ;
  • white-space: nowrap ;
  • }
  •  
  • </style>
  • </head>
  • <body>
  •  
  • <h1>
  • Simple jQuery Pub/Sub With Event Propagation Logic
  • </h1>
  •  
  • <!-- This is ben's brain. -->
  • <div class="brain">
  •  
  • <!-- This is ben's lizard brain. -->
  • <div class="lizardBrain">
  • Nom nom nom. Sexy time! Burp.
  • </div>
  •  
  • <!-- This is ben's conscious brain. -->
  • <div class="consciousBrain">
  • To be or not to be?
  • </div>
  •  
  • </div>
  •  
  •  
  • <!-- When the DOM is ready (ie. now), init the scripts. -->
  • <script type="text/javascript">
  •  
  • // I am the Ben's brain controller. I try to help all parts
  • // of the brain run as well together as possible. You might
  • // call me a "mediator," if that word does it for you.
  • function BrainController( target ){
  • // Store a reference to the target brain.
  • this.target = target;
  •  
  • // This is a flag that determines if we should be
  • // sleaping right now.
  • this.isSleepyTime = false;
  •  
  • // Bind to high-level events that we can moderate for
  • // more efficient overall brain activity.
  • $.subscribe(
  • "lizardBrain.eroticThought",
  • this,
  • "handleEroticThoughts"
  • );
  •  
  • // Create the composed lizard brain.
  • this.lizardBrain = new LizardBrainController(
  • this.target.find( "div.lizardBrain" )
  • );
  •  
  • // Create the composed conscious brain.
  • this.consciousBrain = new ConsciousBrainController(
  • this.target.find( "div.consciousBrain" )
  • );
  • }
  •  
  • // Define the controller class methods.
  • BrainController.prototype = {
  •  
  • // I handle the erotic thoughts coming out of the
  • // ancient lizard brain.
  • handleEroticThoughts: function( event, thought ){
  • // Log that the erotic thought came in.
  • console.log( "Erotic thought:", thought );
  •  
  • // Check to see if it is sleepy time. If so, then
  • // we don't want this erotic thought to be
  • // propagated - Ben needs his sleep and can't spend
  • // the next few hours fantasizing about pub/sub.
  • if (this.isSleepyTime){
  •  
  • // Log that we are stopping propagation.
  • console.log( "Sleepy time - erotic thought being killed...." );
  •  
  • // Return false to kill the event.
  • return( false );
  •  
  • }
  • }
  •  
  • };
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // I am the controller for the lizard brain.
  • function LizardBrainController( target ){
  • // Store the target for our controller.
  • this.target = target;
  •  
  • // Kick off a timer for
  • this.startThoughtTimer();
  • }
  •  
  • // Define the controller class methods.
  • LizardBrainController.prototype = {
  •  
  • // I kick off the thoughts interval.
  • startThoughtTimer: function(){
  • var self = this;
  •  
  • // Define the list of erotic thoughts.
  • var eroticThoughts = [
  • "Pub/Sub is so hot right now!",
  • "I would subscribe to that all night long.",
  • "Event propagation for the win!",
  • "Component decoupling is steamy",
  • "Try subscribing to this!",
  • "I see your events and I want some more."
  • ];
  •  
  • // Kick off timer.
  • setInterval(
  • function(){
  • // Select a random thought.
  • var thought = eroticThoughts[
  • Math.floor( Math.random() * eroticThoughts.length )
  • ];
  •  
  • // Activate the brain.
  • self.target.addClass( "active" );
  •  
  • // Publish the erotic thought.
  • $.publish(
  • self,
  • "lizardBrain.eroticThought",
  • [ thought ]
  • );
  •  
  • // Remove the active class shortly.
  • setTimeout(
  • function(){
  • self.target.removeClass( "active" );
  • },
  • (1 * 1000)
  • );
  • },
  • (6 * 1043)
  • );
  • }
  •  
  • };
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // I am the controller for the conscious brain.
  • function ConsciousBrainController( target ){
  • // Store the target for our controller.
  • this.target = target;
  •  
  • // Clear the contents to make room for the net.
  • this.target.empty();
  •  
  • // Define our neural net container.
  • this.neuralNet = $( "<ul></ul>" )
  • .addClass( "neuralNet" )
  • .appendTo( this.target )
  • ;
  •  
  • // Build the neural net.
  • this.buildNeuralNet();
  •  
  • // Listen for any erotic thoughts.
  • $.subscribe(
  • "lizardBrain.eroticThought",
  • this,
  • "handleEroticThoughts"
  • );
  • }
  •  
  • // Define the controller class methods.
  • ConsciousBrainController.prototype = {
  •  
  • // I build the neurel net, adding a fixed number neurons
  • // in random positions.
  • buildNeuralNet: function(){
  • // Get the dimensions of the net.
  • var width = this.target.innerWidth();
  • var height = this.target.innerHeight();
  •  
  • // Build up our collection of neurons.
  • for (var i = 0 ; i < 200 ; i++){
  •  
  • // Define the neuron.
  • var neuron = $( "<li>Oh yeah!</li>" )
  • .css({
  • opacity: .05,
  • left: ((Math.random() * width) + "px"),
  • top: ((Math.random() * height) + "px")
  • })
  • ;
  •  
  • // Add the neuron to the net.
  • this.neuralNet.append( neuron );
  •  
  • }
  • },
  •  
  •  
  • // I handle the erotic thoughts event.
  • handleEroticThoughts: function( event, thought ){
  • // Fire off all of our neruons.
  • this.neuralNet.children().each(
  • function( index, neuralNode ){
  •  
  • // Flicker neuron to indicate activity.
  • $( neuralNode )
  • .delay( Math.random() * 1000 )
  • .fadeTo( 900, 1 )
  • .fadeTo( 300, .05 )
  • .fadeTo( 900, 1 )
  • .fadeTo( 200, .05 )
  • ;
  •  
  • }
  • );
  • }
  •  
  • };
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Create the brain controller.
  • var brainController = new BrainController(
  • $( "div.brain" )
  • );
  •  
  •  
  • </script>
  •  
  • </body>
  • </html>

In this code, I've factored out the the publication and subscription functionality into its own jQuery plugin - jquery.pubsub.js. This way, the demo code was only concerned with how the pub/sub functionality might be used.

As you can see, within the BrainController constructor, the BrainController subscribes to the following event before it even instantiates the lizard brain or conscious brain controllers:

  • // Bind to high-level events that we can moderate for
  • // more efficient overall brain activity.
  • $.subscribe(
  • "lizardBrain.eroticThought",
  • this,
  • "handleEroticThoughts"
  • );

This way, it has the opportunity to moderate inter-system communication based on "bigger picture" logic. Specifically, if it's time to go to bed, the BrainController will prevent the lizard brain's erotic-thought events from being propagated:

  • // I handle the erotic thoughts coming out of the
  • // ancient lizard brain.
  • handleEroticThoughts: function( event, thought ){
  • // Log that the erotic thought came in.
  • console.log( "Erotic thought:", thought );
  •  
  • // Check to see if it is sleepy time. If so, then
  • // we don't want this erotic thought to be
  • // propagated - Ben needs his sleep and can't spend
  • // the next few hours fantasizing about pub/sub.
  • if (this.isSleepyTime){
  •  
  • // Log that we are stopping propagation.
  • console.log( "Sleepy time - erotic thought being killed...." );
  •  
  • // Return false to kill the event.
  • return( false );
  •  
  • }
  • }

To see this experiment in high-fidelity, check out the video. I tried to have fun with it, animating a flurry of neural-net activity.

I'm sure there are ways to accomplish this same kind of functionality without event propagation logic; however, I don't want to offer any at this time. Rather, I'd love to get feedback from the people who are opponents of event propagation logic. Mostly, I just wanted to present a specific, "real-worldish" situation that could act as a platform for more in-depth discussion.




Reader Comments

LoL, you had a lot of fun with this. I am going to have to rip off your neural network, that is very cool.

This is just me thinking abstractly I am trying to get a feel for ways of using pub/sub.

Are you afraid this sort of logic, is going to lock your code into being more procedural? In my mind, event driven programming allows you to add things as you please. I can load and unload components as I wish, maybe add a spouses Conscious and have the two interact.

Now let's say, I get loaded first now I'm the first subscriber (the one that kills it for everyone). I meet a girl and now I add her conscious. We have something like this

  • SubscriptionQueue = ['my brain', 'my conscious', 'my lizard brain','her brain','her conscious','her lizard brain'];

Okay, now the following conversation occurs.
Her: It's time to go to bed
Me: I want to stay up late and read Ben Nadel's latest post.
Her: Who is Ben Nadel?
Me: One of the best technical bloggers for everything ColdFusion and jQuery!
Her: [ puzzled look, turning frustrated ]
Me: Okay, I'll go to bed.

The preceding scenario can't be accurately depicted in the current pub/sub plugin with propagation control.

She decides to go to bed, but that has no effect on me. I've already had lizard thoughts going through my brain. Here, I can't use simple event propagation control. I would just have to subscribe myself to events fired off by my spouse. For instance,
$.subscribe("spouse.alert",this,"sheWillWinGiveUp");

This function would then handle turning off other events that I would normally fire, like independence, self-confidence, and choice :). However, the propagation aspect of this pub/sub method didn't help me. If I knew somehow the correct order of every subscriber all the time, and could reorder appropriately like adding GirlFriend first then it would cover this problem. I feel like that would overcomplicate a simple idea, I would have to go with Occam's razor. This sort of event hierarchy seems simpler if it were in the objects creating the subscribers and not the subscribers themselves. Activated by some sort of refreshRelationshipStatus timer.

Reply to this Comment

@Drew,

Glad you enjoyed the post :) I think pub/sub just lends itself well to fun times.

The point I think you are making (as well as the points made by Rebecca and Hal) is fundamentally different than mine in one small way perhaps:

The aggregated systems are not truly independent.

Ultimately, they all exist for a single purpose even if that purpose is tremendously broad (ex. fulfilling all of the client's needs).

I think when we talk about evented systems, there is a desire to plan for complete freedom; but ultimately, systems are not completely free. They have to have an over-arching plan - a "reason to be" and that plan has be architected.

Now, I'll agree with you that having to know everyone that is subscribed to a given event and in what order they are subscribed is probably a task that is not easy to know or to maintain. It is brittle. But, at the same time, my mediator (in the example) doesn't necessarily care who is listening - it only cares about one rule: "Don't let this particular event through if we are trying to fall asleep." If some other system came and subscribed to that event, it would also feel the effects of this block.... whether or not that is bad depends on the new system that is being integrated.

Are you afraid this sort of logic, is going to lock your code into being more procedural?

It will make the code procedural, but only when the code needs to be procedural. Even in an evented system, I think there is still a need to have certain sets of tasks execute in a given order. Take Transactions for example. I might have a number of steps that all need to take place and if anyone of those steps fails, I need to roll back pending mutations. I would imagine that in a purely asynchronous model, the concept of transactions would be considerably more difficult, if not impossible?

Of course, please take all of this as half-statement / half-question. I find this topic as interesting as it is confusing :D I am already feeling like this conversation is making me think deeper about it than ever before.

Reply to this Comment

You hit the nail on the head! Your version of pub/sub isn't so much regular pub/sub as it really is pub/sub with transactions (almost). It is really just lacking a way to define a transaction, right now they need to know to load all these things in a predefined order.

That one word helped me understand the whole concept here. This indeed couple help bring order to the chaos of event systems. I'm thinking of a few real life examples still, I hope that is useful in application architecture.

Now the fun part, settling on an implementation of transactions.

Reply to this Comment

This is all much better handled by having a guardian object (just my own made-up term) check to see if *it* should announce an event. Then let events be broadcasted.

Page Controller: Well, I'll be! Someone just clicked the "Create New Customer" button. I should ask the Server Controller for a "new customer" form. Sure, I'll -- hey, wait a minute. How do I know if this user for this account has permission to create a new customer? I better send this over to Gary. Announce Event: NewCustomerCheck

Gary the Guardian. Hmmm...let's see: it looks like the user has permission. And the license lets them create more. OK. Announce: NewCustomer

CustomerPageController: Hey, that's me! Uh, let's see. I'm supposed to...ah, here it is. Let me proxy over to my friend, the CustomerServerController. He's got the forms over there in View.

$.get(
'CustomerServerController?method=new',
{ 'display_context: '#customer_action' },
function( response ) {
if ( response.success ) {
$( response.display_context ).html( response.display );
}
},
'json'
);

CustomerServerController: Yes? What is it? Huh? Oh, sure. Hey View -- need you to accept new customer info.

CustomerFormView: Customer Form at your service, Chief.

CustomerServerController: OK, ready for a little trip to client side?

return {
event : 'NewCustomerForm'
success : true
display : customer_form.cfm
display_context : arguments.display_context
}

CustomerPageController: Man, I wonder what's keeping CustomerServerController. I hope he's not to busy to respon -- wait, here it comes. Ah, success! Now, I'll just place the response.display into the response.display_context.

//

What you're suggesting reminds me of what Niels Bohr said, when shown a paper by a grad student: "This isn't right. This isn't even wrong!" It's fraught with problems, brittleness among them.

Resist the Dark Side, Ben...

Reply to this Comment

@Hal,

I've been playing around with a pattern very much like this in my recent "composed controller" experiments. In those situations, a composed controller doesn't necessarily ask whether or not it should do something, it simply asks the "master" controller to do something:

Composed: Someone clicked me - master, please handle this click.

Master: [internal actions then] Composed, please do XYZ.

Composed: Done!

An example of that would be something like this post:

http://www.bennadel.com/blog/2035-Using-Composed-Javascript-UI-Controllers-With-DOM-Augmentation.htm

I enjoyed that kind of pattern but it definitely couples the master controller to the composed controller; however, since the master controller is set via constructor-injection, at least the coupling is easily altered.

"This isn't right. This isn't even wrong!"

... ha ha ha - that's how my head feels :)

I have to run out, but I'll keep thinking on this. Thanks for all the awesome feedback.

Reply to this Comment

It seems to me Ben that you have added the ability to halt propagation of an event in the publish/subscribe model as a means of emulating a Front Controller pattern. My question is why? Why not just have the request/thought come to the Front Controller, and should the conditions be right allow *it* to publish the event, allowing any subscribers to do what they will with it?

Maybe I'm missing something. It's possible, since I too was up till 1:00am working on some low-level C++ hardware stuff. My brain hurts.

Reply to this Comment

@Adam,

Why: just trying to wrap my head around pub/sub :D I don't use a pub/sub model on the server (which is where I typically use the front-controller approach). I am definitely open to hearing more of your thoughts on this.

How would you get one module to announce an event to the front-controller without the other modules hearing it?

Reply to this Comment

You could implement something similar to channels that most messaging systems (ActiveMQ, RabbitMQ) use. This is where messages are segmented by channels, so only subscribers to the given channel can listen for a specified message.

This would allow you to have a privileged channel for front-controllers, and a non-privileged channel for everybody else, or whatever other scheme your brain comes up with.

I've done a fair bit of publish/subscribe, but not done the channel thing myself, though I've dabbled in it with message queue systems.

Reply to this Comment

@Adam,

That's an interesting idea. My concern with that, however, is that it still uses the same approach as event propagation, albeit by different means. By that I mean that having a front-controller choosing not to echo events on a different channel is not all that different (in a fuzzy sense) from having a front-controller canceling an event's immediate propagation. In the end, the communication of events is still left up to the front-controller.

As such, I wonder if the others here would have the same reservations about a multi-channel approach as they would about a propagation-logic approach?

If this approach is agreed upon as good then, it's the event propagation logic that people have issue with, not the final outcome.

@All,

As I am working on actual client work, I am thinking of all sort of places where events could be announced in one place and used to augment other modules on the page. I know there is some real power here; I am just trying to wrap my head around how to best implement it.

Reply to this Comment

Oh I agree Ben. It's practically the same thing. At this point it boils down to how close you want to stay to the original pattern and preference. In the end there is still some "thing" determining if a message should be sent/continue to subscribers.

In my opinion it is not unreasonable to allow some entity to alter or even stop a message. If you need such a thing though it almost seems as if you are simply doing a filter/pipeline type of pattern, where data passes along a chain, and can be altered/stopped at any point during the chain.

Reply to this Comment

Every time I keep thinking about how awesome pub/sub would be on a project, I inevitably run itno a situation (several times today), where I have two seemly unrelated UI elements that affect one another.

For example, I have a nav bar in an application we are building internally. And now today, we added an "intro" display the first time someone enters a particular section of this app. We want the logic to be such that someone has to "dismiss" the intro message before they can use the navigation.

Up until now, we've never had another UI element that was "blocking" the activity of other elements. But now, we do. What I am trying to wrap my head around is an architecture where that nav bar click (ie. moving to another nav item) could easily be stopped due to the state of the containing context.

I think if this *one* scenario could be ironed out in my mind, this would all start to make a lot more sense. Any thoughts from anyone?

Reply to this Comment

Wow, just came back across this post from a few months ago:

http://www.bennadel.com/blog/1873-FLEX-On-jQuery-Decouple-Components-With-Event-Listeners.htm

It basically uses the same kind of canceling-event logic. This is horrifying - it's like watching a childhood movie of myself struggling to figure something out.

Based on the sheer number of times I have attacked this problem, clearly I just have some massive mental block around this one concept.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
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.