How to build persistent expandable/collapsible navigation with jQuery and the jQuery Cookie plugin
If you have any experience with jQuery, one of the first things you probably learnt to do was show and hide content. jQuery makes this very simple with it’s built in show() and hide() methods, but sometimes it can be useful for the show/hide state of your items to persist from page to page. This allows the user to have a more personal experience of your site or web application by refining the information they are presented with to what is important to them.
In this tutorial I will build a jQuery plugin which remembers the closed/open state a side navigation menu, which conveniently follows the structure of the sidebar navigation in Wordpress.
The finished plugin will work with any list that follows this structure, begin by creating a new HTML page with the following markup in it:
-
Archives
- Nav item
- Nav item
- Nav item
- Nav item
- Nav item
-
Categories
- Nav item
- Nav item
- Nav item
- Nav item
- Nav item
This plugin requires the jQuery cookie plugin, download it and save it as in your root folder.
Create a new file called collapsibleNav.js and inlcude the following files in the document <head> tag:
Open up your new collapsibleNav.js file, start the plugin and give it some default variables:
jQuery.fn.collapsibleNav = function(options) {
var defaults = {
hideByDefault: 'categories,archives', //ids of elements you want hidden by default
clickController: 'h2', //your heading elements
slideSpeed: 150 //speed of animation
};
//initial variables
var opts = jQuery.extend(defaults, options),
$list = jQuery(this), //reference list passed to plugin
$heading = $list.find(opts.clickController), //cache heading element
$listItems = $list.children(), //get each top level list item
closedItems; //define var for items to be stored in cookie
Now create the following function. This will be called each time a list heading is clicked and will add the id of the list item to the cookie, or remove it if it is already in the cookie:
//function that is called when slidable is clicked
function updateArray(e) {
//get id of element clicked for adding to closed items array
var eId = jQuery(e).attr('id');
//look for element in cookie array
var arrPos = jQuery.inArray(eId, closedItems);
if (arrPos == -1) {
//element is not in array, so add it
closedItems.push(eId);
} else {
//element is in array, so remove it
closedItems.splice(arrPos, 1);
}
//update cookie
jQuery.cookie('closedItems', closedItems, { path: '/', expires: 365 });
}
We now need to check if the jQuery Cookie plugin has been included, and if so do the following: if there is no cookie with our closed items in it: create a new cookie with the elements you want hidden by default in, if there is a cookie present: apply a “closed” class to all of the list-items with their id in the cookie, then hide the child ul element of list items with a “closed” class.
//remember closed/open state of nav items
//check that the cookie plugin is available
if (jQuery.cookie) {
/*
if no cookie called "closedItems" exists, then create one with
elements you want hidden by default.
'1' is given as first value to prevent cookie from being
deleted if it contains no ids
*/
if (!jQuery.cookie('closedItems')) {
jQuery.cookie('closedItems', '1,'+opts.hideByDefault, { path: '/', expires: 365 });
}
//if cookie called "closedItems" exists
if (jQuery.cookie('closedItems')) {
//split cookie into array
closedItems = jQuery.cookie('closedItems').split(',');
//iterate through array and apply "closed" class to each element within it
for (var i = 0; i < closedItems.length; ++i) {
jQuery('#' + closedItems[i]).addClass('closed');
}
//hide child ul for list items that have a class of closed
jQuery('.closed').children('ul').hide();
}
}
We will now add an icon to each heading to indicate that it can be clicked to show/hide content. It is important to add this with JavaScript and not write it directly into the markup, as we only want people with JavaScript enabled to know that the headings can be clicked.
//add span to each heading
$heading.append(' ');
We now need to wire up each heading so that when it is clicked it hides or shows the content and sends the element clicked on to our updateArray function. We will finish by returning the jQuery object so that our list can be used again via chaining.
$heading.click(function(){
var $target = jQuery(this),
$parent = $target.parent();
//toggle closed class
$parent.toggleClass('closed');
//show/hide content
$target.next().slideToggle(opts.slideSpeed);
//update cookie with current list-item
updateArray($parent);
return false;
});
return this;
};
Finally we need to trigger the plugin on our list, add the following javascript between script tags in your HTML document after all of the other script tags:
jQuery(function(){
jQuery('#aside').collapsibleNav();
});
I have added some basic css in the example, but I'm sure you can come up with something more creative!
Thats it!
I hope you enjoyed this post, if you did, please share it with others!
hey thanks!
this proved helpful.
hi. Thanks for writing this.. it is the closest I have found to help me figure out how to use the cookie plug-in but still I am a bit lost. I am trying to achieve much simpler functionality.. just have a message box that is on by default but if someone x’s out of it then it disappears (at least until cookies are cleaned). You have much greater functionality with the toggle but mine is just a one time deal so I know it will be much simpler code…having trouble whittling yours down… How would I create the closedItems cookie when someone uses a button with the .close class? If I had that then I guess all I would need is this snippet of your code : /remember closed/open state of nav items
Thanks for sharing and any help to get me moving along would be much appreciated.
Very helpful! Thanks!!!
Excellent, thanks for the clear explanation and examples.
Thanks for the example. It was easy to implement and customize. I am however have a slight issue. One of my lists are getting hung open upon entering the site, even after deleting cookies. Any thoughts? Thanks.
lol, pay no mind to the previous comment. I figured it out. Thanks again for the tutorial, it’s much appreciated!
Actually….sorry. I just have one question. Is there a way to make list items close as you click others? I am using this with multiple lists and they have gotten pretty long and it would be a little more user friendly if each list closed as another opened. I’ve tried to mess with it a little but to no avail. Any guidance would be greatly appreciated.
Scott, many thanks for your post. Really helped me with a rather simple version of your script.
Posted linkback to your post from here:
http://web-kreation.com/index.php/tutorials/nice-clean-sliding-login-panel-built-with-jquery/ comment #281
Scott, great tutorial! One of the best I’ve found, hands down. I do have a question. While it seems the code works on every browser I’ve thrown at it, except one aspect in IE.
When a user has ‘Compatibility View’ mode on in IE it seems it places the “plus/minus” span on the next line below the tag.
Is there any work around to that? Thanks.
Hi Scott, Thanks for the tutorial, I’m using it to set up a menu for wordpress using the wp_list_pages, and I have changed the clickController to a, to make it work with this. The only problem is that when the page is reloaded if both lists are shut they open, or if only one is shut it reopens. I’m trying to understand how this works, and I think it may be because the lists also have links in them, is there a simple way to work around this?
Many thanks,
Caroline
Hello Scott, fantastic tutorial, thanks for writing it.
is there any way to hide by default all IDs of the Nav, rather than declare the individual IDs in the JS file?
hideByDefault: ‘categories,archives, etc, etc’,
regards,
andi