var keyCodeMap = { 48:"0", 49:"1", 50:"2", 51:"3", 52:"4", 53:"5", 54:"6", 55:"7", 56:"8", 57:"9", 59:";", 65:"a", 66:"b", 67:"c", 68:"d", 69:"e", 70:"f", 71:"g", 72:"h", 73:"i", 74:"j", 75:"k", 76:"l", 77:"m", 78:"n", 79:"o", 80:"p", 81:"q", 82:"r", 83:"s", 84:"t", 85:"u", 86:"v", 87:"w", 88:"x", 89:"y", 90:"z", 96:"0", 97:"1", 98:"2", 99:"3", 100:"4", 101:"5", 102:"6", 103:"7", 104:"8", 105:"9" } jQuery.fn.setup_navigation = function(settings) { var $ = jQuery.noConflict(); settings = jQuery.extend({ menuHoverClass: 'show-menu', }, settings); // Add ARIA role to menubar and menu items $(this).attr('role', 'menubar').find('li').attr('role', 'menuitem'); var top_level_links = $(this).find('> li > a'); // Added by Terrill: (removed temporarily: doesn't fix the JAWS problem after all) // Add tabindex="0" to all top-level links // Without at least one of these, JAWS doesn't read widget as a menu, despite all the other ARIA //$(top_level_links).attr('tabindex','0'); // Set tabIndex to -1 so that top_level_links can't receive focus until menu is open $(top_level_links).next('ul') .attr('data-test','true') .attr({ 'aria-hidden': 'true', 'role': 'menu' }) .find('a') .attr('tabIndex',-1); // Adding aria-haspopup for appropriate items $(top_level_links).each(function(){ if($(this).next('ul').length > 0) $(this).parent('li').attr('aria-haspopup', 'true'); }); $(top_level_links).hover(function(){ $(this).closest('ul') .attr('aria-hidden', 'false') .find('.'+settings.menuHoverClass) .attr('aria-hidden', 'true') .removeClass(settings.menuHoverClass) .find('a') .attr('tabIndex',-1); $(this).next('ul') .attr('aria-hidden', 'false') .addClass(settings.menuHoverClass) .find('a').attr('tabIndex',0); }); $(top_level_links).focus(function(){ $(this).closest('ul') // Removed by Terrill // The following was adding aria-hidden="false" to root ul since menu is never hidden // and seemed to be causing flakiness in JAWS (needs more testing) // .attr('aria-hidden', 'false') .find('.'+settings.menuHoverClass) .attr('aria-hidden', 'true') .removeClass(settings.menuHoverClass) .find('a') .attr('tabIndex',-1); $(this).next('ul') .attr('aria-hidden', 'false') .addClass(settings.menuHoverClass) .find('a').attr('tabIndex',0); }); // Bind arrow keys for navigation $(top_level_links).keydown(function(e){ if(e.keyCode == 37) { e.preventDefault(); // This is the first item if($(this).parent('li').prev('li').length == 0) { $(this).parents('ul').find('> li').last().find('a').first().focus(); } else { $(this).parent('li').prev('li').find('a').first().focus(); } } else if(e.keyCode == 38) { e.preventDefault(); if($(this).parent('li').find('ul').length > 0) { $(this).parent('li').find('ul') .attr('aria-hidden', 'false') .addClass(settings.menuHoverClass) .find('a').attr('tabIndex',0) .last().focus(); } } else if(e.keyCode == 39) { e.preventDefault(); // This is the last item if($(this).parent('li').next('li').length == 0) { $(this).parents('ul').find('> li').first().find('a').first().focus(); } else { $(this).parent('li').next('li').find('a').first().focus(); } } else if(e.keyCode == 40) { e.preventDefault(); if($(this).parent('li').find('ul').length > 0) { $(this).parent('li').find('ul') .attr('aria-hidden', 'false') .addClass(settings.menuHoverClass) .find('a').attr('tabIndex',0) .first().focus(); } } else if(e.keyCode == 32) { // If submenu is hidden, open it e.preventDefault(); $(this).parent('li').find('ul[aria-hidden=true]') .attr('aria-hidden', 'false') .addClass(settings.menuHoverClass) .find('a').attr('tabIndex',0) .first().focus(); } else if(e.keyCode == 27) { e.preventDefault(); $('.'+settings.menuHoverClass) .attr('aria-hidden', 'true') .removeClass(settings.menuHoverClass) .find('a') .attr('tabIndex',-1); } else { $(this).parent('li').find('ul[aria-hidden=false] a').each(function(){ if($(this).text().substring(0,1).toLowerCase() == keyCodeMap[e.keyCode]) { $(this).focus(); return false; } }); } }); var links = $(top_level_links).parent('li').find('ul').find('a'); $(links).keydown(function(e){ if(e.keyCode == 38) { e.preventDefault(); // This is the first item if($(this).parent('li').prev('li').length == 0) { $(this).parents('ul').parents('li').find('a').first().focus(); } else { $(this).parent('li').prev('li').find('a').first().focus(); } } else if(e.keyCode == 40) { e.preventDefault(); if($(this).parent('li').next('li').length == 0) { $(this).parents('ul').parents('li').find('a').first().focus(); } else { $(this).parent('li').next('li').find('a').first().focus(); } } else if(e.keyCode == 27 || e.keyCode == 37) { e.preventDefault(); $(this) .parents('ul').first() .prev('a').focus() .parents('ul').first().find('.'+settings.menuHoverClass) .attr('aria-hidden', 'true') .removeClass(settings.menuHoverClass) .find('a') .attr('tabIndex',-1); } else if(e.keyCode == 32) { e.preventDefault(); window.location = $(this).attr('href'); } else { var found = false; $(this).parent('li').nextAll('li').find('a').each(function(){ if($(this).text().substring(0,1).toLowerCase() == keyCodeMap[e.keyCode]) { $(this).focus(); found = true; return false; } }); if(!found) { $(this).parent('li').prevAll('li').find('a').each(function(){ if($(this).text().substring(0,1).toLowerCase() == keyCodeMap[e.keyCode]) { $(this).focus(); return false; } }); } } }); // Hide menu if click or focus occurs outside of navigation $(this).find('a').last().keydown(function(e){ if(e.keyCode == 9) { // If the user tabs out of the navigation hide all menus $('.'+settings.menuHoverClass) .attr('aria-hidden', 'true') .removeClass(settings.menuHoverClass) .find('a') .attr('tabIndex',-1); } }); $(document).click(function(){ $('.'+settings.menuHoverClass).attr('aria-hidden', 'true').removeClass(settings.menuHoverClass).find('a').attr('tabIndex',-1); }); $(this).click(function(e){ e.stopPropagation(); }); };