# Yahoo! UI Library

### Menu 2.9.0

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 © 2011 Yahoo! Inc. All rights reserved.