# Yahoo! UI Library

### Menu Library

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

    /**
    * @module menu
    * @description <p>The Menu Library features a collection of widgets that make
    * it easy to add menus to your website or web application.  With the Menu
    * Library you can create website fly-out menus, customized context menus, or
    * application-style menu bars with just a small amount of scripting.</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 Library
    * @namespace YAHOO.widget
    * @requires Event, Dom, Container
    */
    (function() {

    var Dom = YAHOO.util.Dom;
    var 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 = new function() {

        // Private member variables

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

        var m_bInitializedEventHandlers = false;

        // Collection of menus

        var m_oMenus = {};

        //  Collection of menu items

        var m_oItems = {};

        // Collection of visible menus

        var m_oVisibleMenus = {};

        // Logger

        var m_oLogger = new YAHOO.widget.LogWriter(this.toString());

        // Private methods

        /**
        * 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.
        */
        var addItem = function(p_oItem) {

            var sYUIId = Dom.generateId();

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

                p_oItem.element.setAttribute("yuiid", sYUIId);

                m_oItems[sYUIId] = p_oItem;

                p_oItem.destroyEvent.subscribe(onItemDestroy, p_oItem);

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

            }

        };

        /**
        * 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.
        */
        var removeItem = function(p_oItem) {

            var sYUIId = p_oItem.element.getAttribute("yuiid");

            if(sYUIId && m_oItems[sYUIId]) {

                delete m_oItems[sYUIId];

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

            }

        };

        /**
        * 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.
        */
        var getMenuRootElement = function(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, "bd") &&
                            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

        /**
        * 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).
        */
        var onDOMEvent = function(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

            var oElement = getMenuRootElement(oTarget);

            var oMenuItem;
            var oMenu;

            if(oElement) {

                var sTagName = oElement.tagName.toUpperCase();

                if(sTagName == "LI") {

                    var sYUIId = oElement.getAttribute("yuiid");

                    if(sYUIId) {

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

                    }

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

                    if(oElement.id) {

                        oMenu = m_oMenus[oElement.id];

                    }

                }

            }

            if(oMenu) {

                // Map of DOM event names to CustomEvent names

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

                var sCustomEventType = oEventTypes[p_oEvent.type];

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

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

                    oMenuItem[sCustomEventType].fire(p_oEvent);

                }

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

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

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

                var oActiveItem;

                for(var i in m_oMenus) {

                    if(m_oMenus.hasOwnProperty(i)) {

                        oMenu = m_oMenus[i];

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

                            oMenu.hide();

                        }
                        else {

                            oMenu.clearActiveItem(true);

                        }

                    }

                }

            }

        };

        /**
        * "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 Object representing the menu that
        * fired the event.
        */
        var onMenuDestroy = function(p_sType, p_aArgs, p_oMenu) {

            this.removeMenu(p_oMenu);

        };

        /**
        * "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.
        * @param {YAHOO.widget.MenuItem} p_oItem Object representing the menu item
        * that fired the event.
        */
        var onItemDestroy = function(p_sType, p_aArgs, p_oItem) {

            var sYUIId = p_oItem.element.getAttribute("yuiid");

            if(sYUIId) {

                delete m_oItems[sYUIId];

            }

        };

        /**
        * 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.
        * @param {YAHOO.widget.Menu} p_oMenu Object representing the menu that
        * fired the event.
        */
        var onMenuVisibleConfigChange = function(p_sType, p_aArgs, p_oMenu) {

            var bVisible = p_aArgs[0];

            if(bVisible) {

                m_oVisibleMenus[p_oMenu.id] = p_oMenu;

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

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

                delete m_oVisibleMenus[p_oMenu.id];

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

            }

        };

        /**
        * "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.
        */
        var onItemAdded = function(p_sType, p_aArgs) {

            addItem(p_aArgs[0]);

        };

        /**
        * "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.
        */
        var onItemRemoved = function(p_sType, p_aArgs) {

            removeItem(p_aArgs[0]);

        };

        // 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.
        */
        this.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.addListener(oDoc, "mouseover", onDOMEvent, this, true);
                    Event.addListener(oDoc, "mouseout", onDOMEvent, this, true);
                    Event.addListener(oDoc, "mousedown", onDOMEvent, this, true);
                    Event.addListener(oDoc, "mouseup", onDOMEvent, this, true);
                    Event.addListener(oDoc, "click", onDOMEvent, this, true);
                    Event.addListener(oDoc, "keydown", onDOMEvent, this, true);
                    Event.addListener(oDoc, "keyup", onDOMEvent, this, true);
                    Event.addListener(oDoc, "keypress", onDOMEvent, this, true);

                    m_bInitializedEventHandlers = true;

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

                }

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

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

                p_oMenu.itemAddedEvent.subscribe(onItemAdded);
                p_oMenu.itemRemovedEvent.subscribe(onItemRemoved);

                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.
        */
        this.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.
        */
        this.hideVisible = function() {

            var oMenu;

            for(var i in m_oVisibleMenus) {

                if(m_oVisibleMenus.hasOwnProperty(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}
        */
        this.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}
        */
        this.getMenu = function(p_sId) {

            if(m_oMenus[p_sId]) {

                return m_oMenus[p_sId];

            }

        };

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

            return ("MenuManager");

        };

    };

    })();

Copyright © 2006 Yahoo! Inc. All rights reserved.