Column Navigation

Description: The column navigation is a navigation menu that renders in columns, whenever you click on a selecttion it either performs an action or expands a new column with children elements.

508 Guidelines: Just tab through the menu as regular buttons or use your arrow keys to navigate trhought the menu.

<div class="nav-list" role="application" title="This is a column navigation. Press enter to start browsing the options. Use your arrow keys to navigate through them and press enter, spacebar or the right arrow to make a selection. To exit navigation press The escape key."></div>
            
Copy
<div class="nav-list" role="application" title="This is a column navigation. Press enter to start browsing the options. Use your arrow keys to navigate through them and press enter, spacebar or the right arrow to make a selection. To exit navigation press The escape key."></div><script type="text/javascript">
$.makeId = function() {
    var
        text = "",
        possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

    for (var i = 0; i < 5; i++) {
        text += possible.charAt(Math.floor(Math.random() * possible.length));
    }
    return text;
};
$.fn.navlist = function(a) {
    var
        args = $.extend({
            items: [], // an object that contains the items of the nav.
            growFunction: function() {}, // gets triggered when the nav expands to a new column
            shrinkFunction: function() {}, // gets triggered when the nav shrinks to the previous column,
            width: null,
            height: null
        }, a),
        deployEventListener = function(f) {
            // we bind this only once to the document to prevent multiple binding of the same event over and over.
            // We re-bind only if necessary within the same function
            // we also provide a namespace with the id of the list to overwrite the same eventlisterner if fired more than once
            // just once--V
            $(document).one('keydown.navlist', function(e) {
                var el;
                switch (e.which) {
                    case 37: // left
                        var theUl = f.parent().prev();
                        removeHigherLevels(theUl);
                        el = theUl.find('.selected');
                        clearSelected(el);
                        break;
                    case 38: // up
                        el = f.prev().not('.nav-list-heading');
                        break;
                    case 13:
                    case 32:
                    case 39: // right
                        f.trigger('click');
                        el = f.parent().next().children('li.nav-list-action:first');
                        break;
                    case 40: // down
                        el = f.next();
                        break;
                    case 27: //esc
                        el = f.parent().parent();
                        el.find('li').removeAttr('tabindex');
                        break;
                    default:
                        return; // exit this handler for other keys
                }
                if (el.length) {
                    el.focus();
                } else {
                    deployEventListener(f);
                }
                e.preventDefault(); // prevent the default action (scroll / move caret)

            });
        },
        removeEventListener = function() {
            $(document).off('keydown.navlist');
        },
        removeHigherLevels = function(myUl) {
            // get the index of my ul and remove thos that are after it
            var ulIndex = myUl.index();
            myUl.parent().children(':gt(' + ulIndex + ')').remove();
            if (typeof(args.shrinkFunction) == 'function') args.shrinkFunction();
        },
        clearSelected = function(b) {
            b.parent().find('.selected').removeClass('selected');
        },
        openList = function(b) {
            b.addClass('selected');
            var bid = b.attr('id');
            var myUl = b.closest('ul');
            removeHigherLevels(myUl);
            if ($('#navlist-' + bid).length === 0) {
                var nuUl = genItemsHtml(b.data('children'), bid);
                myUl.after(nuUl);
            }
        },
        createButton = function(ob, ind, heading) {
            var buttonLabel = (heading ? '<p class="sr-only">' + heading + '</p>' : '') + ob.label;
            var button = $(document.createElement('li')).attr({
                'class': 'nav-list-action',
                'data-item-id': ind,
                'id': $.makeId(),
                'tabindex':'-1'
            }).html(buttonLabel).focus(function() {
                deployEventListener($(this));
            }).blur(function() {
                removeEventListener();
            });
            if (typeof(ob.childList) != 'undefined') {
                button.addClass('has-children').data('children', ob.childList).click(function() {
                    clearSelected($(this));
                    openList($(this));
                    if (typeof(args.growFunction) == 'function') args.growFunction();
                }).html(buttonLabel + '<span class="sr-only"> Press enter, spacebar, or the right arrow key to expand the sub menu.</span>');
            } else {
                button.click(function() {
                    var t = $(this);
                    var ul = t.closest('ul');
                    clearSelected($(this));
                    removeHigherLevels(ul);
                    if ((typeof(ob.action) == 'function') && (ob.action)) {
                        ob.action();
                    }
                });
            }
            return button;
        },
        genItemsHtml = function(itemsObj, parentId) {
            var ul = $(document.createElement('ul')).attr('id', 'navlist-' + parentId).addClass('list-block pull-left');
            var heading = (((typeof(itemsObj.heading) !== 'undefined') && itemsObj.heading) ? itemsObj.heading : null);
            if (args.width) {
                ul.width(args.width);
            }
            if (heading) ul.append('<li aria-hidden="true" class="nav-list-heading">' + itemsObj.heading + '</li>');
            for (var i = 0; i < itemsObj.items.length; i++) {
                var li = createButton(itemsObj.items[i], i, heading);
                ul.append(li);
            }
            return ul;
        };
    return this.each(function() {
        var t = $(this);
        if (args.height) t.height(args.height);

        var enterNavEl = function() {
            $(document).one('keydown.activateColumnNav', function(e) {
                switch (e.which) {
                    case 39: //right
                    case 40: // down
                        t.next().focus();
                        break;
                    case 37:
                    case 38:
                        t.prev().focus();
                        break;
                    case 13:
                        // make elements navigable
                        t.find('li.nav-list-action').attr('tabindex', '0');
                        t.find('.nav-list-action:first').focus();
                        break;
                    default:
                        //re-add event listnener
                        enterNavEl();
                        return;
                }
                e.preventDefault(); // prevent the default action (scroll / move caret)
            });
        };
        var escaper = '<span tabindex="-1"></span>';
        t.attr({
            'tabindex':'0'
        }).focus(function() {
            enterNavEl();
        }).blur(function() {
            $(document).off('keypress.activateColumnNav');
        }).append(genItemsHtml(args.items, $.makeId())).after(escaper).before(escaper);
    });
};
$(document).ready(function() {

    var list = {
        'heading': 'Column 1',
        'items': [{
            'label': 'one',
            'childList': {
                'heading': 'one children',
                'items': [{
                    'label': 'one.one'
                }, {
                    'label': 'one.two',
                    'childList': {
                        'heading': 'one.two children',
                        'items': [{
                            'label': 'one.two.one'
                        }, {
                            'label': 'one.two.two'
                        }, {
                            'label': 'one.two.three'
                        }, {
                            'label': 'one.two.four'
                        }, {
                            'label': 'one.two.five (click me)',
                            'action': function() {
                                alert('You clicked on one.two.five');
                            }
                        }]
                    }
                }, {
                    'label': 'one.three'
                }, {
                    'label': 'one.four'
                }]
            }
        }, {
            'label': 'two'
        }, {
            'label': 'three',
            'childList': {
                'heading': 'three children',
                'items': [{
                    'label': 'three.one'
                }, {
                    'label': 'three.two'
                }, {
                    'label': 'three.three'
                }, {
                    'label': 'three.four'
                }]
            }
        }, {
            'label': 'four (clickable)',
            'action': function() {
                alert('Thanks for clicking!');
            }
        }]
    };
    $('.nav-list').navlist({
        items: list,
        growFunction: function() {
            console.log('growing');
        },
        shrinkFunction: function() {
            console.log('shrinking');
        },
        width: '170px',
        height: '200px'
    });
});
</script>
            
Copy