XUL Event Propagation

This article describes the event model in XUL and features event propagation as a way to handle events in different places in the interface.

Introduction

XUL events were introduced in a very general way in a previous XULNote. But to use events effectively in XUL, you must be aware of what the actual process for raising, listening to, and handling events is. Collectively, this process is referred to as the event model. Events are used for different purposes, but they play a particularly important role in creating interactive XUL-based user interfaces, since it is events that bring the information about user actions to the code behind the interface.

The following image describes in a very basic way how the various actors in the event model interact with one another.

The Event Model

The user clicks a button in the interface. This button responds to the user action by raising an event, a message meant to travel from one element within the interface to another. In this case, the event raised was the "click" event, but a variety of events can be raised by the button element. When the mouse moves over the button, for example, the "hover" event is raised. And sometimes elements raise different events for the pressing down of the click and the release. The events raised by a particular element are pre-determined.

The click event is just a message. It's passive in that it doesn't look for a listener; it propagates or "bubbles up" the hierarchy of nodes within the interface until it is either handled in some way or passes out of the interface unhandled. Any element that is interested in the event -- any part of the interface, in other words, that needs to know about and respond to the user's click action on the button -- "handles" the event with an event handler, or chunk of code to be executed when the given event is "heard". The availability of event listeners is also somewhat pre-determined, though XUL provide generalized event listeners (i.e., oncommand event listeners) for most of the elements in the widget hierarchy. Event handlers are written by you, the developer of the interface.

Very often, the event raised by the element is heard by the element itself and handled right there before it bubbles up any higher. This is the case in the example code in the diagram, where the onclick event listener for the button element handles the click event by displaying a simple alert dialog:

<button value="Click Me" onclick="alert('Thank you')" />

The opposite of event bubbling is event capturing. Where event bubbling propagates an event from its target up higher into the node hiearchy, event capturing intercepts an event before it is received by other elements, even by the event target itself.

The combination of basic these two basic event flow mechanisms, event bubbling and event capturing, mean that events raised in the interface can be caught anywhere above the element that raised the event. This flexibility in the XUL event model is the focus of this article.

The Widget Hierarchy

Consider the following XUL file:

<?xml version="1.0"?>

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>

<window id="events"
  xmlns:html="http://www.w3.org/1999/xhtml"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  oncommand="alert('Window handler')">

<vbox>
<vbox style="background-color: lightgrey;" oncommand="alert('Box handler')">

  <menu class="menu" label="File" oncommand="alert('Menu handler')">
    <menupopup>
        <menuitem oncommand="alert('New item alert')" label="New" />
        <menuitem label="Open" />
        <menuitem oncommand="alert('Close handler')" label="Close" />
    </menupopup>
  </menu>

  <menu class="menu" label="Edit">
    <menupopup>
        <menuitem oncommand="alert('Edit Source handler')" label="Edit Source" />
        <menuitem label="Reload" />
        <menuitem label="View Source" />
     </menupopup>
   </menu>

</vbox>
<spring flex="1" />
</vbox>
</window>

In this file, the lowest-down, or "leaf" elements are the menuitems. The hierarchy of the interface can be represented as follows:

The Event Structure of the Sample XUL

When one of these menuitems raises an event, any of the elements above it in the hierarchy can handle it. In the example above, when the menuitem raises the "command" event, indicating that it has been selected, the menupopup, the menu itself, the parent box, or the root window element itself can all take advantage of this.

The Event Path of an Event Raised in a Menuitem in the Sample XUL

Event Bubbling

This availability of events in places other than their element of origin is known as "event propagation" or event bubbling. Event bubbling means you can handle events anywhere above the event-raising element in the hierarchy. If you cut and paste the source code from the example above into a separate file, you can see that the effect of these event handlers is to display as many as four different alert dialogs as the event bubbles up the hierarchy. For example, when you click the New menuitem from the File menu, the event handlers display alerts for "New item alert", "Menu handler", "Box handler", and "Window handler." Any or all of these event handler locations might be useful in your interface development. (See it happen.)

For an event to be useful somewhere higher up, however, it is very often necessary to determine which element below the handler actually raised the event. For example, if an event handler at the menu is handling an event raised by one of the menu items, then the menu should be able to identify the raising element and take the appropriate action, as in the following example, where a JavaScript function determines which menuitem was selected and responds appropriately:

<script>
function doCMD(el) {
  v = el.getAttribute("value");
  if (v == "New") alert("New clicked");
  else if (v == "Open") alert("Open clicked");
  else alert("Close clicked");
}
</script>
...
  <menu class="menu" value="File" oncommand="doCMD(event.target)">
    <menupopup>
      <menuitem oncommand="alert('New item alert')" value="New" />
      <menuitem value="Open" />
      <menuitem oncommand="alert('Close handler')" value="Close" />
    </menupopup>
  </menu>
...

The event handler in the menu finds out which menuitem was actually clicked and takes different actions accordingly.

Event Capturing

Event capturing is the complement of event bubbling. When you register an event listener on an ancestor of the event target (i.e. any node above the event-raising element in the node hierarchy), you can use event capturing to handle the event in the ancestor before it is heard in the target itself or any of the intervening nodes.

Capturing an Event

In the diagram above, the alert dialog invoked by the menuitem itself is not displayed, since the root window element has used event capture to handle the event itself. In another example, an onload event handler for a XUL window can register a box element to capture all the click events that are raised from its child elements:

var bbox = document.getElementById("bigbox");
if (bbox)
  bbox.addEventListener("click", function() { alert('captured'); }, true);

...

<box id="bigbox">
  <menu value="File">
    <menupopup>
      <menuitem value="New" onclick="alert('not captured')" />
      ...
    <menupopup>
  </menu>
</box>

Adding an Event Listener

In order to take advantage of event capturing (or event bubbling with elements that do not already have event listeners), you must add an event listener to the element that wants to capture events taking place below it. Any XUL element may use the DOM addEventListener method to register itself to capture events. The syntax for this in XUL is as follows:

element = document.getElementById("id of the element");
element.addEventListener(event name, event handler function, whether to register a capturing listener);

The event handler code argument can be in-line code or a function. The last boolean parameter specifies whether the event listener wants to use event capturing, or be registered to listen for events that bubble up the hiearchy normally.

Note: The DOM provides the addEventListener method for creating event listeners on nodes that do not otherwise supply them. But in XUL, almost every element includes an "oncommand" event listener attribute for getting events. addEventListener is the way to register event listeners for events other than the generalized "command" event, and the way to register a particular element for event capture, which preempts the regular flow of events.

Original author: Ian Oeschger
Other Documents: W3C DOM Events, Mozilla XUL Events

Original Document Information