Making an Accessible JQuery Sitemap
In the past week or two I’ve had a few people ask me about the sitemap on this portfolio (no idea why people are just noticing it now…but they are). I was asked two different things about it in the past few weeks:
- How did I make it expand like that?
- How did I make it visible even without JavaScript turned on?
For those of you who haven’t tried (or can’t be bothered trying) without JavaScript the sitemap simply shows up as the expanded form (without the plus/minus buttons).
Lets get started
Although the sitemap isn’t a feature of websites that gets used often it is always a good idea to include one in a site – especially a site with many “levels” of content.
Sometimes sitemaps can be very big and if they are in a large list (like my one is) then they can expand the page somewhat and make the user scroll (a lot) to find what they are looking for. Plus, I think my JQuery sitemap looks much better than just a long list of links.
The HTML
<ul class="sitemap">
<li><a href="url">Link Name</a></li>
<ul>
<li><a href="url">Link Name</a></li>
</ul>
<li><a href="portfolio.php" title="Web Design Portfolio">Portfolio</a>
<ul>
<li><a href="url">Link Name</a></li>
<li><a href="url">Link Name</a></li>
<li><a href="url">Link Name</a></li>
</ul>
<li><a href="url">Link Name</a></li>
<li><a href="url">Link Name</a></li>
</ul>All it takes is a bunch of unordered lists to create this sitemap (hence why if you turn JavaScript off it looks like the expanded version). You can create a number of sub levels if you like (i.e. another unordered list inside the root unordered list – you can even create one inside that if you want another “level” of links, but for now we’re just going to use 2 levels, the root and the expanding sections).
Add the JQuery Library
For those who don’t know, JQuery is a library of thoroughly tested JavaScript to make life quicker and easier for JavaScript developers. You can either download it from their site and upload it to your server along with your website, or just link to an external file of it (I usually link to the one google have hosted).
Add this into the head section of your site:
Make it Slide
Now lets add the JavaScript we need to create the expanding sections! I’ve added comments throughout the code to try and explain what each part is doing:
$(document).ready(function(){
$('ul.sitemap').sitemap();
});
jQuery.fn.sitemap = function() {
return this.each(function(){
//set variables
var $map = $(this);
var $roots = $map.find('li');
//add classes
$map.find('li:last-child').addClass('last');
$map.addClass('sitemap');
//hide all lists apart from the root list by default
$map.find('ul').hide();
//repeat through all lists
$roots.each(function(){
//if list-item contains a child list
if ($(this).children('ul').length > 0) {
//add expand/contract control
$(this).addClass('root').prepend('<span class="expand" />');
}
});
//when expand is clicked
$('span.expand').toggle(
function(){
// if clicked once, find all lists inside and expand
$(this).toggleClass('contract').nextAll('ul').slideDown("fast");
},
function(){
//if it's clicked again, find all inside lists and hide them
$(this).toggleClass('contract').nextAll('ul').slideUp("fast");
}
);
});
};Now lets add some style
Now we need to add our CSS stylesheet (which you can edit to make the sitemap suit your site). I don’t think I need to explain what does what in here, CSS is fairly explanatory if you know how to use it (which you should if your following this post with any certainty). Either add this into a separate document and include it into the HTML page via the link element (what I’d recommend), or add it straight into your head section.
.sitemap li {
list-style:none;
padding-left:21px;
line-height:18px;
}
.sitemap li.root {
padding-left:0;
}
.sitemap li li {
background:url(images/root.gif) no-repeat 17px center;
margin-left:10px;
padding-left:31px;
}
.sitemap li li.root {
padding-left:10px;
background:url(images/dot.gif) repeat-y 17px 0;
}
.sitemap li li.root.last {
background:none;
}
.sitemap li li.last {
background:url(images/root_last.gif) no-repeat 17px 0;
}
.expand {
background:url(images/plus_minus.gif) no-repeat;
width:16px;
height:16px;
display:block;
float:left;
margin-top:2px;
padding:0 5px 0 0;
line-height:0;
font-size:0;
}
.contract {
background-position:0 -16px;
}
.expand:hover {
cursor:pointer;
}You will need to either create the images mentioned in the CSS above or you can download this zip file with the ones I have used.
And thats that!
As I think I mentioned before, the best part of this sitemap is that it will display to those who do not have JavaScript turned on in their browser (and is therefore accessible). The JavaScript just adds an extra layer of functionallity to the page.
If you have any questions feel free to leave a comment, I’ll try answer them to the best of my ability.
</ul>
<li><a href="portfolio.php" title="Web Design Portfolio">Portfolio</a>
<ul>





Nice job, just used it inside of a page right now. You should consider adding two links to the sitemap: Open all and close all, which should open/close all branches.
Greetings from Germany!
@André: Thanks
Adding those buttons shouldn’t be that hard, should really just be attaching something like this as a trigger function to a link when it is clicked:
$map.find(‘ul’).slideDown(“fast”);
and to close them all:
$map.find(‘ul’).slideUp(“fast”);
… works like a charm, thank you. I simply did it this way:
$('#sitemap_openall').click(function() {$('.sitemap').find('ul').slideDown('fast');
});
To enhance this a bit, could you give me an advice how to check if the branches are open, remove the link and change the class of the opnerer if they are?
Sorry to be a menace with that, but I really like the way you present your sitemap. Unfortunately I am a total noob with jquery
Hmmm….
You could do it by counting the number of “contract” classes you find in the list, or you could detect if the style of the sub list is display:none or display:block
The 2nd way would probably be best, something like this should do the trick:
// outside of loop
var allexpanded = 0;
// inside a loop of all the root li’s
if($(this).nextAll(‘ul’).is(‘:visible’)) {
allexpanded += 1;
}
else {
// do nothing
}
This will default the allexpanded variable to 0.
You then loop through all the inner roots checking to see if the sub lists are visible or not, they all are then at the end allexpanded will be equal to the number of sub lists you have (it adds 1 on for each sub list that is expanded).
Hope that helps,
Nick
Sorry to say, but the var allexpanded doesn’t change, it allways stays zero. To be shure that I am getting it right, here’s what I have so far (notice the alerts in the onclick event)
jQuery.fn.sitemap = function() {return this.each(function(){
//set variables
var $map = $(this);
var $roots = $map.find('li');
var allexpanded = 0;
//add classes
$map.find('li:last-child').addClass('last');
$map.addClass('sitemap');
//hide all lists apart from the root list by default
$map.find('ul').hide();
//repeat through all lists
$roots.each(function(){
//if list-item contains a child list
if ($(this).children('ul').length > 0) {
//add expand/contract control
$(this).addClass('root').prepend('');
}
if($(this).nextAll('ul').is(':visible')) {
allexpanded += 1;
}
});
//when expand is clicked
$('span.expand').toggle(
function(){
// if clicked once, find all lists inside and expand
$(this).toggleClass('contract').nextAll('ul').slideDown("fast");
},
function(){
//if it's clicked again, find all inside lists and hide them
$(this).toggleClass('contract').nextAll('ul').slideUp("fast");
}
);
// opener/closer
$('#sitemap_openall').click(function() {
$('.sitemap').find('ul').slideDown('fast');
alert(allexpanded);
});
$('#sitemap_closeall').click(function() {
$('.sitemap').find('ul').slideUp('fast');
alert(allexpanded);
});
});
};
And if I see it right, we need some sort of counter in the trigger as well, don’t we?
Actually, you could do it much simpler, how didn’t I think of this before?
Not tested it, but this should work. Just adding and subtracting from the variable when the click trigger goes.
jQuery.fn.sitemap = function() {return this.each(function(){
//set variables
var $map = $(this);
var $roots = $map.find('li');
var allexpanded = parseInt(0);
//add classes
$map.find('li:last-child').addClass('last');
$map.addClass('sitemap');
//hide all lists apart from the root list by default
$map.find('ul').hide();
//repeat through all lists
$roots.each(function(){
//if list-item contains a child list
if ($(this).children('ul').length > 0) {
//add expand/contract control
$(this).addClass('root').prepend('');
}
});
//when expand is clicked
$('span.expand').toggle(
function(){
// if clicked once, find all lists inside and expand
$(this).toggleClass('contract').nextAll('ul').slideDown("fast");
},
function(){
//if it's clicked again, find all inside lists and hide them
$(this).toggleClass('contract').nextAll('ul').slideUp("fast");
}
);
// opener/closer
$('#sitemap_openall').click(function() {
$('.sitemap').find('ul').slideDown('fast');
allexpanded += 1;
});
$('#sitemap_closeall').click(function() {
$('.sitemap').find('ul').slideUp('fast');
allexpanded -= 1;
});
});
};
Yep, I see. But what if a plus/minus sign is clicked? I guess we need a separate function named i.e. checkstatus() { … } for this!
Oh, just do the same thing in the function that deals with that:
jQuery.fn.sitemap = function() {
return this.each(function(){
//set variables
var $map = $(this);
var $roots = $map.find(‘li’);
var allexpanded = parseInt(0);
//add classes
$map.find(‘li:last-child’).addClass(‘last’);
$map.addClass(‘sitemap’);
//hide all lists apart from the root list by default
$map.find(‘ul’).hide();
//repeat through all lists
$roots.each(function(){
//if list-item contains a child list
if ($(this).children(‘ul’).length > 0) {
//add expand/contract control
$(this).addClass(‘root’).prepend(”);
}
});
//when expand is clicked
$(‘span.expand’).toggle(
function(){
// if clicked once, find all lists inside and expand
$(this).toggleClass(‘contract’).nextAll(‘ul’).slideDown(“fast”);
allexpanded += 1;
},
function(){
//if it’s clicked again, find all inside lists and hide them
$(this).toggleClass(‘contract’).nextAll(‘ul’).slideUp(“fast”);
allexpanded -= 1;
}
);
// opener/closer
$(‘#sitemap_openall’).click(function() {
$(‘.sitemap’).find(‘ul’).slideDown(‘fast’);
// show hide all button
$(‘#sitemap_openall’).hide();
$(‘#sitemap_closeall’).show();
});
$(‘#sitemap_closeall’).click(function() {
$(‘.sitemap’).find(‘ul’).slideUp(‘fast’);
// show expand all button
$(‘#sitemap_closeall’).hide();
$(‘#sitemap_openall’).show();
});
});
};
Then after all of that you’d need to do something with the variable allexpanded (i.e. if it’s equal to the number of nested lists you have then show the close all button, else show the expand all button).
Brilliant, works perfect now. Here’s my final solution:
jQuery.fn.sitemap = function() {
return this.each(function(){
// close closeall link
$('#sitemap_closeall').hide();
//set variables
var $map = $(this);
var $roots = $map.find('li');
var allexpanded = parseInt(0);
var expanders = parseInt(0);
//add classes
$map.find('li:last-child').addClass('last');
$map.addClass('sitemap');
//hide all lists apart from the root list by default
$map.find('ul').hide();
//repeat through all lists
$roots.each(function(){
//if list-item contains a child list
if ($(this).children('ul').length > 0) {
//add expand/contract control
$(this).addClass('root').prepend('');
//increment the counter
expanders += 1;
}
});
//when expand is clicked
$('span.expand').toggle(
function(){
// if clicked once, find all lists inside and expand
$(this).toggleClass('contract').nextAll('ul').slideDown("fast");
allexpanded += 1;
check_controls();
},
function(){
//if it's clicked again, find all inside lists and hide them
$(this).toggleClass('contract').nextAll('ul').slideUp("fast");
allexpanded -= 1;
check_controls();
}
);
// opener/closer
$('#sitemap_openall').click(function() {
$('.sitemap').find('ul').slideDown('fast');
allexpanded += 1;
check_controls();
});
$('#sitemap_closeall').click(function() {
$('.sitemap').find('ul').slideUp('fast');
allexpanded -= 1;
$('#sitemap_closeall').hide();
});
// check the open/close controls
function check_controls(){
if(allexpanded > 0) {
$('#sitemap_closeall').show();
} else {
$('#sitemap_closeall').hide();
}
if(allexpanded >= expanders){
$('#sitemap_openall').hide();
} else {
$('#sitemap_openall').show();
}
}
});
};
To trigger the events, I simply added
.sitemap, #sitemap_controls {
float: left;
}
to the styles and
<ul id="sitemap_controls">
<li id="sitemap_openall"><a href="javascript:void(0)">Open all</a></li>
<li id="sitemap_closeall"><a href="javascript:void(0)">Close all</a></li>
to the html.
Thanks again for your friendly support and greetings from Germany.