O'Reilly logo

JavaScript Cookbook by Shelley Powers

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

7.5. Preventing an Event from Propagating Through a Set of Nested Elements

Problem

You have one element nested in another. Both capture the click event. You want to prevent the click event from the inner element from bubbling up or propagating to the outer event.

Solution

Stop the event from propagating with a generic routine that can be used with the element and any event:

// stop event propagation
function  cancelPropagation (event) {
   if (event.stopPropagation) {
       event.stopPropagation();
   } else {
      event.cancelBubble = true;
   }
}

In the event handler for the inner element click event, call the function, passing in the event object:

cancelPropagation(event);

Discussion

If we don’t want to cancel an event, but do want to prevent it from propagating, we need to stop the event propagation process from occurring. For now, we have to use a cross-browser method, since IE8 doesn’t currently support DOM Level 2 event handling.

In the solution, the event is tested to see if it has a method called stopPropagation. If it does, this event is called. If not, then the event cancelBubble property is set to true. The first method works with Chrome, Safari, Firefox, and Opera, while the latter property works with IE.

To see this work, Example 7-2 shows a web page with two div elements, one nested in another and both assigned a click event handler function. When the inner div element is clicked, two messages pop up: one for the inner div element, one for the other. When a button in the page is pressed, propagation is turned off for the event. When you click the inner div again, only the message for the inner div element is displayed.

Example 7-2. Preventing an event from propagating among nested elements

<!DOCTYPE html>
<head>
<title>Prevent Propagation</title>
<style>
#one
{
   width: 100px; height: 100px; background-color: #0f0;
}
#two {
   width: 50px; height: 50px; background-color: #f00;
}
#stop
{
  display: block;
}
</style>
<script>

// global for signaling propagation cancel
var stopPropagation = false;

function listenEvent(eventTarget, eventType, eventHandler) {
   if (eventTarget.addEventListener) {
      eventTarget.addEventListener(eventType, eventHandler,false);
   } else if (eventTarget.attachEvent) {
      eventType = "on" + eventType;
      eventTarget.attachEvent(eventType, eventHandler);
   } else {
      eventTarget["on" + eventType] = eventHandler;
   }
}

// cancel propagation
function  cancelPropagation (event) {
   if (event.stopPropagation) {
      event.stopPropagation();
   } else {
      event.cancelBubble = true;
   }
}

listenEvent(window,"load",function() {
   listenEvent(document.getElementById("one"),"click",clickBoxOne);
   listenEvent(document.getElementById("two"),"click",clickBoxTwo);
   listenEvent(document.getElementById("stop"),"click",stopProp);
  });

function stopProp() {
    stopPropagation = true;
}

function clickBoxOne(evt) {
  alert("Hello from One");
}

function clickBoxTwo(evt) {
  alert("Hi from Two");
  if (stopPropagation) {
     cancelPropagation(evt);
  }
}
</script>

</head>
<body>
<div id="one">
<div id="two">
<p>Inner</p>
</div>
</div>
<button id="stop">Stop Propagation</button>
</body>

The button event handler only sets a global variable because we’re not worried about its event propagation. Instead, we need to call cancelPropagation in the click event handler for the div elements, when we’ll have access to the actual event we want to modify.

Example 7-2 also demonstrates one of the challenges associated with cross-browser event handling. There are two click event handler functions: one for the inner div element, one for the outer. Here’s a more efficient click handler method function:

function clickBox(evt) {
   evt = evt || window.event;
   alert("Hi from " + this.id);
   if (stopPropagation) {
      cancelPropagation(evt);
   }
}

This function combines the functionality contained in the two functions in the example. If stopPropagation is set to false, both elements receive the event. To personify the message, the identifier is accessed from the element context, via this.

Unfortunately, this won’t work with IE8. The reason is that event handling with attachEvent is managed via the window object, rather than the DOM. The element context, this, is not available. You can access the element that receives the event via the event object’s srcElement property. However, even this doesn’t work in the example, because the srcElement property is set to the first element that receives the event, and isn’t updated when the event is processed for the next element as the event propagates through the nested elements.

When using DOM Level 0 event handling, these problems don’t occur. Microsoft has access to the element context, this, in the handler function, regardless of propagation. We could use the above function only if we assign the event handler for the two div elements using DOM Level 0:

document.getElementById("one").onclick=clickBox;
document.getElementById("two").onclick=clickBox;

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required