/**
* @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 {
if (oMenu.cfg.getProperty("showdelay") > 0) {
oMenu._cancelShowDelay();
}
if (oMenu.activeItem) {
oMenu.activeItem.blur();
oMenu.activeItem.cfg.setProperty("selected", false);
oMenu.activeItem = null;
}
}
}
}
}
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><div></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><li></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><li></code> elements are child
* nodes of the <code><ul></code> element with the
* specified id.
* @param {String} p_sId String specifying the id of the
* <code><ul></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";
}
};
}();
})();