Event Propagation: Capturing and Bubbling

Contact Us or call 1-877-932-8228
Event Propagation: Capturing and Bubbling

Event Propagation: Capturing and Bubbling

Let's look back at the useCapture argument in the observeEvent() function. Consider a user who clicks on a word in a table cell. The user's intent might be to click the word, the cell, the row, or maybe even the entire table. To illustrate, take a look at the following code sample.

Code Sample:

HTMLDOM/Demos/event-propagation-table.html
---- C O D E   O M I T T E D ----
<script type="text/javascript">
	observeEvent(window,"load",function() {
		var tbl = document.getElementById("tbl");
		var rows = tbl.getElementsByTagName("tr");
		var edits = getElementsByClassName(tbl,"edit");
		for (var r=0; r<rows.length; ++r) {
			showBirthday(rows[r]);
		}
		for (var i=0; i<edits.length; ++i) {
			editRow(edits[i]);
		}
		
		function showBirthday(row) {
			observeEvent(row,"click",function() {
				alert(row.title);
			},false);
		}
		
		function editRow(row) {
			var id=row.parentNode.parentNode.id;
			observeEvent(row,"click",function() {
				alert('Edit record form: ' + id);
			},false);
		}
	});
</script>
---- C O D E   O M I T T E D ----

<table id="tbl">
	<caption>The Beatles</caption>
	<tr title="Born October 9, 1940." id="1">
		<td>John</td>
		<td>Lennon</td>
		<td><img src="../Images/edit.gif" alt="Edit Row" class="edit"></td>
	</tr>
	<tr title="Born June 18, 1942." id="2">
		<td>Paul</td>
		<td>McCartney</td>
		<td><img src="../Images/edit.gif" alt="Edit Row" class="edit"></td>
	</tr>
	<tr title="Born February 25, 1943." id="3">
		<td>Ringo</td>
		<td>Starr</td>
		<td><img src="../Images/edit.gif" alt="Edit Row" class="edit"></td>
	</tr>
	<tr title="Born July 7, 1940." id="4">
		<td>George</td>
		<td>Harrison</td>
		<td><img src="../Images/edit.gif" alt="Edit Row" class="edit"></td>
	</tr>
</table>
---- C O D E   O M I T T E D ----

Notice that both the trs and the imgs have onclick event handlers:

  • When the user clicks on any of the row itself, an alert pops up stating the birthday of that Beatle. (Imagine a more informative detail window.)
  • When the user clicks on the edit icon, an alert pops up reading "Edit record form: 1," where 1 is the id of the row. (Imagine a real edit form.) In this case, we don't want to show the "birthday" alert. But it does pop up.

We will look at how to fix this shortly, but first, let's consider the order of the alerts.

In IE8 and earlier, events are captured starting with the innermost element, in this case the image, and then "bubble up" to the parent elements. There is no easy way to change this behavior. So, using IE8 and earlier, when you click on the edit icon in our demo, the "edit form" alert will pop up first followed by the "birthday" alert.

In all other modern browsers, you have a choice between "bubbling up" or "trickling down". The trickling down phase is called the capture phase. The third argument of the built-in addEventListener() method is useCapture. When true, it uses the "trickle down" behavior. When false, it "bubbles up" as illustrated in the diagram below:

Our observeEvent() function also takes a useCapture argument, which it passes into the built-in addEventListener() method. If we don't pass in a value for useCapture, the value will be undefined, which is falsy. This will make modern browsers work in the same way as IE8 and earlier (bubbling up).

This behavior can be a bit complicated to understand, so before moving on, let's take a look at the following two examples:

Code Sample:

HTMLDOM/Demos/bubble-up.html
---- C O D E   O M I T T E D ----

<script type="text/javascript">
	observeEvent(window,"load",function() {
		var output = document.getElementById("output");
		observeEvent(document.getElementById("outer"),"click",function() {
			output.innerHTML+="<li>outer div clicked</li>";
		}, false);
		observeEvent(document.getElementById("inner"),"click",function() {
			output.innerHTML+="<li>inner div clicked</li>";
		}, false);
	});
</script>
<title>Event Propagation: Bubbling Up</title>
</head>
<body>
	<h1>Bubbling Up</h1>
	<div id="outer">outer
		<div id="inner">inner</div>
	</div>
	<ol id="output"></ol>
</body>
</html>

Click on the inner div and you'll see this:

Code Sample:

HTMLDOM/Demos/trickle-down.html
---- C O D E   O M I T T E D ----

<script type="text/javascript">
	observeEvent(window,"load",function() {
		var output = document.getElementById("output");
		observeEvent(document.getElementById("outer"),"click",function() {
			output.innerHTML+="<li>outer div clicked</li>";
		}, true);
		observeEvent(document.getElementById("inner"),"click",function() {
			output.innerHTML+="<li>inner div clicked</li>";
		},true);
	});
</script>
<title>Event Propagation: Trickling Down</title>
</head>
<body>
	<h1>Trickling Down</h1>
	<div id="outer">outer
		<div id="inner">inner</div>
	</div>
	<ol id="output"></ol>
</body>
</html>

Click on the inner div and you'll see this:

Notice the different order of the list. The only difference between these two files is the value of the useCapture argument.

Back to our earlier table example. In that example, we passed in false for useCapture so that the behavior across browsers would be consistent. Now, we need to stop that second alert from popping up. Most modern browsers stop events from propagating using the stopPropagation() method of the event, which automatically gets passed into a callBack function. Look at the following example, which is a modification of HTMLDOM/Demos/bubble-up.html:

Code Sample:

HTMLDOM/Demos/stop-prop.html
---- C O D E   O M I T T E D ----

	observeEvent(window,"load",function() {
		var output = document.getElementById("output");
		observeEvent(document.getElementById("outer"),"click",function() {
			output.innerHTML+="<li>outer div clicked</li>";
		}, false);
		observeEvent(document.getElementById("inner"),"click",function(e) {
			output.innerHTML+="<li>inner div clicked</li>";
			e.stopPropagation();
		}, false);
	});
---- C O D E   O M I T T E D ----

The two things to notice:

  1. The anonymous function passed into the observeEvent() function on line 8 now takes an argument: e.
  2. After writing out the "inner div clicked" list item, we call e.stopPropagation() to put a stop to the bubbling up.

The result is that the second line item ("outer div clicked") doesn't appear.

And it would be as easy as that, except that this won't work in IE until version 9 for two reasons:

  1. In IE8 and earlier, the event was not automatically passed in to the callBack function. Instead, the last event was stored in window.event.
  2. IE8 and earlier provided a different way of stopping event propagation. Instead of calling stopPropagation() you have to set the cancelBubble property of the event to true: e.cancelBubble = true;.

In ClassFiles/lib.js, there is a cross-browser stopPropagation() function (shown below) that addresses the browser differences. You can use this safely to stop events from propagating.

function stopPropagation(e) {
	e = e || window.event;
	if (e.stopPropagation) {
	e.stopPropagation();
	} else {
	e.cancelBubble = true;
	}
	}

So we can finally return to our Beatles table and show the working code:

Code Sample:

HTMLDOM/Demos/event-propagation-table-fixed.html
---- C O D E   O M I T T E D ----
<script type="text/javascript">
	observeEvent(window,"load",function() {
		var tbl = document.getElementById("tbl");
		var rows = tbl.getElementsByTagName("tr");
		var edits = getElementsByClassName(tbl,"edit");
		for (var r=0; r<rows.length; ++r) {
			showBirthDay(rows[r]);
		}
		
		for (var i=0; i<edits.length; ++i) {
			editRow(edits[i]);
		}
		
		function showBirthDay(row){
			observeEvent(row,"click",function() {
				alert(row.title);
			},false);
		}
		
		function editRow(row){
			var id=row.parentNode.parentNode.id;
			observeEvent(row,"click",function(e) {
				alert('Edit record form: ' + id);
				stopPropagation(e);
			},false)	
		}
	});
</script>
---- C O D E   O M I T T E D ----

The call to stopPropagation() will stop the event from bubbling up to the table row, so we'll just get the "edit form" alert.

Next