Thursday, May 26, 2011

Remembering jQuery UI Accordion Selections

The jQuery UI accordion widget is a great way to group logical sections of your page together.  The accordion widget is actually an alternative to the tabs widget - they're both just layout containers.

So if I'm using an accordion widget as the main layout component on one of my pages, it would be nice if we had a way to preserve the selected section.  My approach to this is by using hashes in the URL.  Below is the basic HTML markup:

<html>
    <head>
        
        <title>jQuery UI Accordion Selection</title>
        
        <link type="text/css" href="jqueryuitheme.css" rel="stylesheet"/>
        <script type="text/javascript" src="jquery.min.js"></script>
        <script type="text/javascript" src="jqueryui.min.js"></script>
        <script type="text/javascript" src="accordion.js"></script>
        
    </head>
    <body>
        
        <div id="accordion">
            <h3><a href="#section1">Section 1</a></h3>
            <div>
                Section 1 content...
            </div>
            <h3><a href="#section2">Section 2</a></h3>
            <div>
                Section 2 content...
            </div>
            <h3><a href="#section3">Section 3</a></h3>
            <div>
                Section 3 content...
            </div>
        </div>
                    
    </body>
</html>

And here is the accordion.js file:

$(document).ready(function(){
    
    //Get the selected accordion index, based on the URL hash.
    var index = $('#accordion h3').index(
                $('#accordion h3 a[href="'+window.location.hash+'"]')
                .parent());
                
    //The index will be -1 if there is no hash in the URL.  This
    //is necessary if we want the first section expanded by default.
    if (index < 0){
        
        index = 0;
        
    }
    
    //The change event handler will add the hash to the URL when
    //a section is selected.
    var change = function(event, ui){
    
        window.location.hash = ui.newHeader.children('a').attr('href');
        
    }
       
    //Build the accordion, using the URL hash selection and the
    //change event handler.
    $('#accordion').accordion({active: index,
                               change: change});
                                   
});

We can now use the URL hash to set the accordion selection.  This is how it works:

The first variable - index - is the index of the accordion selection.  So 0 is the first accordion section, 1 the second, and so on.  This selector is a little tricky because inside the #accordion div, we have alternating h3 and div elements, so the h3 indexes aren't linear.  To get around this, we have two selectors.  The first one filters out all the h3 elements we're interested in.  Now that we have only h3 elements, we can call index() on them - we won't get invalid indexes due to intermingled divs.  We pass index() the h3 element we're interested in, namely, the one with the a that has an href matching the current URL hash.

Phew, so now we've got an index we can use to select the appropriate section.  Or do we?  If the user is visiting this page for the first time, there won't be any hash in the URL.  This means the value of index will be -1.  We don't want that, so we give it a value of 0 if there is now real index to use.  This way the first section is active be default.  This is the expected behaviour, I would say, 99% of the time.

Next, we define an event handler - change - that is triggered when the user changes accordion sections.  All this handler does is set the URL hash, allowing the default functionality associated with changing sections to run normally.  For example, changing to section two in our example will add #section2 to the URL.

Finally, we build the accordion widget using the selected index and the change event handler.  This isn't a perfect solution, but it does solve some headaches with accordion widgets - like the back button and bookmarks.