Bad Poetry

Entries categorized as ‘JavaScript’

Long Lost Nephew of Suckerfish

September 4, 2008 · 2 Comments

I needed to create a CSS-based drop-down list that would work cross-browser and had some extra features for better usability including a mouse-off delay and keyboard accessibility.

I was very familiar with the suckerfish menu, but it didn’t meet my needs to I decided to scrap it and start from scratch. So here I’ll go through what I came up with.

Our HTML structure is the standard nested list format that we all know and love:

<ul id="nav">
	<li>
		<a href="#">Home</a>
	</li>
	<li>
		<a href="#">News</a>
	</li>
	<li>
		<a href="#">Photos</a>
		<ul>
			<li>
				<a href="#">Reader Shots</a>
			</li>
			<li>
				<a href="#">Wallpapers</a>
				<ul>
					<li>
						<a href="#">800 x 600</a>
					</li>
					<li>
						<a href="#">1024 x 768</a>
					</li>
					<li>
						<a href="#">1280 x 1024</a>
					</li>
				</ul>
			</li>
			<li>
				<a href="#">Photo Galleries</a>
			</li>
		</ul>
	</li>
	<li>
		<a href="#">Videos</a>
	</li>
	<li>
		<a href="#">Results</a>
		<ul>
			<li>
				<a href="#">National</a>
			</li>
			<li>
				<a href="#">International</a>
			</li>
		</ul>
	</li>
</ul>

Once we have our HTML structure the first thing we’re going to do is set the styles for how the menus should look when they’re expanded. In this case we’re going with some very simple styles, but you can make this as complicated as you like.

#nav,
#nav li ul				{ list-style: none; margin: 0; padding: 0; width: 150px; }
#nav li					{ padding: 0 3px 3px 0; position: relative; }
#nav li a				{ border: 1px solid #000; display: block; text-decoration: none; width: 145px; }

#nav li ul				{ left: 150px; position: absolute; top: 0; }
#nav li a				{ background: #fff; color: #000; }

Example 1: The basic structure for our menu, but no functionality yet

Now we have our menu in place we’ve got to get it acting like a dropdown. We’re using a negative margin-left to achieve this, and then setting the margin to 0 when the list item is hovered over:

#nav li:hover ul li ul,
#nav li ul				{ margin-left: -9999px; }

#nav li ul li ul,
#nav li:hover ul,
#nav li ul li:hover ul			{ margin-left: 0; }

#nav li a,
#nav li:hover ul li a,
#nav li ul li:hover ul li a		{ background: #fff; color: #000; }

#nav li:hover a,
#nav li a:hover,
#nav li ul li:hover a,
#nav li ul li a:hover,
#nav li ul li:hover ul li:hover a,
#nav li ul li:hover ul li a:hover	{ background: #000; color: #fff; }

Example 2: Basic CSS Dropdown Menu

The next step is to make it keyboard accessible in modern browsers (everything except IE). We can do this by using a combination of the :focus pseudo class and the + combinator to provide some basic keyboard functionality. It isn’t relevant yet, but I’ve seperated selectors using the + combinator from other selectors because IE6 ignores all of the selectors in a list if one is using the + combinator.

#nav li:hover ul li ul,
#nav li ul				{ margin-left: -9999px; }
#nav li a:focus + ul li ul		{ margin-left: -9999px; }

#nav li ul li ul,
#nav li:hover ul,
#nav li ul li:hover ul,
#nav li.hover ul li a:focus,
#nav li ul li.hover ul li a:focus	{ margin-left: 0; }
#nav li a:focus + ul,
#nav li ul li a:focus + ul		{ margin-left: 0; }

#nav li a,
#nav li:hover ul li a,
#nav li ul li:hover ul li a		{ background: #fff; color: #000; }

#nav li:hover a,
#nav li a:hover,
#nav li a:focus,
#nav li a:active,
#nav li ul li:hover a,
#nav li ul li a:hover,
#nav li ul li:hover ul li:hover a,
#nav li ul li:hover ul li a:hover	{ background: #000; color: #fff; }

#nav li ul li a:focus,
#nav li ul li ul li a:focus		{ margin-left: 9999px; }

As you can see in our example page there is a lot to be desired for keyboard users, but it’s a good start. We will use JavaScript to provide better usability for keyboard users, but we’ll get to that later.

Example 3: CSS-based dropdown menu that is keyboard accessible in modern browsers

If you’ve had a look at any of these examples in IE6 you probably will have seen that the menu doesn’t work. At all. We need to use JavaScript to fix our IE6 issues, so while we’re at it let’s add some usability aids, such as a mouse-off delay.

All of this is done by the function below. It is completely parameterised so you can apply it to any number of dropdown lists and change the timing, etc.

//	dropDownId	string	ID of the UL containing the drop down
//	hoverClass	string	Class to be applied to the LIs on mouse over
//	mouseOffDelay	int	Delay from when the mouse is lifted from the drop down until hoverClass is removed (in milliseconds)
function drop_down(dropDownId, hoverClass, mouseOffDelay) {
	if(dropDown = document.getElementById(dropDownId)) {
		var listItems = dropDown.getElementsByTagName('li');
		for(var i = 0; i < listItems.length; i++) {
			listItems[i].onmouseover = function() {
				this.className += ' ' + hoverClass;
			}
			listItems[i].onmouseout = function() {
				var that = this;
				setTimeout(function() {
					that.className = that.className.replace(hoverClass, "")
				}, mouseOffDelay);
				this.className = that.className;
			}
		}
	}
}

And we call the function like so:

drop_down('nav', 'hover', 250);

In our examples we have used Simon Willison’s great Add Load Event function to call the function on page load, however you can use any method you like.

Now we need to change our CSS to use our new .hover class:

#nav li:hover ul li ul,
#nav li ul,
#nav li.hover ul li ul			{ margin-left: -9999px; }
#nav li a:focus + ul li ul		{ margin-left: -9999px; }

#nav li.hover ul,
#nav li ul li ul,
#nav li ul li.hover ul,
#nav li:hover ul,
#nav li ul li:hover ul,
#nav li.hover ul li a:focus,
#nav li ul li.hover ul li a:focus	{ margin-left: 0; }
#nav li a:focus + ul,
#nav li ul li a:focus + ul		{ margin-left: 0; }

#nav li a,
#nav li:hover ul li a,
#nav li.hover ul li a,
#nav li ul li:hover ul li a,
#nav li ul li.hover ul li a		{ background: #fff; color: #000; }

#nav li:hover a,
#nav li.hover a,
#nav li a:hover,
#nav li a:focus,
#nav li a:active,
#nav li ul li:hover a,
#nav li ul li.hover a,
#nav li ul li a:hover,
#nav li ul li:hover ul li:hover a,
#nav li ul li.hover ul li.hover a,
#nav li ul li:hover ul li a:hover	{ background: #000; color: #fff; }

Example 4: JavaScript enhancements allow IE6 users to use the menu and adds a usability improvement

The final piece of the puzzle is to make our menu keyboard accessible for all browsers and more usable to-boot. We do this by mimicking the effect of hovering of a list-item when you are focused on it’s child anchor. Inside our loop we’ll insert the following piece of script:

var anchor = listItems[i].getElementsByTagName('a');
anchor = anchor[0];
anchor.onfocus = function() {
	if(this.parentNode.parentNode.parentNode.parentNode.parentNode.nodeName == 'LI') {
		this.parentNode.parentNode.parentNode.parentNode.parentNode.className += ' ' + hoverClass;
	}
	if(this.parentNode.parentNode.parentNode.nodeName == 'LI') {
		this.parentNode.parentNode.parentNode.className += ' ' + hoverClass;
	}
	if(this.parentNode.nodeName == 'LI') {
		this.parentNode.className += ' ' + hoverClass;
	}
}
anchor.onblur = function() {
	if(this.parentNode.parentNode.parentNode.parentNode.parentNode.nodeName == 'LI') {
		this.parentNode.parentNode.parentNode.parentNode.parentNode.className = this.parentNode.parentNode.parentNode.parentNode.parentNode.className.replace(hoverClass, "");
	}
	if(this.parentNode.parentNode.parentNode.nodeName == 'LI') {
		this.parentNode.parentNode.parentNode.className = this.parentNode.parentNode.parentNode.className.replace(hoverClass, "");
	}
	if(this.parentNode.nodeName == 'LI') {
		this.parentNode.className = this.parentNode.className.replace(hoverClass, "");
	}
}

This script finds the first anchor and assigns onfocus and onblur events to it (the keyboard equivalent to onmouseover and onmouseout, except that it can only be applied to focusable elements i.e. links and form controls).

When we tab onto a link we want to assign our hoverClass to the anchors parent LI, and depending how deep it is we want to apply hoverClass to it’s great grandparent LI and it’s great great great grandparent LI. This allows us to style all of it’s parents, helping to give keyboard users a better indication of where they are.

Example 5: Final JavaScript enhancements make the menu more accessible to keyboard users on all browsers

Please grab the finished CSS and JavaScript files.

Check out the finished product, kick the tyres, and let me know what you think. Feel free to use abuse and modify as you need. Attribution is always appreciated.

Categories: CSS · HTML · JavaScript · Tutorial · Web Development
Tagged: , , , , , , , ,