# Yahoo! UI Library

### Menu 2.2.2

Yahoo! UI Library > menu > menumanager.js (source view)

    /**
    * @module menu
    * @description <p>The Menu family of components features a collection of
    * controls that make it easy to add menus to your website or web application.
    * With the Menu Controls you can create website fly-out menus, customized
    * context menus, or application-style menu bars with just a small amount of
    * scripting.</p><p>The Menu family of controls features:</p>
    * <ul>
    *    <li>Screen-reader accessibility.</li>
    *    <li>Keyboard and mouse navigation.</li>
    *    <li>A rich event model that provides access to all of a menu's
    *    interesting moments.</li>
    *    <li>Support for
    *    <a href="http://en.wikipedia.org/wiki/Progressive_Enhancement">Progressive
    *    Enhancement</a>; Menus can be created from simple,
    *    semantic markup on the page or purely through JavaScript.</li>
    * </ul>
    * @title Menu
    * @namespace YAHOO.widget
    * @requires Event, Dom, Container
    */
    (function() {

    var Dom = YAHOO.util.Dom,
        Event = YAHOO.util.Event;

    /**
    * Singleton that manages a collection of all menus and menu items.  Listens for
    * DOM events at the document level and dispatches the events to the
    * corresponding menu or menu item.
    *
    * @namespace YAHOO.widget
    * @class MenuManager
    * @static
    */
    YAHOO.widget.MenuManager = function() {

        // Private member variables

        // Flag indicating if the DOM event handlers have been attached

        var m_bInitializedEventHandlers = false,

            // Collection of menus

            m_oMenus = {},

            //  Collection of menu items

            m_oItems = {},

            // Collection of visible menus

            m_oVisibleMenus = {},

            // Map of DOM event types to their equivalent CustomEvent types

            m_oEventTypes =  {
                "click": "clickEvent",
                "mousedown": "mouseDownEvent",
                "mouseup": "mouseUpEvent",
                "mouseover": "mouseOverEvent",
                "mouseout": "mouseOutEvent",
                "keydown": "keyDownEvent",
                "keyup": "keyUpEvent",
                "keypress": "keyPressEvent"
            },

            m_oFocusedMenuItem = null;

        var m_oLogger = new YAHOO.widget.LogWriter("MenuManager");

        // Private methods

        /**
        * @method addItem
        * @description Adds an item to the collection of known menu items.
        * @private
        * @param {YAHOO.widget.MenuItem} p_oItem Object specifying the MenuItem
        * instance to be added.
        */
        function addItem(p_oItem) {

            var sId = p_oItem.id;

            if(p_oItem && m_oItems[sId] != p_oItem) {

                m_oItems[sId] = p_oItem;

                p_oItem.destroyEvent.subscribe(onItemDestroy);

                m_oLogger.log("Item: " +
                    p_oItem.toString() + " successfully registered.");

            }

        }

        /**
        * @method removeItem
        * @description Removes an item from the collection of known menu items.
        * @private
        * @param {YAHOO.widget.MenuItem} p_oItem Object specifying the MenuItem
        * instance to be removed.
        */
        function removeItem(p_oItem) {

            var sId = p_oItem.id;

            if(sId && m_oItems[sId]) {

                delete m_oItems[sId];

                m_oLogger.log("Item: " +
                    p_oItem.toString() + " successfully unregistered.");

            }

        }

        /**
        * @method getMenuRootElement
        * @description Finds the root DIV node of a menu or the root LI node of a
        * menu item.
        * @private
        * @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-
        * one-html.html#ID-58190037">HTMLElement</a>} p_oElement Object specifying
        * an HTML element.
        */
        function getMenuRootElement(p_oElement) {

            var oParentNode;

            if(p_oElement && p_oElement.tagName) {

                switch(p_oElement.tagName.toUpperCase()) {

                    case "DIV":

                        oParentNode = p_oElement.parentNode;

                        // Check if the DIV is the inner "body" node of a menu

                        if(
                            (
                                Dom.hasClass(p_oElement, "hd") ||
                                Dom.hasClass(p_oElement, "bd") ||
                                Dom.hasClass(p_oElement, "ft")
                            )
                            &&
                            oParentNode &&
                            oParentNode.tagName &&
                            oParentNode.tagName.toUpperCase() == "DIV"
                        ) {

                            return oParentNode;

                        }
                        else {

                            return p_oElement;

                        }

                    break;

                    case "LI":

                        return p_oElement;

                    default:

                        oParentNode = p_oElement.parentNode;

                        if(oParentNode) {

                            return getMenuRootElement(oParentNode);

                        }

                    break;

                }

            }

        }

        // Private event handlers

        /**
        * @method onDOMEvent
        * @description Generic, global event handler for all of a menu's DOM-based
        * events.  This listens for events against the document object.  If the
        * target of a given event is a member of a menu or menu item's DOM, the
        * instance's corresponding Custom Event is fired.
        * @private
        * @param {Event} p_oEvent Object representing the DOM event object passed
        * back by the event utility (YAHOO.util.Event).
        */
        function onDOMEvent(p_oEvent) {

            // Get the target node of the DOM event

            var oTarget = Event.getTarget(p_oEvent),

            // See if the target of the event was a menu, or a menu item

                oElement = getMenuRootElement(oTarget),
                oMenuItem,
                oMenu;

            if(oElement) {

                var sTagName = oElement.tagName.toUpperCase();

                if(sTagName == "LI") {

                    var sId = oElement.id;

                    if(sId && m_oItems[sId]) {

                        oMenuItem = m_oItems[sId];
                        oMenu = oMenuItem.parent;

                    }

                }
                else if(sTagName == "DIV") {

                    if(oElement.id) {

                        oMenu = m_oMenus[oElement.id];

                    }

                }

            }

            if(oMenu) {

                var sCustomEventType = m_oEventTypes[p_oEvent.type];

                // Fire the Custom Event that corresponds the current DOM event

                if(oMenuItem && !oMenuItem.cfg.getProperty("disabled")) {

                    oMenuItem[sCustomEventType].fire(p_oEvent);

                    if (p_oEvent.type == "keyup" || p_oEvent.type == "mousedown") {

                        if (m_oFocusedMenuItem != oMenuItem) {

                            if(m_oFocusedMenuItem) {

                                m_oFocusedMenuItem.blurEvent.fire();

                            }

                            oMenuItem.focusEvent.fire();

                        }

                    }

                }

                oMenu[sCustomEventType].fire(p_oEvent, oMenuItem);

            }
            else if(p_oEvent.type == "mousedown") {

                if(m_oFocusedMenuItem) {

                    m_oFocusedMenuItem.blurEvent.fire();

                    m_oFocusedMenuItem = null;

                }

                /*
                    If the target of the event wasn't a menu, hide all
                    dynamically positioned menus
                */

                for(var i in m_oMenus) {

                    if(YAHOO.lang.hasOwnProperty(m_oMenus,i)) {

                        oMenu = m_oMenus[i];

                        if(
                            oMenu.cfg.getProperty("clicktohide") &&
                            oMenu.cfg.getProperty("position") == "dynamic"
                        ) {

                            oMenu.hide();

                        }
                        else {

                            oMenu.clearActiveItem(true);

                        }

                    }

                }

            }
            else if(p_oEvent.type == "keyup") {

                if(m_oFocusedMenuItem) {

                    m_oFocusedMenuItem.blurEvent.fire();

                    m_oFocusedMenuItem = null;

                }

            }

        }

        /**
        * @method onMenuDestroy
        * @description "destroy" event handler for a menu.
        * @private
        * @param {String} p_sType String representing the name of the event that
        * was fired.
        * @param {Array} p_aArgs Array of arguments sent when the event was fired.
        */
        function onMenuDestroy(p_sType, p_aArgs) {

            if(m_oMenus[this.id]) {

                delete m_oMenus[this.id];

                m_oLogger.log("Menu: " +
                    this.toString() + " successfully unregistered.");

            }

        }

        /**
        * @method onMenuFocus
        * @description "focus" event handler for a MenuItem instance.
        * @private
        * @param {String} p_sType String representing the name of the event that
        * was fired.
        * @param {Array} p_aArgs Array of arguments sent when the event was fired.
        */
        function onMenuFocus(p_sType, p_aArgs) {

            var oItem = p_aArgs[0];

            if (oItem) {

                m_oFocusedMenuItem = oItem;

            }

        }

        /**
        * @method onMenuBlur
        * @description "blur" event handler for a MenuItem instance.
        * @private
        * @param {String} p_sType String representing the name of the event that
        * was fired.
        * @param {Array} p_aArgs Array of arguments sent when the event was fired.
        */
        function onMenuBlur(p_sType, p_aArgs) {

            m_oFocusedMenuItem = null;

        }

        /**
        * @method onItemDestroy
        * @description "destroy" event handler for a MenuItem instance.
        * @private
        * @param {String} p_sType String representing the name of the event that
        * was fired.
        * @param {Array} p_aArgs Array of arguments sent when the event was fired.
        */
        function onItemDestroy(p_sType, p_aArgs) {

            var sId = this.id;

            if(sId && m_oItems[sId]) {

                delete m_oItems[sId];

            }

        }

        /**
        * @method onMenuVisibleConfigChange
        * @description Event handler for when the "visible" configuration property
        * of a Menu instance changes.
        * @private
        * @param {String} p_sType String representing the name of the event that
        * was fired.
        * @param {Array} p_aArgs Array of arguments sent when the event was fired.
        */
        function onMenuVisibleConfigChange(p_sType, p_aArgs) {

            var bVisible = p_aArgs[0];

            if(bVisible) {

                m_oVisibleMenus[this.id] = this;

                m_oLogger.log("Menu: " +
                    this.toString() +
                    " registered with the collection of visible menus.");

            }
            else if(m_oVisibleMenus[this.id]) {

                delete m_oVisibleMenus[this.id];

                m_oLogger.log("Menu: " +
                    this.toString() +
                    " unregistered from the collection of visible menus.");

            }

        }

        /**
        * @method onItemAdded
        * @description "itemadded" event handler for a Menu instance.
        * @private
        * @param {String} p_sType String representing the name of the event that
        * was fired.
        * @param {Array} p_aArgs Array of arguments sent when the event was fired.
        */
        function onItemAdded(p_sType, p_aArgs) {

            addItem(p_aArgs[0]);

        }

        /**
        * @method onItemRemoved
        * @description "itemremoved" event handler for a Menu instance.
        * @private
        * @param {String} p_sType String representing the name of the event that
        * was fired.
        * @param {Array} p_aArgs Array of arguments sent when the event was fired.
        */
        function onItemRemoved(p_sType, p_aArgs) {

            removeItem(p_aArgs[0]);

        }

        return {

            // Privileged methods

            /**
            * @method addMenu
            * @description Adds a menu to the collection of known menus.
            * @param {YAHOO.widget.Menu} p_oMenu Object specifying the Menu
            * instance to be added.
            */
            addMenu: function(p_oMenu) {

                if(p_oMenu && p_oMenu.id && !m_oMenus[p_oMenu.id]) {

                    m_oMenus[p_oMenu.id] = p_oMenu;

                    if(!m_bInitializedEventHandlers) {

                        var oDoc = document;

                        Event.on(oDoc, "mouseover", onDOMEvent, this, true);
                        Event.on(oDoc, "mouseout", onDOMEvent, this, true);
                        Event.on(oDoc, "mousedown", onDOMEvent, this, true);
                        Event.on(oDoc, "mouseup", onDOMEvent, this, true);
                        Event.on(oDoc, "click", onDOMEvent, this, true);
                        Event.on(oDoc, "keydown", onDOMEvent, this, true);
                        Event.on(oDoc, "keyup", onDOMEvent, this, true);
                        Event.on(oDoc, "keypress", onDOMEvent, this, true);

                        m_bInitializedEventHandlers = true;

                        m_oLogger.log("DOM event handlers initialized.");

                    }

                    p_oMenu.destroyEvent.subscribe(onMenuDestroy);

                    p_oMenu.cfg.subscribeToConfigEvent(
                        "visible",
                        onMenuVisibleConfigChange
                    );

                    p_oMenu.itemAddedEvent.subscribe(onItemAdded);
                    p_oMenu.itemRemovedEvent.subscribe(onItemRemoved);
                    p_oMenu.focusEvent.subscribe(onMenuFocus);
                    p_oMenu.blurEvent.subscribe(onMenuBlur);

                    m_oLogger.log("Menu: " +
                        p_oMenu.toString() + " successfully registered.");

                }

            },

            /**
            * @method removeMenu
            * @description Removes a menu from the collection of known menus.
            * @param {YAHOO.widget.Menu} p_oMenu Object specifying the Menu
            * instance to be removed.
            */
            removeMenu: function(p_oMenu) {

                if(p_oMenu && m_oMenus[p_oMenu.id]) {

                    delete m_oMenus[p_oMenu.id];

                    m_oLogger.log("Menu: " +
                        p_oMenu.toString() + " successfully unregistered.");

                }

            },

            /**
            * @method hideVisible
            * @description Hides all visible, dynamically positioned menus.
            */
            hideVisible: function() {

                var oMenu;

                for(var i in m_oVisibleMenus) {

                    if(YAHOO.lang.hasOwnProperty(m_oVisibleMenus,i)) {

                        oMenu = m_oVisibleMenus[i];

                        if(oMenu.cfg.getProperty("position") == "dynamic") {

                            oMenu.hide();

                        }

                    }

                }

            },

            /**
            * @method getMenus
            * @description Returns an array of all menus registered with the
            * menu manger.
            * @return {Array}
            */
            getMenus: function() {

                return m_oMenus;

            },

            /**
            * @method getMenu
            * @description Returns a menu with the specified id.
            * @param {String} p_sId String specifying the id of the menu to
            * be retrieved.
            * @return {YAHOO.widget.Menu}
            */
            getMenu: function(p_sId) {

                if(m_oMenus[p_sId]) {

                    return m_oMenus[p_sId];

                }

            },

            /**
            * @method getFocusedMenuItem
            * @description Returns a reference to the menu item that currently
            * has focus.
            * @return {YAHOO.widget.MenuItem}
            */
            getFocusedMenuItem: function() {

                return m_oFocusedMenuItem;

            },

            /**
            * @method getFocusedMenu
            * @description Returns a reference to the menu that currently has focus.
            * @return {YAHOO.widget.Menu}
            */
            getFocusedMenu: function() {

                if(m_oFocusedMenuItem) {

                    return (m_oFocusedMenuItem.parent.getRoot());

                }

            },

            /**
            * @method toString
            * @description Returns a string representing the menu manager.
            * @return {String}
            */
            toString: function() {

                return ("MenuManager");

            }

        };

    }();

    })();

Copyright © 2007 Yahoo! Inc. All rights reserved.