Global JavaScript Events with jQuery

When building web applications with sophisticated client-side behavior (i.e., you have a lot of JavaScript) I work hard to define loosely-coupled JavaScript modules. Intentional developers strive for structured and modular server-side code, but because of JavaScript’s history we tend not to take the same care with it. We then repeat the sins of the past when we find ourselves with hundreds or thousands of lines of JavaScript that aren’t encapsulated and decoupled.

Key Point: Write loosely-coupled JavaScript modules within your application.

Of course, you do need some coupling between modules. To realize the interactions that must occur between JavaScript modules, consider using a global event technique. Modules publish information to the world when something interesting happens; other modules subscribe to information they need to react to.

Key Point: Publishers shouldn’t need to know anything about their subscribers.

Here are a few ways to set up a global publish/subscribe mechanism your JavaScript modules to use:

Roll Your Own

We don’t want to succumb to the “Not Invented Here” problem, but these few lines create a simple but useful window.Bus object that supports global events.

(<span class="kwrd">function</span>() {

    var subscriptions = null;

    window.Bus = {

        reset: <span class="kwrd">function</span>() {
             subscriptions = {};
        },

        subscribe: <span class="kwrd">function</span>(messageType, callback) {
            <span class="kwrd">if</span> (<span class="kwrd">typeof</span> subscriptions[messageType] === <span class="rem">'undefined') {</span>
                subscriptions[messageType] = [];
            }
            subscriptions[messageType].push(callback);
        },

        publish: <span class="kwrd">function</span>(messageType, args) {
            <span class="kwrd">if</span> (<span class="kwrd">typeof</span> subscriptions[messageType] === <span class="rem">'undefined') return;</span>
            var subscribers = subscriptions[messageType];
            <span class="kwrd">for</span> (var i=0; i&lt;subscribers.length; i++) {
                subscribers[i](args);
            }
        }

    };

    window.Bus.reset();

}());

// Subscriber usage:
window.Bus.subscribe(<span class="rem">'UserCreated', function(user) {</span>
    alert(<span class="rem">'User ' + user.Name + ' created!');</span>
});

// Publisher usage:
var newUser = { Name: <span class="str">"The Hulk"</span> };
window.Bus.publish(<span class="rem">'UserCreated', newUser);</span>

jQuery

One quick way to get there without adding any more dependencies than you probably already have is it just use features jQuery’s eventing system. Here’s code that demonstrates how to use jQuery as a global pub/sub framework. In this example, there are two modules, a publisher and a subscriber, each with a bit of HTML and a bit of JavaScript. The JavaScript comments explain the main points.

<span class="kwrd">&lt;</span><span class="html">html</span><span class="kwrd">&gt;</span>
<span class="kwrd">&lt;</span><span class="html">head</span><span class="kwrd">&gt;</span>
    <span class="kwrd">&lt;</span><span class="html">title</span><span class="kwrd">&gt;</span>Client-side Pub/Sub with jQuery<span class="kwrd">&lt;/</span><span class="html">title</span><span class="kwrd">&gt;</span>
    <span class="rem">&lt;!-- add reference to jQuery --&gt;</span>
<span class="kwrd">&lt;/</span><span class="html">head</span><span class="kwrd">&gt;</span>

<span class="kwrd">&lt;</span><span class="html">body</span><span class="kwrd">&gt;</span>

    <span class="kwrd">&lt;</span><span class="html">div</span><span class="kwrd">&gt;</span>
        <span class="kwrd">&lt;</span><span class="html">input</span> <span class="attr">id</span><span class="kwrd">='PublishButton'</span> <span class="attr">type</span><span class="kwrd">='button'</span> <span class="attr">value</span><span class="kwrd">='Publish'</span> <span class="kwrd">/&gt;</span>
    <span class="kwrd">&lt;/</span><span class="html">div</span><span class="kwrd">&gt;</span>

    <span class="kwrd">&lt;</span><span class="html">b</span><span class="kwrd">&gt;</span>Events:<span class="kwrd">&lt;/</span><span class="html">b</span><span class="kwrd">&gt;</span>
    <span class="kwrd">&lt;</span><span class="html">div</span> <span class="attr">id</span><span class="kwrd">='Subscriber1'</span><span class="kwrd">&gt;</span>
    <span class="kwrd">&lt;/</span><span class="html">div</span><span class="kwrd">&gt;</span>

<span class="kwrd">&lt;/</span><span class="html">body</span><span class="kwrd">&gt;</span>

<span class="kwrd">&lt;</span><span class="html">script</span> <span class="attr">type</span><span class="kwrd">='text/javascript'</span><span class="kwrd">&gt;</span>
<span class="rem">// This function block correlates with the publisher's scope.</span>
$(<span class="kwrd">function</span>() {

    <span class="rem">// Represents a secret value only the publisher knows about directly,</span>
    <span class="rem">// but will include in its event args.</span>
    <span class="kwrd">var</span> someNumber = 7;

    <span class="rem">// When the button is clicked we publish an event.</span>
    $(<span class="str">"#PublishButton"</span>).click(<span class="kwrd">function</span>() {
        <span class="kwrd">var</span> dt = <span class="kwrd">new</span> Date();
        <span class="rem">//</span>
        <span class="rem">// The next line is the important one; notice how the event args</span>
        <span class="rem">// are passed as an array.</span>
        <span class="rem">//</span>
        $.<span class="kwrd">event</span>.trigger(<span class="str">'CustomEventName'</span>, [someNumber, dt]);
        someNumber++;
    });

});

<span class="rem">// This function block correlates with the subscriber's scope.</span>
$(<span class="kwrd">function</span>() {
    <span class="rem">//</span>
    <span class="rem">// Notice how we're not calling bind() on the subscriber like a typical</span>
    <span class="rem">// jQuery event, but calling it on our own UI component.</span>
    <span class="rem">// Also notice how the event args, which were published as an array,</span>
    <span class="rem">// are flattened into individual parameters.</span>
    <span class="rem">//</span>
    $(<span class="str">"#Subscriber1"</span>).bind(<span class="str">'CustomEventName'</span>, <span class="kwrd">function</span>(<span class="kwrd">event</span>, number, date) {
        $(<span class="str">"&lt;div&gt;"</span>).text(<span class="str">"Event published: "</span> + number + <span class="str">" @ "</span> + date).appendTo($(<span class="kwrd">this</span>));
    });
});
<span class="kwrd">&lt;/</span><span class="html">script</span><span class="kwrd">&gt;</span>

<span class="kwrd">&lt;/</span><span class="html">html</span><span class="kwrd">&gt;</span>
<!--
.csharpcode, .csharpcode pre
{
	font-size: small;
	color: black;
	font-family: consolas, "Courier New", courier, monospace;
	background-color: #ffffff;
	/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt 
{
	background-color: #f4f4f4;
	width: 100%;
	margin: 0em;
}
.csharpcode .lnum { color: #606060; }
-->

Use a Global Event Library

The following JavaScript libraries are a few that provide a global pub/sub infrastructure:

This entry was posted in JavaScript. Bookmark the permalink.
  • Al

    What’s “intentional developers”?

    • pettys

      Good question!

      I’d say it’s one who applies “intentional living” to their software development. See here: http://en.wikipedia.org/wiki/Intentional_living and especially this sentence:

      “Intentional living is any lifestyle based on an individual or group’s conscious attempts to live according to their values and beliefs.”

      An intentional developer has certain beliefs about software code design and architecture, and makes an intentional effort to follow those beliefs. For example, I believe a modular design is beneficial over the long run, so instead of just diving into a coding problem by doing the first thing that comes to mind, and stop and think to intentionally write modular code.

      The phrase seemed useful in this article because I think most developers are “intentional” about designing their server-side code, but JavaScript has traditionally not received the same level of attention. As the industry is moving towards more JavaScript in enterprise solutions, some of us have a need to “shift gears” and treat our JavaScript with the same deliberateness of our server code.

      Does that answer your question?