How to build an expandable sitemap with jQuery and CSS
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.
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
- Sub page - level two
- Sub page - level one
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:
- Hide all lists that are not on the top level
- Add an expand/contract button next to each list-item that contains a list
- Add classes to the last item in the each list for presentational purposes
- 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();
});
If you liked this post, please share it with others!
[...] This post was Twitted by dustyedwards – Real-url.org [...]
This is awesome! I love it. Thank you for your work.
What a lovely tree !
I’m building dynamic sitemapping !
Do you know some nice looping algorithm to display links in their appropriate level from a Data Base :
Main
—–>Category
——–>Pages or products
thanks for sharing
Hi Scott,
Nice script, I found it very useful. I modified it to work with WordPress using multiple instances of quickTree on the same page.
The calling script needs to use jQuery’s noConflict mode like so:
var $j = jQuery.noConflict();
$j(document).ready(function(){
$j(‘ul.quickTree’).quickTree();
});
And I had to modify the plugin as follows:
jQuery.fn.quickTree = function() {
return this.each(function(){
//set variables
var $tree = jQuery(this);
var $roots = $tree.find(‘li’);
//set last list-item as variable (to allow different background graphic to be applied)
$tree.find(‘li:last-child’).addClass(‘last’);
//add class to allow styling
$tree.addClass(‘tree’);
//hide all lists inside of main list by default
$tree.find(‘ul’).hide();
//iterate through all list items
$roots.each(function(){
//if list-item contains a child list
if (jQuery(this).children(‘ul’).length > 0) {
//add expand/contract control
jQuery(this).addClass(‘root’).prepend(”);
}
}); //end .each
//handle clicking on expand/contract control
$tree.find(’span.expand’).toggle(
//if it’s clicked once, find all child lists and expand
function(){
jQuery(this).toggleClass(‘contract’).nextAll(‘ul’).slideDown();
},
//if it’s clicked again, find all child lists and contract
function(){
jQuery(this).toggleClass(‘contract’).nextAll(‘ul’).slideUp();
}
);
});
};
I hope someone finds this to be useful!
Thanks Scott, working beautifully for me.
It’d be nice if users could click a link/button to expand the entire tree – any ideas?
eg
$(‘.expandall’).click(function () {
do stuff
});
Hi Scott! Really nice piece of work! I found something odd on CSS, what’s the purpose of _width ?
Great script. Very clean with minimal impact on markup.
I would like a way to specify that the page open to expand to level (x). Is this a difficult mod?
Thanks.
Don’t know enough about script yet to know any better. If I add this script as offered will it mess up naviagation? How do I target it toward just the right list? Do I follow Allan’s directions? …off to learn more on Lynda.com.
Cool script. Changing the text size smaller (ex 12px) makes the +’s indent to the right. Any ideas how to avoid this?
[...] Build an expandable sitemap with jQuery and CSS | Demo [...]
Brilliant script, but I find the same problem as Andrew. It clashes with my css style sheet as my body text is set to 0.72em and it causes the indentations to jump (to the left in my case). Has anyone discovered a way around this?
Hi All there!
I follow the way given by Scott and it works perfectly but when i create another treeview in the same page, one of the treeviews does not work the way i intended, it expands and then collapses immediately. How do i prevent this problem? Thank you for any help!
I have solved my problem applying the way Allan provided and also modified to prevent click events of the subnodes that fires the click event of the parents. I put the modified script here, hope someone find helpful!
//The plugin
jQuery.fn.quickTree = function() {
return this.each(function(){
//set variables
var $tree = $(this);
var $roots = $tree.find(‘li’);
//set last list-item as variable (to allow different background graphic to be applied)
$tree.find(‘li:last-child’).addClass(‘last’);
//add class to allow styling
$tree.addClass(‘tree’);
//hide all lists inside of main list by default
$tree.find(‘ul’).hide();
//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(”);
}
$(this).click(function(evt){
evt.stopPropagation();
});
}); //end .each
//handle clicking on expand/contract control
$tree.find(’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();
}
);
});
};
[...] Build an expandable sitemap with jQuery and CSS | Demo 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. [...]