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.