How to build an expandable sitemap with jQuery and CSS

By Scott Darby

If you are working on a site with hundreds of pages, it’s sitemap can become huge and unmanageable, leaving you to scroll through a page of links before you find what you want (yes you could just hit ctrl+f, but that’s not the point). Computer operating systems often employ tree-view navigation to aid users in navigating hierarchical data. In this tutorial I will use the power of jQuery to transform an unordered list of hierarchically structured pages into an expandable, tree-view navigation system – the perfect way to tame those huge sitemaps produced by your CMS.

View a working example

Download

Getting started

We will start with a simple unordered list:

  • Top level page
    • Sub page - level one
      • Sub page - level two
        • Sub page - level three

Nothing fancy there, hopefully your sitemap is already structured in this way.

jQuery time

We will now create a jQuery plugin which will do the following:

  1. Hide all lists that are not on the top level
  2. Add an expand/contract button next to each list-item that contains a list
  3. Add classes to the last item in the each list for presentational purposes
  4. Add interactivity so that if the expand/contract button is clicked, the list will show/hide

We will use jQuery’s plugin interface, so begin by starting the plugin:

jQuery.fn.quickTree = function() {
    return this.each(function(){

This adds a new function to jQuery called ‘quickTree’. We will pass the <ul> element we want to transform to this function. We then use jQuery’s built in .each() iterator method to access each of the elements passed to this function.

Now we need to set our variables to be used in the function, by setting them once here, we can then use them again in our plugin without having to search through the DOM each time, saving valuable processor cycles. N.B. I am prefixing variables that are jQuery objects with a $ sign to aid readability

	//access element passed to the plugin
    var $tree = $(this);

	//get all of the list-items
    var $roots = $tree.find('li');

	//add class to last item in each list for styling
	$tree.find('li:last-child').addClass('last');

We now need to hide all lists lower than the top level which can be done simply by finding all of the <ul> elements inside the main list and using jQuery’s .hide() method:

        $tree.find('ul').hide();

We now need to iterate through all of the list items and if they contain any child <ul> elements, place a <span> element in front of them. This <span> will act as our button to expand & contract the list.

		//iterate through all list items
        $roots.each(function(){

			//if list-item contains a child list
            if ($(this).children('ul').length > 0) {

				//add expand/contract control
                $(this).addClass('root').prepend('');

            }

        }); //end .each

We will now add interactivity to the buttons with jQuery’s .toggle() method. .toggle() acts like a switch and alternates between two functions on click. Here, the first function toggles the class of the expand button (so that it can show a contract symbol with css) and uses jQuery’s .slideDown() method to reveal the next level of <ul> elements. The second function is essentially the first function in reverse.

        $('span.expand').toggle(
			//if it's clicked once, find all child lists and expand
            function(){
                $(this).toggleClass('contract').nextAll('ul').slideDown();
            },
			//if it's clicked again, find all child lists and contract
            function(){
                $(this).toggleClass('contract').nextAll('ul').slideUp();
            }
        );
    });
};

Presentation

Now we need to use CSS to enhance the presentation of the list so that it resembles a tree-structure.

First of all, we will reset all the margins and padding of the elements within the list with the * selector, to level the playing field. We then add some spacing to the list-items so that each consecutive lower level is indented. We also add a tree-branch background image to each list-item. This tree-branch image is a ‘T’ shape rotated left 90° and is positioned so that it is vertically centered, but horizontally fixed. The graphic has been constructed so that it is much taller than the size of the text, so that if the text size is increased, the connecting lines do not have a gap.

We also give the list-items that are the last in their list an “L” shaped background image. We have to then write a new rule for any list-items that are the last item in their list, but also contain lists themselves so that the “L” shaped background image is removed.

.tree * {
	margin:0; padding:0;
}
.tree li {
	list-style:none;
	padding-left:21px;
}
.tree li.root {
	padding-left:0;
}
.tree li li {
	background:url(rootNode.gif) no-repeat 17px center;
	margin-left:10px;
	padding-left:31px;
}
.tree li li.root {
	padding-left:10px;
	background:url(justOne.gif) repeat-y 17px 0;
}
.tree li li.root.last {
	background:none;
}
.tree li li.last {
	background:url(lastRoot.gif) no-repeat 17px 0;
}

The last thing left to do presentation-wise is to create our expand/contract button style. I have used an image replacement technique to change the span element added with JavaScript into a button (read more about image replacement). Rather than create two images for the expanded/contracted state, I have rolled them into one image, then all I have to do is change the background-position property to change the background image.

.expand {
	background:url(plusMinus.gif) no-repeat;
	width:16px;
	_width:13px;
	height:16px;
	display:block;
	float:left;
	margin-top:2px;
	padding:0 5px 0 0;
	text-indent:-9999px;
	line-height:0;
	font-size:0;}
.contract {
	background-position:0 -16px;
}
.expand:hover {
	cursor:pointer;
}

The plug-in can now be used on any unordered list element on your page with the following jQuery statement:

	$(document).ready(function(){
		$('ul.my-class').quickTree();
	});

View a working example

Download

If you liked this post, please share it with others!

Posted in:
Tags: , ,