# Yahoo! UI Library

### Menu 2.8.0r4

Yahoo! UI Library > menu > menuariaplugin.js (source view)
Search:
Filters Show Private Show Protected Show Deprecated

    (function () {

    	var Event = YAHOO.util.Event,
    		Dom = YAHOO.util.Dom,
    		Lang = YAHOO.lang,
    		UA = YAHOO.env.ua,
    		ContextMenu = YAHOO.widget.ContextMenu,

    		MenuPrototype = YAHOO.widget.Menu.prototype,
    		fnMenuInitDefaultConfig = MenuPrototype.initDefaultConfig,

    		// The currently focused MenuItem label, or the MenuItem label that can be focused
    		// by the user.

    		m_oCurrentItemLabel,

    		// Private constants for strings

    		_ARIA_PREFIX = "aria-",
    		_HAS_POPUP = "haspopup",
    		_ROLE = "role",
    		_PRESENTATION = "presentation",
    		_MENUITEM = "menuitem",
    		_HREF = "href",
    		_SUBMENU = "submenu",
    		_MENU = "menu",
    		_MENUBAR = "menubar",
    		_LABELLED_BY = "labelledby",
    		_FOCUS = "focus",
    		_BLUR = "blur",
    		_ITEM_ADDED = "itemAdded",
    		_USE_ARIA = "usearia"
    		_TRIGGER = "trigger";

    	// Menu ARIA plugin

    	var setARIARole = function (element, role) {

    		element.setAttribute(_ROLE, role);

    	};

    	var setARIAProperty = function (element, property, value) {

    		element.setAttribute((_ARIA_PREFIX + property), value);

    	};

    	var addHasPopupRole = function (element) {

    		if (element.nodeType === 1) {
    			setARIAProperty(element, _HAS_POPUP, true);
    		}

    	};

    	var removeHasPopupRole = function (element) {

    		if (element.nodeType === 1) {
    			element.removeAttribute(_ARIA_PREFIX + _HAS_POPUP);
    		}

    	};

    	var onFocus = function (type, args) {

    		var oEvent = args[0],
    			oTarget = Event.getTarget(oEvent);	// The currently focused element

    		// Modify value of the tabIndex attribute so that the currently
    		// focused MenuItem label is in the browser's default tab order.

    		if (m_oCurrentItemLabel) {
    			m_oCurrentItemLabel.tabIndex = -1;
    		}

    		m_oCurrentItemLabel = oTarget;
    		m_oCurrentItemLabel.tabIndex = 0;

    	};

    	var onBlur = function (type, args) {

    		var oEvent = args[0],
    			oTarget = Event.getTarget(oEvent);	// The currently focused element

    		// If the focus has moved to an element on the page that is not a
    		// part of the Menu, restore the Menu to its original state
    		// so that the first item is in the browser's default tab index.

    		m_oCurrentItemLabel.tabIndex = -1;

    		m_oCurrentItemLabel = Dom.getFirstChild(this.getItem(0).element);
    		m_oCurrentItemLabel.tabIndex = 0;

    	};

    	var onConfigSubmenu = function (type, args) {

    		var oSubmenu = args[0];

    		if (oSubmenu) {
    			addHasPopupRole(Dom.getFirstChild(this.element));
    		}

    	};

    	var addMenuItemRole = function (menuitem) {

    		var oMenuItemEl = menuitem.element;

    		// For NVDA: add the role of "presentation" to reach LI and UL
    		// element to prevent NVDA from announcing the "list" role
    		// as well as the menu-related roles.

    		setARIARole(oMenuItemEl.parentNode, _PRESENTATION);
    		setARIARole(oMenuItemEl, _PRESENTATION);

    		// Retrieve a reference to the anchor element that serves as the
    		// label for each MenuItem.

    		var oMenuItemLabel = Dom.getFirstChild(oMenuItemEl);

    		// Set the "role" attribute of the label to "menuitem"

    		setARIARole(oMenuItemLabel, _MENUITEM);

    		// Remove the label from the browser's default tab order

    		oMenuItemLabel.tabIndex = -1;

    		// JAWS & NVDA announce the value of each anchor
    		// element's "href" attribute when it recieves focus, so remove
    		// the attribute so that its value isn't announced.

    		oMenuItemLabel.removeAttribute(_HREF);

    		// If the MenuItem has a submenu, set the "aria-haspopup"
    		// attribute to true so that the screen reader can announce

    		if (menuitem.cfg.getProperty(_SUBMENU)) {
    			addHasPopupRole(oMenuItemLabel);
    		}
    		else {
    			menuitem.cfg.subscribeToConfigEvent(_SUBMENU, onConfigSubmenu);
    		}

    	};

    	var onItemAdded = function (type, args) {

    		addMenuItemRole(args[0]);

    	};

    	Lang.augmentObject(MenuPrototype, {

    		configUseARIA: function (type, args) {

    			var bUseARIA = args[0],
    				oParent = this.parent,
    				oElement = this.element,
    				sMenuRole = (this instanceof YAHOO.widget.MenuBar) ? _MENUBAR : _MENU,
    				aMenuItems,
    				nMenuItems,
    				oMenuItem,
    				oMenuItemLabel,
    				sId,
    				i;

    			if (bUseARIA) {

    				// Apply the "role" attribute of "menu" or "menubar" depending on the
    				// type of the Menu control being rendered.

    				setARIARole(oElement, sMenuRole);

    				// Use the "aria-labelledby" attribute to label each submenu.  This
    				// will provide the user with the name of the submenu the first time
    				// one of its items receives focus.

    				if (oParent) {

    					sId = Dom.generateId(Dom.getFirstChild(oParent.element));

    					setARIAProperty(oElement, _LABELLED_BY, sId);

    				}

    				// Apply the appropriate "role" and "aria-[state]" attributes to the
    				// label of each MenuItem instance.

    				aMenuItems = this.getItems();
    				nMenuItems = aMenuItems.length;

    				if (nMenuItems > 0) {

    					i = nMenuItems - 1;

    					do {
    						oMenuItem = aMenuItems[i];
    						addMenuItemRole(oMenuItem);
    						i = i - 1;
    					}
    					while ((i > -1));

    					// Set the "tabindex" of the first MenuItem's label to 0 so the user
    					// can easily tab into and out of the control.

    					if (this.getRoot() === this) {
    						m_oCurrentItemLabel = Dom.getFirstChild(this.getItem(0).element);
    						m_oCurrentItemLabel.tabIndex = 0;
    					}

    				}

    				if (this === this.getRoot()) {
    					this.subscribe(_FOCUS, onFocus);
    					this.subscribe(_BLUR, onBlur);
    				}

    				this.subscribe(_ITEM_ADDED, onItemAdded);

    			}

    		},

    		initDefaultConfig: function () {

    			fnMenuInitDefaultConfig.call(this);

    			this.cfg.addProperty(
    				_USE_ARIA,
    				{
    					handler: this.configUseARIA,
    					value: (UA.gecko && UA.gecko >= 1.9) || (UA.ie && UA.ie >= 8),
    					validator: Lang.isBoolean
    				}
    			 );

    		}

    	}, "initDefaultConfig", "configUseARIA");

    	// ContextMenu ARIA plugin

    	var m_oTriggers = {};

    	var toggleARIAForTrigger = function () {

    		var sMenuId = this.element.id,
    			oTrigger = this.cfg.getProperty(_TRIGGER),
    			oCurrentTrigger = m_oTriggers[sMenuId],
    			oElement;

    		if (oCurrentTrigger) {

    			if (Lang.isString(oCurrentTrigger)) {	// String id

    				oElement = Dom.get(oCurrentTrigger);

    				if (oElement) {
    					removeHasPopupRole(oElement);
    				}

    			}
    			else if (oCurrentTrigger.nodeType === 1) {	// Element reference
    				removeHasPopupRole(oCurrentTrigger);
    			}
    			else if (oCurrentTrigger.length) { // NodeList or Array
    				Dom.batch(oCurrentTrigger, removeHasPopupRole);
    			}

    		}

    		if (oTrigger) {

    			if (Lang.isString(oTrigger)) {	// String id

    				oElement = Dom.get(oTrigger);

    				if (oElement) {
    					addHasPopupRole(oElement);
    				}

    			}
    			else if (oTrigger.nodeType === 1) {	// Element reference
    				addHasPopupRole(oTrigger);
    			}
    			else if (oTrigger.length) {	// NodeList or Array
    				Dom.batch(oTrigger, addHasPopupRole);
    			}

    			m_oTriggers[sMenuId] = oTrigger;

    		}

    	};

    	ContextMenu.prototype.configUseARIA = function (type, args) {

    		ContextMenu.superclass.configUseARIA.apply(this, arguments);

    		toggleARIAForTrigger.call(this);

    		this.cfg.subscribeToConfigEvent(_TRIGGER, toggleARIAForTrigger);

    	};

    }());

Copyright © 2009 Yahoo! Inc. All rights reserved.