/**
* @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 _DIV = "DIV",
_HD = "hd",
_BD = "bd",
_FT = "ft",
_LI = "LI",
_DISABLED = "disabled",
_MOUSEOVER = "mouseover",
_MOUSEOUT = "mouseout",
_MOUSEDOWN = "mousedown",
_MOUSEUP = "mouseup",
_FOCUS = YAHOO.env.ua.ie ? "focusin" : "focus",
_CLICK = "click",
_KEYDOWN = "keydown",
_KEYUP = "keyup",
_KEYPRESS = "keypress",
_CLICK_TO_HIDE = "clicktohide",
_POSITION = "position",
_DYNAMIC = "dynamic",
_SHOW_DELAY = "showdelay",
_SELECTED = "selected",
_VISIBLE = "visible",
_UL = "UL",
_MENUMANAGER = "MenuManager",
Dom = YAHOO.util.Dom,
Event = YAHOO.util.Event,
Lang = YAHOO.lang;
/**
* 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",
"focus": "focusEvent",
"focusin": "focusEvent",
"blur": "blurEvent",
"focusout": "blurEvent"
},
// The element in the DOM that currently has focus
m_oFocusedElement = null,
m_oFocusedMenuItem = null;
// 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,
returnVal;
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) {
returnVal = oParentNode;
}
else {
returnVal = p_oElement;
}
break;
case _LI:
returnVal = p_oElement;
break;
default:
oParentNode = p_oElement.parentNode;
if (oParentNode) {
returnVal = getMenuRootElement(oParentNode);
}
break;
}
}
return returnVal;
}
// 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);
}
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
*/
for (var i in m_oVisibleMenus) {
if (Lang.hasOwnProperty(m_oVisibleMenus, i)) {
oMenu = m_oVisibleMenus[i];
if (oMenu.cfg.getProperty(_CLICK_TO_HIDE) &&
!(oMenu instanceof YAHOO.widget.MenuBar) &&
oMenu.cfg.getProperty(_POSITION) == _DYNAMIC) {
oMenu.hide();
}
else {
if (oMenu.cfg.getProperty(_SHOW_DELAY) > 0) {
oMenu._cancelShowDelay();
}
if (oMenu.activeItem) {
oMenu.activeItem.blur();
oMenu.activeItem.cfg.setProperty(_SELECTED, false);
oMenu.activeItem = null;
}
}
}
}
}
else if (p_oEvent.type == _FOCUS) {
m_oFocusedElement = oTarget;
}
}
/**
* @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[1];
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 onMenuHide
* @description "hide" 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.
* @param <a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
* level-one-html.html#ID-58190037">p_oFocusedElement</a> The HTML element that had focus
* prior to the Menu being made visible
*/
function onMenuHide(p_sType, p_aArgs, p_oFocusedElement) {
/*
Restore focus to the element in the DOM that had focus prior to the Menu
being made visible
*/
if (p_oFocusedElement && p_oFocusedElement.focus) {
try {
p_oFocusedElement.focus();
}
catch(ex) {
}
}
this.hideEvent.unsubscribe(onMenuHide, p_oFocusedElement);
}
/**
* @method onMenuShow
* @description "show" 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 onMenuShow(p_sType, p_aArgs) {
/*
Dynamically positioned, root Menus focus themselves when visible, and will then,
when hidden, restore focus to the UI control that had focus before the Menu was
made visible
*/
if (this === this.getRoot() && this.cfg.getProperty(_POSITION) === _DYNAMIC) {
this.hideEvent.subscribe(onMenuHide, m_oFocusedElement);
this.focus();
}
}
/**
* @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;
YAHOO.log(this + " added to the collection of visible menus.",
"info", _MENUMANAGER);
}
else if (m_oVisibleMenus[sId]) {
delete m_oVisibleMenus[sId];
YAHOO.log(this + " removed from the collection of visible menus.",
"info", _MENUMANAGER);
}
}
/**
* @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);
}
/**
* @method removeItem
* @description Removes a MenuItem instance from the MenuManager's collection of MenuItems.
* @private
* @param {MenuItem} p_oMenuItem The MenuItem instance to be removed.
*/
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);
YAHOO.log(p_oMenuItem + " successfully unregistered.", "info", _MENUMANAGER);
}
}
/**
* @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);
YAHOO.log(oItem + " successfully registered.", "info", _MENUMANAGER);
}
}
}
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);
Event.onFocus(oDoc, onDOMEvent, this, true);
Event.onBlur(oDoc, onDOMEvent, this, true);
m_bInitializedEventHandlers = true;
YAHOO.log("DOM event handlers initialized.", "info", _MENUMANAGER);
}
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);
p_oMenu.showEvent.subscribe(onMenuShow);
YAHOO.log(p_oMenu + " successfully registered.", "info", _MENUMANAGER);
}
},
/**
* @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 ((sId in m_oMenus) && (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];
YAHOO.log(p_oMenu + " successfully unregistered.", "info", _MENUMANAGER);
/*
Unregister the menu from the collection of
visible menus
*/
if ((sId in m_oVisibleMenus) && (m_oVisibleMenus[sId] == p_oMenu)) {
delete m_oVisibleMenus[sId];
YAHOO.log(p_oMenu + " unregistered from the" +
" collection of visible menus.", "info", _MENUMANAGER);
}
// 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 (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 {Object}
*/
getVisible: function () {
return m_oVisibleMenus;
},
/**
* @method getMenus
* @description Returns a collection of all menus registered with the
* menu manger.
* @return {Object}
*/
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 returnVal;
if (p_sId in m_oMenus) {
returnVal = m_oMenus[p_sId];
}
return returnVal;
},
/**
* @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 returnVal;
if (p_sId in m_oItems) {
returnVal = m_oItems[p_sId];
}
return returnVal;
},
/**
* @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,
returnVal;
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) {
returnVal = aItems;
}
}
}
return returnVal;
},
/**
* @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 () {
var returnVal;
if (m_oFocusedMenuItem) {
returnVal = m_oFocusedMenuItem.parent.getRoot();
}
return returnVal;
},
/**
* @method toString
* @description Returns a string representing the menu manager.
* @return {String}
*/
toString: function () {
return _MENUMANAGER;
}
};
}();
})();