diff --git a/examples/menubar/menubar-1/css/menubarLinks.css b/examples/menubar/menubar-1/css/menubarLinks.css new file mode 100644 index 0000000000..a3589858c4 --- /dev/null +++ b/examples/menubar/menubar-1/css/menubarLinks.css @@ -0,0 +1,60 @@ +ul[role="menubar"] { + margin: 10px; + padding: 10px; + font-size: 110%; + list-style: none; + background-color: #EEEEEE; +} + +ul[role="menubar"] [role="menuitem"], +ul[role="menubar"] [role="separator"] { + padding: 0.25em; + background-color: #EEEEEE; + border: 2px solid #EEEEEE; +} + +ul[role="menubar"] [role="separator"] { + padding-top: 0.15em; + background-image: url('../images/separator.png'); + background-position: center; + background-repeat: repeat-x; +} + +ul[role="menubar"] [role="menuitem"]:focus, +ul[role="menubar"] [role="menuitem"]:hover, +ul[role="menubar"] [role="separator"]:focus, +ul[role="menubar"] [role="separator"]:hover { + background-color: black; + color: white; +} + +ul[role="menubar"] a[role="menuitem"] { + text-decoration: none; + color: black; +} + +ul[role="menubar"] li { + list-style: none; + margin: 0; + padding: 0; +} + +ul[role="menubar"] > li { + display: inline; +} + +ul[role="menubar"] > li > a:after { + content: url('../images/pull-down-menu-icon.png'); +} + +ul[role="menubar"] ul[role="menu"] { + display: none; + position: absolute; + margin: 0; + padding: 0; +} + +ul[role="menubar"] ul[role="menu"] li a { + display: block; +} + diff --git a/examples/menubar/menubar-1/images/down-arrow.png b/examples/menubar/menubar-1/images/down-arrow.png new file mode 100644 index 0000000000..694e80519b Binary files /dev/null and b/examples/menubar/menubar-1/images/down-arrow.png differ diff --git a/examples/menubar/menubar-1/images/pull-down-menu-icon.paint b/examples/menubar/menubar-1/images/pull-down-menu-icon.paint new file mode 100644 index 0000000000..f646943e03 Binary files /dev/null and b/examples/menubar/menubar-1/images/pull-down-menu-icon.paint differ diff --git a/examples/menubar/menubar-1/images/pull-down-menu-icon.png b/examples/menubar/menubar-1/images/pull-down-menu-icon.png new file mode 100644 index 0000000000..b6c5714d1d Binary files /dev/null and b/examples/menubar/menubar-1/images/pull-down-menu-icon.png differ diff --git a/examples/menubar/menubar-1/images/separator.paint b/examples/menubar/menubar-1/images/separator.paint new file mode 100644 index 0000000000..9e313a1cc1 Binary files /dev/null and b/examples/menubar/menubar-1/images/separator.paint differ diff --git a/examples/menubar/menubar-1/images/separator.png b/examples/menubar/menubar-1/images/separator.png new file mode 100644 index 0000000000..a837fbce93 Binary files /dev/null and b/examples/menubar/menubar-1/images/separator.png differ diff --git a/examples/menubar/menubar-1/js/MenuItemLinks.js b/examples/menubar/menubar-1/js/MenuItemLinks.js new file mode 100644 index 0000000000..18957e115e --- /dev/null +++ b/examples/menubar/menubar-1/js/MenuItemLinks.js @@ -0,0 +1,187 @@ +/* +* This content is licensed according to the W3C Software License at +* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document +* +* File: MenuItem.js +* +* Desc: Popup Menu Menuitem widget that implements ARIA Authoring Practices +* +* Author: Jon Gunderson, Ku Ja Eun and Nicholas Hoyt +*/ + +/* +* @constructor MenuItem +* +* @desc +* Wrapper object for a simple menu item in a popup menu +* +* @param domNode +* The DOM element node that serves as the menu item container. +* The menuObj PopupMenu is responsible for checking that it has +* requisite metadata, e.g. role="menuitem". +* +* @param menuObj +* The object that is a wrapper for the PopupMenu DOM element that +* contains the menu item DOM element. See PopupMenu.js +*/ +var MenuItem = function (domNode, menuObj) { + + this.domNode = domNode; + this.menu = menuObj; + + this.keyCode = Object.freeze({ + 'TAB': 9, + 'RETURN': 13, + 'ESC': 27, + 'SPACE': 32, + 'PAGEUP': 33, + 'PAGEDOWN': 34, + 'END': 35, + 'HOME': 36, + 'LEFT': 37, + 'UP': 38, + 'RIGHT': 39, + 'DOWN': 40 + }); +}; + +MenuItem.prototype.init = function () { + this.domNode.tabIndex = -1; + + if (!this.domNode.getAttribute('role')) { + this.domNode.setAttribute('role', 'menuitem'); + } + + this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); + this.domNode.addEventListener('keypress', this.handleKeypress.bind(this)); + this.domNode.addEventListener('click', this.handleClick.bind(this)); + this.domNode.addEventListener('focus', this.handleFocus.bind(this)); + this.domNode.addEventListener('blur', this.handleBlur.bind(this)); + this.domNode.addEventListener('mouseover', this.handleMouseover.bind(this)); + this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this)); + +}; + +/* EVENT HANDLERS */ + +MenuItem.prototype.handleKeydown = function (event) { + var tgt = event.currentTarget, + flag = false, + clickEvent; + + // Console.log("[MenuItem][handleKeydown]: " + event.keyCode + " " + this.menu) + + switch (event.keyCode) { + case this.keyCode.SPACE: + case this.keyCode.RETURN: + // Create simulated mouse event to mimic the behavior of ATs + // and let the event handler handleClick do the housekeeping. + try { + clickEvent = new MouseEvent('click', { + 'view': window, + 'bubbles': true, + 'cancelable': true + }); + } + catch (err) { + if (document.createEvent) { + // DOM Level 3 for IE 9+ + clickEvent = document.createEvent('MouseEvents'); + clickEvent.initEvent('click', true, true); + } + } + tgt.dispatchEvent(clickEvent); + flag = true; + break; + + case this.keyCode.ESC: + this.menu.setFocusToController(); + this.menu.close(true); + flag = true; + break; + + case this.keyCode.UP: + this.menu.setFocusToPreviousItem(this); + flag = true; + break; + + case this.keyCode.DOWN: + this.menu.setFocusToNextItem(this); + flag = true; + break; + + case this.keyCode.LEFT: + this.menu.setFocusToController('previous'); + this.menu.close(true); + flag = true; + break; + + case this.keyCode.RIGHT: + this.menu.setFocusToController('next'); + this.menu.close(true); + flag = true; + break; + + case this.keyCode.HOME: + case this.keyCode.PAGEUP: + this.menu.setFocusToFirstItem(); + flag = true; + break; + + case this.keyCode.END: + case this.keyCode.PAGEDOWN: + this.menu.setFocusToLastItem(); + flag = true; + break; + + case this.keyCode.TAB: + this.menu.setFocusToController(); + this.menu.close(true); + break; + + default: + break; + } + + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } +}; + +MenuItem.prototype.handleKeypress = function (event) { + var char = String.fromCharCode(event.charCode); + + function isPrintableCharacter (str) { + return str.length === 1 && str.match(/\S/); + } + + if (isPrintableCharacter(char)) { + this.menu.setFocusByFirstCharacter(this, char); + } +}; + +MenuItem.prototype.handleClick = function (event) { + this.menu.setFocusToController(); + this.menu.close(true); +}; + +MenuItem.prototype.handleFocus = function (event) { + this.menu.hasFocus = true; +}; + +MenuItem.prototype.handleBlur = function (event) { + this.menu.hasFocus = false; + setTimeout(this.menu.close.bind(this.menu, false), 300); +}; + +MenuItem.prototype.handleMouseover = function (event) { + this.menu.hasHover = true; + this.menu.open(); + +}; + +MenuItem.prototype.handleMouseout = function (event) { + this.menu.hasHover = false; + setTimeout(this.menu.close.bind(this.menu, false), 300); +}; diff --git a/examples/menubar/menubar-1/js/MenubarItemLinks.js b/examples/menubar/menubar-1/js/MenubarItemLinks.js new file mode 100644 index 0000000000..68ea1c6e64 --- /dev/null +++ b/examples/menubar/menubar-1/js/MenubarItemLinks.js @@ -0,0 +1,192 @@ +/* +* This content is licensed according to the W3C Software License at +* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document +* +* File: MenubarItemLinks.js +* +* Desc: Menubar Menuitem widget that implements ARIA Authoring Practices +* for a menu of links +* +* Author: Jon Gunderson, Ku Ja Eun and Nicholas Hoyt +*/ + +/* +* @constructor MenubarItem +* +* @desc +* Object that configures menu item elements by setting tabIndex +* and registering itself to handle pertinent events. +* +* While menuitem elements handle many keydown events, as well as +* focus and blur events, they do not maintain any state variables, +* delegating those responsibilities to its associated menu object. +* +* Consequently, it is only necessary to create one instance of +* MenubarItem from within the menu object; its configure method +* can then be called on each menuitem element. +* +* @param domNode +* The DOM element node that serves as the menu item container. +* The menuObj PopupMenu is responsible for checking that it has +* requisite metadata, e.g. role="menuitem". +* +* @param menuObj +* The PopupMenu object that is a delegate for the menu DOM element +* that contains the menuitem element. +*/ +var MenubarItem = function (domNode, menuObj) { + + this.menubar = menuObj; + this.domNode = domNode; + this.popupMenu = false; + + this.hasFocus = false; + this.hasHover = false; + + this.keyCode = Object.freeze({ + 'TAB': 9, + 'RETURN': 13, + 'ESC': 27, + 'SPACE': 32, + 'PAGEUP': 33, + 'PAGEDOWN': 34, + 'END': 35, + 'HOME': 36, + 'LEFT': 37, + 'UP': 38, + 'RIGHT': 39, + 'DOWN': 40 + }); +}; + +MenubarItem.prototype.init = function () { + this.domNode.tabIndex = -1; + + this.domNode.setAttribute('role', 'menuitem'); + this.domNode.setAttribute('aria-haspopup', 'true'); + this.domNode.setAttribute('aria-expanded', 'false'); + + this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); + this.domNode.addEventListener('keypress', this.handleKeypress.bind(this)); + this.domNode.addEventListener('click', this.handleClick.bind(this)); + this.domNode.addEventListener('focus', this.handleFocus.bind(this)); + this.domNode.addEventListener('blur', this.handleBlur.bind(this)); + this.domNode.addEventListener('mouseover', this.handleMouseover.bind(this)); + this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this)); + + // Initialize pop up menus + + var nextElement = this.domNode.nextElementSibling; + + if (nextElement && nextElement.tagName === 'UL') { + this.popupMenu = new PopupMenu(nextElement, this); + this.popupMenu.init(); + } + +}; + +MenubarItem.prototype.handleKeydown = function (event) { + var tgt = event.currentTarget, + flag = false, + clickEvent; + + switch (event.keyCode) { + case this.keyCode.SPACE: + case this.keyCode.RETURN: + if (this.popupMenu) { + if (this.domNode.getAttribute('aria-expanded') == 'true') { + this.popupMenu.close(true); + } + else { + this.popupMenu.open(); + } + } + flag = true; + break; + + case this.keyCode.LEFT: + if (this.popupMenu) { + this.popupMenu.close(true); + } + this.menubar.setFocusToPreviousItem(this); + flag = true; + break; + + case this.keyCode.RIGHT: + if (this.popupMenu) { + this.popupMenu.close(true); + } + this.menubar.setFocusToNextItem(this); + flag = true; + break; + + case this.keyCode.UP: + if (this.popupMenu) { + this.popupMenu.open(); + this.popupMenu.setFocusToLastItem(); + flag = true; + } + break; + + case this.keyCode.DOWN: + if (this.popupMenu) { + this.popupMenu.open(); + this.popupMenu.setFocusToFirstItem(); + flag = true; + } + break; + + case this.keyCode.HOME: + case this.keyCode.PAGEUP: + this.menubar.setFocusToFirstItem(); + flag = true; + break; + + case this.keyCode.END: + case this.keyCode.PAGEDOWN: + this.menubar.setFocusToLastItem(); + flag = true; + break; + + default: + break; + } + + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } +}; + +MenubarItem.prototype.handleKeypress = function (event) { + var char = String.fromCharCode(event.charCode); + + function isPrintableCharacter (str) { + return str.length === 1 && str.match(/\S/); + } + + if (isPrintableCharacter(char)) { + this.menubar.setFocusByFirstCharacter(this, char); + } +}; + +MenubarItem.prototype.handleClick = function (event) { +}; + +MenubarItem.prototype.handleFocus = function (event) { + this.menubar.hasFocus = true; +}; + +MenubarItem.prototype.handleBlur = function (event) { + this.menubar.hasFocus = false; +}; + +MenubarItem.prototype.handleMouseover = function (event) { + this.hasHover = true; + this.popupMenu.open(); +}; + +MenubarItem.prototype.handleMouseout = function (event) { + this.hasHover = false; + setTimeout(this.popupMenu.close.bind(this.popupMenu, false), 300); +}; diff --git a/examples/menubar/menubar-1/js/MenubarLinks.js b/examples/menubar/menubar-1/js/MenubarLinks.js new file mode 100644 index 0000000000..5a89a8e502 --- /dev/null +++ b/examples/menubar/menubar-1/js/MenubarLinks.js @@ -0,0 +1,193 @@ +/* +* This content is licensed according to the W3C Software License at +* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document +* +* File: Menubar.js +* +* Desc: Menubar widget that implements ARIA Authoring Practices +* +* Author: Jon Gunderson, Ku Ja Eun and Nicholas Hoyt +*/ + +/* +* @constructor Menubar +* +* @desc +* Wrapper object for a menubar (with nested submenus of links) +* +* @param domNode +* The DOM element node that serves as the menubar container. Each +* child element of menubarNode that represents a menubaritem must +* be an A element +*/ + +var Menubar = function (domNode) { + var elementChildren, + msgPrefix = 'Menubar constructor argument menubarNode '; + + // Check whether menubarNode is a DOM element + if (!domNode instanceof Element) { + throw new TypeError(msgPrefix + 'is not a DOM Element.'); + } + + // Check whether menubarNode has descendant elements + if (domNode.childElementCount === 0) { + throw new Error(msgPrefix + 'has no element children.'); + } + + // Check whether menubarNode has A elements + e = domNode.firstElementChild; + while (e) { + var menubarItem = e.firstElementChild; + if (e && menubarItem && menubarItem.tagName !== 'A') { + throw new Error(msgPrefix + 'has child elements are note A elements.'); + } + e = e.nextElementSibling; + } + + this.domNode = domNode; + + this.menubarItems = []; // See Menubar init method + this.firstChars = []; // See Menubar init method + + this.firstItem = null; // See Menubar init method + this.lastItem = null; // See Menubar init method + + this.hasFocus = false; // See MenubarItem handleFocus, handleBlur + this.hasHover = false; // See Menubar handleMouseover, handleMouseout +}; + +/* +* @method Menubar.prototype.init +* +* @desc +* Adds ARIA role to the menubar node +* Traverse menubar children for A elements to configure each A element as a ARIA menuitem +* and populate menuitems array. Initialize firstItem and lastItem properties. +*/ +Menubar.prototype.init = function () { + var menubarItem, childElement, menuElement, textContent, numItems; + + this.domNode.setAttribute('role', 'menubar'); + + // Traverse the element children of menubarNode: configure each with + // menuitem role behavior and store reference in menuitems array. + e = this.domNode.firstElementChild; + + while (e) { + var menuElement = e.firstElementChild; + + if (e && menuElement && menuElement.tagName === 'A') { + menubarItem = new MenubarItem(menuElement, this); + menubarItem.init(); + this.menubarItems.push(menubarItem); + textContent = menuElement.textContent.trim(); + this.firstChars.push(textContent.substring(0, 1).toLowerCase()); + } + + e = e.nextElementSibling; + } + + // Use populated menuitems array to initialize firstItem and lastItem. + numItems = this.menubarItems.length; + if (numItems > 0) { + this.firstItem = this.menubarItems[ 0 ]; + this.lastItem = this.menubarItems[ numItems - 1 ]; + } + this.firstItem.domNode.tabIndex = 0; +}; + +/* FOCUS MANAGEMENT METHODS */ + +Menubar.prototype.setFocusToFirstItem = function () { + this.firstItem.domNode.focus(); +}; + +Menubar.prototype.setFocusToLastItem = function () { + this.lastItem.domNode.focus(); +}; + +Menubar.prototype.setFocusToPreviousItem = function (currentItem) { + var index; + currentItem.domNode.tabIndex = -1; + + if (currentItem === this.firstItem) { + this.lastItem.domNode.focus(); + this.lastItem.domNode.tabIndex = 0; + } + else { + index = this.menubarItems.indexOf(currentItem); + this.menubarItems[ index - 1 ].domNode.focus(); + this.menubarItems[ index - 1 ].domNode.tabIndex = 0; + } +}; + +Menubar.prototype.setFocusToNextItem = function (currentItem) { + var index; + currentItem.domNode.tabIndex = -1; + + if (currentItem === this.lastItem) { + this.firstItem.domNode.focus(); + this.firstItem.domNode.tabIndex = 0; + } + else { + index = this.menubarItems.indexOf(currentItem); + this.menubarItems[ index + 1 ].domNode.focus(); + this.menubarItems[ index + 1 ].domNode.tabIndex = 0; + } +}; + +Menubar.prototype.setFocusByFirstCharacter = function (currentItem, char) { + var start, index, char = char.toLowerCase(); + + // Get start index for search based on position of currentItem + start = this.menubarItems.indexOf(currentItem) + 1; + if (start === this.menubarItems.length) { + start = 0; + } + + // Check remaining slots in the menu + index = this.getIndexFirstChars(start, char); + + // If not found in remaining slots, check from beginning + if (index === -1) { + index = this.getIndexFirstChars(0, char); + } + + // If match was found... + if (index > -1) { + this.menubarItems[ index ].domNode.focus(); + this.menubarItems[ index ].domNode.tabIndex = 0; + currentItem.tabIndex = -1; + } +}; + +Menubar.prototype.getIndexFirstChars = function (startIndex, char) { + for (var i = startIndex; i < this.firstChars.length; i++) { + if (char === this.firstChars[ i ]) { + return i; + } + } + return -1; +}; + +/* MENU DISPLAY METHODS */ + +Menubar.prototype.getPosition = function (element) { + var x = 0, + y = 0; + + while (element) { + x += (element.offsetLeft - element.scrollLeft + element.clientLeft); + y += (element.offsetTop - element.scrollTop + element.clientTop); + element = element.offsetParent; + } + + return {x: x, y: y}; +}; + +Menubar.prototype.open = function () { +}; + +Menubar.prototype.close = function (force) { +}; diff --git a/examples/menubar/menubar-1/js/PopupMenuLinks.js b/examples/menubar/menubar-1/js/PopupMenuLinks.js new file mode 100644 index 0000000000..8d9ed4fe58 --- /dev/null +++ b/examples/menubar/menubar-1/js/PopupMenuLinks.js @@ -0,0 +1,247 @@ +/* +* This content is licensed according to the W3C Software License at +* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document +* +* File: PopupMenu.js +* +* Desc: Popup menu widget that implements ARIA Authoring Practices +* +* Author: Jon Gunderson and Ku Ja Eun +*/ + +/* +* @constructor PopupMenu +* +* @desc +* Wrapper object for a simple popup menu (without nested submenus) +* +* @param domNode +* The DOM element node that serves as the popup menu container. Each +* child element of domNode that represents a menuitem must have a +* 'role' attribute with value 'menuitem'. +* +* @param controllerObj +* The object that is a wrapper for the DOM element that controls the +* menu, e.g. a button element, with an 'aria-controls' attribute that +* references this menu's domNode. See MenuButton.js +* +* The controller object is expected to have the following properties: +* 1. domNode: The controller object's DOM element node, needed for +* retrieving positioning information. +* 2. hasHover: boolean that indicates whether the controller object's +* domNode has responded to a mouseover event with no subsequent +* mouseout event having occurred. +*/ +var PopupMenu = function (domNode, controllerObj) { + var elementChildren, + msgPrefix = 'PopupMenu constructor argument domNode '; + + // Check whether domNode is a DOM element + if (!domNode instanceof Element) { + throw new TypeError(msgPrefix + 'is not a DOM Element.'); + } + // Check whether domNode has child elements + if (domNode.childElementCount === 0) { + throw new Error(msgPrefix + 'has no element children.'); + } + // Check whether domNode descendant elements have A elements + var childElement = domNode.firstElementChild; + while (childElement) { + var menuitem = childElement.firstElementChild; + if (menuitem && menuitem === 'A') { + throw new Error(msgPrefix + 'has descendant elements that are not A elements.'); + } + childElement = childElement.nextElementSibling; + } + + this.domNode = domNode; + this.controller = controllerObj; + + this.menuitems = []; // See PopupMenu init method + this.firstChars = []; // See PopupMenu init method + + this.firstItem = null; // See PopupMenu init method + this.lastItem = null; // See PopupMenu init method + + this.hasFocus = false; // See MenuItem handleFocus, handleBlur + this.hasHover = false; // See PopupMenu handleMouseover, handleMouseout +}; + +/* +* @method PopupMenu.prototype.init +* +* @desc +* Add domNode event listeners for mouseover and mouseout. Traverse +* domNode children to configure each menuitem and populate menuitems +* array. Initialize firstItem and lastItem properties. +*/ +PopupMenu.prototype.init = function () { + var childElement, menuElement, menuItem, textContent, numItems, label; + + // Configure the domNode itself + this.domNode.tabIndex = -1; + + this.domNode.setAttribute('role', 'menu'); + + if (!this.domNode.getAttribute('aria-labelledby') && !this.domNode.getAttribute('aria-label') && !this.domNode.getAttribute('title')) { + label = this.controller.domNode.innerHTML; + this.domNode.setAttribute('aria-label', label); + } + + this.domNode.addEventListener('mouseover', this.handleMouseover.bind(this)); + this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this)); + + // Traverse the element children of domNode: configure each with + // menuitem role behavior and store reference in menuitems array. + childElement = this.domNode.firstElementChild; + + while (childElement) { + menuElement = childElement.firstElementChild; + + if (menuElement && menuElement.tagName === 'A') { + menuItem = new MenuItem(menuElement, this); + menuItem.init(); + this.menuitems.push(menuItem); + textContent = menuElement.textContent.trim(); + this.firstChars.push(textContent.substring(0, 1).toLowerCase()); + } + childElement = childElement.nextElementSibling; + } + + // Use populated menuitems array to initialize firstItem and lastItem. + numItems = this.menuitems.length; + if (numItems > 0) { + this.firstItem = this.menuitems[ 0 ]; + this.lastItem = this.menuitems[ numItems - 1 ]; + } +}; + +/* EVENT HANDLERS */ + +PopupMenu.prototype.handleMouseover = function (event) { + this.hasHover = true; +}; + +PopupMenu.prototype.handleMouseout = function (event) { + this.hasHover = false; + setTimeout(this.close.bind(this, false), 300); +}; + +/* FOCUS MANAGEMENT METHODS */ + +PopupMenu.prototype.setFocusToController = function (command) { + if (typeof command !== 'string') { + command = ''; + } + if (command === 'previous') { + this.controller.menubar.setFocusToPreviousItem(this.controller); + } + else if (command === 'next') { + this.controller.menubar.setFocusToNextItem(this.controller); + } + else { + this.controller.domNode.focus(); + } +}; + +PopupMenu.prototype.setFocusToFirstItem = function () { + this.firstItem.domNode.focus(); +}; + +PopupMenu.prototype.setFocusToLastItem = function () { + this.lastItem.domNode.focus(); +}; + +PopupMenu.prototype.setFocusToPreviousItem = function (currentItem) { + var index; + + if (currentItem === this.firstItem) { + this.lastItem.domNode.focus(); + } + else { + index = this.menuitems.indexOf(currentItem); + this.menuitems[ index - 1 ].domNode.focus(); + } +}; + +PopupMenu.prototype.setFocusToNextItem = function (currentItem) { + var index; + + if (currentItem === this.lastItem) { + this.firstItem.domNode.focus(); + } + else { + index = this.menuitems.indexOf(currentItem); + this.menuitems[ index + 1 ].domNode.focus(); + } +}; + +PopupMenu.prototype.setFocusByFirstCharacter = function (currentItem, char) { + var start, index, char = char.toLowerCase(); + + // Get start index for search based on position of currentItem + start = this.menuitems.indexOf(currentItem) + 1; + if (start === this.menuitems.length) { + start = 0; + } + + // Check remaining slots in the menu + index = this.getIndexFirstChars(start, char); + + // If not found in remaining slots, check from beginning + if (index === -1) { + index = this.getIndexFirstChars(0, char); + } + + // If match was found... + if (index > -1) { + this.menuitems[ index ].domNode.focus(); + } +}; + +PopupMenu.prototype.getIndexFirstChars = function (startIndex, char) { + for (var i = startIndex; i < this.firstChars.length; i++) { + if (char === this.firstChars[ i ]) { + return i; + } + } + return -1; +}; + +/* MENU DISPLAY METHODS */ + +PopupMenu.prototype.getPosition = function (element) { + var x = 0, + y = 0; + + while (element) { + x += (element.offsetLeft - element.scrollLeft + element.clientLeft); + y += (element.offsetTop - element.scrollTop + element.clientTop); + element = element.offsetParent; + } + + return {x: x, y: y}; +}; + +PopupMenu.prototype.open = function () { + // Get position and bounding rectangle of controller object's DOM node + var pos = this.getPosition(this.controller.domNode); + var rect = this.controller.domNode.getBoundingClientRect(); + + // Set CSS properties + this.domNode.style.display = 'block'; + this.domNode.style.position = 'absolute'; + this.domNode.style.top = (pos.y + rect.height) + 'px'; + this.domNode.style.left = pos.x + 'px'; + + // Set aria-expanded attribute + this.controller.domNode.setAttribute('aria-expanded', 'true'); +}; + +PopupMenu.prototype.close = function (force) { + + if (force || (!this.hasFocus && !this.hasHover && !this.controller.hasHover)) { + this.domNode.style.display = 'none'; + this.controller.domNode.setAttribute('aria-expanded', 'false'); + } +}; diff --git a/examples/menubar/menubar-1/js/npm-debug.log b/examples/menubar/menubar-1/js/npm-debug.log new file mode 100644 index 0000000000..df59cda30b --- /dev/null +++ b/examples/menubar/menubar-1/js/npm-debug.log @@ -0,0 +1,28 @@ +0 info it worked if it ends with ok +1 verbose cli [ '/usr/local/bin/node', +1 verbose cli '/usr/local/bin/npm', +1 verbose cli 'run', +1 verbose cli 'jscs', +1 verbose cli '-fix' ] +2 info using npm@3.10.3 +3 info using node@v6.7.0 +4 verbose stack Error: missing script: jscs +4 verbose stack at run (/usr/local/lib/node_modules/npm/lib/run-script.js:151:19) +4 verbose stack at /usr/local/lib/node_modules/npm/lib/run-script.js:61:5 +4 verbose stack at /usr/local/lib/node_modules/npm/node_modules/read-package-json/read-json.js:356:5 +4 verbose stack at checkBinReferences_ (/usr/local/lib/node_modules/npm/node_modules/read-package-json/read-json.js:320:45) +4 verbose stack at final (/usr/local/lib/node_modules/npm/node_modules/read-package-json/read-json.js:354:3) +4 verbose stack at then (/usr/local/lib/node_modules/npm/node_modules/read-package-json/read-json.js:124:5) +4 verbose stack at /usr/local/lib/node_modules/npm/node_modules/read-package-json/read-json.js:311:12 +4 verbose stack at /usr/local/lib/node_modules/npm/node_modules/graceful-fs/graceful-fs.js:78:16 +4 verbose stack at tryToString (fs.js:455:3) +4 verbose stack at FSReqWrap.readFileAfterClose [as oncomplete] (fs.js:442:12) +5 verbose cwd /Users/jku/jemma-aria-practice/examples/menubar/menubar-1/js +6 error Darwin 15.6.0 +7 error argv "/usr/local/bin/node" "/usr/local/bin/npm" "run" "jscs" "-fix" +8 error node v6.7.0 +9 error npm v3.10.3 +10 error missing script: jscs +11 error If you need help, you may report this error at: +11 error +12 verbose exit [ 1, true ] diff --git a/examples/menubar/menubar-1/mb-about.html b/examples/menubar/menubar-1/mb-about.html new file mode 100644 index 0000000000..2cc085fbc5 --- /dev/null +++ b/examples/menubar/menubar-1/mb-about.html @@ -0,0 +1,32 @@ + + + + Menubar Example Landing Page: About + + + + + +
+

Menubar Example Landing Page

+
+
+

About

+

Back to menubar example + +

Overview

+

Back to menubar example + +

Administration +

Back to menubar example + +

Facts +

Back to menubar example + +

Campus Tours +

Back to menubar example + +

+ + + \ No newline at end of file diff --git a/examples/menubar/menubar-1/mb-academics.html b/examples/menubar/menubar-1/mb-academics.html new file mode 100644 index 0000000000..a5711a6a75 --- /dev/null +++ b/examples/menubar/menubar-1/mb-academics.html @@ -0,0 +1,52 @@ + + + + Menubar Example Landing Page: Academics + + + + + +
+

Menubar Example Landing Page

+
+ +
+

Academics

+

Back to menubar example + +

Colleges & Schools

+

Back to menubar example + + +

Programs of Study

+

Back to menubar example + + +

Honors Programs

+

Back to menubar example + + +

Online Courses

+

Back to menubar example + + +

Course Explorer

+

Back to menubar example + + +

Register for Class

+

Back to menubar example + + +

Academic Calendar

+

Back to menubar example + + +

Transscripts

+

Back to menubar example + +

+ + + \ No newline at end of file diff --git a/examples/menubar/menubar-1/mb-admissions.html b/examples/menubar/menubar-1/mb-admissions.html new file mode 100644 index 0000000000..b2805416c6 --- /dev/null +++ b/examples/menubar/menubar-1/mb-admissions.html @@ -0,0 +1,39 @@ + + + + Menubar Example Landing Page: Admissions + + + + + +
+

Menubar Example Landing Page

+
+
+

Admissions

+

Back to menubar example + +

Apply

+

Back to menubar example + +

Tuition

+

Back to menubar example + +

Sign Up

+

Back to menubar example + +

Visit

+

Back to menubar example + +

Photo Tour

+

Back to menubar example + +

Connect

+

Back to menubar example + + +

+ + + \ No newline at end of file diff --git a/examples/menubar/menubar-1/mb-arts.html b/examples/menubar/menubar-1/mb-arts.html new file mode 100644 index 0000000000..88325c8dde --- /dev/null +++ b/examples/menubar/menubar-1/mb-arts.html @@ -0,0 +1,42 @@ + + + + Menubar Example Landing Page: Arts and Culture + + + + + +
+

Menubar Example Landing Page

+
+ +
+

Arts and Culture

+

Back to menubar example + +

College of Fine and Applied Arts

+

Back to menubar example

+ +

College of Liberal Arts and Sciences

+

Back to menubar example

+ +

Cultural Center

+

Back to menubar example

+ +

Performing Arts

+

Back to menubar example

+ +

Art Museum +

Back to menubar example

+ +

History Museum

+

Back to menubar example

+ +

Student Union +

Back to menubar example

+ +

+ + + \ No newline at end of file diff --git a/examples/menubar/menubar-1/mb-outreach.html b/examples/menubar/menubar-1/mb-outreach.html new file mode 100644 index 0000000000..445b86efc1 --- /dev/null +++ b/examples/menubar/menubar-1/mb-outreach.html @@ -0,0 +1,33 @@ + + + + Menubar Example Landing Page: Outreach + + + + + +
+

Menubar Example Landing Page

+
+ +
+

Outreach

+

Back to menubar example

+ +

Community Engagement

+

Back to menubar example

+ +

Corporate Relations

+

Back to menubar example

+ +

Extension

+

Back to menubar example

+ +

Outreach Events

+

Back to menubar example

+ +
+ + + \ No newline at end of file diff --git a/examples/menubar/menubar-1/mb-research.html b/examples/menubar/menubar-1/mb-research.html new file mode 100644 index 0000000000..ef7a45c6f7 --- /dev/null +++ b/examples/menubar/menubar-1/mb-research.html @@ -0,0 +1,36 @@ + + + + Menubar Example Landing Page: Research + + + + + +
+

Menubar Example Landing Page

+
+ +
+

Research

+

Back to menubar example + +

Research News

+

Back to menubar example + +

Reseach Centers

+

Back to menubar example + +

Find an Expert

+

Back to menubar example + +

Research Park

+

Back to menubar example + +

Technology Commercialization

+

Back to menubar example + +

+ + + \ No newline at end of file diff --git a/examples/menubar/menubar-1/menubar-1.html b/examples/menubar/menubar-1/menubar-1.html new file mode 100644 index 0000000000..d1a03f78de --- /dev/null +++ b/examples/menubar/menubar-1/menubar-1.html @@ -0,0 +1,325 @@ + + + + + + + + ARIA Menubar Example: Links + + + + + + + + + + + + + +
+

ARIA Menubar Example: Links

+ + + +

Authoring Practice Design Patten for Menubar

+ +
+ +

Example 1a: Explicit ARIA markup

+
    +
  • This example includes the ARIA roles and properties in the source code for authors to easily see which elements the roles and properties need to be included.
  • +
  • This is useful for people writing their own scripts to see which elements need roles and properties.
  • +
+ + + +

Example 1b: ARIA markup added dynamically

+ +
    +
  • This example the ARIA roles and properties are added by the scripts based on the structure of the HTML elements.
  • +
  • This can be used by authors to more easily add this script to their own projects and websites.
  • +
+ + + + + +
+ +

Keyboard Support

+ +

Menubar Menu Items

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyFunction
Space or Enter Key +
    +
  • Open or Close popup menu (e.g. toggle).
  • +
+
Left Arrow Key +
    +
  • Moves focus to previous menubar menu item.
  • +
  • If focus on first menubar menu item, focus is moved to last menubar menu item.
  • +
+
Right Arrow Key +
    +
  • Moves focus to next menubar menu item.
  • +
  • If focus on last menubar menu item, focus is moved to first menubar menu item.
  • +
+
Down Arrow Key +
    +
  • Open popup menu.
  • +
  • Moves focus to first popup menu item.
  • +
+
Up Arrow Key +
    +
  • Open popup menu.
  • +
  • Moves focus to last popup menu item.
  • +
+
Home +
    +
  • Moves focus to first menubar menu item.
  • +
+
End +
    +
  • Moves focus to last menubar menu item.
  • +
+
Character Key
    +
  • Moves focus to next menubar item that starts with that letter.
  • +
  • If no menubar menu item starts with the letter, focus does not move.
  • +
+
+ +

Popup Menu Items

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyFunction
Space or Enter Key +
    +
  • Follow link.
  • +
  • NOTE: In this example all the links are all internal and therefore not leave the page.
  • +
+
Escape Key +
    +
  • Closes popup menu.
  • +
  • Moves focus to current menubar menu item.
  • +
+
Left Arrow Key +
    +
  • Closes popup menu.
  • +
  • Moves focus to previous menubar menu item.
  • +
+
Right Arrow Key +
    +
  • Closes popup menu.
  • +
  • Moves focus to next menubar menu item.
  • +
+
Up Arrow Key +
    +
  • Moves focus to previous popup menu item.
  • +
  • If first popup menu item, focus is moved to the last popup menu item.
  • +
+
Down Arrow Key +
    +
  • Moves focus to next popup menu item.
  • +
  • If last popup menu item, focus is moved to the first popup menu item.
  • +
+
Home +
    +
  • Moves focus to first popup menu item.
  • +
+
End +
    +
  • Moves focus to last popup menu item.
  • +
+
Character Key +
    +
  • Moves focus to the next popup item that starts with that letter.
  • +
  • If no popup menu item starts with the letter, focus does not move.
  • +
+
+ +
+ + + + + \ No newline at end of file diff --git a/examples/menubar/menubar-2/css/menubarAction.css b/examples/menubar/menubar-2/css/menubarAction.css new file mode 100644 index 0000000000..e2639bb684 --- /dev/null +++ b/examples/menubar/menubar-2/css/menubarAction.css @@ -0,0 +1,158 @@ +ul[role="menubar"] { + margin: 10px; + padding: 10px; + font-size: 110%; + list-style: none; + background-color: #EEEEEE; +} + +ul[role="menubar"] ul[role="group"] { + margin: 0; + padding: 0; +} + +ul[role="menubar"] li[aria-disabled="true"] { + color: #666666; + text-decoration: line-through; +} + +ul[role="menubar"] [role="menuitem"], +ul[role="menubar"] [role="menuitemcheckbox"], +ul[role="menubar"] [role="menuitemradio"], +ul[role="menubar"] [role="separator"] { + padding: 0.25em; + background-color: #EEEEEE; + border: 2px solid #EEEEEE; +} + +ul[role="menubar"] ul li[role="menuitem"], +ul[role="menubar"] ul li[role="menuitemcheckbox"], +ul[role="menubar"] ul li[role="menuitemradio"], +ul[role="menubar"] ul li[role="separator"] { + padding-left: 1.5em; +} + + +ul[role="menubar"] [role="separator"] { + padding-top: 0.15em; + background-image: url('../images/separator.png'); + background-position: center; + background-repeat: repeat-x; +} + +ul[role="menubar"] [role="menuitem"]:focus, +ul[role="menubar"] [role="menuitem"]:hover, +ul[role="menubar"] [role="menuitemcheckbox"]:focus, +ul[role="menubar"] [role="menuitemcheckbox"]:hover, +ul[role="menubar"] [role="menuitemradio"]:focus, +ul[role="menubar"] [role="menuitemradio"]:hover, +ul[role="menubar"] [role="separator"]:focus, +ul[role="menubar"] [role="separator"]:hover { + background-color: black; + color: white; +} + +ul[role="menubar"] li[aria-checked='true']:before { + background-image: url('../images/checkmark.png'); + background-repeat: no-repeat; + background-position: 0.1em 0.2em; +} + +ul[role="menubar"] li[aria-checked='true']:hover, +ul[role="menubar"] li[aria-checked='true']:focus { + background-image: url('../images/checkmark-inverse.png'); + background-repeat: no-repeat; +} + +ul[role="menubar"] li { + list-style: none; + margin: 0; + padding: 0; +} + +ul[role="menubar"] > li { + display: inline; +} + +ul[role="menubar"] > li > span:after { +/* content: url('../images/pull-down-menu-icon.png'); */ + content: " \25bc"; + font-size: 80%; + color: gray; +} + +ul[role="menubar"] ul[role="menu"] { + display: none; + position: absolute; + margin: 0; + padding: 0; +} + +ul[role="menubar"] ul[role="menu"] li span { + display: block; +} + + + +/* +* checked, hover and focus styling +*/ + + +li.checked { + font-weight: bold; + background-image: url('http://www.oaa-accessibility.org/media/examples/images/dot.png'); + background-repeat: no-repeat; + background-position: 5px 10px; +} + +.menu-hover { + background-color: #700 !important; + color: white !important; +} + +.menu-hover-checked { + background-color: #700 !important; + color: white !important; + background-image: url('http://www.oaa-accessibility.org/media/examples/images/dot-selected.png') !important; +} + +.menu-focus { + background-color: black; + font-weight: bold; + color: white !important; +} + +li.menu-focus-checked { + background-color: black; + font-weight: bold; + color: white !important; + background-image: url('http://www.oaa-accessibility.org/media/examples/images/dot-selected.png') !important; +} +.hidden { + position: absolute; + left: -200em; + top: -20em; +} + + +/* +* Text area styles +*/ +.italic { + font-style: italic; +} +.bold { + font-weight: bold; +} +.underline { + text-decoration: underline; +} +#textarea1 { + padding: .5em; + border: 1px solid black; + height: 400px; + width: 70%; + font-size: medium; + font-family: sans-serif; +} diff --git a/examples/menubar/menubar-2/images/checkmark-inverse.paint b/examples/menubar/menubar-2/images/checkmark-inverse.paint new file mode 100644 index 0000000000..561f232efb Binary files /dev/null and b/examples/menubar/menubar-2/images/checkmark-inverse.paint differ diff --git a/examples/menubar/menubar-2/images/checkmark-inverse.png b/examples/menubar/menubar-2/images/checkmark-inverse.png new file mode 100644 index 0000000000..c1f2f86c45 Binary files /dev/null and b/examples/menubar/menubar-2/images/checkmark-inverse.png differ diff --git a/examples/menubar/menubar-2/images/checkmark.paint b/examples/menubar/menubar-2/images/checkmark.paint new file mode 100644 index 0000000000..6c9e483d60 Binary files /dev/null and b/examples/menubar/menubar-2/images/checkmark.paint differ diff --git a/examples/menubar/menubar-2/images/checkmark.png b/examples/menubar/menubar-2/images/checkmark.png new file mode 100644 index 0000000000..503d19edeb Binary files /dev/null and b/examples/menubar/menubar-2/images/checkmark.png differ diff --git a/examples/menubar/menubar-2/images/down-arrow.png b/examples/menubar/menubar-2/images/down-arrow.png new file mode 100644 index 0000000000..694e80519b Binary files /dev/null and b/examples/menubar/menubar-2/images/down-arrow.png differ diff --git a/examples/menubar/menubar-2/images/pull-down-menu-icon.paint b/examples/menubar/menubar-2/images/pull-down-menu-icon.paint new file mode 100644 index 0000000000..f646943e03 Binary files /dev/null and b/examples/menubar/menubar-2/images/pull-down-menu-icon.paint differ diff --git a/examples/menubar/menubar-2/images/pull-down-menu-icon.png b/examples/menubar/menubar-2/images/pull-down-menu-icon.png new file mode 100644 index 0000000000..b6c5714d1d Binary files /dev/null and b/examples/menubar/menubar-2/images/pull-down-menu-icon.png differ diff --git a/examples/menubar/menubar-2/images/separator.paint b/examples/menubar/menubar-2/images/separator.paint new file mode 100644 index 0000000000..9e313a1cc1 Binary files /dev/null and b/examples/menubar/menubar-2/images/separator.paint differ diff --git a/examples/menubar/menubar-2/images/separator.png b/examples/menubar/menubar-2/images/separator.png new file mode 100644 index 0000000000..a837fbce93 Binary files /dev/null and b/examples/menubar/menubar-2/images/separator.png differ diff --git a/examples/menubar/menubar-2/js/MenuItemAction.js b/examples/menubar/menubar-2/js/MenuItemAction.js new file mode 100644 index 0000000000..efce7adbf6 --- /dev/null +++ b/examples/menubar/menubar-2/js/MenuItemAction.js @@ -0,0 +1,187 @@ +/* +* This content is licensed according to the W3C Software License at +* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document +* +* File: MenuItem.js +* +* Desc: Popup Menu Menuitem widget that implements ARIA Authoring Practices +* +* Author: Jon Gunderson, Ku Ja Eun and Nicholas Hoyt +*/ + +/* +* @constructor MenuItem +* +* @desc +* Wrapper object for a simple menu item in a popup menu +* +* @param domNode +* The DOM element node that serves as the menu item container. +* The menuObj PopupMenu is responsible for checking that it has +* requisite metadata, e.g. role="menuitem". +* +* @param menuObj +* The object that is a wrapper for the PopupMenu DOM element that +* contains the menu item DOM element. See PopupMenu.js +*/ +var MenuItem = function (domNode, menuObj) { + + this.domNode = domNode; + this.menu = menuObj; + + this.keyCode = Object.freeze({ + 'TAB': 9, + 'RETURN': 13, + 'ESC': 27, + 'SPACE': 32, + 'PAGEUP': 33, + 'PAGEDOWN': 34, + 'END': 35, + 'HOME': 36, + 'LEFT': 37, + 'UP': 38, + 'RIGHT': 39, + 'DOWN': 40 + }); +}; + +MenuItem.prototype.init = function () { + this.domNode.tabIndex = -1; + + if (!this.domNode.getAttribute('role')) { + this.domNode.setAttribute('role', 'menuitem'); + } + + this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); + this.domNode.addEventListener('keypress', this.handleKeypress.bind(this)); + this.domNode.addEventListener('click', this.handleClick.bind(this)); + this.domNode.addEventListener('focus', this.handleFocus.bind(this)); + this.domNode.addEventListener('blur', this.handleBlur.bind(this)); + this.domNode.addEventListener('mouseover', this.handleMouseover.bind(this)); + this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this)); + +}; + +/* EVENT HANDLERS */ + +MenuItem.prototype.handleKeydown = function (event) { + var tgt = event.currentTarget, + flag = false, + clickEvent; + + // console.log("[MenuItem][handleKeydown]: " + event.keyCode + " " + this.menu) + + switch (event.keyCode) { + case this.keyCode.SPACE: + case this.keyCode.RETURN: + // Create simulated mouse event to mimic the behavior of ATs + // and let the event handler handleClick do the housekeeping. + try { + clickEvent = new MouseEvent('click', { + 'view': window, + 'bubbles': true, + 'cancelable': true + }); + } + catch (err) { + if (document.createEvent) { + // DOM Level 3 for IE 9+ + clickEvent = document.createEvent('MouseEvents'); + clickEvent.initEvent('click', true, true); + } + } + tgt.dispatchEvent(clickEvent); + flag = true; + break; + + case this.keyCode.ESC: + this.menu.setFocusToController(); + this.menu.close(true); + flag = true; + break; + + case this.keyCode.UP: + this.menu.setFocusToPreviousItem(this); + flag = true; + break; + + case this.keyCode.DOWN: + this.menu.setFocusToNextItem(this); + flag = true; + break; + + case this.keyCode.LEFT: + this.menu.setFocusToController('previous'); + this.menu.close(true); + flag = true; + break; + + case this.keyCode.RIGHT: + this.menu.setFocusToController('next'); + this.menu.close(true); + flag = true; + break; + + case this.keyCode.HOME: + case this.keyCode.PAGEUP: + this.menu.setFocusToFirstItem(); + flag = true; + break; + + case this.keyCode.END: + case this.keyCode.PAGEDOWN: + this.menu.setFocusToLastItem(); + flag = true; + break; + + case this.keyCode.TAB: + this.menu.setFocusToController(); + this.menu.close(true); + break; + + default: + break; + } + + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } +}; + +MenuItem.prototype.handleKeypress = function (event) { + var char = String.fromCharCode(event.charCode); + + function isPrintableCharacter (str) { + return str.length === 1 && str.match(/\S/); + } + + if (isPrintableCharacter(char)) { + this.menu.setFocusByFirstCharacter(this, char); + } +}; + +MenuItem.prototype.handleClick = function (event) { + this.menu.setFocusToController(); + this.menu.close(true); +}; + +MenuItem.prototype.handleFocus = function (event) { + this.menu.hasFocus = true; +}; + +MenuItem.prototype.handleBlur = function (event) { + this.menu.hasFocus = false; + setTimeout(this.menu.close.bind(this.menu, false), 300); +}; + +MenuItem.prototype.handleMouseover = function (event) { + this.menu.hasHover = true; + this.menu.open(); + +}; + +MenuItem.prototype.handleMouseout = function (event) { + this.menu.hasHover = false; + setTimeout(this.menu.close.bind(this.menu, false), 300); +}; diff --git a/examples/menubar/menubar-2/js/MenubarAction.js b/examples/menubar/menubar-2/js/MenubarAction.js new file mode 100644 index 0000000000..8f5d25bbeb --- /dev/null +++ b/examples/menubar/menubar-2/js/MenubarAction.js @@ -0,0 +1,192 @@ +/* +* This content is licensed according to the W3C Software License at +* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document +* +* File: MenubarAction.js +* +* Desc: Menubar widget that implements ARIA Authoring Practices +* +* Author: Jon Gunderson, Ku Ja Eun and Nicholas Hoyt +*/ + +/* +* @constructor MenubarAction +* +* @desc +* Wrapper object for a menubar (with nested submenus of links) +* +* @param domNode +* The DOM element node that serves as the menubar container. Each +* child element of menubarNode that represents a menubaritem must +* be an A element +*/ +var MenubarAction = function (domNode) { + var elementChildren, + msgPrefix = 'Menubar constructor argument menubarNode '; + + // Check whether menubarNode is a DOM element + if (!domNode instanceof Element) { + throw new TypeError(msgPrefix + 'is not a DOM Element.'); + } + + // Check whether menubarNode has descendant elements + if (domNode.childElementCount === 0) { + throw new Error(msgPrefix + 'has no element children.'); + } + // Check whether menubarNode has SPAN elements + e = domNode.firstElementChild; + while (e) { + var menubarItem = e.firstElementChild; + if (e && menubarItem && menubarItem.tagName !== 'SPAN') { + throw new Error(msgPrefix + 'has child elements are not SPAN elements.'); + } + e = e.nextElementSibling; + } + + this.domNode = domNode; + + this.menubarItems = []; // see Menubar init method + this.firstChars = []; // see Menubar init method + + this.firstItem = null; // see Menubar init method + this.lastItem = null; // see Menubar init method + + this.hasFocus = false; // see MenubarItem handleFocus, handleBlur + this.hasHover = false; // see Menubar handleMouseover, handleMouseout +}; + +/* +* @method MenubarAction.prototype.init +* +* @desc +* Adds ARIA role to the menubar node +* Traverse menubar children for A elements to configure each A element as a ARIA menuitem +* and populate menuitems array. Initialize firstItem and lastItem properties. +*/ +MenubarAction.prototype.init = function () { + var menubarItem, childElement, menuElement, textContent, numItems; + + this.domNode.setAttribute('role', 'menubar'); + + // Traverse the element children of menubarNode: configure each with + // menuitem role behavior and store reference in menuitems array. + e = this.domNode.firstElementChild; + + while (e) { + var menuElement = e.firstElementChild; + + if (e && menuElement && menuElement.tagName === 'SPAN') { + menubarItem = new MenubarItemAction(menuElement, this); + menubarItem.init(); + this.menubarItems.push(menubarItem); + textContent = menuElement.textContent.trim(); + this.firstChars.push(textContent.substring(0, 1).toLowerCase()); + } + + e = e.nextElementSibling; + } + + // Use populated menuitems array to initialize firstItem and lastItem. + numItems = this.menubarItems.length; + if (numItems > 0) { + this.firstItem = this.menubarItems[0]; + this.lastItem = this.menubarItems[numItems - 1]; + } + this.firstItem.domNode.tabIndex = 0; +}; + +/* FOCUS MANAGEMENT METHODS */ + +MenubarAction.prototype.setFocusToFirstItem = function () { + this.firstItem.domNode.focus(); +}; + +MenubarAction.prototype.setFocusToLastItem = function () { + this.lastItem.domNode.focus(); +}; + +MenubarAction.prototype.setFocusToPreviousItem = function (currentItem) { + var index; + currentItem.domNode.tabIndex = -1; + + if (currentItem === this.firstItem) { + this.lastItem.domNode.focus(); + this.lastItem.domNode.tabIndex = 0; + } + else { + index = this.menubarItems.indexOf(currentItem); + this.menubarItems[index - 1].domNode.focus(); + this.menubarItems[index - 1].domNode.tabIndex = 0; + } +}; + +MenubarAction.prototype.setFocusToNextItem = function (currentItem) { + var index; + currentItem.domNode.tabIndex = -1; + + if (currentItem === this.lastItem) { + this.firstItem.domNode.focus(); + this.firstItem.domNode.tabIndex = 0; + } + else { + index = this.menubarItems.indexOf(currentItem); + this.menubarItems[index + 1].domNode.focus(); + this.menubarItems[index + 1].domNode.tabIndex = 0; + } +}; + +MenubarAction.prototype.setFocusByFirstCharacter = function (currentItem, char) { + var start, index, char = char.toLowerCase(); + + // Get start index for search based on position of currentItem + start = this.menubarItems.indexOf(currentItem) + 1; + if (start === this.menubarItems.length) { + start = 0; + } + + // Check remaining slots in the menu + index = this.getIndexFirstChars(start, char); + + // If not found in remaining slots, check from beginning + if (index === -1) { + index = this.getIndexFirstChars(0, char); + } + + // If match was found... + if (index > -1) { + this.menubarItems[index].domNode.focus(); + this.menubarItems[index].domNode.tabIndex = 0; + currentItem.tabIndex = -1; + } +}; + +MenubarAction.prototype.getIndexFirstChars = function (startIndex, char) { + for (var i = startIndex; i < this.firstChars.length; i++) { + if (char === this.firstChars[i]) { + return i; + } + } + return -1; +}; + +/* MENU DISPLAY METHODS */ + +MenubarAction.prototype.getPosition = function (element) { + var x = 0, + y = 0; + + while (element) { + x += (element.offsetLeft - element.scrollLeft + element.clientLeft); + y += (element.offsetTop - element.scrollTop + element.clientTop); + element = element.offsetParent; + } + + return {x: x, y: y}; +}; + +MenubarAction.prototype.open = function () { +}; + +MenubarAction.prototype.close = function (force) { +}; + diff --git a/examples/menubar/menubar-2/js/MenubarItemAction.js b/examples/menubar/menubar-2/js/MenubarItemAction.js new file mode 100644 index 0000000000..69f32478cb --- /dev/null +++ b/examples/menubar/menubar-2/js/MenubarItemAction.js @@ -0,0 +1,196 @@ +/* +* This content is licensed according to the W3C Software License at +* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document +* +* File: MenubarItemAction.js +* +* Desc: Menubar Menuitem widget that implements ARIA Authoring Practices +* +* Author: Jon Gunderson, Ku Ja Eun and Nicholas Hoyt +*/ + +/* +* @constructor MenubarItem +* +* @desc +* Object that configures menu item elements by setting tabIndex +* and registering itself to handle pertinent events. +* +* While menuitem elements handle many keydown events, as well as +* focus and blur events, they do not maintain any state variables, +* delegating those responsibilities to its associated menu object. +* +* Consequently, it is only necessary to create one instance of +* MenubarItem from within the menu object; its configure method +* can then be called on each menuitem element. +* +* @param domNode +* The DOM element node that serves as the menu item container. +* The menuObj PopupMenu is responsible for checking that it has +* requisite metadata, e.g. role="menuitem". +* +* @param menuObj +* The PopupMenu object that is a delegate for the menu DOM element +* that contains the menuitem element. +*/ +var MenubarItemAction = function (domNode, menuObj) { + + this.menubar = menuObj; + this.domNode = domNode; + this.popupMenu = false; + + this.hasFocus = false; + this.hasHover = false; + + this.keyCode = Object.freeze({ + 'TAB': 9, + 'RETURN': 13, + 'ESC': 27, + 'SPACE': 32, + 'PAGEUP': 33, + 'PAGEDOWN': 34, + 'END': 35, + 'HOME': 36, + 'LEFT': 37, + 'UP': 38, + 'RIGHT': 39, + 'DOWN': 40 + }); +}; + +MenubarItemAction.prototype.init = function () { + this.domNode.tabIndex = -1; + + this.domNode.setAttribute('role', 'menuitem'); + this.domNode.setAttribute('aria-haspopup', 'true'); + this.domNode.setAttribute('aria-expanded', 'false'); + this.domNode.tabIndex = -1; + + this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); + this.domNode.addEventListener('keypress', this.handleKeypress.bind(this)); + this.domNode.addEventListener('click', this.handleClick.bind(this)); + this.domNode.addEventListener('focus', this.handleFocus.bind(this)); + this.domNode.addEventListener('blur', this.handleBlur.bind(this)); + this.domNode.addEventListener('mouseover', this.handleMouseover.bind(this)); + this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this)); + + // initialize pop up menus + + var nextElement = this.domNode.nextElementSibling; + + if (nextElement && nextElement.tagName === 'UL') { + this.popupMenu = new PopupMenuAction(nextElement, this); + this.popupMenu.init(); + } + +}; + +MenubarItemAction.prototype.handleKeydown = function (event) { + var tgt = event.currentTarget, + flag = false, + clickEvent; + + switch (event.keyCode) { + case this.keyCode.SPACE: + case this.keyCode.RETURN: + if (tgt.getAttribute('aria-expanded') === 'true' && this.popupMenu) { + this.popupMenu.close(); + } + else { + this.popupMenu.open(); + } + flag = true; + break; + + case this.keyCode.LEFT: + this.menubar.setFocusToPreviousItem(this); + if (this.popupMenu) { + this.popupMenu.close(); + } + flag = true; + break; + + case this.keyCode.RIGHT: + this.menubar.setFocusToNextItem(this); + if (this.popupMenu) { + this.popupMenu.close(); + } + flag = true; + break; + + case this.keyCode.UP: + if (this.popupMenu) { + this.popupMenu.open(); + this.popupMenu.setFocusToLastItem(); + flag = true; + } + break; + + case this.keyCode.DOWN: + if (this.popupMenu) { + this.popupMenu.open(); + this.popupMenu.setFocusToFirstItem(); + flag = true; + } + break; + + case this.keyCode.HOME: + case this.keyCode.PAGEUP: + this.menubar.setFocusToFirstItem(); + if (this.popupMenu) { + this.popupMenu.close(); + } + flag = true; + break; + + case this.keyCode.END: + case this.keyCode.PAGEDOWN: + this.menubar.setFocusToLastItem(); + if (this.popupMenu) { + this.popupMenu.close(); + } + flag = true; + break; + + default: + break; + } + + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } +}; + +MenubarItemAction.prototype.handleKeypress = function (event) { + var char = String.fromCharCode(event.charCode); + + function isPrintableCharacter (str) { + return str.length === 1 && str.match(/\S/); + } + + if (isPrintableCharacter(char)) { + this.menubar.setFocusByFirstCharacter(this, char); + } +}; + +MenubarItemAction.prototype.handleClick = function (event) { +}; + +MenubarItemAction.prototype.handleFocus = function (event) { + this.menubar.hasFocus = true; +}; + +MenubarItemAction.prototype.handleBlur = function (event) { + this.menubar.hasFocus = false; +}; + +MenubarItemAction.prototype.handleMouseover = function (event) { + this.hasHover = true; + this.popupMenu.open(); +}; + +MenubarItemAction.prototype.handleMouseout = function (event) { + this.hasHover = false; + setTimeout(this.popupMenu.close.bind(this.popupMenu, false), 300); +}; diff --git a/examples/menubar/menubar-2/js/PopupMenuAction.js b/examples/menubar/menubar-2/js/PopupMenuAction.js new file mode 100644 index 0000000000..17bc40f0bf --- /dev/null +++ b/examples/menubar/menubar-2/js/PopupMenuAction.js @@ -0,0 +1,250 @@ +/* +* This content is licensed according to the W3C Software License at +* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document +* +* File: PopupMenuAction.js +* +* Desc: Popup menu widget that implements ARIA Authoring Practices +* +* Author: Jon Gunderson and Ku Ja Eun +*/ + +/* +* @constructor PopupMenuAction +* +* @desc +* Wrapper object for a simple popup menu (without nested submenus) +* +* @param domNode +* The DOM element node that serves as the popup menu container. Each +* child element of domNode that represents a menuitem must have a +* 'role' attribute with value 'menuitem'. +* +* @param controllerObj +* The object that is a wrapper for the DOM element that controls the +* menu, e.g. a button element, with an 'aria-controls' attribute that +* references this menu's domNode. See MenuButton.js +* +* The controller object is expected to have the following properties: +* 1. domNode: The controller object's DOM element node, needed for +* retrieving positioning information. +* 2. hasHover: boolean that indicates whether the controller object's +* domNode has responded to a mouseover event with no subsequent +* mouseout event having occurred. +*/ +var PopupMenuAction = function (domNode, controllerObj) { + var elementChildren, + msgPrefix = 'PopupMenu constructor argument domNode '; + + // Check whether domNode is a DOM element + if (!domNode instanceof Element) { + throw new TypeError(msgPrefix + 'is not a DOM Element.'); + } + + // Check whether domNode has child elements + if (domNode.childElementCount === 0) { + throw new Error(msgPrefix + 'has no element children.'); + } + + // Check whether domNode descendant elements have A elements + var childElement = domNode.firstElementChild; + while (childElement) { + var menuitem = childElement.firstElementChild; + if (menuitem && menuitem === 'A') { + throw new Error(msgPrefix + 'has descendant elements that are not A elements.'); + } + childElement = childElement.nextElementSibling; + } + + this.domNode = domNode; + this.controller = controllerObj; + + this.menuitems = []; // see PopupMenu init method + this.firstChars = []; // see PopupMenu init method + + this.firstItem = null; // see PopupMenu init method + this.lastItem = null; // see PopupMenu init method + + this.hasFocus = false; // see MenuItem handleFocus, handleBlur + this.hasHover = false; // see PopupMenu handleMouseover, handleMouseout +}; + +/* +* @method PopupMenuAction.prototype.init +* +* @desc +* Add domNode event listeners for mouseover and mouseout. Traverse +* domNode children to configure each menuitem and populate menuitems +* array. Initialize firstItem and lastItem properties. +*/ +PopupMenuAction.prototype.init = function () { + var childElement, menuElement, firstChildElement, menuItem, textContent, numItems, label; + + // Configure the domNode itself + this.domNode.tabIndex = -1; + + this.domNode.setAttribute('role', 'menu'); + + if (!this.domNode.getAttribute('aria-labelledby') && !this.domNode.getAttribute('aria-label') && !this.domNode.getAttribute('title')) { + label = this.controller.domNode.innerHTML; + this.domNode.setAttribute('aria-label', label); + } + + this.domNode.addEventListener('mouseover', this.handleMouseover.bind(this)); + this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this)); + + // Traverse the element children of domNode: configure each with + // menuitem role behavior and store reference in menuitems array. + menuElements = this.domNode.getElementsByTagName('LI'); + + for (var i = 0; i < menuElements.length; i++) { + + menuElement = menuElements[i]; + + if (!menuElement.firstElementChild && menuElement.getAttribute('role') != 'separator') { + menuItem = new MenuItem(menuElement, this); + menuItem.init(); + this.menuitems.push(menuItem); + textContent = menuElement.textContent.trim(); + this.firstChars.push(textContent.substring(0, 1).toLowerCase()); + } + + } + + // Use populated menuitems array to initialize firstItem and lastItem. + numItems = this.menuitems.length; + if (numItems > 0) { + this.firstItem = this.menuitems[0]; + this.lastItem = this.menuitems[numItems - 1]; + } +}; + +/* EVENT HANDLERS */ + +PopupMenuAction.prototype.handleMouseover = function (event) { + this.hasHover = true; +}; + +PopupMenuAction.prototype.handleMouseout = function (event) { + this.hasHover = false; + setTimeout(this.close.bind(this, false), 300); +}; + +/* FOCUS MANAGEMENT METHODS */ + +PopupMenuAction.prototype.setFocusToController = function (command) { + if (typeof command !== 'string') { + command = ''; + } + if (command === 'previous') { + this.controller.menubar.setFocusToPreviousItem(this.controller); + } + else if (command === 'next') { + this.controller.menubar.setFocusToNextItem(this.controller); + } + else { + this.controller.domNode.focus(); + } +}; + +PopupMenuAction.prototype.setFocusToFirstItem = function () { + this.firstItem.domNode.focus(); +}; + +PopupMenuAction.prototype.setFocusToLastItem = function () { + this.lastItem.domNode.focus(); +}; + +PopupMenuAction.prototype.setFocusToPreviousItem = function (currentItem) { + var index; + + if (currentItem === this.firstItem) { + this.lastItem.domNode.focus(); + } + else { + index = this.menuitems.indexOf(currentItem); + this.menuitems[index - 1].domNode.focus(); + } +}; + +PopupMenuAction.prototype.setFocusToNextItem = function (currentItem) { + var index; + + if (currentItem === this.lastItem) { + this.firstItem.domNode.focus(); + } + else { + index = this.menuitems.indexOf(currentItem); + this.menuitems[index + 1].domNode.focus(); + } +}; + +PopupMenuAction.prototype.setFocusByFirstCharacter = function (currentItem, char) { + var start, index, char = char.toLowerCase(); + + // Get start index for search based on position of currentItem + start = this.menuitems.indexOf(currentItem) + 1; + if (start === this.menuitems.length) { + start = 0; + } + + // Check remaining slots in the menu + index = this.getIndexFirstChars(start, char); + + // If not found in remaining slots, check from beginning + if (index === -1) { + index = this.getIndexFirstChars(0, char); + } + + // If match was found... + if (index > -1) { + this.menuitems[index].domNode.focus(); + } +}; + +PopupMenuAction.prototype.getIndexFirstChars = function (startIndex, char) { + for (var i = startIndex; i < this.firstChars.length; i++) { + if (char === this.firstChars[i]) { + return i; + } + } + return -1; +}; + +/* MENU DISPLAY METHODS */ + +PopupMenuAction.prototype.getPosition = function (element) { + var x = 0, + y = 0; + + while (element) { + x += (element.offsetLeft - element.scrollLeft + element.clientLeft); + y += (element.offsetTop - element.scrollTop + element.clientTop); + element = element.offsetParent; + } + + return {x: x, y: y}; +}; + +PopupMenuAction.prototype.open = function () { + // get position and bounding rectangle of controller object's DOM node + var pos = this.getPosition(this.controller.domNode); + var rect = this.controller.domNode.getBoundingClientRect(); + + // set CSS properties + this.domNode.style.display = 'block'; + this.domNode.style.position = 'absolute'; + this.domNode.style.top = (pos.y + rect.height) + 'px'; + this.domNode.style.left = pos.x + 'px'; + + // set aria-expanded attribute + this.controller.domNode.setAttribute('aria-expanded', 'true'); +}; + +PopupMenuAction.prototype.close = function (force) { + + if (force || (!this.hasFocus && !this.hasHover && !this.controller.hasHover)) { + this.domNode.style.display = 'none'; + this.controller.domNode.setAttribute('aria-expanded', 'false'); + } +}; diff --git a/examples/menubar/menubar-2/js/TextStyling.js b/examples/menubar/menubar-2/js/TextStyling.js new file mode 100644 index 0000000000..aab53b703e --- /dev/null +++ b/examples/menubar/menubar-2/js/TextStyling.js @@ -0,0 +1,347 @@ +/* +* This content is licensed according to the W3C Software License at +* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document +* +* File: TextStyling.js +* +* Desc: Styling functions for changing the style of an item +* +* Author: Jon Gunderson and Ku Ja Eun +*/ + +/* +* @eventHandler setFontFamily +* @desc +* Sets the CSS font-family property of the text content +* identified by the id and updates the menuitemradio +* group in the menu to indicate which font is selected +* @param event +* Event object of menuitemradio in the radio group +* @param id +* id property of the element to apply the styling +* @param value +* value for the CSS font-family property +* +*/ +function setFontFamily (event, id) { + var currentTarget = event.currentTarget; + + var value = event.target.innerHTML; + + if (value) { + document.getElementById(id).style.fontFamily = value; + } + + var childElement = currentTarget.firstElementChild; + + while (childElement) { + if (childElement.innerHTML === value) { + childElement.setAttribute('aria-checked', 'true'); + } + else { + childElement.setAttribute('aria-checked', 'false'); + } + childElement = childElement.nextElementSibling; + } +}; + +/* +* @eventHandler setTextDecoration +* +* @desc +* Sets the CSS font-family property of the text content +* identified by the id and updates the menuitemradio +* group in the menu to indicate which decoration is selected +* +* @param event +* Event object of menuitemradio in the radio group +* +* @param id +* id property of the element to apply the styling +* +*/ +function setTextDecoration (event, id) { + + var currentTarget = event.currentTarget; + + var value = event.target.innerHTML; + + if (value) { + document.getElementById(id).style.textDecoration = value; + } + + var childElement = currentTarget.firstElementChild; + + while (childElement) { + if (childElement.innerHTML === value) { + childElement.setAttribute('aria-checked', 'true'); + } + else { + childElement.setAttribute('aria-checked', 'false'); + } + childElement = childElement.nextElementSibling; + } +}; + +/* +* @eventHandler setTextAlign +* +* @desc +* Sets the CSS text-align property of the text content +* identified by the id and updates the menuitemradio +* group in the menu to indicate which alignment is selected +* +* @param event +* Event object of menuitemradio in the radio group +* +* @param id +* id property of the element to apply the styling +*/ +function setTextAlign (event, id) { + + var currentTarget = event.currentTarget; + + var value = event.target.innerHTML; + + if (value) { + document.getElementById(id).style.textAlign = value; + } + + var childElement = currentTarget.firstElementChild; + + while (childElement) { + if (childElement.innerHTML === value) { + childElement.setAttribute('aria-checked', 'true'); + } + else { + childElement.setAttribute('aria-checked', 'false'); + } + childElement = childElement.nextElementSibling; + } +}; + +/* +* @eventHandler setFontSize +* +* @desc +* Sets the CSS font-size property of the text content +* identified by the id and updates the menuitemradio +* group in the menu to indicate which size is selected +* +* @param event +* Event object of menuitemradio in the radio group +* +* @param id +* id property of the element to apply the styling +* +* @param value +* value for the CSS font-size property +* +*/ +function setFontSize (event, id) { + + var target = event.target; + var currentTarget = event.currentTarget; + var radioGroup = currentTarget.querySelectorAll('[role=\'group\']')[0]; + var value = false; + var childElement; + var flag; + var i; + var disableSmaller; + var disableLarger; + + value = target.innerHTML; + + if (value.toLowerCase() === 'larger') { + + childElement = radioGroup.firstElementChild; + + while (childElement) { + flag = childElement.getAttribute('aria-checked'); + childElement = childElement.nextElementSibling; + if (flag === 'true') { + break; + } + } + + if (childElement) { + value = childElement.innerHTML; + } + else { + value = false; + } + + } + else { + if (value.toLowerCase() === 'smaller') { + + var childElement = radioGroup.lastElementChild; + + while (childElement) { + var flag = childElement.getAttribute('aria-checked'); + childElement = childElement.previousElementSibling; + if (flag === 'true') { + break; + } + } + + if (childElement) { + value = childElement.innerHTML; + } + else { + value = false; + } + } + } + + console.log('VALUE: ' + value); + + if (value) { + document.getElementById(id).style.fontSize = value; + + childElement = radioGroup.firstElementChild; + + while (childElement) { + if (childElement.innerHTML === value) { + childElement.setAttribute('aria-checked', 'true'); + } + else { + childElement.setAttribute('aria-checked', 'false'); + } + childElement = childElement.nextElementSibling; + + } + + if (value === 'X-Small') { + disableSmaller = 'true'; + } + else { + disableSmaller = 'false'; + } + + if (value === 'X-Large') { + disableLarger = 'true'; + } + else { + disableLarger = 'false'; + } + + childElement = currentTarget.firstElementChild; + + while (childElement) { + + if (childElement.innerHTML === 'Smaller') { + childElement.setAttribute('aria-disabled', disableSmaller); + } + if (childElement.innerHTML === 'Larger') { + childElement.setAttribute('aria-disabled', disableLarger); + } + + childElement = childElement.nextElementSibling; + } + } + +}; + +/* +* @eventHandler setColor +* +* @desc +* Sets the CSS color property of the text content +* identified by the id and updates the menuitemradio +* group in the menu to indicate which color is selected +* +* @param event +* Event object of menuitemradio in the radio group +* +* @param id +* id property of the element to apply the styling +* +* @param value +* value for the CSS color property +* +*/ +function setColor (event, id, value) { + + var currentTarget = event.currentTarget; + + var value = event.target.innerHTML; + + if (value) { + document.getElementById(id).style.color = value; + } + + var childElement = currentTarget.firstElementChild; + + while (childElement) { + if (childElement.innerHTML === value) { + childElement.setAttribute('aria-checked', 'true'); + } + else { + childElement.setAttribute('aria-checked', 'false'); + } + childElement = childElement.nextElementSibling; + } +}; + +/* +* @eventHandler toggleBold +* +* @desc +* Sets the CSS font-size property of the text content +* identified by the id and updates the menuitemradio +* group in the menu to indicate which size is selected +* by using the aria-checked property +* +* @param event +* Event object of menuitemradio in the radio group +* +* @param id +* id property of the element to apply the styling +* +*/ +function toggleBold (event, id) { + var target = event.currentTarget; + var flag = target.getAttribute('aria-checked'); + + if (flag === 'true') { + document.getElementById(id).style.fontWeight = 'normal'; + target.setAttribute('aria-checked', 'false'); + } + else { + document.getElementById(id).style.fontWeight = 'bold'; + target.setAttribute('aria-checked', 'true'); + } +}; + +/* +* @eventHandler toggleItalic +* +* @desc +* Sets the CSS font-size property of the text content +* identified by the id and updates the menuitemradio +* group in the menu to indicate which size is selected +* by using the aria-checked property +* +* @param event +* Event object of menuitemradio in the radio group +* +* @param id +* id property of the element to apply the styling +* +*/ + +function toggleItalic (event, id) { + var target = event.currentTarget; + var flag = target.getAttribute('aria-checked'); + + if (flag === 'true') { + document.getElementById(id).style.fontStyle = 'normal'; + target.setAttribute('aria-checked', 'false'); + } + else { + document.getElementById(id).style.fontStyle = 'italic'; + target.setAttribute('aria-checked', 'true'); + } +}; + diff --git a/examples/menubar/menubar-2/menubar-2.html b/examples/menubar/menubar-2/menubar-2.html new file mode 100644 index 0000000000..b1cde96151 --- /dev/null +++ b/examples/menubar/menubar-2/menubar-2.html @@ -0,0 +1,303 @@ + + + + + + + + ARIA Menubar Example: Menuitemradio and Menuitemcheckbox + + + + + + + + + + + + + + + +
+ +

ARIA Menubar Example: menuitemradio and menuitemcheckbox

+ + + +

Authoring Practice Design Patten for Menubar

+ +
+ +

Example: Explcit ARIA markup indentifying role of each menu item

+ + + +
+ + + +

More information on Gettysburg Address

+ + +

Keyboard Support

+ +

Menubar Menu Items

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyFunction
Space or Enter Key +
    +
  • Open or close popup menu.
  • +
  • Focuse stays on menubar menu item.
  • +
+
Left Arrow Key +
    +
  • Moves focus to previous menubar menu item.
  • +
  • If focus on first menubar menu item, focus is moved to last menubar menu item.
  • +
+
Right Arrow Key +
    +
  • Moves focus to next menubar menu item.
  • +
  • If focus on last menubar menu item, focus is moved to first menubar menu item.
  • +
+
Down Arrow Key +
    +
  • Open popup menu.
  • +
  • Moves focus to first popup menu item.
  • +
+
Up Arrow Key +
    +
  • Open popup menu.
  • +
  • Moves focus to last popup menu item.
  • +
+
Home +
    +
  • Moves focus to first menubar menu item.
  • +
+
End +
    +
  • Moves focus to last menubar menu item.
  • +
+
Character Key
    +
  • Moves focus to next menubar item that starts with that letter.
  • +
  • If no menubar menu item starts with the letter, focus does not move.
  • +
+
+ +

Popup Menu Items

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyFunction
Space or Enter Key +
    +
  • Executes "onclick" action (e.g. bolding text, changing font).
  • +
+
Escape Key +
    +
  • Closes popup menu.
  • +
  • Moves focus to current menubar menu item.
  • +
+
Left Arrow Key +
    +
  • Closes popup menu.
  • +
  • Moves focus to previous menubar menu item.
  • +
+
Right Arrow Key +
    +
  • Closes popup menu.
  • +
  • Moves focus to next menubar menu item.
  • +
+
Up Arrow Key +
    +
  • Moves focus to previous popup menu item.
  • +
  • If first popup menu item, focus is moved to the last popup menu item.
  • +
+
Down Arrow Key +
    +
  • Moves focus to next popup menu item.
  • +
  • If last popup menu item, focus is moved to the first popup menu item.
  • +
+
Home +
    +
  • Moves focuse to first popup menu item.
  • +
+
End +
    +
  • Moves focus to last popup menu item.
  • +
+
Character Key +
    +
  • Moves focus to the next popup item that starts with that letter.
  • +
  • If no popup menu item starts with the letter, focus does not move.
  • +
+
+ +
+ + + + + \ No newline at end of file