Direct communication channels are constructed by explicitly chaining together functions and/or DOM events so that when one executes, another is automatically invoked afterward. For example, each time an object changes via a "setter" method, you may want to automatically trigger a change in the application's visual interface. Or, perhaps each time one object changes, you might want to automatically update a derived property on another object. The possibilities are endless.
The two primary methods involved in a direct communication
scheme are dojo.connect
and
dojo.disconnect
. In short, you use
dojo.connect
to chain together a
series of events. Each call to dojo.connect
returns a handle that you
should keep and explicitly pass to dojo.disconnect
whenever you are ready to
dispose of the connection. Conveniently, all handles are disconnected
automatically when the page unloads, but manual management of the
handles may be necessary for preventing memory leaks in long-running
applications that invoke a lot of connections that are used
temporarily. (This is particularly the case on IE.) Coming up is the
API that was introduced in Chapter 1.
Warning
Don't ever connect anything until after the page is loaded.
Trying to use dojo.connect
before
the page is loaded is a very common mistake and can cause you to
sink a lot of time into trying to debug something that isn't very
easy to track down the first time you run into it. You should always
set up your connections within the function that you pass into
dojo.addOnLoad
to stay
safe.
Setting up and tearing down connections is easy. Here's the basic API:
/* Set up a connection */ dojo.connect(/*Object|null*/ obj, /*String*/ event, /*Object|null*/ context, /*String|Function*/ method) // Returns a Handle /* Tear down a connection */ dojo.disconnect(/*Handle*/handle);
Tip
For all practical purposes, you should treat the handle that
is returned from a call to dojo.connect
as an opaque object that you
don't do anything with except pass to disconnect at a later time.
(In case you're wondering, it is nothing special—just a collection
of information that is used to manage the connection
internally.)
Let's take a look at an example that illustrates a kind of
problem that dojo.connect
would be
suitable for helping us to solve:
function Foo( ) { this.greet = function( ) { console.log("Hi, I'm Foo"); } } function Bar( ) { this.greet = function( ) { console.log("Hi, I'm Bar"); } } foo = new Foo; bar = new Bar; foo.greet( ); //bar should greet foo back without foo //ever having to know that bar exists.
As it turns out, we can solve this little conundrum with one line of code. Modify the previous listing like so, and test this out in Firebug:
function Foo( ) { this.greet = function( ) { console.log("Hi, I'm foo"); } } function Bar( ) { this.greet = function( ) { console.log("Hi, I'm bar"); } } foo = new Foo; bar = new Bar; //Anytime foo.greet fires, fire bar.greet afterward... var handle = dojo.connect(foo, "greet", bar, "greet"); //set up the connection foo.greet( ); //bar automatically greets back now!
The payout for writing that one line of code was pretty high,
don't you think? Notice that the second and fourth parameters to
dojo.connect
are string literals
for their respective contexts and that a handle is returned that can
later be used to tear down the connection. In general, you
always want to tear down the connection at some
point, whether it be to accomplish some kind of functional requirement
in your design, or when you're performing some final cleanup—such as
when an object is destroyed or the page is unloaded. Here's
how:
var handle = dojo.connect(foo, "greet", bar, "greet");
foo.greet( );
dojo.disconnect(handle);
foo.greet( ); //silent treatment this time
In addition to dojo.connect
accomplishing so much with so little effort, notice how clean and
maintainable the source code remains. No boilerplate, no spaghetti
code, no wiring up your own solution, no maintenance nightmare.
Firing methods off in response to happenings in the page is
really useful, but sooner or later you'll need to pass around some
arguments. As it turns out, one additional feature of connect
is that it automatically passes the
arguments from the first context's function to the second context's
function. Here's an example that shows how:
function Foo( ) { this.greet = function(greeting) { console.log("Hi, I'm Foo.", greeting); }; } function Bar( ) { this.greet = function(greeting) { console.log("Hi, I'm Bar.", greeting); }; } foo = new Foo; bar = new Bar; var handle= dojo.connect(foo, "greet", bar, "greet"); foo.greet("Nice to meet you");
As you might imagine, having the arguments get passed around automatically is quite handy, and this is especially the case when a function is connected to a DOM event such as a mouse click because it gives the function instant access to all of the important particulars of the event such as the target, the mouse coordinates, and so on. Let's investigate with yet another example:
//Note that the third argument is skipped altogether since the handler is a //standalone anonymous function. Using null to placehold the third parameter would //have produced the very same effect. dojo.connect( dojo.byId("foo"), //Some DOM element "onmouseover", function(evt) { console.log(evt); });
If you set up a sample page, wire up the connection, and watch the Firebug console, you'll see that the entire event object is available to the event-handling function, empowering you with just about everything you'd ever need to know about what just happened.
"But it's so easy to specify handlers for DOM events. Why would I even bother with learning another fancy library function?" you wonder. Yes, it may not take a brain surgeon to put together some simple event handlers, but what about when you have a complex application that may need to handle lots of sophisticated event handling based on user preferences, custom events, or some other event-driven behavior? Sure, you could handle all of this work manually, but would you be able to connect or disconnect in one line of code with a single consistent interface that's already been written and battle-tested?
Finally, note that while the examples only illustrated one event being chained to another one, there's no reason you couldn't wire up any arbitrary number of ordinary functions, object methods, and DOM events to fire in succession.
There may be times when you need to suppress the browser's
built-in handling of some DOM events and instead provide custom
handlers for these tasks yourself via dojo.connect
. Two fairly common cases that
occur are when you'd like to suppress the browser from automatically
navigating when a hyperlink is clicked and when you'd like to
prevent the browser from automatically submitting a form when the
Enter key is pressed or the Submit button is clicked.
Fortunately, stopping the browser from handling these DOM
events once your custom handlers have finished is as easy as using
dojo.stopEvent
or the DOMEvent
's preventDefault
method to prevent the event
from propagating to the browser. The stopEvent
function simply takes a DOMEvent
as a parameter:
dojo.stopEvent(/*DOMEvent*/evt)
Tip
While you can suppress DOM events that participate in a
series of dojo.connect
functions, there is no way to stop the dojo.connect
event chain from within an
ordinary function or JavaScript object method.
The following example illustrates stopEvent
at work:
var foo = dojo.byId("foo"); //some anchor element
dojo.connect(foo, "onclick", function(evt) {
console.log("anchor clicked");
dojo.stopEvent(evt); //suppress browser navigation and squash any event bubbling
});
Likewise, suppressing automatic submission of a form is just
as easy; simply swap out the context of the connection and associate
with the submit
event. This time,
though, we'll use the preventDefault
method of a DOMEvent
to suppress the event, while
allowing bubbling to continue:
var bar = dojo.byId("bar"); //some form element
dojo.connect(bar, "onsubmit", function(evt) {
console.log("form submitted");
evt.preventDefault( ); //suppress browser navigation but allow event bubbling
});
This section covers some semi-advanced content that you may want to skim over but not get bogged down with your first time through this chapter. Do come back to it though, because sooner or later you'll find yourself needing it.
Consider a situation in which you need to establish and soon thereafter tear down a connection that fires only a single time. The following example gets the job done with minimal effort:
varhandle
= dojo.connect( dojo.byId("foo"), //some div element "onmouseover", function(evt) { //some handler goes here... dojo.disconnect(handle
); } );
If you're still getting comfortable with closures, your
first reaction might be to object and claim that what we've just
done is not possible. After all, the variable handle
is returned from the call to
dojo.connect
, and yet it is
being referenced inside of a function that gets passed to dojo.connect
as a parameter. To better
understand the situation, consider the following analysis of
what's going on:
The
dojo.connect
function executes, and although an anonymous function is one of its parameters, the anonymous function has not yet been executed.Any variables inside of the anonymous function (such as
handle
) are bound to its scope chain, and although they might exist within the function, they aren't actually referenced until the function actually executes, so there's no possible error that could happen yet.The
dojo.connect
function returns thehandle
variable before the anonymous function ever can ever be executed, so when the anonymous function does execute, it is readily available and passed to thedojo.disconnect
call.
Another situation that frequently occurs during development
is that you need to set up connections in the body of a loop.
Suppose for now that you simply have a series of elements on the
page, foo0, foo1,...foo9
, and
you want to log a unique number when you move the mouse over each
of them. As a first attempt, you might end up with the following
code block that will not accomplish what you
would expect:
/* The following code does not work as expected! */ for (var i=0; i < 10; i++) { var foo = dojo.byId("foo"+i); var handle = dojo.connect(foo, "onmouseover", function(evt) { console.log(i); dojo.disconnect(handle); }); }
If you run the snippet of code in Firebug on a page with a
series of named elements, you'll quickly find that there's a
problem. Namely, the value 10
is always printed in the console, which means that the final value
of i
is being referenced across
the board and that the same connection is erroneously trying to be
torn down in each of the 10 handlers. Taking a moment to ponder
the situation, however, it suddenly occurs to you that the
behavior that is happening actually makes sense because the
closure provided by the anonymous function that is passed into
dojo.connect
doesn't resolve
i
until it is actually
executed—at which time it is in a final state.
The following modification fixes the problem by trapping the
value of i
in the scope chain
so that when it is referenced later it will actually resolve to
whatever value it held at the time the dojo.connect
statement executed:
for (var i=0; i < 10; i++) { (function( ) { var foo = dojo.byId("foo"+i); var current_i = i; //trap in closure var handle = dojo.connect(foo, "onmouseover", function(evt) { console.log(current_i); dojo.disconnect(handle); } ); })( ); // execute anonymous function immediately }
The block of code may seem a little bit convoluted at first,
but it's actually pretty simple. The entire body of the loop is an
anonymous function that is executed inline, and because the
anonymous function provides closure for everything that is in it,
the value of i
is "trapped" as
current_i
, which can be
resolved when the event handler executes. Likewise, the proper
handle
reference is also
resolved because it too exists within the closure provided by the
inline anonymous function.
If you've never seen closures in action like this before, you may want to take a few more moments to carefully study the code and make sure you fully understand it. You're probably tired of hearing it by now, but a firm grasp on closures will serve you well in your JavaScript pursuits.
It is worth noting that it is also possible to set up
connections for dijits without even the minimal JavaScript writing
required by using special dojo/connect
SCRIPT
tags that appear in markup. You can read more about
this topic in Chapter 11 when Dijit is
formally introduced.
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.