Thursday, January 30, 2014

Add Anchors to jQuery UI Accordion Headers

If you want jQuery UI's accordion (version 1.10.0+) to be able to open the desired section based on the window hash, you are stuck with using a hash that looks something like this "#ui-accordion-accordion-header-1", and well, the panel won't open. It requires some extra coding.

With this code you can add a link within the header which opens the panel and updates the hash. It also opens the panel based on the hash after the accordion initializes.
I originally answered this code on Stackoverflow, but thought it would be nice to have a write up with demo.
var hashId = 0,
    $accordion = $('#accordion');
if (window.location.hash) {
    $accordion.children('h3').each(function (i) {
        var txt = this.textContent.toLowerCase().replace(/\s+/g, '_');
        if ( txt === window.location.hash.slice(1) ) {
            hashId = i;
        }
    });
}
This above first block of code sets a default hash id (zero-based index of the currently open header), then looks within each accordion header's text and checks if it matches the current window hash. The line with this.textContent (may not work in older browsers, so change it to $(this).text() as needed), gets the header text and replaces any spaces, tabs, etc that don't work within an element ID. If the header text is really long, contains punctuation like commas, exclamation points, etc, then you may need to truncate the text with something like this:
var txt = $(this).text()
    .toLowerCase()
    .replace(/[^a-z\s]/g,'')
    .replace(/\s+/g, '_')
    .substring(0,10)
This code replaces any text that isn't a letter in the alphabet, or a space, replaces spaces with an underscore, then saves the first 10 characters.
Then it compares it to the window hash. The .slice(1) removes the hash ("#") before comparing it to the accordion header text.
The hash id is then set using the header index.

Now we can initialize the accordion...
$accordion.accordion({
    active: hashId,
    animate: false,
    heightStyle: 'content',
    collapsible: true,
    create: function (event, ui) {
        $accordion.children('h3').each(function (i) {
            // set id here because jQuery UI sets them as "ui-accordion-#-header-#"
            this.id = this.textContent.toLowerCase().replace(/\s+/g, '_');
            // add the anchor
            $(this).before('');
        });
        $accordion.find('.accordion-link').click(function () {
            // the active option requires a numeric value (not a string, e.g. "1")
            $accordion.accordion( "option", "active", $(this).data('index') );

            // uncomment out the return false below to prevent the header jump
            // return false;
        });
    }
});

Set the active option to initialize the accordion with the hash tag matching section open, it uses a zero-based index.

The animate option is set to false because when the user clicks on a link, the page jumps to the section... then the animation opens the panel. It sometimes animates so the entire page scrolls up and the section header is no longer at the top of the page.

The heightStyle and collapsible options are set to my personal preference, change them as desired.

Now the create option needs to contain code to add the links and make them clickable. It cycles through all of the section headers, replaces the id to match the header text - use the code that was used to match the text in the first block of code, so the code will compare the text parsed in the same way - then add the link before the header. If the link is added after the header, the accordion messes up because it is set to look for the element (a <div>) immediately following the header. The link contains a "data-index" attribute which contains the header zero-based index used to set the active accordion panel.

And finally the code to make the link clickable. After setting the "active" accordion option, you can chose to return false, or not. If not included, the selected header will jump to the top of the browser page and if included, the page will not scroll.

Lastly, you'll need to include some css to position the link image within the section header:
.ui-accordion {
    position: relative;
}
.ui-accordion .accordion-link {
    position: absolute;
    right: 2%;
    margin-top: 16px; /* adjust as needed to vertically center the icon */
    z-index: 1;
    width: 12px; /* approx 12x12 link icon */
    height: 12px;
    background: url(http://i57.tinypic.com/fyfns4.png) center center no-repeat;
}
Here is a demo of what it looks like: or, try this full screen version of the same demo