skip to content

The Sun on Your Face

Blog Entry: Javascript Event Handling and Event Bubbling

Javascript Event Handling and Event Bubbling


8th April 2010

Recently I built a web page to demonstrate how to provide progressive enhancement with Javascript by using the DOM. The basic approach is to have an HTML structure in place for non Javascript users, but if Javascript is enabled use the DOM to provide the necessary HTML required for the extra behaviour provided by Javascript. We basically create a dynamic Javascript page that renders differently if Javascript is disabled.

The extra functionality provided was in the form of a series of dropdown boxes containing sublinks that appeared when the user hovered over a section heading listed in a horizontal navigation bar. If Javascript was disabled, then the dropdown links appeared as visible boxes beneath the section heading.

Example of a horizontal navigation bar with a vertical dropdown box of links

Using accessible Javascript to display the dropdown boxes was not an issue. The problem occured when trying to capture the correct event to handle the closing of the dropdown box. The event that I needed to capture was when the user moved the mouse outside of the dropdown box.

Screenshot to depict a user moving the focus outside of the dropdown box.

However, event bubbling made this seemingly simple process more complicated.

My initial approach was to use Javascript event handling to track what the user was doing and call an appropriate function e.g.

It seemed pretty straightforward, but I found it difficult to implement. To understand why I need to explain a little more about the HTML structure. The whole horizontal navigation bar is contained within a <ul> element, with each <li> element corresponding to a section heading, and containing a link that triggers the dropdown box on hover. The dropdown box itself consists of a <div> containing all the sub links:

    <ul id=mainContent">
	  <li><a>Photographic Techniques</a></li>
	   	  <a href="depthOfField.html">Depth of Field</a> 
		  <a href="shutter.html">Shutter Speed</a>
		  <a href="colour.html">Colour</a>
	  <li> etc...

Opening the dropbox was easy enough, I just associated a OpenDropdown() event handler with the onmouseover event for each <li><a>SectionHeading</a><li> link.

Closing the dropdown would also be simple I thought, I could simply simply associate an onMouseout event handler with the <div> that held the dropdown links. The problem is concerned with the way in which all the browsers perceive the event order associated with the firing of an event when the HTML elements are nested within each other. This then causes complications when trying to target the correct event.

Consider our dropdown box again.

A horizontal navigation bar with a vertical dropdown box of links displayed.

We need to register an event handler when the mouse moves from the dropdown <div> to the outside, but we do not want to register an event when the mouse moves from sublink to sublink within this enclosing <div>.

Unfortunately, Event bubbling cause an onmouseout event to fire on the dropdown <div> even when the mouse moves from a sublink within the <div>.

Screenshot to depict a user moving the focus within the dropdown box of links, from 
	    one sublink to another.

Even more strangely, an onmouseout is also fired on the dropdown <div> if you move from within the <div> to within a contained sublink!

Clearly, targeting the correct event is not straightforward.

Quirksmode recommend the following approach to registering an onmouseout event handler that fires only when the mouse leaves the dropdrown <div>.

	function doSomething(e) {
		if (!e) var e = window.event;
		var tg = (window.event) ? e.srcElement :;
		if (tg.nodeName != 'DIV') return;
		var reltg = (e.relatedTarget) ? e.relatedTarget : e.toElement;
		while (reltg != tg && reltg.nodeName != 'BODY')
			reltg= reltg.parentNode
		if (reltg== tg) return;
		// Mouseout took place when mouse actually left layer
		// Handle event

This should work, and indeed often did, however it was not consistent. If I moved the mouse very quickly it seemed like the event handler sometimes did not fire.

I abandoned this approach and decided to look for another solution. The one I implemented in the end does not require Javascript at all, but rather relies on the :hover pseudo class in css.

The trick was to apply a class to the <li> element that held the section header that triggers the drop down box, and then use the cascade within the stylesheet to deal with hiding and showing the dropdown <div>. When the page loads, a class="hide" is associated with the <li>sectionHeadings</li>. The cascade is then used to target the dropdown <div> and set a left margin of -999px essentially hiding the <div> (although a screen reader will still recognize it.)

  /*Create a CSS rule that if a hide class is attached to the li elements
    that make up the top links, then div elements beneath (the dropdown divs) are hidden*/

	#mainContent li.hide div{
	  position: absolute;
	  margin: 0px;
	  padding: 0px;
	  left: -9999px;

Finally, we use the :hover pseudo class associated with a <li> element with a class="hide" to reset the left margin of the dropdown <div> so that the dropdown box becomes visible.

  /*Create a CSS rule that if a mouse hover occurs on li element with a class hide, 
    then div elements beneath (the dropdown divs) are displayed. This is achieved by setting the 
    left property to auto*/
      #mainContent li.hide:hover div{
        position: absolute;
        margin: 0px;
        padding: 0px;
        left: auto;

I realised that using this approach, I did not need Javascript at all. I could create a horizontal navigaton bar with dropdown boxes purely by setting the apprpropriate class elements and manipulating the css style rules.

However, I decided to still use Javascript and the DOM to create the relevant elements and add the right classes so that users who did not want to use drop boxes, but would prefer a page with visible links boxed according to category would be able to do so by turning off Javascript.