jQuery Accordion Menu

This is my first article about the wonderful jQuery Javascript library, its been my library of choice for around a year and it's ideal for web development because of it's small size and powerful selector methods.

I'm going to go through the process of creating an expandable/collapsible navigation menu that has a series of top level links with each having a number of sub links. When a top level link is clicked the menu expands and shows the related sub links. An example of which is shown below:

Downloading and Implementing jQuery

The first thing to do is get the jQuery library, for this article I've used the packed version which is a compressed form of the library for a smaller file size. Save the file in your project, I usually create a js folder for all my javascript files, and then link to the file in your HTML so we can start to use jQuery. Also create a new file called common.js in the same folder, this is where I'm going to write the Javascript code for the menu.

<script type="text/javascript" src="/js/jquery-1.2.3.pack.js"></script>
<script type="text/javascript" src="/common/common.js"></script>

Unordered Lists

Next up is the actual HTML code for the menu, I'm going to be using a series of unordered lists to create the navigation. This keeps code clean and will validate which is always good practice.

<ul id='nav'>
	<li><a href='/heading'>Heading</a></li>
	<li><a href='/heading'>Heading</a></li>
	<li><a href='/heading'>Heading</a></li>
</ul>

For the sub headings I'm going to use another list which is nested inside the heading list item.

<ul id='nav'>
	<li><a href='/heading'>Heading</a>
		<ul>
			<li><a href="/link">Sub Heading</a></li>
			<li><a href="/link">Sub Heading</a></li>
			<li><a href="/link">Sub Heading</a></li>
		</ul>
	</li>
	<li><a href='/heading'>Heading</a>
		<ul>
			<li><a href="/link">Sub Heading</a></li>
			<li><a href="/link">Sub Heading</a></li>
			<li><a href="/link">Sub Heading</a></li>
		</ul>
	</li>
	<li><a href='/heading'>Heading</a>
		<ul>
			<li><a href="/link">Sub Heading</a></li>
			<li><a href="/link">Sub Heading</a></li>
			<li><a href="/link">Sub Heading</a></li>
		</ul>
	</li>
</ul>
Unordered List

CSS List Styling

At the moment the menu is a bog standard nested unordered list, but with a bit of CSS we can style it to look a lot better.

/*
 * file: /css/style.css
 * I usually keep my css rules on one line for easier reading.
 */

/* main list style */
ul#nav { 
	list-style:none;
	padding:0; 
	margin:15px 15px 15px 0; 
	width: 300px;
}
/* heading links */
ul#nav li a { 
	display:block; 
	height: 28px; 
	line-height:28px; 
	background:#dcd7ce; 
	border-bottom: 1px solid #b9b09d;
	border-top: 1px solid #eeebe7; 
	color:#595441; 
	text-decoration:none; 
	text-align:center;
}
/* heading links hover effect */
ul#nav li a:hover {
	background:#b9b09d;
	color:#fff;
}

/* sub heading list */
ul#nav ul {
	list-style-type:none; 
	padding:0; 
	margin: 0;
}
/* sub heading links */
ul#nav ul li a { 
	display:block; 
	height: 24px; 
	line-height:25px; 
	background:#f5f3f0; 
	color:#b9b09d; 
	text-decoration:none; 
	border-bottom: 1px solid #d7d1c6; 
	font-size:10px;
}
/* sub heading links hover effect */
ul#nav ul li a:hover {
	background:#fff;
	color:#595441;
}
Unordered List CSS Styling

After applying the CSS styles the menu is coming along nicely, I've used an Id on the unordered list id='nav' for two reasons. The first is that I can use this Id to style it using CSS and second I'll use this Id to target the list in my Javascript so that the accordion menu is applied unobtrusively ( more info ).

The Javascript

Now I'm going to create some Javascript code to select my navigation menu and apply some effects on the sub lists. If your familiar with writing Javascript without a library if you wanted to do some scripting when the page loads you would have to use window.onload, with jQuery you use the $(document).ready method to ensure that the DOM has been loaded so that all the page has loaded. This is to ensure that you can selector elements on the page.

Once the page has been loaded the first thing I'm going to do is to hide all the sub heading links and this is done with the hide() method. In jQuery we can use CSS selectors to identify the element we need to access and in this case we will target all the unordered lists inside #testnav.

// execute only when the whole document is ready
$(document).ready(function() {
	// hide all sub heading lists
	$('#testnav li ul').hide();
});
jQuery Accordion

Next I'm going to add an onClick event handler to all the main heading links so that we can expand and collapse the sub level links. Again we use the special $("css selector") method to access the element and then we simply need to add a .click() method to it. One of the main benefits of using jQuery is something called method chaining and allows us to access an element once and then apply multiple effects by chaining methods e.g. .effect1().effect2()

To test that the click handler is working you can either do a simple alert() to open up a box when a link is clicked or use the brilliant firebug extension for firefox and log the testing message to the console using console.log()

	// add a click handler to the heading links
	$('#testnav li a').click(function(){
	// alert('clicky'); console.log('clicky');
	// return false to stop link following the href
	return false;
	});

When a main heading link has been clicked we need to close all the other sub heading lists and open the sub heading list for the chosen link. To close all the open lists we can use our selectors as before but this time only target the visible ones using :visible and then apply a .slideUp() method to the element to close the list using an effect. There are many more effects available in jQuery so experiment with them.

To open up the correct sub heading list we can use the .next() method to specifically target the next wanted element. The $(this) in the code may seem a bit unusual but it's quite common and just refers to the current element that is selected, in this case it is the link of the main heading that we have applied the click event handler too.

	$('#testnav li a').click(function(){
		// close all open sub heading lists
		$('#testnav li ul:visible').slideUp();

		// slide open the next list
		$(this).next('ul').slideToggle('normal');
	
	// return false to stop link following the href
	return false;
	});

Everything is nearly complete and you can test your menu out and it should expand and collapse according. There is just one minor bug that needs fixing which is when you click on a heading to show the sub links and click on the same heading again to close them. The menu just closes and re-opens which is quite annoying. I spent ages trying to fix this problem and at first I started out adding and removing classes to open and closed lists but this didn't work. Finally I've come up with a pretty good solution.

Before I open or close any of the lists I'm going to first check whether the next sub heading list is open, if it is open then the only thing I need to do is close it. If not I can just do the same as normal. The code for this check is as follows:

	// if the current sub heading list is already open
	if($(this).next('ul:visible').length) {
		// close the sub heading list
		$(this).next('ul:visible').slideUp();
	} else {
		// close all open sub heading lists
		$('#testnav li ul:visible').slideUp();
		// slide open the next list
		$(this).next('ul').slideToggle('normal');
	}

Wrapping Up

jQuery Accordion

With only a few lines of code I've created a fully animated navigation menu that works well, is unobtrusive and works even if Javascript is disabled. jQuery is an extremely powerful library and I've only really scratched the surface on what you can achieve with it. Here is a zip file containing the source code used in this example.

If your having trouble with anything jQuery related then check out the google group or send me an email and I'll try and help.

Posted on 5th April 2008
8 years, 8 months, 4 days ago

comments powered by Disqus