Difference between revisions of "ARIA Menu"

From Level Access Web Labs
Jump to navigation Jump to search
 
(26 intermediate revisions by 5 users not shown)
Line 1: Line 1:
<html lang="en-US">
+
This example is not working correctly.
<head>
 
<meta charset="utf-8">
 
<title>ARIA Menu Example</title>
 
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
 
<style>
 
#nav a {
 
display: block;
 
text-decoration: none;
 
color: black;
 
padding: 2px 1em;
 
}
 
#nav a:hover, #nav a:focus {
 
  background-color: #340449 !important;
 
  color: white !important;
 
  outline: none;
 
}
 
ul.menubar {
 
  margin: 0;
 
padding: 0 .25em;
 
list-style: none;
 
font-size: 1em;
 
height: 1.85em;
 
width: 50em;
 
border: 1px solid black;
 
background: #ddd;
 
}
 
ul.menubar > li {
 
float: left;
 
display: inline; position: relative;
 
margin: 0;
 
padding: .25em .35em;
 
height: 1.25em;
 
width: 10em;
 
}
 
ul.menu {
 
position: absolute;
 
left: 0;
 
top: 1.75em;
 
display: none;
 
list-style: none;
 
margin: 0;
 
padding: 0;
 
font-weight: normal;
 
font-size: 100%;
 
border: 1px solid black;
 
background-color: #ccc;
 
width: 10em;
 
}
 
ul.menu li {
 
float: none;
 
display: block;
 
white-space: nowrap;
 
}
 
li.menu-hover, li.menu-focus {
 
background-color: #340449 !important;
 
color: white;
 
outline: none;
 
}
 
li.menu-hover > a, li.menu-focus > a {
 
color: white !important;
 
outline: none;
 
}
 
</style>
 
<script type="text/javascript">
 
$(document).ready(function() {
 
var menu1 = new menubar('nav', false);
 
}); // end ready()
 
  
//
+
<html lang="en">
// Function menubar() is the constructor of a menu widget
+
  <head>
// The widget will bind to the ul passed to it.
+
  <title>
//
+
    ARIA menus
// @param(id string) id is the HTML id of the ul to bind to
+
  </title>
//
+
  <style type="text/css">
// @param(vmenu boolean) vmenu is true if menu is vertical; false if horizontal
+
    #l1, #l2  {
//
+
      float:left;
// @return N/A
+
      width:10em;
//
+
      background-color:darkblue;
function menubar(id, vmenu) {
+
      color:white;
 +
      padding:.2em;
 +
    }
 +
    li {
 +
      list-style-type:none !important;
 +
    }
 +
    #label1 {
 +
    }
 +
    #u1, #u2 {
 +
      display:none;
 +
    }
 +
    .focus {
 +
    outline: dotted;
 +
    }
 +
  </style>
 +
  <script type="text/javascript">
 +
    function showHide(evt) {
 +
      var e = window.event ? window.event : evt;
  
// define widget properties
+
            obj = e.target;
this.$id = $('#' + id);
+
            toggle(obj,1);
 +
    }
  
this.$rootItems = this.$id.children('li'); // jQuerry array of all root-level menu items
+
        function visible(obj) {
 +
            if (window.getComputedStyle(obj,null).display == "none")
 +
                return false;
 +
            else
 +
              return true;
 +
        }
  
this.$items = this.$id.find('.menu-item').not('.separator'); // jQuery array of menu items
+
        // set force to 1 to force only that one to be displayed
+
    function toggle(obj, force) {
this.$parents = this.$id.find('.menu-parent'); // jQuery array of menu items
 
  
this.$allItems = this.$parents.add(this.$items); // jQuery array of all menu items
+
                if (obj.tagName == "SPAN")
+
                  obj = obj.parentNode;
console.log('All items:');
 
console.log(this.$allItems);
 
console.log('Parents: ');
 
console.log(this.$parents);
 
 
 
this.$activeItem = null; // jQuery object of the menu item with focus
 
  
this.vmenu = vmenu;
+
            if (obj.id == "l1" || obj.id == "l2") {
this.bChildOpen = false; // true if child menu is open
+
                obj = obj.children[1]
 +
          }
  
this.keys = {
+
          if (obj.tagName == "UL") {
tab:    9,
+
                if (force) {
enter:  13,
+
                        var o = document.getElementsByClassName('menu')
esc:    27,
+
                        for (var i=0; i < o.length; i++) {
space:  32,
 
left:  37,
 
up:    38,
 
right:  39,
 
down:  40
 
};
 
  
// bind event handlers
+
                          o[i].style.display = "none";
this.bindHandlers();
+
                        }
 +
                  obj.style.display = "block";
 +
            }
 +
                else if (!visible(obj)) {
 +
                  obj.style.display = "block";
 +
                  obj.setAttribute("aria-expanded","true");
 +
                  obj.querySelector("li").focus();
 +
                }
 +
                else
 +
                    obj.style.display = "none";
 +
          }
 +
          else if (obj.tagName == "LI") {
 +
            obj.onclick();
 +
          }
 +
    }
  
// associate the menu with the textArea it controls
+
    function drawFocus() {
// this.textarea = new textArea(this.$id.attr('aria-controls'));
+
      removeFocus();
};
 
  
//
+
      var id = document.getElementById('m1').getAttribute('aria-activedescendant');
// Function bindHandlers() is a member function to bind event handlers for the widget.
+
      var obj = document.getElementById(id);
//
 
// @return N/A
 
//
 
menubar.prototype.bindHandlers = function() {
 
  
var thisObj = this;
+
      if (!visible(obj)) {
 +
            toggle(obj,0);
 +
      }
 +
      if (!visible(obj.parentNode)) {
 +
            toggle(obj.parentNode,0);
 +
      }
 +
      if (document.getElementById(id).className)
 +
        document.getElementById(id).className += " focus";
 +
      else
 +
        document.getElementById(id).className = "focus";
 +
    }
  
///////// bind mouse event handlers //////////
+
    function removeFocus() {
 +
            var o = document.getElementsByClassName('focus')
  
// bind a handler for the menu items
+
            for(var i = 0; i < o.length; i++) {
this.$items.mouseenter(function(e) {
+
                o[i].className = o[i].className.replace( /(?:^|\s)focus(?!\S)/g,'');
$(this).addClass('menu-hover');
+
            }
return true;
 
});
 
  
// bind a mouseout handler for the menu items
+
    }
this.$items.mouseout(function(e) {
+
    function keystroke(evt) {
$(this).removeClass('menu-hover');
+
      var e = window.event ? window.event : evt;
return true;
+
            var activeID = document.getElementById('m1').getAttribute('aria-activedescendant');
});
 
  
// bind a mouseenter handler for the menu parents
+
            if (e.which == 13) { // enter
this.$parents.mouseenter(function(e) {
+
                toggle(document.getElementById(activeID),0);
return thisObj.handleMouseEnter($(this), e);
+
            }
});
+
            else if (e.which == 40) { // down arrow
 +
          document.getElementById('m1').setAttribute('aria-activedescendant',document.getElementById(activeID).getAttribute('data-next'));
 +
          drawFocus();
 +
            }
 +
            else if (e.which == 38) { // up arrow
 +
          document.getElementById('m1').setAttribute('aria-activedescendant',document.getElementById(activeID).getAttribute('data-previous'));
 +
          drawFocus();
 +
            }
 +
            else if (e.which == 39) {            // right arrow
 +
                    if (activeID.indexOf("l1") > -1) {
 +
              toggle(document.getElementById('l2'),1);
 +
            document.getElementById('m1').setAttribute('aria-activedescendant',"l2");
 +
          }
 +
          else {
 +
              toggle(document.getElementById('l1'),1);
 +
            document.getElementById('m1').setAttribute('aria-activedescendant',"l1");
 +
          }
 +
          drawFocus();
 +
            }
 +
            else if (e.which == 37) {            // left arrow
 +
                    if (activeID.indexOf("l1") > -1) {
 +
              toggle(document.getElementById('l2'),1);
 +
            document.getElementById('m1').setAttribute('aria-activedescendant',"l2");
 +
          }
 +
          else {
 +
                toggle(document.getElementById('l1'),1);
 +
            document.getElementById('m1').setAttribute('aria-activedescendant',"l1");
 +
          }
 +
          drawFocus();
 +
            }
 +
    }
 +
  </script>
 +
  </head>
 +
<body>
  
// bind a mouseleave handler
+
<h2>Menubar with menu items that contain menus with other menu items</h2>
this.$parents.mouseleave(function(e) {
+
<div>
return thisObj.handleMouseLeave($(this), e);
+
<ul tabindex="0" onclick="showHide(event);" onkeyup="keystroke(event);" id="m1" onfocus="drawFocus();" onblur="removeFocus();" role="menubar" aria-activedescendant="l1" >
});
+
  <li id="l1" role="menuitem" aria-haspopup="true" aria-labelledby="label1" data-next="l1a" data-previous="l1c"><span id="label1" aria-expanded="false" >File &#9660;</span>
 +
    <ul role="menu" id="u1" class="menu" >
 +
      <li tabindex="-1" id="l1a" data-next="l1b" data-previous="l1" onclick="alert('Saving file');" role="menuitem">Save</li>
 +
      <li tabindex="-1" id="l1b" data-next="l1c" data-previous="l1a" onclick="alert('Saving file as ...');" role="menuitem">Save as ...</li>
 +
      <li tabindex="-1" id="l1c" data-next="l1" data-previous="l1b" onclick="alert('Printing file');" role="menuitem">Print</li>
 +
    </ul>
 +
  </li>
 +
  <li id="l2" tabindex="-1" role="menuitem" aria-haspopup="true" aria-labeleldby="label2" data-next="l2a" data-previous="l2c"><span id="label2" aria-expanded="false">Review</span>
 +
    <ul role="menu" id="u2" class="menu" >
 +
      <li id="l2a" data-next="l2b" data-previous="l2" tabindex="-1" onclick="alert('Checking spelling');" role="menuitem">Spelling</li>
 +
      <li id="l2b" data-next="l2c" data-previous="l2a" tabindex="-1" onclick="alert('Opening thesaurus');" role="menuitem">Thesaurus</li>
 +
      <li id="l2c" data-next="l2" data-previous="l2b" tabindex="-1" onclick="alert('Checking Accessibility');" role="menuitem">Accessibility</li>
 +
    </ul>
 +
  </li>
 +
</ul>
  
// bind a click handler
+
</div>
this.$allItems.click(function(e) {
 
return thisObj.handleClick($(this), e);
 
});
 
  
//////////// bind key event handlers //////////////////
+
<div style="clear:both; padding-top:4em;">&nbsp;</div>
 
+
</html>
// bind a keydown handler
 
this.$allItems.keydown(function(e) {
 
return thisObj.handleKeyDown($(this), e);
 
});
 
 
 
// bind a keypress handler
 
this.$allItems.keypress(function(e) {
 
return thisObj.handleKeyPress($(this), e);
 
});
 
 
 
// bind a focus handler
 
this.$allItems.focus(function(e) {
 
return thisObj.handleFocus($(this), e);
 
});
 
 
 
// bind a blur handler
 
this.$allItems.blur(function(e) {
 
return thisObj.handleBlur($(this), e);
 
});
 
 
 
// bind a document click handler
 
$(document).click(function(e) {
 
return thisObj.handleDocumentClick(e);
 
});
 
 
 
} // end bindHandlers()
 
 
 
//
 
// Function handleMouseEnter() is a member function to process mouseover
 
// events for the top menus.
 
//
 
// @param($item object) $item is the jquery object of the item firing the event
 
//
 
// @param(e object) e is the associated event object
 
//
 
// @return(boolean) Returns false;
 
//
 
menubar.prototype.handleMouseEnter = function($item, e) {
 
 
 
// add hover style
 
$item.addClass('menu-hover');
 
 
 
// expand the first level submenu
 
if ($item.attr('aria-haspopup') == 'true') {
 
$item.children('ul').show().attr('aria-hidden', 'false');
 
this.bChildOpen = true;
 
}
 
//e.stopPropagation();
 
return true;
 
 
 
} // end handleMouseEnter()
 
 
 
//
 
// Function handleMouseOut() is a member function to process mouseout
 
// events for the top menus.
 
//
 
// @param($item object) $item is the jquery object of the item firing the event
 
//
 
// @param(e object) e is the associated event object
 
//
 
// @return(boolean) Returns false;
 
//
 
menubar.prototype.handleMouseOut = function($item, e) {
 
 
 
// Remover hover styles
 
$item.removeClass('menu-hover');
 
 
 
//e.stopPropagation();
 
return true;
 
 
 
} // end handleMouseOut()
 
 
 
//
 
// Function handleMouseLeave() is a member function to process mouseout
 
// events for the top menus.
 
//
 
// @param($menu object) $menu is the jquery object of the item firing the event
 
//
 
// @param(e object) e is the associated event object
 
//
 
// @return(boolean) Returns false;
 
//
 
menubar.prototype.handleMouseLeave = function($menu, e) {
 
 
 
console.log('mouseLeave');
 
 
 
var $active = $menu.find('.menu-focus'); //???
 
 
 
$active = $active.add($menu.find('.menu-focus'));
 
 
 
// Remove hover style
 
$menu.removeClass('menu-hover');
 
 
 
// if any item in the child menu has focus, move focus to the root item
 
if ($active.length > 0) {
 
 
 
this.bChildOpen = false;
 
 
 
// remove the focus style from the active item
 
$active.removeClass('menu-focus');
 
 
 
// store the active item
 
this.$activeItem = $menu;
 
 
 
// cannot hide items with focus -- move focus to root item
 
$menu.focus();
 
}
 
 
 
// hide the child menu
 
$menu.children('ul').hide().attr('aria-hidden', 'true');
 
 
 
//e.stopPropagation();
 
return true;
 
 
 
} // end handleMouseLeave()
 
 
 
//
 
// Function handleClick() is a member function to process click events
 
// for the top menus.
 
//
 
// @param($item object) $item is the jquery object of the item firing the event
 
//
 
// @param(e object) e is the associated event object
 
//
 
// @return(boolean) Returns false;
 
//
 
menubar.prototype.handleClick = function($item, e) {
 
 
 
var $parentUL = $item.parent();
 
 
 
if ($parentUL.is('.root-level')) {
 
// open the child menu if it is closed
 
$item.children('ul').first().show().attr('aria-hidden', 'false');
 
this.bChildOpen = true;
 
}
 
else {
 
// remove hover and focus styling
 
this.$allItems.removeClass('menu-hover menu-focus');
 
 
 
// close the menu
 
this.$id.find('ul').not('.root-level').hide().attr('aria-hidden','true');
 
}
 
 
 
// if menu item triggers some behavior other than going to a link,
 
// would stop propagation and return false
 
// e.stopPropagation();
 
// return false;
 
return true;
 
 
 
} // end handleClick()
 
 
 
//
 
// Function handleFocus() is a member function to process focus events
 
// for the menu.
 
//
 
// @param($item object) $item is the jquery object of the item firing the event
 
//
 
// @param(e object) e is the associated event object
 
//
 
// @return(boolean) Returns true;
 
//
 
menubar.prototype.handleFocus = function($item, e) {
 
 
 
// if activeItem is null, we are getting focus from outside the menu. Store
 
// the item that triggered the event
 
if (this.$activeItem == null) {
 
this.$activeItem = $item;
 
}
 
else if ($item[0] != this.$activeItem[0]) {
 
return true;
 
}
 
 
 
// get the set of jquery objects for all the parent items of the active item
 
var $parentItems = this.$activeItem.parentsUntil('div').filter('li');
 
 
 
// remove focus styling from all other menu items
 
this.$allItems.removeClass('menu-focus');
 
 
 
// add styling to the active item
 
this.$activeItem.addClass('menu-focus');
 
/*
 
if (this.$activeItem.hasClass('menu-parent')) {
 
// for parent items, add .menu-focus directly to the list item
 
this.$activeItem.addClass('menu-focus');
 
}
 
else {
 
// for sub-menu items, add .menu-focus to the anchor
 
this.$activeItem.find('a').addClass('menu-focus');
 
}
 
*/
 
// add styling to all parent items.
 
$parentItems.addClass('menu-focus');
 
 
 
if (this.vmenu == true) {
 
// if the bChildOpen is true, open the active item's child menu (if applicable)
 
if (this.bChildOpen == true) {
 
 
 
var $itemUL = $item.parent();
 
 
 
// if the itemUL is a root-level menu and item is a parent item,
 
// show the child menu.
 
if ($itemUL.is('.root-level') && ($item.attr('aria-haspopup') == 'true')) {
 
$item.children('ul').show().attr('aria-hidden', 'false');
 
}
 
}
 
else {
 
this.vmenu = false;
 
}
 
}
 
 
 
return true;
 
 
 
} // end handleFocus()
 
 
 
//
 
// Function handleBlur() is a member function to process blur events
 
// for the menu.
 
//
 
// @param($item object) $item is the jquery object of the item firing the event
 
//
 
// @param(e object) e is the associated event object
 
//
 
// @return(boolean) Returns true;
 
//
 
menubar.prototype.handleBlur = function($item, e) {
 
 
 
// $item.find('a').removeClass('menu-focus');
 
$item.removeClass('menu-focus');
 
 
 
return true;
 
 
 
} // end handleBlur()
 
 
 
//
 
// Function handleKeyDown() is a member function to process keydown events
 
// for the menus.
 
//
 
// @param($item object) $item is the jquery object of the item firing the event
 
//
 
// @param(e object) e is the associated event object
 
//
 
// @return(boolean) Returns false if consuming; true if propagating
 
//
 
menubar.prototype.handleKeyDown = function($item, e) {
 
 
 
if (e.altKey || e.ctrlKey) {
 
// Modifier key pressed: Do not process
 
return true;
 
}
 
 
 
switch(e.keyCode) {
 
case this.keys.tab: {
 
 
 
// hide all menu items and update their aria attributes
 
this.$id.find('ul').hide().attr('aria-hidden', 'true');
 
 
 
// remove focus styling from all menu items
 
this.$allItems.removeClass('menu-focus');
 
 
 
this.$activeItem = null;
 
this.bChildOpen == false;
 
 
 
break;
 
}
 
case this.keys.esc: {
 
 
 
var $itemUL = $item.parent();
 
 
 
if ($itemUL.is('.root-level')) {
 
// hide the child menu and update the aria attributes
 
$item.children('ul').first().hide().attr('aria-hidden', 'true');
 
}
 
else {
 
 
 
// move up one level
 
this.$activeItem = $itemUL.parent();
 
 
 
// reset the childOpen flag
 
this.bChildOpen = false;
 
 
 
// set focus on the new item
 
this.$activeItem.focus();
 
 
 
// hide the active menu and update the aria attributes
 
$itemUL.hide().attr('aria-hidden', 'true');
 
}
 
 
 
e.stopPropagation();
 
return false;
 
}
 
case this.keys.enter:
 
case this.keys.space: {
 
 
 
if (!($item.hasClass('menu-parent'))) {
 
// user pressed enter or space on a dropdown menu item,
 
// not an item on the menu bar
 
// get the target href and go there
 
window.location = $item.find('a').attr('href');
 
return false;
 
}
 
 
 
var $parentUL = $item.parent();
 
 
 
if ($parentUL.is('.root-level')) {
 
// open the child menu if it is closed
 
$item.children('ul').first().show().attr('aria-hidden', 'false');
 
this.bChildOpen = true;
 
}
 
else {
 
// remove hover styling
 
this.$allItems.removeClass('menu-hover');
 
this.$allItems.removeClass('menu-focus');
 
 
 
// close the menu
 
this.$id.find('ul').not('.root-level').hide().attr('aria-hidden','true');
 
 
 
// clear the active item
 
this.$activeItem = null;
 
 
 
}
 
e.stopPropagation();
 
return false;
 
}
 
 
 
case this.keys.left: {
 
 
 
if (this.vmenu == true && $itemUL.is('.root-level')) {
 
// If this is a vertical menu and the root-level is active, move
 
// to the previous item in the menu
 
this.$activeItem = this.moveUp($item);
 
}
 
else {
 
this.$activeItem = this.moveToPrevious($item);
 
}
 
 
 
this.$activeItem.focus();
 
 
 
e.stopPropagation();
 
return false;
 
}
 
case this.keys.right: {
 
 
 
if (this.vmenu == true && $itemUL.is('.root-level')) {
 
// If this is a vertical menu and the root-level is active, move
 
// to the next item in the menu
 
this.$activeItem = this.moveDown($item);
 
}
 
else {
 
this.$activeItem = this.moveToNext($item);
 
}
 
 
 
this.$activeItem.focus();
 
 
 
e.stopPropagation();
 
return false;
 
}
 
case this.keys.up: {
 
 
 
if (this.vmenu == true && $itemUL.is('.root-level')) {
 
// If this is a vertical menu and the root-level is active, move
 
// to the previous root-level menu
 
this.$activeItem = this.moveToPrevious($item);
 
}
 
else {
 
this.$activeItem = this.moveUp($item);
 
}
 
 
 
this.$activeItem.focus();
 
 
 
e.stopPropagation();
 
return false;
 
}
 
case this.keys.down: {
 
 
 
if (this.vmenu == true && $itemUL.is('.root-level')) {
 
// If this is a vertical menu and the root-level is active, move
 
// to the next root-level menu
 
this.$activeItem = this.moveToNext($item);
 
}
 
else {
 
this.$activeItem = this.moveDown($item);
 
}
 
 
 
this.$activeItem.focus();
 
 
 
e.stopPropagation();
 
return false;
 
}
 
} // end switch
 
return true;
 
 
 
} // end handleKeyDown()
 
 
 
//
 
// Function moveToNext() is a member function to move to the next menu level.
 
// This will be either the next root-level menu or the child of a menu parent. If
 
// at the root level and the active item is the last in the menu, this function will loop
 
// to the first menu item.
 
//
 
// If the menu is a horizontal menu, the first child element of the newly selected menu will
 
// be selected
 
//
 
// @param($item object) $item is the active menu item
 
//
 
// @return (object) Returns the item to move to. Returns $item is no move is possible
 
//
 
menubar.prototype.moveToNext = function($item) {
 
 
 
var $itemUL = $item.parent(); // $item's containing menu
 
var $menuItems = $itemUL.children('li'); // the items in the currently active menu
 
var menuNum = $menuItems.length; // the number of items in the active menu
 
var menuIndex = $menuItems.index($item); // the items index in its menu
 
var $newItem = null;
 
var $newItemUL = null;
 
 
 
if ($itemUL.is('.root-level')) {
 
// this is the root level move to next sibling. This will require closing
 
// the current child menu and opening the new one.
 
 
 
if (menuIndex < menuNum-1) { // not the last root menu
 
$newItem = $item.next();
 
}
 
else { // wrap to first item
 
$newItem = $menuItems.first();
 
}
 
 
 
// close the current child menu (if applicable)
 
if ($item.attr('aria-haspopup') == 'true') {
 
 
 
var $childMenu = $item.children('ul').first();
 
 
 
if ($childMenu.attr('aria-hidden') == 'false') {
 
// hide the child and update aria attributes accordingly
 
$childMenu.hide().attr('aria-hidden', 'true');
 
this.bChildOpen = true;
 
}
 
}
 
 
 
// remove the focus styling from the current menu
 
$item.removeClass('menu-focus');
 
 
 
// open the new child menu (if applicable)
 
if (($newItem.attr('aria-haspopup') == 'true') && (this.bChildOpen == true)) {
 
 
 
var $childMenu = $newItem.children('ul').first();
 
 
 
// open the child and update aria attributes accordingly
 
$childMenu.show().attr('aria-hidden', 'false');
 
 
 
if (!this.vmenu) {
 
// select the first item in the child menu
 
$newItem = $childMenu.children('li').first();
 
}
 
 
 
}
 
}
 
else {
 
// this is not the root level. If there is a child menu to be moved into, do that;
 
// otherwise, move to the next root-level menu if there is one
 
if ($item.attr('aria-haspopup') == 'true') {
 
       
 
var $childMenu = $item.children('ul').first();
 
 
 
$newItem = $childMenu.children('li').first();
 
 
 
// show the child menu and update its aria attributes
 
$childMenu.show().attr('aria-hidden', 'false');
 
this.bChildOpen = true;
 
}
 
else {
 
// at deepest level, move to the next root-level menu
 
 
 
if (this.vmenu == true) {
 
// do nothing
 
return $item;
 
}
 
 
 
var $parentMenus = null;
 
var $rootItem = null;
 
  
// get list of all parent menus for item, up to the root level
+
[[category:ARIA]]
$parentMenus = $item.parentsUntil('div').filter('ul').not('.root-level');
 
 
 
// hide the current menu and update its aria attributes accordingly
 
$parentMenus.hide().attr('aria-hidden', 'true');
 
 
 
// remove the focus styling from the active menu
 
$parentMenus.find('li').removeClass('menu-focus');
 
$parentMenus.last().parent().removeClass('menu-focus');
 
 
 
$rootItem = $parentMenus.last().parent(); // the containing root for the menu
 
 
 
menuIndex = this.$rootItems.index($rootItem);
 
 
 
// if this is not the last root menu item, move to the next one
 
if (menuIndex < this.$rootItems.length-1) {
 
$newItem = $rootItem.next();
 
}
 
else { // loop
 
$newItem = this.$rootItems.first();
 
}
 
 
 
if ($newItem.attr('aria-haspopup') == 'true') {
 
var $childMenu = $newItem.children('ul').first();
 
 
 
$newItem = $childMenu.children('li').first();
 
 
 
// show the child menu and update it's aria attributes
 
$childMenu.show().attr('aria-hidden', 'false');
 
this.bChildOpen = true;
 
}
 
}
 
}
 
 
 
return $newItem;
 
}
 
 
 
//
 
// Function moveToPrevious() is a member function to move to the previous menu level.
 
// This will be either the previous root-level menu or the child of a menu parent. If
 
// at the root level and the active item is the first in the menu, this function will loop
 
// to the last menu item.
 
//
 
// If the menu is a horizontal menu, the first child element of the newly selected menu will
 
// be selected
 
//
 
// @param($item object) $item is the active menu item
 
//
 
// @return (object) Returns the item to move to. Returns $item is no move is possible
 
//
 
menubar.prototype.moveToPrevious = function($item) {
 
 
 
var $itemUL = $item.parent(); // $item's containing menu
 
var $menuItems = $itemUL.children('li'); // the items in the currently active menu
 
var menuNum = $menuItems.length; // the number of items in the active menu
 
var menuIndex = $menuItems.index($item); // the items index in its menu
 
var $newItem = null;
 
var $newItemUL = null;
 
 
 
if ($itemUL.is('.root-level')) {
 
// this is the root level move to previous sibling. This will require closing
 
// the current child menu and opening the new one.
 
 
 
if (menuIndex > 0) { // not the first root menu
 
$newItem = $item.prev();
 
}
 
else { // wrap to last item
 
$newItem = $menuItems.last();
 
}
 
 
 
// close the current child menu (if applicable)
 
if ($item.attr('aria-haspopup') == 'true') {
 
 
 
var $childMenu = $item.children('ul').first();
 
 
 
if ($childMenu.attr('aria-hidden') == 'false') {
 
// hide the child and update aria attributes accordingly
 
$childMenu.hide().attr('aria-hidden', 'true');
 
this.bChildOpen = true;
 
}
 
}
 
 
 
// remove the focus styling from the current menu
 
$item.removeClass('menu-focus');
 
 
 
// open the new child menu (if applicable)
 
if (($newItem.attr('aria-haspopup') == 'true') && this.bChildOpen == true) {
 
 
 
var $childMenu = $newItem.children('ul').first();
 
 
 
// open the child and update aria attributes accordingly
 
$childMenu.show().attr('aria-hidden', 'false');
 
 
 
if (!this.vmenu) {
 
// select the first item in the child menu
 
$newItem = $childMenu.children('li').first();
 
}
 
}
 
}
 
else {
 
// this is not the root level. If there is a parent menu that is not the
 
// root menu, move up one level; otherwise, move to first item of the previous
 
// root menu.
 
 
 
var $parentLI = $itemUL.parent();
 
var $parentUL = $parentLI.parent();
 
 
 
var $parentMenus = null;
 
var $rootItem = null;
 
 
 
// if this is a vertical menu or is not the first child menu
 
// of the root-level menu, move up one level.
 
if (this.vmenu == true || !$parentUL.is('.root-level')) {
 
 
 
$newItem = $itemUL.parent();
 
 
 
// hide the active menu and update aria-hidden
 
$itemUL.hide().attr('aria-hidden', 'true');
 
 
 
// remove the focus highlight from the $item
 
$item.removeClass('menu-focus');
 
 
 
if (this.vmenu == true) {
 
// set a flag so the focus handler does't reopen the menu
 
this.bChildOpen = false;
 
}
 
}
 
else { // move to previous root-level menu
 
 
 
// hide the current menu and update the aria attributes accordingly
 
$itemUL.hide().attr('aria-hidden', 'true');
 
 
 
// remove the focus styling from the active menu
 
$item.removeClass('menu-focus');
 
$parentLI.removeClass('menu-focus');
 
 
 
menuIndex = this.$rootItems.index($parentLI);
 
 
 
if (menuIndex > 0) {
 
// move to the previous root-level menu
 
$newItem = $parentLI.prev();
 
}
 
else { // loop to last root-level menu
 
$newItem = this.$rootItems.last();
 
}
 
 
 
// add the focus styling to the new menu
 
$newItem.addClass('menu-focus');
 
 
 
if ($newItem.attr('aria-haspopup') == 'true') {
 
var $childMenu = $newItem.children('ul').first();
 
 
 
// show the child menu and update it's aria attributes
 
$childMenu.show().attr('aria-hidden', 'false');
 
this.bChildOpen = true;
 
 
 
$newItem = $childMenu.children('li').first();
 
}
 
}
 
}
 
return $newItem;
 
}
 
 
 
//
 
// Function moveDown() is a member function to select the next item in a menu.
 
// If the active item is the last in the menu, this function will loop to the
 
// first menu item.
 
//
 
// @param($item object) $item is the active menu item
 
//
 
// @param(startChr char) [optional] startChr is the character to attempt to match against the beginning of the
 
// menu item titles. If found, focus moves to the next menu item beginning with that character.
 
//
 
// @return (object) Returns the item to move to. Returns $item is no move is possible
 
//
 
menubar.prototype.moveDown = function($item, startChr) {
 
 
 
var $itemUL = $item.parent(); // $item's containing menu
 
var $menuItems = $itemUL.children('li').not('.separator'); // the items in the currently active menu
 
var menuNum = $menuItems.length; // the number of items in the active menu
 
var menuIndex = $menuItems.index($item); // the items index in its menu
 
var $newItem = null;
 
var $newItemUL = null;
 
 
 
if ($itemUL.is('.root-level')) { // this is the root level menu
 
 
 
if ($item.attr('aria-haspopup') != 'true') {
 
// No child menu to move to
 
return $item;
 
}
 
 
 
// Move to the first item in the child menu
 
$newItemUL = $item.children('ul').first();
 
$newItem = $newItemUL.children('li').first();
 
 
 
// make sure the child menu is visible
 
$newItemUL.show().attr('aria-hidden', 'false');
 
this.bChildOpen = true;
 
 
 
return $newItem;
 
}
 
 
 
// if $item is not the last item in its menu, move to the next item. If startChr is specified, move
 
// to the next item with a title that begins with that character.
 
//
 
if (startChr) {
 
 
var bMatch = false;
 
var curNdx = menuIndex+1;
 
 
 
// check if the active item was the last one on the list
 
if (curNdx == menuNum) {
 
curNdx = 0;
 
}
 
 
 
// Iterate through the menu items (starting from the current item and wrapping) until a match is found
 
// or the loop returns to the current menu item
 
while (curNdx != menuIndex)  {
 
 
 
// Use the first of the two following lines if menu does not contain anchor tags.
 
// Otherwise use the second
 
// var titleChr = $menuItems.eq(curNdx).html().charAt(0);
 
var titleChr = $menuItems.eq(curNdx).find('a').html().charAt(0);
 
 
if (titleChr.toLowerCase() == startChr) {
 
bMatch = true;
 
break;
 
}
 
 
 
curNdx = curNdx+1;
 
 
 
if (curNdx == menuNum) {
 
// reached the end of the list, start again at the beginning
 
curNdx = 0;
 
}
 
}
 
 
 
if (bMatch == true) {
 
$newItem = $menuItems.eq(curNdx);
 
 
 
// remove the focus styling from the current item
 
$item.removeClass('menu-focus');
 
 
 
return $newItem
 
}
 
else {
 
return $item;
 
}
 
}
 
else {
 
if (menuIndex < menuNum-1) {
 
$newItem = $menuItems.eq(menuIndex+1);
 
}
 
else {
 
$newItem = $menuItems.first();
 
}
 
}
 
 
 
// remove the focus styling from the current item
 
$item.removeClass('menu-focus');
 
 
 
return $newItem;
 
}
 
 
 
//
 
// Function moveUp() is a member function to select the previous item in a menu.
 
// If the active item is the first in the menu, this function will loop to the
 
// last menu item.
 
//
 
// @param($item object) $item is the active menu item
 
//
 
// @return (object) Returns the item to move to. Returns $item is no move is possible
 
//
 
menubar.prototype.moveUp = function($item) {
 
 
 
var $itemUL = $item.parent(); // $item's containing menu
 
var $menuItems = $itemUL.children('li').not('.separator'); // the items in the currently active menu
 
var menuNum = $menuItems.length; // the number of items in the active menu
 
var menuIndex = $menuItems.index($item); // the items index in its menu
 
var $newItem = null;
 
var $newItemUL = null;
 
 
 
if ($itemUL.is('.root-level')) { // this is the root level menu
 
 
 
// nothing to do
 
return $item;
 
}
 
 
 
// if $item is not the first item in its menu, move to the previous item
 
if (menuIndex > 0) {
 
 
 
$newItem = $menuItems.eq(menuIndex-1);
 
}
 
else {
 
// loop to top of menu
 
$newItem = $menuItems.last();
 
}
 
 
 
// remove the focus styling from the current item
 
$item.removeClass('menu-focus');
 
 
 
return $newItem;
 
}
 
 
 
//
 
// Function handleKeyPress() is a member function to process keydown events
 
// for the menus.
 
//
 
// The Opera browser performs some window commands from the keypress event,
 
// not keydown like Firefox, Safari, and IE. This event handler consumes
 
// keypresses for relevant keys so that Opera behaves when the user is
 
// manipulating the menu with the keyboard.
 
//
 
// @param($item object) $menu is the jquery object of the item firing the event
 
//
 
// @param(e object) e is the associated event object
 
//
 
// @return(boolean) Returns false if consuming; true if propagating
 
//
 
menubar.prototype.handleKeyPress = function($item, e) {
 
 
 
if (e.altKey || e.ctrlKey || e.shiftKey) {
 
// Modifier key pressed: Do not process
 
return true;
 
}
 
 
 
switch(e.keyCode) {
 
case this.keys.tab: {
 
return true;
 
}
 
case this.keys.esc:
 
case this.keys.enter:
 
case this.keys.space:
 
case this.keys.up:
 
case this.keys.down:
 
case this.keys.left:
 
case this.keys.right: {
 
 
 
e.stopPropagation();
 
return false;
 
}
 
default : {
 
var chr = String.fromCharCode(e.which);
 
 
 
this.$activeItem = this.moveDown($item, chr);
 
this.$activeItem.focus();
 
 
 
e.stopPropagation();
 
return false;
 
}
 
} // end switch
 
return true;
 
 
 
} // end handleKeyPress()
 
 
 
//
 
// Function handleDocumentClick() is a member function to process click events on the document. Needed
 
// to close an open menu if a user clicks outside the menu
 
//
 
// @param(e object) e is the associated event object
 
//
 
// @return(boolean) Returns true;
 
//
 
menubar.prototype.handleDocumentClick = function(e) {
 
 
 
// get a list of all child menus
 
var $childMenus = this.$id.find('ul').not('.root-level');
 
 
 
// hide the child menus
 
$childMenus.hide().attr('aria-hidden', 'true');
 
 
 
this.$allItems.removeClass('menu-focus');
 
 
 
this.$activeItem = null;
 
 
 
// allow the event to propagate
 
return true;
 
 
 
} // end handleDocumentClick()
 
</script>
 
</head>
 
<body>
 
<p>This is an example of an ARIA menubar.</p>
 
<div role="navigation" aria-label="Main menu" tabindex="-1">
 
<ul id="nav" class="menubar root-level" role="menubar">
 
<li class="menu-parent" role="menuitem" tabindex="0" aria-haspopup="true" title="About Menu">
 
About
 
<ul id="about" role="menu" class="menu" aria-hidden="true">
 
<li role="menuitem" class="menu-item" tabindex="-1">
 
<a href="https://www.ssbbartgroup.com/about_us/index.php">Company Overview</a></li>
 
<li role="menuitem" class="menu-item" tabindex="-1">
 
<a href="https://www.ssbbartgroup.com/about_us/executive_team.php">Executive Team</a></li>
 
<li role="menuitem" class="menu-item" tabindex="-1">
 
<a href="https://www.ssbbartgroup.com/about_us/the_ssb_advantage.php">The SSB Advantage</a></li>
 
<li role="menuitem" class="menu-item" tabindex="-1">
 
<a href="https://www.ssbbartgroup.com/about_us/clients.php">Sample Clients</a></li>
 
</ul>
 
</li>
 
<li class="menu-parent" role="menuitem" tabindex="0" aria-haspopup="true" title="Services Menu">
 
Services
 
<ul role="menu" class="menu" aria-hidden="true">
 
<li role="menuitem" class="menu-item" tabindex="-1">
 
<a href="https://www.ssbbartgroup.com/services/accessibility_staffing.php">Staffing</a></li>
 
<li role="menuitem" class="menu-item" tabindex="-1">
 
<a href="https://www.ssbbartgroup.com/services/audit/index.php">Audits</a></li>
 
<li role="menuitem" class="menu-item" tabindex="-1">
 
<a href="https://www.ssbbartgroup.com/services/implementation_support/index.php">Implementation Support</a></li>
 
<li role="menuitem" class="menu-item" tabindex="-1">
 
<a href="https://www.ssbbartgroup.com/services/accessibility_policy_development.php">Accessibility Policy Development</a></li>
 
</ul>
 
</li>
 
<li class="menu-parent" role="menuitem" tabindex="0" aria-haspopup="true" title="AMP Menu">
 
AMP
 
<ul role="menu" class="menu" aria-hidden="true">
 
<li role="menuitem" class="menu-item" tabindex="-1">
 
<a href="https://amp.ssbbartgroup.com/register/">Free Trial</a></li>
 
<li role="menuitem" class="menu-item" tabindex="-1">
 
<a href="https://www.ssbbartgroup.com/amp/the_amp_advantage.php">The AMP Advantage</a></li>
 
<li role="menuitem" class="menu-item" tabindex="-1">
 
<a href="https://www.ssbbartgroup.com/amp/supportedstandards.php">Supported Standards</a></li>
 
</ul>
 
</li>
 
<li class="menu-parent" role="menuitem" tabindex="0" aria-haspopup="true" title="Training Menu">
 
Training
 
<ul role="menu" class="menu" aria-hidden="true">
 
<li role="menuitem" class="menu-item" tabindex="-1">
 
<a href="https://www.ssbbartgroup.com/training/index.php">Overview</a></li>
 
<li role="menuitem" class="menu-item" tabindex="-1">
 
<a href="https://www.ssbbartgroup.com/training/course_list.php">Course List</a></li>
 
<li role="menuitem" class="menu-item" tabindex="-1">
 
<a href="https://www.ssbbartgroup.com/training/delivery_options.php">Delivery Options</a></li>
 
</ul>
 
</li>
 
</ul>
 
</div>
 
<p>To exit the menu, <a href="#">press tab again</a> and you should land safely in this paragraph.</p>
 
</body>
 
</html>
 
[[Category:ARIA]]
 

Latest revision as of 15:09, 7 March 2018

This example is not working correctly.

ARIA menus

Menubar with menu items that contain menus with other menu items