text: What is event-driven design?
  Event-driven design is a programming paradigm in which the flow of the program is determined by events—rather than a series of functions—executing in a predetermined or linear flow. Instead of the execution cycle of one function finishing and then calling another, or that function registering to a cycle and waiting for a callback to be fired, the module "listens" for the completion of the cycle; this is signaled by an event.
  Advantages
  Web pages are essentially user interfaces triggered by mouse actions or keyboard inputs. Because event-driven design is already built into the browser, it's natural to extend it to web applications. The JavaScript language uses event listeners to detect user actions and resource loading. You can use this same pattern to listen to custom events or actions within your code. The advantage of this design is that JavaScript modules (or classes) can be more effectively self-contained as black boxes; these are loosely coupled objects that can be more easily added, removed, replaced, and maintained with little or no disruption to the overall program code.
  Disadvantages
  Cross-browser issues make it difficult to implement an event-listening
  system with JavaScript code. Microsoft® Windows®
  Internet Explorer uses a nonstandard and improperly implemented event
  listener that in most cases can't be disconnected, doesn't pass the event
  as an argument, and doesn't provide a clear way to block the events. While
  all other browsers use the standardized
  addListener system, the events generated tend to be different
  not only from browser to browser, but also within the same browser on
  different operating systems.
  What is loose coupling?
  Coupling refers to the degree to which one class knows about another. This is not to say that the classes can't know of each other's property or methods, but instead addresses their knowledge and reliance upon the class itself. When you have two classes that are strongly coupled, you can't make changes to one without upsetting the implementation of the other.
  An example of strong coupling might be that
  Class
  A loads some data, then calls
  dataLoaded() on
  Class B.
  Class B then gets the
  Foo property from
  Class A. This
  system can easily break if
  Class B is removed,
  the
  Foo property is renamed, or the function
  signature of
  dataLoaded is changed.
  An example of loose coupling might be that
  Class
  A loads data and then fires the
  onDataLoaded event.
  Class B uses an
  event listener on
  onDataLoaded, and, when it
  fires, reads the event object to get the
  Foo
  property.
  Coupling is relative; that is to say, while the second example is
  considered loose,
  Class B still needs to know
  about
  Class A to connect to one of its events.
  Global event systems are used to achieve a higher level of loose coupling.
  What is dojo.connect?
  dojo.connect allows the source of events to be either a regular JavaScript function or a DOM event. It provides a uniform interface for listening to all the types of events that an application is likely to encounter though a single, unified interface. This function is valuable for developers who have tried to create code that connects, disconnects, and normalizes DOM events.
  In Listing 1, the code connects to the
  myButton
  click event. When the
  click event fires, the anonymous function is invoked.
  Listing 1. Simple dojo.connect example
  dojo.connect(dojo.byId("myButton"), "click", function(evt){ console.log("The button was clicked: ", evt.target.id); });
  Advantages
  All libraries have a function that connects to DOM events. What sets dojo.connect apart is that it also includes the ability to connect to custom events or routine functions, as shown in Listing 2.
  Listing 2. A dojo.connect to function example
  myObject = { method: function(){ // } }; dojo.connect("myObject", "method", function(evt){ console.log("myObject.method fired"); });
  Disadvantages
  Since dojo.connect was intended to handle everything, sometimes baffling errors that are difficult to track sneak in. In addition, the code to make both multiple connections and a connect once function can get touchy. Finally, by nature of the fact that you have to connect to a specific object to listen to it, you have to be aware of that object in the first place. This can make for tricky code, which developers want to avoid.
  Problems that dojo.connect solves
  As shown in Listing 3, it is not difficult to write your own function callbacks.
  Listing 3. Custom callback example
  myObject = { method: function(data, callback){ // do something asynchronous here... setTimeout(function(){ var value = doSomething(data); callback(value); }); } }; myObject.method(42, function(result){ console.log("the result: ", result); })
  But callbacks can be limiting. A second call might step on the first. You might find you have several areas in your application that need a callback, each of which needs new code. This requires much repetition, which is hardly efficient.
  Example project
  This section provides a simple yet tightly coupled application to demonstrate an event-driven design, and then shows how to decouple the classes with dojo.connect. Only the relevant parts of the code are shown within the article. For the complete code, see the Download section.
  The sample application has two drop-down selects. The first contains American states, and the second lists a few cities from the selected state. The selection of one state from the first list alters the list of city options in the second drop-down, as shown in Figure 1.
  Figure 1. Sample application with state and city drop-down selects
  Listing 4 shows the base Hypertext Markup Language
  (HTML) code that is used for all the examples. It loads the necessary Dojo
  files for a
  dijit.form.Select, including the
  new Claro style sheet. Two custom widgets are instantiated in markup.
  Listing 4. Base HTML code for examples
  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 5//EN" ""> <html lang="en"> <head> <title>Tightly Coupled Example</title> <link href="" rel="stylesheet" /> <link href="" rel="stylesheet" /> <script src=""></script> <script> dojo.require("dijit.form.Select"); </script> <script> dojo.ready(function(){ // code will go here dojo.parser.parse(); }); </script> </head> <body class="claro"> <h1>Tightly Coupled Example</h1> <div dojoType="StateSelect" id="stateSelect"></div> <div dojoType="CitySelect" id="citySelect"></div> </body> </html>
  Tightly coupled application
  The code replaces the comment
  code will go here
  and is placed inside a
  dojo.ready that fires
  when all the code is loaded from the CDN. When the DOM is ready to access,
  it is completed with
  dojo.parser.parse(), which
  instantiates the markup. Two custom widgets extend
  dijit.form.Select:
  StateSelect is
  used in the first example and does all the heavy lifting, and
  CitySelect is used in the second example but
  doesn't do anything special yet. Refer to Listing 5.
  Listing 5. Tightly coupled code
  dojo.declare("StateSelect", [dijit.form.Select], { postCreate: function(){ this.inherited(arguments); dojo.xhrGet({ url:"geography.json", handleAs:"json", load: dojo.hitch(this, function(data){ this.data = data; this.addOption(this.data.items); }) }); }, onChange: function(value){ for(var i = 0; i < this.data.items.length; i++){ if(this.data.items[i].value == value){ var menu = dijit.byId("citySelect").getOptions(); dijit.byId("citySelect").removeOption(menu); var newMenu = this.data.items[i].children; dijit.byId("citySelect").addOption(newMenu); } } } }); dojo.declare("CitySelect", [dijit.form.Select], { });
  This example is tightly coupled. When
  StateSelect is created, the Dijit system fires
  postCreate. At that point, the geography.json
  file is loaded with
  dojo.xhrGet, and the
  drop-down menu is created within that callback mechanism (
  load).
  The Dijit system then fires
  onChange when a new
  menu item is selected from the drop-down, passing the value of that
  option. The code loops through the data until the corresponding data
  item—which contains the child items for
  CitySelect—is found. The
  CitySelect is found using
  dijit.byId; its current menu is removed and replaced by a new
  one.
  What makes this code tightly coupled? A desirable approach to creating
  maintainable code is to reduce its side effects; you can estimate the
  number of side effects by counting the number of methods and comparing
  that to the number of actions taken. This example demonstrates two methods
  (
  postCreate and
  onChange) and four actions:
  - Initialize widget (
  postCreate)
  - Handle selection (
  onChange)
  - Load data
  - Handle data load
  Another reason why this code is tightly coupled is that
  StateSelect takes liberties with
  CitySelect's methods. Though these methods are all public and
  intended to be accessed externally, this access can be removed or reduced
  to achieve loose coupling. The goal is to create a black
  box—an object that is self-sufficient. Semantics also
  make code more maintainable: loading data happens in a
  loadData method, and handling the loaded data happens in an
  onDataLoaded method. This application is
  quite small. If the data were 100 times larger and used this style,
  finding where the data is loaded and parsed would be quite difficult. And
  if you were to encounter a problem with
  CitySelect, you might be surprised to find that there is no
  code—at least not in this child class.
  Loosely coupled application
  Listing 6 shows the reworked application that uses the proper semantic methods and results in fewer side effects.
  Listing); }, onChange: function(value){ // stub to connect to } }); dojo.declare("CitySelect", [dijit.form.Select], { postCreate: function(){ this.inherited(arguments); this.stateSelect = dijit.byId("stateSelect"); dojo.connect(this.stateSelect, "onChange", this, "onStateChange"); dojo.connect(this.stateSelect, "onDataLoaded",); } });
  By separating the functionality, the
  CitySelect
  now has methods it can attach to, using dojo.connect.
  CitySelect can now take care of itself and change its menu
  options when the
  onStateChange event is fired.
  CitySelect now maintains its own reference
  to the data as well.
  If your initial reaction to the application rewrite was surprise at how much code is included, remember this: in a small application, the difference in size is obvious, but as the application grows, you'll find much less code repetition. This means less code. And even if the actual size of the code is slightly greater, the tradeoff for better maintenance is worth the cost.
  Extending dojo.connect
  The reason that you can't just pass a node ID as the first argument with dojo.connect is that you can pass as few as two arguments to dojo.connect as strings representing two global functions, as shown in Listing 7.
  Listing 7. Connecting two global functions
  foo = function(){} bar = function(){ console.log("bar"); } dojo.connect("foo","bar"); foo(); // outouts "bar"
  With this functionality, you can't assume that if the first argument is a string, then it is a DOM ID simply because the traditional assumption is that of a function name. But the beauty of the JavaScript language is that you are not locked in to any of Dojo's default functionality.
  Accept a DOM ID
  Listing 8 shows how simple it is to overwrite
  dojo.connect by assigning it to another name, adding your own
  conditionals, and then passing all the arguments to the newly named
  dojo._extconnect.
  Listing 8. Adding ID capability
  dojo._extconnect = dojo.connect; dojo.connect = function(source, event, object, method, once){ source = typeof(source)=="string" ? dojo.byId(source) : source; if(!source) throw new Error("Bad source passed to dojo.connect:", source); dojo._extconnect.apply(dojo, arguments); } myObject = { onMouseAction: function(evt){ console.log("mouse action:", evt.type); } }; dojo.ready(function(){ dojo.connect("btn", "click", myObject, "onMouseAction"); }); // HTML: <button id="btn">Mouse Actions</button>
  Now you can pass
  "btn" instead of
  dojo.byId("btn"). Notice that I added a line to
  check for a
  source so that it gives an error
  message if it's not found. In my opinion, this can be much more useful
  than having Dojo assume that "null equals window."
  Of course, adding this line is optional. This extension and the subsequent extensions inhibit some of the default functionality. In this case, you can't pass two strings as global functions. I personally find that all these extensions outweigh the seldom-used default behaviors.
  Multiple connects
  You might have come across code that uses some form of connecting to DOM events repeatedly, as shown in Listing 9.
  Listing 9. Gratuitous use of connect
  dojo.connect(dojo.byId("btn"), "click", myObject, "onMouseAction"); dojo.connect(dojo.byId("btn"), "mouseover", myObject, "onMouseAction"); dojo.connect(dojo.byId("btn"), "mousedown", myObject, "onMouseAction"); dojo.connect(dojo.byId("btn"), "mouseout", myObject, "onMouseAction");
  Since
  connect is already modified, you can
  modify it further to reduce this type of repetitive pattern. Test whether
  the second argument is a string, as usual, or an array to connect the
  source to multiple times, as shown in Listing 10.
  Listing 10. Multiple connect code
  dojo._extconnect = dojo.connect; dojo.connect = function(source, event, object, method, once){ source = typeof(source)=="string" ? dojo.byId(source) : source; if(!source) throw new Error("Bad source passed to dojo.connect:", source); if(dojo.isArray(event)){ dojo.forEach(event, function(e){ dojo.connect(source, e, object, method, once); }); }else{ dojo._extconnect.apply(dojo, arguments); } } myObject = { onMouseAction: function(evt){ console.log("mouse action:", evt.type); } }; dojo.ready(function(){ dojo.connect("btn", ["click", "mousedown", "mouseover"], myObject, "onMouseAction"); });
  At this point, all the events in the second argument have been passed to
  the method. This is convenient if you handle a lot of connections. (Notice
  that this example doesn't allow for the ability to disconnect the events.
  You can do that by extending
  dojo.disconnect
  and checking the type of argument.)
  Connect once
  One popular custom extension is to connect to an event and then disconnect it after it fires once. This is useful for custom events that double fire or fire multiple times over the life of the application. The connect once code is shown in Listing 11.
  Listing 11. Connect once code
  dojo._extconnect = dojo.connect; dojo.connect = function(source, event, object, method, once){ source = typeof(source)=="string" ? dojo.byId(source) : source; if(!source) throw new Error("Bad source passed to dojo.connect:", source); if(once){ var callback = dojo.hitch(object, method); var handle = dojo._extconnect(source, event, function(){ callback.apply(object, arguments); dojo.disconnect(handle); }); return handle; }else{ return dojo._extconnect.apply(dojo, arguments); } } myObject = { onMouseAction: function(evt){ console.log("mouse action:", evt.type); } }; dojo.ready(function(){ dojo.connect("btn", "click", myObject, "onMouseAction", true); });
  Because
  true is passed as a fifth argument, the
  connected method only fires once. Clicking the button a second time has no
  effect. Notice that the use of
  once as the
  fifth argument overwrites the default functionality of
  don't fix, a functionality that I've never used.
  You can wrap these extensions into one master function and add other functionality, such as throttling the firing of events, that can be used to connect to window resize.
  Dojo pub/sub
  The Dojo pub/sub system follows the computer science pub/sub pattern, which is a simple global collection of callbacks kept in a registry of topics. A simple example of pub/sub code is shown Listing 12.
  Listing 12. Simple pub/sub code
  dojo.subscribe("data/event/load", function(data){ console.log("data loaded: ", data) }); dojo.publish("data/event/load", [{answer:42}]);
  Because the collections are global in the Dojo namespace, you can write code that is very loosely coupled. On the other hand, loosely coupled code can be a double-edged sword; if the use of pub/sub is abused, your application can unravel and become difficult to maintain. Remember that loose coupling does not mean lack of structure. In addition, be aware that topics might begin to fire out of order if you have a lot of pub/subs that depend on each other. And if a topic fires or "pre-fires" before you subscribe, you've missed it.
  Extending Dojo pub/sub
  You can fix pre-firing topics by extending Dojo's topic system to handle a
  backlog for missed topics; this means that
  a publish can be fired first, and a subsequent subscribe will still
  receive the topic when it's registered, as shown in Listing 13.
  Listing 13. Code for extending Dojo pub/sub
  (function(){ var topics = {}; var topicsHandles = {}; var backlog = {}; var uid = 0; dojo.subscribe = function(topic, object, method){ var callback = dojo.hitch(object, method); if(!topics[topic]) topics[topic] = {}; var handle = "topic_"+uid++; topics[topic][handle] = callback; topicsHandles[handle] = topic; if(backlog[topic] !== undefined) callback(backlog[topic]); return handle; } dojo.unsubscribe = function(handle){ delete topics[topicsHandles[handle]][handle]; } dojo.publish = function(topic, data){ backlog[topic] = data; if(topics[topic]){ for(var nm in topics[topic]){ topics[topic][nm](data); } } }; })(); // firing our publish with nothing subscribed yet: dojo.publish("data/event/load", {answer:42}); // act of subscribing will fire topic: var handle = dojo.subscribe("data/event/load", function(data){ console.log("data loaded: ", data.answer); // fires right away! }); // test that a subsequent topic still fires: dojo.publish("data/event/load", {answer:24}); // unsubscribe from the topic: dojo.unsubscribe(handle); // should not fire: dojo.publish("data/event/load", {answer:99});
  Never miss a topic again! Don't be intimidated by the amount of code; it includes the ability to unsubscribe (which explains the use of all the hash maps to track the topics and handles). This is not an extension, but a complete rewrite over the existing methods because Dojo's pub/sub system is so small.
  More loosely coupled application
  Now you can revisit the application and make it more loosely coupled. To visualize the potential problems with the current structure, change the order of the widgets and watch how out-of-order loading breaks it, as shown in Listing 14.
  Listing 14. Out-of-order widgets
  <div dojoType="CitySelect" id="citySelect"></div> <div dojoType="StateSelect" id="stateSelect"></div>
  Listing 14 shows a situation where the Dojo parser creates
  CitySelect, followed by
  CitySelect's
  postCreate firing;
  this calls
  dijit.byId("stateSelect"), only to
  fail because
  StateSelect does not yet exist.
  The coupling is obviously still too tight if rearranging the widgets
  breaks the code.
  By replacing dojo.connect with pub/sub, you remove the need for the widgets to be aware of each other. And by implementing the backlog-extended pub/sub, you don't have to worry about the order in which things are created or when events begin to fire. Listing 15 shows the final, loosely coupled application code. The extended pub/sub is used but not shown for clarity. Again, see the Download section for all the code.
  Listing 15.); dojo.publish("data/event/loaded", data); }, onChange: function(value){ dojo.publish("state/change", value); } }); dojo.declare("CitySelect", [dijit.form.Select], { postCreate: function(){ this.inherited(arguments); dojo.subscribe("state/change", this, "onStateChange"); dojo.subscribe("data/event/loaded",); } }); // HTML - "backwards" widgets still work! <div dojoType="CitySelect" id="citySelect"></div> <div dojoType="StateSelect" id="stateSelect"></div>
  Conclusion
  JavaScript is already an event-based language, and this article demonstrated new ways to embrace event-based patterns in web applications to accompany it. Dojo provides powerful tools with dojo.connect and pub/sub, which you can modify to make your applications more efficient. You can use these tools to create code that is not only event-based, but also loosely coupled for maximum maintainability.
  Download
  Resources
  Learn
  - See the Wikipedia definition of event-driven programming.
  - Consider Wikipedia's briefing of loose coupling.
  - The dojo.connect documentation provides in-depth information about how dojo.connect works.
  - The Dojo documentation provides more information on JavaScript events and Dojo.
  - dojo.subscribe gives more information about Dojo documentation.
  - The developerWorks Web development zone specializes in articles covering various web-based solutions.
  Get products and technologies
  - The Dojo Toolkit: Visit the Dojo home page.
  Discuss
  - Create your developerWorks community profile today and set up a watch list.