Multiselect Accordion

HTML Source Code

<div class="accordion" role="navigation">
    <ul id="mainnav">
        <li class="level1 parent">
            <span tabindex="0" role="button" id="prospectHeader" class="category">Prospective Students</span>
            <ul class="accordionSubMenu" aria-labelledby="prospectHeader">
                <li class="first level2"><a href="#">Prospective Students Home Page</a></li>
                <li class="level2"><a href="#">Admissions</a></li>
                <li class="last level2"><a href="#">Financing Your Education</a></li>
            </ul>
        </li>
        <li class="level1 parent">
            <span role="button" id="studentHeader" class="category" tabindex="0">Students</span>
            <ul class="accordionSubMenu" aria-labelledby="studentHeader">
                <li class="first level2"><a href="#">Students Home Page</a></li>
                <li class="level2"><a href="#">Records</a></li>
                <li class="level2"><a href="#">Student Life</a></li>
                <li class="level2"><a href="#">Student Organizations</a></li>
                <li class="level2"><a href="#">Technology</a></li>
                <li class="last level2"><a href="#">Library</a></li>
            </ul>
        </li>
        <li class="level1 parent">
            <span role="button" id="facultyHeader" class="category" tabindex="0">Faculty</span>
            <ul class="accordionSubMenu" aria-labelledby="facultyHeader">
                <li class="first level2"><a href="#">Faculty Home</a></li>
                <li class="level2"><a href="#">Directory</a></li>
                <li class="level2"><a href="#">News</a></li>
                <li class="last level2"><a href="#">Publications</a></li>
            </ul>
        </li>
        <li class="level1 parent">
            <span role="button" id="alumniHeader" class="category" tabindex="0">Alumni</span>
            <ul class="accordionSubMenu" aria-labelledby="alumniHeader">
                <li class="first level2"><a href="#">Alumni Home Page</a></li>
                <li class="level2"><a href="#">Benefits and Services</a></li>
                <li class="level2"><a href="#">Giving</a></li>
                <li class="level2"><a href="#">Alumni Association</a></li>
                <li class="last level2"><a href="#">Volunteer Opportunities</a></li>
            </ul>
        </li>
    </ul>
</div>

JavaScript Source Code

Dependencies:

  • JQuery
var iOS = (navigator.userAgent.match(/(iPad|iPhone|iPod)/g) ? true : false );
var titleAttr = (iOS) ? 'title' : 'data-title';
var initNavMenu = function() {
    //add keyboard/mouse handler to top level items; add kbd handler to children

    $('#mainnav > li').keydown(menuTopKeyPress);
    $('#mainnav ul li').keydown(menuChildKeyPress);
    $('#mainnav > li > span').click(menuTopClick);
    //add ARIA tags
    //
    //aria-haspopup="true"
    //aria-controls="id_of_subnav"
    //aria-expanded="false" (changes to true when sub-nav is visible)

    $('#mainnav > li').has('ul').children("span").attr("aria-expanded", "false")
                                              .attr(titleAttr, 'click to display');


    //ARIA tags for the submenu
    //
    //role="group"
    //aria-expanded="false" (changes to true when sub-nav is visible)
    //aria-labelledby="id_of_top_level_link"

    $('#mainnav > li ul').attr("aria-expanded", "false")
                         .attr("role", "group");
};

var menuTopClick = function(event) {
    var subMenu = $(event.currentTarget).parent().find("ul");
    if (!subMenu.hasClass("expanded")) {
        expandMenu(subMenu);
    } else {
        collapseMenu(subMenu);
    }
    event.preventDefault();
};

var menuTopKeyPress = function(event) {
    var subMenu = $(event.currentTarget).find("ul");
    if (event.which === 13 || event.which === 32) { //enter key
        if (!subMenu.hasClass("expanded")) {
            expandMenu(subMenu);
        } else {
            collapseMenu(subMenu);
        }
    } else if (event.which === 37) { //left arrow key
        //if there's a previous one, go to that; otherwise nothing
        var prevItem = $(event.currentTarget).prev("li").children("span");
        if (prevItem.length > 0) {
            prevItem.focus();
        };
    } else if (event.which === 39 || event.which === 40) { //right arrow key
        //if there's a next one, go to that; otherwise nothing
        var nextItem = $(event.currentTarget).next("li").children("span");
        if (nextItem.length > 0) {
            nextItem.focus();
        };
    } else if (event.which === 38) { //up arrow
        var prevItem = $(event.currentTarget).prev("li");
        if (prevItem.length > 0) {
            if (prevItem.children("ul").hasClass("expanded")) {
                prevItem.find("ul li").last().children("a").focus();
            } else {
                prevItem.children("span").focus();
            }
        }
        event.preventDefault();
    } else if (event.which === 27) { // esc
        //close the menu
        if (subMenu.hasClass("expanded")) {
            collapseMenu(subMenu);
        }
        event.preventDefault();
    }
};


var menuChildKeyPress = function(event) {
    if (event.which === 13 || event.which === 32) { //enter key or space key
        event.stopPropagation();
    } else if (event.which === 40) { //down arrow
        //go to the next item if there is one; otherwise go to next menu if there is one
        var nextItem = $(event.currentTarget).next("li");
        if (nextItem.length > 0) {
            nextItem.children("a").focus();
        } else {
            var nextMenu = $(event.currentTarget).parent().parent().next("li");
            if (nextMenu.length > 0) {
                nextMenu.children("span").focus();
            }
        }
        event.stopPropagation();
        event.preventDefault();
    } else if (event.which === 38) { //up arrow
        //go to prev item if there is one; otherwise, go up to the parent menu
        var prevItem = $(event.currentTarget).prev("li");
        if (prevItem.length > 0) {
            prevItem.children("a").focus();
        } else {
            $(event.currentTarget).parent().closest("li").children("span").focus();
        }
        event.stopPropagation();
        event.preventDefault();
    }
};


var expandMenu = function(menu) {
    menu.toggleClass("expanded", true);
    menu.attr("aria-expanded", "true");
    menu.parent().closest("li").addClass("here");
    menu.parent().closest("li").children("span").attr(titleAttr, "select to hide").attr("aria-expanded", "true").focus();
    menu.parent().closest("li").children("ul").show();
};

var collapseMenu = function(menu) {
    //Close the menu, and if focus was inside it, put focus on the menu itself
    var focusedChild = menu.find(":focus");
    if (focusedChild.length > 0) {
        menu.parent().children("span").focus();
    }
    menu.parent().removeClass("here");
    menu.parent().children("span").attr("aria-expanded", "false").attr(titleAttr, "select to display");
    menu.toggleClass("expanded", false).attr("aria-expanded", "false");
    menu.parent().children("ul").hide();
};

$(document).ready(initNavMenu);

CSS Source Code

.accordion ul {
    font-family:"Trebuchet MS", Arial, Helvetica, sans-serif;
    list-style:none;
    margin:0;
    padding:0;
    width:300px;
}
.accordion ul.accordionSubMenu {
    display:none;
}
.accordion ul .here ul {
    display:block;
}
.accordion li {
    list-style:none;
    margin:0;
    padding:0;
}
.accordion li .category {
    background:#375898;
    border:1px solid #fff;
    color:#fff;
    display:block;
    padding:3px 6px;
}
.accordion li li {
    background:#fff;
    color:#375898;
}
.accordion li a {
    border-bottom:1px dashed #375898;
    color:#375898;
    display:block;
    padding:3px 5px 3px 20px;
    text-decoration:none; 
}
.accordion li.last a {
    border-bottom:none;
}
.accordion li .category:hover {
    background:#4b6fb6;
    color: #FFC;
    cursor:pointer;
}
.accordion li a:hover {
    background:#ffc;
    cursor:pointer;
}
.expanded {
    display: block;
}
.accordion li.self a {
    background:#FFC;
    color:#990000;
}

Copy and Paste Full Page Example