# Yahoo! UI Library

### Menu 2.5.0

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

Show Private Show Protected

    /**
    * @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>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 visible menus

            m_oVisibleMenus = {},

            //  Collection of menu items

            m_oItems = {},

            // 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 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),
                sCustomEventType,
                sTagName,
                sId,
                oMenuItem,
                oMenu;

                if (oElement) {

                    sTagName = oElement.tagName.toUpperCase();

                    if (sTagName == "LI") {

                        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) {

                    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_oVisibleMenus) {

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

                            oMenu = m_oVisibleMenus[i];

                            if (oMenu.cfg.getProperty("clicktohide") &&
                                !(oMenu instanceof YAHOO.widget.MenuBar) &&
                                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.
            * @param {YAHOO.widget.Menu} p_oMenu The menu that fired the event.
            */
            function onMenuDestroy(p_sType, p_aArgs, p_oMenu) {

                if (m_oMenus[p_oMenu.id]) {

                    this.removeMenu(p_oMenu);

                }

            }

            /**
            * @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 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],
                    sId = this.id;

                if (bVisible) {

                    m_oVisibleMenus[sId] = this;

                    m_oLogger.log(
                                this +
                                " added to the collection of visible menus.");

                }
                else if (m_oVisibleMenus[sId]) {

                    delete m_oVisibleMenus[sId];

                    m_oLogger.log(
                                this +
                                " removed from the collection of visible menus.");

                }

            }

            /**
            * @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) {

                removeItem(this);

            }

            function removeItem(p_oMenuItem) {

                var sId = p_oMenuItem.id;

                if (sId && m_oItems[sId]) {

                    if (m_oFocusedMenuItem == p_oMenuItem) {

                        m_oFocusedMenuItem = null;

                    }

                    delete m_oItems[sId];

                    p_oMenuItem.destroyEvent.unsubscribe(onItemDestroy);

                    m_oLogger.log(p_oMenuItem + " successfully unregistered.");

                }

            }

            /**
            * @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) {

                var oItem = p_aArgs[0],
                    sId;

                if (oItem instanceof YAHOO.widget.MenuItem) {

                    sId = oItem.id;

                    if (!m_oItems[sId]) {

                        m_oItems[sId] = oItem;

                        oItem.destroyEvent.subscribe(onItemDestroy);

                        m_oLogger.log(oItem + " successfully registered.");

                    }

                }

            }

            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) {

                    var oDoc;

                    if (p_oMenu instanceof YAHOO.widget.Menu && p_oMenu.id &&
                        !m_oMenus[p_oMenu.id]) {

                        m_oMenus[p_oMenu.id] = p_oMenu;

                        if (!m_bInitializedEventHandlers) {

                            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.cfg.subscribeToConfigEvent("visible",
                            onMenuVisibleConfigChange);

                        p_oMenu.destroyEvent.subscribe(onMenuDestroy, p_oMenu,
                                                this);

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

                        m_oLogger.log(p_oMenu + " 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) {

                    var sId,
                        aItems,
                        i;

                    if (p_oMenu) {

                        sId = p_oMenu.id;

                        if (m_oMenus[sId] == p_oMenu) {

                            // Unregister each menu item

                            aItems = p_oMenu.getItems();

                            if (aItems && aItems.length > 0) {

                                i = aItems.length - 1;

                                do {

                                    removeItem(aItems[i]);

                                }
                                while (i--);

                            }

                            // Unregister the menu

                            delete m_oMenus[sId];

                            m_oLogger.log(p_oMenu + " successfully unregistered.");

                            /*
                                 Unregister the menu from the collection of
                                 visible menus
                            */

                            if (m_oVisibleMenus[sId] == p_oMenu) {

                                delete m_oVisibleMenus[sId];

                                m_oLogger.log(p_oMenu + " unregistered from the" +
                                            " collection of visible menus.");

                            }

                            // Unsubscribe event listeners

                            if (p_oMenu.cfg) {

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

                            }

                            p_oMenu.destroyEvent.unsubscribe(onMenuDestroy,
                                p_oMenu);

                            p_oMenu.itemAddedEvent.unsubscribe(onItemAdded);
                            p_oMenu.focusEvent.unsubscribe(onMenuFocus);
                            p_oMenu.blurEvent.unsubscribe(onMenuBlur);

                        }

                    }

                },

                /**
                * @method hideVisible
                * @description Hides all visible, dynamically positioned menus
                * (excluding instances of YAHOO.widget.MenuBar).
                */
                hideVisible: function () {

                    var oMenu;

                    for (var i in m_oVisibleMenus) {

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

                            oMenu = m_oVisibleMenus[i];

                            if (!(oMenu instanceof YAHOO.widget.MenuBar) &&
                                oMenu.cfg.getProperty("position") == "dynamic") {

                                oMenu.hide();

                            }

                        }

                    }

                },

                /**
                * @method getVisible
                * @description Returns a collection of all visible menus registered
                * with the menu manger.
                * @return {Array}
                */
                getVisible: function () {

                    return m_oVisibleMenus;

                },

                /**
                * @method getMenus
                * @description Returns a collection 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
                * <code>&#60;div&#62;</code> element representing the menu to
                * be retrieved.
                * @return {YAHOO.widget.Menu}
                */
                getMenu: function (p_sId) {

                    var oMenu = m_oMenus[p_sId];

                    if (oMenu) {

                        return oMenu;

                    }

                },

                /**
                * @method getMenuItem
                * @description Returns a menu item with the specified id.
                * @param {String} p_sId String specifying the id of the
                * <code>&#60;li&#62;</code> element representing the menu item to
                * be retrieved.
                * @return {YAHOO.widget.MenuItem}
                */
                getMenuItem: function (p_sId) {

                    var oItem = m_oItems[p_sId];

                    if (oItem) {

                        return oItem;

                    }

                },

                /**
                * @method getMenuItemGroup
                * @description Returns an array of menu item instances whose
                * corresponding <code>&#60;li&#62;</code> elements are child
                * nodes of the <code>&#60;ul&#62;</code> element with the
                * specified id.
                * @param {String} p_sId String specifying the id of the
                * <code>&#60;ul&#62;</code> element representing the group of
                * menu items to be retrieved.
                * @return {Array}
                */
                getMenuItemGroup: function (p_sId) {

                    var oUL = Dom.get(p_sId),
                        aItems,
                        oNode,
                        oItem,
                        sId;

                    if (oUL && oUL.tagName &&
                        oUL.tagName.toUpperCase() == "UL") {

                        oNode = oUL.firstChild;

                        if (oNode) {

                            aItems = [];

                            do {

                                sId = oNode.id;

                                if (sId) {

                                    oItem = this.getMenuItem(sId);

                                    if (oItem) {

                                        aItems[aItems.length] = oItem;

                                    }

                                }

                            }
                            while ((oNode = oNode.nextSibling));

                            if (aItems.length > 0) {

                                return aItems;

                            }

                        }

                    }

                },

                /**
                * @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.