Publish/Subscribe Communication

While there are plenty of times when the direct "chained" style of communication provided by dojo.connect is exactly what you'll need to solve a problem, there are also a lot of times when you'll want a much more indirect "broadcast" style of communication in which various widgets communicate anonymously. For these circumstances, you might instead use dojo.publish and dojo.subscribe.

A classic example is a JavaScript object that needs to communicate with other objects in a one-to-many type relationship. Instead of setting up and managing multiple dojo.connect connections for what seems like one cohesive action, it's considerably simpler to have one widget publish a notification that an event has transpired (optionally passing along data with it) and other widgets can subscribe to this notification and automatically take action accordingly. The beauty of the approach is that the object performing the broadcast doesn't need to know anything whatsoever about the other objects—or even if they exist, for that matter. Another classic example for this kind of communication involves portlets—pluggable interface components (http://en.wikipedia.org/wiki/Portlet) that are managed within a web portal, kind of like a dashboard.

Tip

The OpenAjax Hub (http://www.openajax.org/OpenAjax%20Hub.html), which you'll read more about in Chapter 4, calls for publish/subscribe communication to be used as the vehicle for effectively employing multiple JavaScript libraries in the same page.

In many situations, you can achieve exactly the same functionality with pub/sub style communication as you could by establishing connections, so the decision to use pub/sub may often boil down to pragmatism, the specific problem being solved, and overall convenience of one approach over another.

As a starting point for determining which style of communication to use, consider the following issues:

  • Do you want to (and can you reliably) expose an API for a widget you're developing? If not, you should strongly prefer pub/sub communication so that you can transparently change the underlying design without constantly wrangling the API.

  • Does your design contain multiple widgets of the same type that are all going to be responding to the same kind of event? If so, you should strongly prefer connections because you'd have to write additional logic to disambiguate which widgets should respond to which notifications.

  • Are you designing a widget that contains child widgets in a "has-a" relationship? If so, you should prefer setting up and maintaining connections.

  • Does your design involve one-to-many or many-to-many relationships? If so, you should strongly prefer pub/sub communication to minimize the overall burden of communication.

  • Does your communication need to be completely anonymous and require the loosest coupling possible? If so, you should use pub/sub communication.

Without further delay, here's the pub/sub API. Note that in the case of dojo.subscribe, you may omit the context parameter and the function will internally normalize the arguments on your behalf (just as was the case with dojo.connect ):

dojo.publish(/*String*/topic, /*Array*/args)
dojo.subscribe(/*String*/topic, /*Object|null*/context,
   /*String|Function*/method) //Returns a Handle
dojo.unsubscribe(/*Handle*/handle)

Tip

Just as the handle that is returned from dojo.connect should be considered opaque, the same applies here for dojo.subscribe.

Let's get to work with a simple example involving dojo.subscribe and dojo.publish :

function Foo(topic) {

  this.topic = topic;

  this.greet = function(  ) {
    console.log("Hi, I'm Foo");

    /* Foo directly publishes information, but not to a specific destination... */
    dojo.publish(this.topic);
  }

}

function Bar(topic) {

  this.topic = topic;

  this.greet = function(  ) {
    console.log("Hi, I'm Bar");
  }

  / * Bar directly subscribes to information, but not from a specific source */
  dojo.subscribe(this.topic, this, "greet");

}

var foo = new Foo("/dtdg/salutation");
var bar = new Bar("/dtdg/salutation");

foo.greet(  ); //Hi, I'm Foo...Hi, I'm Bar

Tip

Although there is no formal standard, the toolkit uses the convention of prefixing and using a forward slash to separate the components of topic names. An advantage of this approach is that the forward slash is uncommon enough in JavaScript code that it is fairly easy to spot (whereas using a dot to separate topic names in source code would be a lot more difficult).

As you can see, whereas connect involves a connection from a specific source to a specific destination, publish/subscribe involves a broadcast that could be sent from any source and could be received by any destination that cares to respond to it in some way. Some amazing power comes built-in with a very loosely coupled architecture because with minimal effort and great simplicity comes the ability to have what amounts to an application that is conceptually a collection of coherent plug-ins.

Let's illustrate how to unsubscribe with an interesting variation on Bar 's implementation. Let's have Bar respond to the topic that Foo publishes only a single time:

function Bar(topic) {

    this.topic = topic;

    this.greet = function(  ) {
        console.log("Hi, I'm bar");
        dojo.unsubscribe(this.handle);

        //yackety yack, don't talk back
    }

    this.handle = dojo.subscribe(this.topic, this, "greet");
}

Note that you can also send along an array of arguments by providing an additional second argument to publish that is an Array of values, which gets passed to the subscribe handler as named parameters.

Warning

It's a common mistake to forget that the arguments passed from dojo.publish must be contained in an Array and that dojo.subscribe 's handler receives these arguments as individual parameters.

For a final rendition of our example, let's say you are not able to reliably change Foo 's greet method to include a dojo.publish call because an external constraint exists that prohibits it; perhaps it is code that you do not own or should not be mucking with, for example. Not to worry—we'll use another function, dojo.connectPublisher, to take care of the publishing for us each time a particular event occurs:

function Foo(  ) {
  this.greet = function(  ) {
    console.log("Hi, I'm foo");
  }
}

function Bar(  ) {
  this.greet = function(  ) {
    console.log("Hi, I'm bar");
  }

}

var foo = new Foo;
var bar = new Bar;

var topic = "/dtdg/salutation";
dojo.subscribe(topic, bar, "greet");
dojo.connectPublisher(topic, foo, "greet");

foo.greet(  );

Tip

In case you're interested, behind-the-scenes connectPublisher is basically using dojo.connect to create a connection between a dojo.publish call each time a particular function is called.

In this final example, the primary takeaway is that the dojo.connectPublisher call allowed us to achieve the same result as adding a dojo.publish call to its greet method, but without mangling its source code to achieve that result. In this regard, foo is an indirect sender of the notification and is not even aware that any communication is going on at all. Bar, on the other hand, as a subscriber of the notification, did require explicit knowledge of the communications scheme. This is essentially the opposite of a typical dojo.connect call in which the object that provides the context for a connection has explicit knowledge about some other object or function that provides the "target" of the connection.

Get Dojo: The Definitive Guide now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.