(function () {
var Lang = YAHOO.lang,
// String constants
_MENU = "Menu",
_DIV_UPPERCASE = "DIV",
_DIV_LOWERCASE = "div",
_ID = "id",
_SELECT = "SELECT",
_XY = "xy",
_Y = "y",
_UL_UPPERCASE = "UL",
_UL_LOWERCASE = "ul",
_FIRST_OF_TYPE = "first-of-type",
_LI = "LI",
_OPTGROUP = "OPTGROUP",
_OPTION = "OPTION",
_DISABLED = "disabled",
_NONE = "none",
_SELECTED = "selected",
_GROUP_INDEX = "groupindex",
_INDEX = "index",
_SUBMENU = "submenu",
_VISIBLE = "visible",
_HIDE_DELAY = "hidedelay",
_POSITION = "position",
_DYNAMIC = "dynamic",
_STATIC = "static",
_DYNAMIC_STATIC = _DYNAMIC + "," + _STATIC,
_WINDOWS = "windows",
_URL = "url",
_HASH = "#",
_TARGET = "target",
_MAX_HEIGHT = "maxheight",
_TOP_SCROLLBAR = "topscrollbar",
_BOTTOM_SCROLLBAR = "bottomscrollbar",
_UNDERSCORE = "_",
_TOP_SCROLLBAR_DISABLED = _TOP_SCROLLBAR + _UNDERSCORE + _DISABLED,
_BOTTOM_SCROLLBAR_DISABLED = _BOTTOM_SCROLLBAR + _UNDERSCORE + _DISABLED,
_MOUSEMOVE = "mousemove",
_SHOW_DELAY = "showdelay",
_SUBMENU_HIDE_DELAY = "submenuhidedelay",
_IFRAME = "iframe",
_CONSTRAIN_TO_VIEWPORT = "constraintoviewport",
_PREVENT_CONTEXT_OVERLAP = "preventcontextoverlap",
_SUBMENU_ALIGNMENT = "submenualignment",
_AUTO_SUBMENU_DISPLAY = "autosubmenudisplay",
_CLICK_TO_HIDE = "clicktohide",
_CONTAINER = "container",
_SCROLL_INCREMENT = "scrollincrement",
_MIN_SCROLL_HEIGHT = "minscrollheight",
_CLASSNAME = "classname",
_SHADOW = "shadow",
_KEEP_OPEN = "keepopen",
_HD = "hd",
_HAS_TITLE = "hastitle",
_CONTEXT = "context",
_EMPTY_STRING = "",
_MOUSEDOWN = "mousedown",
_KEYDOWN = "keydown",
_HEIGHT = "height",
_WIDTH = "width",
_PX = "px",
_EFFECT = "effect",
_MONITOR_RESIZE = "monitorresize",
_DISPLAY = "display",
_BLOCK = "block",
_VISIBILITY = "visibility",
_ABSOLUTE = "absolute",
_ZINDEX = "zindex",
_YUI_MENU_BODY_SCROLLED = "yui-menu-body-scrolled",
_NON_BREAKING_SPACE = " ",
_SPACE = " ",
_MOUSEOVER = "mouseover",
_MOUSEOUT = "mouseout",
_ITEM_ADDED = "itemAdded",
_ITEM_REMOVED = "itemRemoved",
_HIDDEN = "hidden",
_YUI_MENU_SHADOW = "yui-menu-shadow",
_YUI_MENU_SHADOW_VISIBLE = _YUI_MENU_SHADOW + "-visible",
_YUI_MENU_SHADOW_YUI_MENU_SHADOW_VISIBLE = _YUI_MENU_SHADOW + _SPACE + _YUI_MENU_SHADOW_VISIBLE;
/**
* The Menu class creates a container that holds a vertical list representing
* a set of options or commands. Menu is the base class for all
* menu containers.
* @param {String} p_oElement String specifying the id attribute of the
* <code><div></code> element of the menu.
* @param {String} p_oElement String specifying the id attribute of the
* <code><select></code> element to be used as the data source
* for the menu.
* @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
* level-one-html.html#ID-22445964">HTMLDivElement</a>} p_oElement Object
* specifying the <code><div></code> element of the menu.
* @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
* level-one-html.html#ID-94282980">HTMLSelectElement</a>} p_oElement
* Object specifying the <code><select></code> element to be used as
* the data source for the menu.
* @param {Object} p_oConfig Optional. Object literal specifying the
* configuration for the menu. See configuration class documentation for
* more details.
* @namespace YAHOO.widget
* @class Menu
* @constructor
* @extends YAHOO.widget.Overlay
*/
YAHOO.widget.Menu = function (p_oElement, p_oConfig) {
if (p_oConfig) {
this.parent = p_oConfig.parent;
this.lazyLoad = p_oConfig.lazyLoad || p_oConfig.lazyload;
this.itemData = p_oConfig.itemData || p_oConfig.itemdata;
}
YAHOO.widget.Menu.superclass.constructor.call(this, p_oElement, p_oConfig);
};
/**
* @method checkPosition
* @description Checks to make sure that the value of the "position" property
* is one of the supported strings. Returns true if the position is supported.
* @private
* @param {Object} p_sPosition String specifying the position of the menu.
* @return {Boolean}
*/
function checkPosition(p_sPosition) {
var returnVal = false;
if (Lang.isString(p_sPosition)) {
returnVal = (_DYNAMIC_STATIC.indexOf((p_sPosition.toLowerCase())) != -1);
}
return returnVal;
}
var Dom = YAHOO.util.Dom,
Event = YAHOO.util.Event,
Module = YAHOO.widget.Module,
Overlay = YAHOO.widget.Overlay,
Menu = YAHOO.widget.Menu,
MenuManager = YAHOO.widget.MenuManager,
CustomEvent = YAHOO.util.CustomEvent,
UA = YAHOO.env.ua,
m_oShadowTemplate,
EVENT_TYPES = [
["mouseOverEvent", _MOUSEOVER],
["mouseOutEvent", _MOUSEOUT],
["mouseDownEvent", _MOUSEDOWN],
["mouseUpEvent", "mouseup"],
["clickEvent", "click"],
["keyPressEvent", "keypress"],
["keyDownEvent", _KEYDOWN],
["keyUpEvent", "keyup"],
["focusEvent", "focus"],
["blurEvent", "blur"],
["itemAddedEvent", _ITEM_ADDED],
["itemRemovedEvent", _ITEM_REMOVED]
],
VISIBLE_CONFIG = {
key: _VISIBLE,
value: false,
validator: Lang.isBoolean
},
CONSTRAIN_TO_VIEWPORT_CONFIG = {
key: _CONSTRAIN_TO_VIEWPORT,
value: true,
validator: Lang.isBoolean,
supercedes: [_IFRAME,"x",_Y,_XY]
},
PREVENT_CONTEXT_OVERLAP_CONFIG = {
key: _PREVENT_CONTEXT_OVERLAP,
value: true,
validator: Lang.isBoolean,
supercedes: [_CONSTRAIN_TO_VIEWPORT]
},
POSITION_CONFIG = {
key: _POSITION,
value: _DYNAMIC,
validator: checkPosition,
supercedes: [_VISIBLE, _IFRAME]
},
SUBMENU_ALIGNMENT_CONFIG = {
key: _SUBMENU_ALIGNMENT,
value: ["tl","tr"]
},
AUTO_SUBMENU_DISPLAY_CONFIG = {
key: _AUTO_SUBMENU_DISPLAY,
value: true,
validator: Lang.isBoolean,
suppressEvent: true
},
SHOW_DELAY_CONFIG = {
key: _SHOW_DELAY,
value: 250,
validator: Lang.isNumber,
suppressEvent: true
},
HIDE_DELAY_CONFIG = {
key: _HIDE_DELAY,
value: 0,
validator: Lang.isNumber,
suppressEvent: true
},
SUBMENU_HIDE_DELAY_CONFIG = {
key: _SUBMENU_HIDE_DELAY,
value: 250,
validator: Lang.isNumber,
suppressEvent: true
},
CLICK_TO_HIDE_CONFIG = {
key: _CLICK_TO_HIDE,
value: true,
validator: Lang.isBoolean,
suppressEvent: true
},
CONTAINER_CONFIG = {
key: _CONTAINER,
suppressEvent: true
},
SCROLL_INCREMENT_CONFIG = {
key: _SCROLL_INCREMENT,
value: 1,
validator: Lang.isNumber,
supercedes: [_MAX_HEIGHT],
suppressEvent: true
},
MIN_SCROLL_HEIGHT_CONFIG = {
key: _MIN_SCROLL_HEIGHT,
value: 90,
validator: Lang.isNumber,
supercedes: [_MAX_HEIGHT],
suppressEvent: true
},
MAX_HEIGHT_CONFIG = {
key: _MAX_HEIGHT,
value: 0,
validator: Lang.isNumber,
supercedes: [_IFRAME],
suppressEvent: true
},
CLASS_NAME_CONFIG = {
key: _CLASSNAME,
value: null,
validator: Lang.isString,
suppressEvent: true
},
DISABLED_CONFIG = {
key: _DISABLED,
value: false,
validator: Lang.isBoolean,
suppressEvent: true
},
SHADOW_CONFIG = {
key: _SHADOW,
value: true,
validator: Lang.isBoolean,
suppressEvent: true,
supercedes: [_VISIBLE]
},
KEEP_OPEN_CONFIG = {
key: _KEEP_OPEN,
value: false,
validator: Lang.isBoolean
};
YAHOO.lang.extend(Menu, Overlay, {
// Constants
/**
* @property CSS_CLASS_NAME
* @description String representing the CSS class(es) to be applied to the
* menu's <code><div></code> element.
* @default "yuimenu"
* @final
* @type String
*/
CSS_CLASS_NAME: "yuimenu",
/**
* @property ITEM_TYPE
* @description Object representing the type of menu item to instantiate and
* add when parsing the child nodes (either <code><li></code> element,
* <code><optgroup></code> element or <code><option></code>)
* of the menu's source HTML element.
* @default YAHOO.widget.MenuItem
* @final
* @type YAHOO.widget.MenuItem
*/
ITEM_TYPE: null,
/**
* @property GROUP_TITLE_TAG_NAME
* @description String representing the tagname of the HTML element used to
* title the menu's item groups.
* @default H6
* @final
* @type String
*/
GROUP_TITLE_TAG_NAME: "h6",
/**
* @property OFF_SCREEN_POSITION
* @description Array representing the default x and y position that a menu
* should have when it is positioned outside the viewport by the
* "poistionOffScreen" method.
* @default "-999em"
* @final
* @type String
*/
OFF_SCREEN_POSITION: "-999em",
// Private properties
/**
* @property _useHideDelay
* @description Boolean indicating if the "mouseover" and "mouseout" event
* handlers used for hiding the menu via a call to "YAHOO.lang.later" have
* already been assigned.
* @default false
* @private
* @type Boolean
*/
_useHideDelay: false,
/**
* @property _bHandledMouseOverEvent
* @description Boolean indicating the current state of the menu's
* "mouseover" event.
* @default false
* @private
* @type Boolean
*/
_bHandledMouseOverEvent: false,
/**
* @property _bHandledMouseOutEvent
* @description Boolean indicating the current state of the menu's
* "mouseout" event.
* @default false
* @private
* @type Boolean
*/
_bHandledMouseOutEvent: false,
/**
* @property _aGroupTitleElements
* @description Array of HTML element used to title groups of menu items.
* @default []
* @private
* @type Array
*/
_aGroupTitleElements: null,
/**
* @property _aItemGroups
* @description Multi-dimensional Array representing the menu items as they
* are grouped in the menu.
* @default []
* @private
* @type Array
*/
_aItemGroups: null,
/**
* @property _aListElements
* @description Array of <code><ul></code> elements, each of which is
* the parent node for each item's <code><li></code> element.
* @default []
* @private
* @type Array
*/
_aListElements: null,
/**
* @property _nCurrentMouseX
* @description The current x coordinate of the mouse inside the area of
* the menu.
* @default 0
* @private
* @type Number
*/
_nCurrentMouseX: 0,
/**
* @property _bStopMouseEventHandlers
* @description Stops "mouseover," "mouseout," and "mousemove" event handlers
* from executing.
* @default false
* @private
* @type Boolean
*/
_bStopMouseEventHandlers: false,
/**
* @property _sClassName
* @description The current value of the "classname" configuration attribute.
* @default null
* @private
* @type String
*/
_sClassName: null,
// Public properties
/**
* @property lazyLoad
* @description Boolean indicating if the menu's "lazy load" feature is
* enabled. If set to "true," initialization and rendering of the menu's
* items will be deferred until the first time it is made visible. This
* property should be set via the constructor using the configuration
* object literal.
* @default false
* @type Boolean
*/
lazyLoad: false,
/**
* @property itemData
* @description Array of items to be added to the menu. The array can contain
* strings representing the text for each item to be created, object literals
* representing the menu item configuration properties, or MenuItem instances.
* This property should be set via the constructor using the configuration
* object literal.
* @default null
* @type Array
*/
itemData: null,
/**
* @property activeItem
* @description Object reference to the item in the menu that has is selected.
* @default null
* @type YAHOO.widget.MenuItem
*/
activeItem: null,
/**
* @property parent
* @description Object reference to the menu's parent menu or menu item.
* This property can be set via the constructor using the configuration
* object literal.
* @default null
* @type YAHOO.widget.MenuItem
*/
parent: null,
/**
* @property srcElement
* @description Object reference to the HTML element (either
* <code><select></code> or <code><div></code>) used to
* create the menu.
* @default null
* @type <a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
* level-one-html.html#ID-94282980">HTMLSelectElement</a>|<a
* href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-one-html.
* html#ID-22445964">HTMLDivElement</a>
*/
srcElement: null,
// Events
/**
* @event mouseOverEvent
* @description Fires when the mouse has entered the menu. Passes back
* the DOM Event object as an argument.
*/
/**
* @event mouseOutEvent
* @description Fires when the mouse has left the menu. Passes back the DOM
* Event object as an argument.
* @type YAHOO.util.CustomEvent
*/
/**
* @event mouseDownEvent
* @description Fires when the user mouses down on the menu. Passes back the
* DOM Event object as an argument.
* @type YAHOO.util.CustomEvent
*/
/**
* @event mouseUpEvent
* @description Fires when the user releases a mouse button while the mouse is
* over the menu. Passes back the DOM Event object as an argument.
* @type YAHOO.util.CustomEvent
*/
/**
* @event clickEvent
* @description Fires when the user clicks the on the menu. Passes back the
* DOM Event object as an argument.
* @type YAHOO.util.CustomEvent
*/
/**
* @event keyPressEvent
* @description Fires when the user presses an alphanumeric key when one of the
* menu's items has focus. Passes back the DOM Event object as an argument.
* @type YAHOO.util.CustomEvent
*/
/**
* @event keyDownEvent
* @description Fires when the user presses a key when one of the menu's items
* has focus. Passes back the DOM Event object as an argument.
* @type YAHOO.util.CustomEvent
*/
/**
* @event keyUpEvent
* @description Fires when the user releases a key when one of the menu's items
* has focus. Passes back the DOM Event object as an argument.
* @type YAHOO.util.CustomEvent
*/
/**
* @event itemAddedEvent
* @description Fires when an item is added to the menu.
* @type YAHOO.util.CustomEvent
*/
/**
* @event itemRemovedEvent
* @description Fires when an item is removed to the menu.
* @type YAHOO.util.CustomEvent
*/
/**
* @method init
* @description The Menu class's initialization method. This method is
* automatically called by the constructor, and sets up all DOM references
* for pre-existing markup, and creates required markup if it is not
* already present.
* @param {String} p_oElement String specifying the id attribute of the
* <code><div></code> element of the menu.
* @param {String} p_oElement String specifying the id attribute of the
* <code><select></code> element to be used as the data source
* for the menu.
* @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
* level-one-html.html#ID-22445964">HTMLDivElement</a>} p_oElement Object
* specifying the <code><div></code> element of the menu.
* @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
* level-one-html.html#ID-94282980">HTMLSelectElement</a>} p_oElement
* Object specifying the <code><select></code> element to be used as
* the data source for the menu.
* @param {Object} p_oConfig Optional. Object literal specifying the
* configuration for the menu. See configuration class documentation for
* more details.
*/
init: function (p_oElement, p_oConfig) {
this._aItemGroups = [];
this._aListElements = [];
this._aGroupTitleElements = [];
if (!this.ITEM_TYPE) {
this.ITEM_TYPE = YAHOO.widget.MenuItem;
}
var oElement;
if (Lang.isString(p_oElement)) {
oElement = Dom.get(p_oElement);
}
else if (p_oElement.tagName) {
oElement = p_oElement;
}
if (oElement && oElement.tagName) {
switch(oElement.tagName.toUpperCase()) {
case _DIV_UPPERCASE:
this.srcElement = oElement;
if (!oElement.id) {
oElement.setAttribute(_ID, Dom.generateId());
}
/*
Note: we don't pass the user config in here yet
because we only want it executed once, at the lowest
subclass level.
*/
Menu.superclass.init.call(this, oElement);
this.beforeInitEvent.fire(Menu);
YAHOO.log("Source element: " + this.srcElement.tagName, "info", this.toString());
break;
case _SELECT:
this.srcElement = oElement;
/*
The source element is not something that we can use
outright, so we need to create a new Overlay
Note: we don't pass the user config in here yet
because we only want it executed once, at the lowest
subclass level.
*/
Menu.superclass.init.call(this, Dom.generateId());
this.beforeInitEvent.fire(Menu);
YAHOO.log("Source element: " + this.srcElement.tagName, "info", this.toString());
break;
}
}
else {
/*
Note: we don't pass the user config in here yet
because we only want it executed once, at the lowest
subclass level.
*/
Menu.superclass.init.call(this, p_oElement);
this.beforeInitEvent.fire(Menu);
YAHOO.log("No source element found. Created element with id: " + this.id, "info", this.toString());
}
if (this.element) {
Dom.addClass(this.element, this.CSS_CLASS_NAME);
// Subscribe to Custom Events
this.initEvent.subscribe(this._onInit);
this.beforeRenderEvent.subscribe(this._onBeforeRender);
this.renderEvent.subscribe(this._onRender);
this.beforeShowEvent.subscribe(this._onBeforeShow);
this.hideEvent.subscribe(this._onHide);
this.showEvent.subscribe(this._onShow);
this.beforeHideEvent.subscribe(this._onBeforeHide);
this.mouseOverEvent.subscribe(this._onMouseOver);
this.mouseOutEvent.subscribe(this._onMouseOut);
this.clickEvent.subscribe(this._onClick);
this.keyDownEvent.subscribe(this._onKeyDown);
this.keyPressEvent.subscribe(this._onKeyPress);
this.blurEvent.subscribe(this._onBlur);
// Fixes an issue in Firefox 2 and Webkit where Dom's "getX" and "getY"
// methods return values that don't take scrollTop into consideration
if ((UA.gecko && UA.gecko < 1.9) || UA.webkit) {
this.cfg.subscribeToConfigEvent(_Y, this._onYChange);
}
if (p_oConfig) {
this.cfg.applyConfig(p_oConfig, true);
}
// Register the Menu instance with the MenuManager
MenuManager.addMenu(this);
this.initEvent.fire(Menu);
}
},
// Private methods
/**
* @method _initSubTree
* @description Iterates the childNodes of the source element to find nodes
* used to instantiate menu and menu items.
* @private
*/
_initSubTree: function () {
var oSrcElement = this.srcElement,
sSrcElementTagName,
nGroup,
sGroupTitleTagName,
oNode,
aListElements,
nListElements,
i;
if (oSrcElement) {
sSrcElementTagName =
(oSrcElement.tagName && oSrcElement.tagName.toUpperCase());
if (sSrcElementTagName == _DIV_UPPERCASE) {
// Populate the collection of item groups and item group titles
oNode = this.body.firstChild;
if (oNode) {
nGroup = 0;
sGroupTitleTagName = this.GROUP_TITLE_TAG_NAME.toUpperCase();
do {
if (oNode && oNode.tagName) {
switch (oNode.tagName.toUpperCase()) {
case sGroupTitleTagName:
this._aGroupTitleElements[nGroup] = oNode;
break;
case _UL_UPPERCASE:
this._aListElements[nGroup] = oNode;
this._aItemGroups[nGroup] = [];
nGroup++;
break;
}
}
}
while ((oNode = oNode.nextSibling));
/*
Apply the "first-of-type" class to the first UL to mimic
the ":first-of-type" CSS3 psuedo class.
*/
if (this._aListElements[0]) {
Dom.addClass(this._aListElements[0], _FIRST_OF_TYPE);
}
}
}
oNode = null;
YAHOO.log("Searching DOM for items to initialize.", "info", this.toString());
if (sSrcElementTagName) {
switch (sSrcElementTagName) {
case _DIV_UPPERCASE:
aListElements = this._aListElements;
nListElements = aListElements.length;
if (nListElements > 0) {
YAHOO.log("Found " + nListElements + " item groups to initialize.",
"info", this.toString());
i = nListElements - 1;
do {
oNode = aListElements[i].firstChild;
if (oNode) {
YAHOO.log("Scanning " +
aListElements[i].childNodes.length +
" child nodes for items to initialize.", "info", this.toString());
do {
if (oNode && oNode.tagName &&
oNode.tagName.toUpperCase() == _LI) {
YAHOO.log("Initializing " +
oNode.tagName + " node.", "info", this.toString());
this.addItem(new this.ITEM_TYPE(oNode,
{ parent: this }), i);
}
}
while ((oNode = oNode.nextSibling));
}
}
while (i--);
}
break;
case _SELECT:
YAHOO.log("Scanning " +
oSrcElement.childNodes.length +
" child nodes for items to initialize.", "info", this.toString());
oNode = oSrcElement.firstChild;
do {
if (oNode && oNode.tagName) {
switch (oNode.tagName.toUpperCase()) {
case _OPTGROUP:
case _OPTION:
YAHOO.log("Initializing " +
oNode.tagName + " node.", "info", this.toString());
this.addItem(
new this.ITEM_TYPE(
oNode,
{ parent: this }
)
);
break;
}
}
}
while ((oNode = oNode.nextSibling));
break;
}
}
}
},
/**
* @method _getFirstEnabledItem
* @description Returns the first enabled item in the menu.
* @return {YAHOO.widget.MenuItem}
* @private
*/
_getFirstEnabledItem: function () {
var aItems = this.getItems(),
nItems = aItems.length,
oItem,
returnVal;
for(var i=0; i<nItems; i++) {
oItem = aItems[i];
if (oItem && !oItem.cfg.getProperty(_DISABLED) && oItem.element.style.display != _NONE) {
returnVal = oItem;
break;
}
}
return returnVal;
},
/**
* @method _addItemToGroup
* @description Adds a menu item to a group.
* @private
* @param {Number} p_nGroupIndex Number indicating the group to which the
* item belongs.
* @param {YAHOO.widget.MenuItem} p_oItem Object reference for the MenuItem
* instance to be added to the menu.
* @param {String} p_oItem String specifying the text of the item to be added
* to the menu.
* @param {Object} p_oItem Object literal containing a set of menu item
* configuration properties.
* @param {Number} p_nItemIndex Optional. Number indicating the index at
* which the menu item should be added.
* @return {YAHOO.widget.MenuItem}
*/
_addItemToGroup: function (p_nGroupIndex, p_oItem, p_nItemIndex) {
var oItem,
nGroupIndex,
aGroup,
oGroupItem,
bAppend,
oNextItemSibling,
nItemIndex,
returnVal;
function getNextItemSibling(p_aArray, p_nStartIndex) {
return (p_aArray[p_nStartIndex] || getNextItemSibling(p_aArray, (p_nStartIndex+1)));
}
if (p_oItem instanceof this.ITEM_TYPE) {
oItem = p_oItem;
oItem.parent = this;
}
else if (Lang.isString(p_oItem)) {
oItem = new this.ITEM_TYPE(p_oItem, { parent: this });
}
else if (Lang.isObject(p_oItem)) {
p_oItem.parent = this;
oItem = new this.ITEM_TYPE(p_oItem.text, p_oItem);
}
if (oItem) {
if (oItem.cfg.getProperty(_SELECTED)) {
this.activeItem = oItem;
}
nGroupIndex = Lang.isNumber(p_nGroupIndex) ? p_nGroupIndex : 0;
aGroup = this._getItemGroup(nGroupIndex);
if (!aGroup) {
aGroup = this._createItemGroup(nGroupIndex);
}
if (Lang.isNumber(p_nItemIndex)) {
bAppend = (p_nItemIndex >= aGroup.length);
if (aGroup[p_nItemIndex]) {
aGroup.splice(p_nItemIndex, 0, oItem);
}
else {
aGroup[p_nItemIndex] = oItem;
}
oGroupItem = aGroup[p_nItemIndex];
if (oGroupItem) {
if (bAppend && (!oGroupItem.element.parentNode ||
oGroupItem.element.parentNode.nodeType == 11)) {
this._aListElements[nGroupIndex].appendChild(oGroupItem.element);
}
else {
oNextItemSibling = getNextItemSibling(aGroup, (p_nItemIndex+1));
if (oNextItemSibling && (!oGroupItem.element.parentNode ||
oGroupItem.element.parentNode.nodeType == 11)) {
this._aListElements[nGroupIndex].insertBefore(
oGroupItem.element, oNextItemSibling.element);
}
}
oGroupItem.parent = this;
this._subscribeToItemEvents(oGroupItem);
this._configureSubmenu(oGroupItem);
this._updateItemProperties(nGroupIndex);
YAHOO.log("Item inserted." +
" Text: " + oGroupItem.cfg.getProperty("text") + ", " +
" Index: " + oGroupItem.index + ", " +
" Group Index: " + oGroupItem.groupIndex, "info", this.toString());
this.itemAddedEvent.fire(oGroupItem);
this.changeContentEvent.fire();
returnVal = oGroupItem;
}
}
else {
nItemIndex = aGroup.length;
aGroup[nItemIndex] = oItem;
oGroupItem = aGroup[nItemIndex];
if (oGroupItem) {
if (!Dom.isAncestor(this._aListElements[nGroupIndex], oGroupItem.element)) {
this._aListElements[nGroupIndex].appendChild(oGroupItem.element);
}
oGroupItem.element.setAttribute(_GROUP_INDEX, nGroupIndex);
oGroupItem.element.setAttribute(_INDEX, nItemIndex);
oGroupItem.parent = this;
oGroupItem.index = nItemIndex;
oGroupItem.groupIndex = nGroupIndex;
this._subscribeToItemEvents(oGroupItem);
this._configureSubmenu(oGroupItem);
if (nItemIndex === 0) {
Dom.addClass(oGroupItem.element, _FIRST_OF_TYPE);
}
YAHOO.log("Item added." +
" Text: " + oGroupItem.cfg.getProperty("text") + ", " +
" Index: " + oGroupItem.index + ", " +
" Group Index: " + oGroupItem.groupIndex, "info", this.toString());
this.itemAddedEvent.fire(oGroupItem);
this.changeContentEvent.fire();
returnVal = oGroupItem;
}
}
}
return returnVal;
},
/**
* @method _removeItemFromGroupByIndex
* @description Removes a menu item from a group by index. Returns the menu
* item that was removed.
* @private
* @param {Number} p_nGroupIndex Number indicating the group to which the menu
* item belongs.
* @param {Number} p_nItemIndex Number indicating the index of the menu item
* to be removed.
* @return {YAHOO.widget.MenuItem}
*/
_removeItemFromGroupByIndex: function (p_nGroupIndex, p_nItemIndex) {
var nGroupIndex = Lang.isNumber(p_nGroupIndex) ? p_nGroupIndex : 0,
aGroup = this._getItemGroup(nGroupIndex),
aArray,
oItem,
oUL;
if (aGroup) {
aArray = aGroup.splice(p_nItemIndex, 1);
oItem = aArray[0];
if (oItem) {
// Update the index and className properties of each member
this._updateItemProperties(nGroupIndex);
if (aGroup.length === 0) {
// Remove the UL
oUL = this._aListElements[nGroupIndex];
if (this.body && oUL) {
this.body.removeChild(oUL);
}
// Remove the group from the array of items
this._aItemGroups.splice(nGroupIndex, 1);
// Remove the UL from the array of ULs
this._aListElements.splice(nGroupIndex, 1);
/*
Assign the "first-of-type" class to the new first UL
in the collection
*/
oUL = this._aListElements[0];
if (oUL) {
Dom.addClass(oUL, _FIRST_OF_TYPE);
}
}
this.itemRemovedEvent.fire(oItem);
this.changeContentEvent.fire();
}
}
// Return a reference to the item that was removed
return oItem;
},
/**
* @method _removeItemFromGroupByValue
* @description Removes a menu item from a group by reference. Returns the
* menu item that was removed.
* @private
* @param {Number} p_nGroupIndex Number indicating the group to which the
* menu item belongs.
* @param {YAHOO.widget.MenuItem} p_oItem Object reference for the MenuItem
* instance to be removed.
* @return {YAHOO.widget.MenuItem}
*/
_removeItemFromGroupByValue: function (p_nGroupIndex, p_oItem) {
var aGroup = this._getItemGroup(p_nGroupIndex),
nItems,
nItemIndex,
returnVal,
i;
if (aGroup) {
nItems = aGroup.length;
nItemIndex = -1;
if (nItems > 0) {
i = nItems-1;
do {
if (aGroup[i] == p_oItem) {
nItemIndex = i;
break;
}
}
while (i--);
if (nItemIndex > -1) {
returnVal = this._removeItemFromGroupByIndex(p_nGroupIndex, nItemIndex);
}
}
}
return returnVal;
},
/**
* @method _updateItemProperties
* @description Updates the "index," "groupindex," and "className" properties
* of the menu items in the specified group.
* @private
* @param {Number} p_nGroupIndex Number indicating the group of items to update.
*/
_updateItemProperties: function (p_nGroupIndex) {
var aGroup = this._getItemGroup(p_nGroupIndex),
nItems = aGroup.length,
oItem,
oLI,
i;
if (nItems > 0) {
i = nItems - 1;
// Update the index and className properties of each member
do {
oItem = aGroup[i];
if (oItem) {
oLI = oItem.element;
oItem.index = i;
oItem.groupIndex = p_nGroupIndex;
oLI.setAttribute(_GROUP_INDEX, p_nGroupIndex);
oLI.setAttribute(_INDEX, i);
Dom.removeClass(oLI, _FIRST_OF_TYPE);
}
}
while (i--);
if (oLI) {
Dom.addClass(oLI, _FIRST_OF_TYPE);
}
}
},
/**
* @method _createItemGroup
* @description Creates a new menu item group (array) and its associated
* <code><ul></code> element. Returns an aray of menu item groups.
* @private
* @param {Number} p_nIndex Number indicating the group to create.
* @return {Array}
*/
_createItemGroup: function (p_nIndex) {
var oUL,
returnVal;
if (!this._aItemGroups[p_nIndex]) {
this._aItemGroups[p_nIndex] = [];
oUL = document.createElement(_UL_LOWERCASE);
this._aListElements[p_nIndex] = oUL;
returnVal = this._aItemGroups[p_nIndex];
}
return returnVal;
},
/**
* @method _getItemGroup
* @description Returns the menu item group at the specified index.
* @private
* @param {Number} p_nIndex Number indicating the index of the menu item group
* to be retrieved.
* @return {Array}
*/
_getItemGroup: function (p_nIndex) {
var nIndex = Lang.isNumber(p_nIndex) ? p_nIndex : 0,
aGroups = this._aItemGroups,
returnVal;
if (nIndex in aGroups) {
returnVal = aGroups[nIndex];
}
return returnVal;
},
/**
* @method _configureSubmenu
* @description Subscribes the menu item's submenu to its parent menu's events.
* @private
* @param {YAHOO.widget.MenuItem} p_oItem Object reference for the MenuItem
* instance with the submenu to be configured.
*/
_configureSubmenu: function (p_oItem) {
var oSubmenu = p_oItem.cfg.getProperty(_SUBMENU);
if (oSubmenu) {
/*
Listen for configuration changes to the parent menu
so they they can be applied to the submenu.
*/
this.cfg.configChangedEvent.subscribe(this._onParentMenuConfigChange, oSubmenu, true);
this.renderEvent.subscribe(this._onParentMenuRender, oSubmenu, true);
}
},
/**
* @method _subscribeToItemEvents
* @description Subscribes a menu to a menu item's event.
* @private
* @param {YAHOO.widget.MenuItem} p_oItem Object reference for the MenuItem
* instance whose events should be subscribed to.
*/
_subscribeToItemEvents: function (p_oItem) {
p_oItem.destroyEvent.subscribe(this._onMenuItemDestroy, p_oItem, this);
p_oItem.cfg.configChangedEvent.subscribe(this._onMenuItemConfigChange, p_oItem, this);
},
/**
* @method _onVisibleChange
* @description Change event handler for the the menu's "visible" configuration
* property.
* @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.
*/
_onVisibleChange: function (p_sType, p_aArgs) {
var bVisible = p_aArgs[0];
if (bVisible) {
Dom.addClass(this.element, _VISIBLE);
}
else {
Dom.removeClass(this.element, _VISIBLE);
}
},
/**
* @method _cancelHideDelay
* @description Cancels the call to "hideMenu."
* @private
*/
_cancelHideDelay: function () {
var oTimer = this.getRoot()._hideDelayTimer;
if (oTimer) {
oTimer.cancel();
}
},
/**
* @method _execHideDelay
* @description Hides the menu after the number of milliseconds specified by
* the "hidedelay" configuration property.
* @private
*/
_execHideDelay: function () {
this._cancelHideDelay();
var oRoot = this.getRoot();
oRoot._hideDelayTimer = Lang.later(oRoot.cfg.getProperty(_HIDE_DELAY), this, function () {
if (oRoot.activeItem) {
if (oRoot.hasFocus()) {
oRoot.activeItem.focus();
}
oRoot.clearActiveItem();
}
if (oRoot == this && !(this instanceof YAHOO.widget.MenuBar) &&
this.cfg.getProperty(_POSITION) == _DYNAMIC) {
this.hide();
}
});
},
/**
* @method _cancelShowDelay
* @description Cancels the call to the "showMenu."
* @private
*/
_cancelShowDelay: function () {
var oTimer = this.getRoot()._showDelayTimer;
if (oTimer) {
oTimer.cancel();
}
},
/**
* @method _execSubmenuHideDelay
* @description Hides a submenu after the number of milliseconds specified by
* the "submenuhidedelay" configuration property have ellapsed.
* @private
* @param {YAHOO.widget.Menu} p_oSubmenu Object specifying the submenu that
* should be hidden.
* @param {Number} p_nMouseX The x coordinate of the mouse when it left
* the specified submenu's parent menu item.
* @param {Number} p_nHideDelay The number of milliseconds that should ellapse
* before the submenu is hidden.
*/
_execSubmenuHideDelay: function (p_oSubmenu, p_nMouseX, p_nHideDelay) {
p_oSubmenu._submenuHideDelayTimer = Lang.later(50, this, function () {
if (this._nCurrentMouseX > (p_nMouseX + 10)) {
p_oSubmenu._submenuHideDelayTimer = Lang.later(p_nHideDelay, p_oSubmenu, function () {
this.hide();
});
}
else {
p_oSubmenu.hide();
}
});
},
// Protected methods
/**
* @method _disableScrollHeader
* @description Disables the header used for scrolling the body of the menu.
* @protected
*/
_disableScrollHeader: function () {
if (!this._bHeaderDisabled) {
Dom.addClass(this.header, _TOP_SCROLLBAR_DISABLED);
this._bHeaderDisabled = true;
}
},
/**
* @method _disableScrollFooter
* @description Disables the footer used for scrolling the body of the menu.
* @protected
*/
_disableScrollFooter: function () {
if (!this._bFooterDisabled) {
Dom.addClass(this.footer, _BOTTOM_SCROLLBAR_DISABLED);
this._bFooterDisabled = true;
}
},
/**
* @method _enableScrollHeader
* @description Enables the header used for scrolling the body of the menu.
* @protected
*/
_enableScrollHeader: function () {
if (this._bHeaderDisabled) {
Dom.removeClass(this.header, _TOP_SCROLLBAR_DISABLED);
this._bHeaderDisabled = false;
}
},
/**
* @method _enableScrollFooter
* @description Enables the footer used for scrolling the body of the menu.
* @protected
*/
_enableScrollFooter: function () {
if (this._bFooterDisabled) {
Dom.removeClass(this.footer, _BOTTOM_SCROLLBAR_DISABLED);
this._bFooterDisabled = false;
}
},
/**
* @method _onMouseOver
* @description "mouseover" event handler for the menu.
* @protected
* @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.
*/
_onMouseOver: function (p_sType, p_aArgs) {
var oEvent = p_aArgs[0],
oItem = p_aArgs[1],
oTarget = Event.getTarget(oEvent),
oRoot = this.getRoot(),
oSubmenuHideDelayTimer = this._submenuHideDelayTimer,
oParentMenu,
nShowDelay,
bShowDelay,
oActiveItem,
oItemCfg,
oSubmenu;
var showSubmenu = function () {
if (this.parent.cfg.getProperty(_SELECTED)) {
this.show();
}
};
if (!this._bStopMouseEventHandlers) {
if (!this._bHandledMouseOverEvent && (oTarget == this.element ||
Dom.isAncestor(this.element, oTarget))) {
// Menu mouseover logic
if (this._useHideDelay) {
this._cancelHideDelay();
}
this._nCurrentMouseX = 0;
Event.on(this.element, _MOUSEMOVE, this._onMouseMove, this, true);
/*
If the mouse is moving from the submenu back to its corresponding menu item,
don't hide the submenu or clear the active MenuItem.
*/
if (!(oItem && Dom.isAncestor(oItem.element, Event.getRelatedTarget(oEvent)))) {
this.clearActiveItem();
}
if (this.parent && oSubmenuHideDelayTimer) {
oSubmenuHideDelayTimer.cancel();
this.parent.cfg.setProperty(_SELECTED, true);
oParentMenu = this.parent.parent;
oParentMenu._bHandledMouseOutEvent = true;
oParentMenu._bHandledMouseOverEvent = false;
}
this._bHandledMouseOverEvent = true;
this._bHandledMouseOutEvent = false;
}
if (oItem && !oItem.handledMouseOverEvent && !oItem.cfg.getProperty(_DISABLED) &&
(oTarget == oItem.element || Dom.isAncestor(oItem.element, oTarget))) {
// Menu Item mouseover logic
nShowDelay = this.cfg.getProperty(_SHOW_DELAY);
bShowDelay = (nShowDelay > 0);
if (bShowDelay) {
this._cancelShowDelay();
}
oActiveItem = this.activeItem;
if (oActiveItem) {
oActiveItem.cfg.setProperty(_SELECTED, false);
}
oItemCfg = oItem.cfg;
// Select and focus the current menu item
oItemCfg.setProperty(_SELECTED, true);
if (this.hasFocus() || oRoot._hasFocus) {
oItem.focus();
oRoot._hasFocus = false;
}
if (this.cfg.getProperty(_AUTO_SUBMENU_DISPLAY)) {
// Show the submenu this menu item
oSubmenu = oItemCfg.getProperty(_SUBMENU);
if (oSubmenu) {
if (bShowDelay) {
oRoot._showDelayTimer =
Lang.later(oRoot.cfg.getProperty(_SHOW_DELAY), oSubmenu, showSubmenu);
}
else {
oSubmenu.show();
}
}
}
oItem.handledMouseOverEvent = true;
oItem.handledMouseOutEvent = false;
}
}
},
/**
* @method _onMouseOut
* @description "mouseout" event handler for the menu.
* @protected
* @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.
*/
_onMouseOut: function (p_sType, p_aArgs) {
var oEvent = p_aArgs[0],
oItem = p_aArgs[1],
oRelatedTarget = Event.getRelatedTarget(oEvent),
bMovingToSubmenu = false,
oItemCfg,
oSubmenu,
nSubmenuHideDelay,
nShowDelay;
if (!this._bStopMouseEventHandlers) {
if (oItem && !oItem.cfg.getProperty(_DISABLED)) {
oItemCfg = oItem.cfg;
oSubmenu = oItemCfg.getProperty(_SUBMENU);
if (oSubmenu && (oRelatedTarget == oSubmenu.element ||
Dom.isAncestor(oSubmenu.element, oRelatedTarget))) {
bMovingToSubmenu = true;
}
if (!oItem.handledMouseOutEvent && ((oRelatedTarget != oItem.element &&
!Dom.isAncestor(oItem.element, oRelatedTarget)) || bMovingToSubmenu)) {
// Menu Item mouseout logic
if (!bMovingToSubmenu) {
oItem.cfg.setProperty(_SELECTED, false);
if (oSubmenu) {
nSubmenuHideDelay = this.cfg.getProperty(_SUBMENU_HIDE_DELAY);
nShowDelay = this.cfg.getProperty(_SHOW_DELAY);
if (!(this instanceof YAHOO.widget.MenuBar) && nSubmenuHideDelay > 0 &&
nShowDelay >= nSubmenuHideDelay) {
this._execSubmenuHideDelay(oSubmenu, Event.getPageX(oEvent),
nSubmenuHideDelay);
}
else {
oSubmenu.hide();
}
}
}
oItem.handledMouseOutEvent = true;
oItem.handledMouseOverEvent = false;
}
}
if (!this._bHandledMouseOutEvent && ((oRelatedTarget != this.element &&
!Dom.isAncestor(this.element, oRelatedTarget)) || bMovingToSubmenu)) {
// Menu mouseout logic
if (this._useHideDelay) {
this._execHideDelay();
}
Event.removeListener(this.element, _MOUSEMOVE, this._onMouseMove);
this._nCurrentMouseX = Event.getPageX(oEvent);
this._bHandledMouseOutEvent = true;
this._bHandledMouseOverEvent = false;
}
}
},
/**
* @method _onMouseMove
* @description "click" event handler for the menu.
* @protected
* @param {Event} p_oEvent Object representing the DOM event object passed
* back by the event utility (YAHOO.util.Event).
* @param {YAHOO.widget.Menu} p_oMenu Object representing the menu that
* fired the event.
*/
_onMouseMove: function (p_oEvent, p_oMenu) {
if (!this._bStopMouseEventHandlers) {
this._nCurrentMouseX = Event.getPageX(p_oEvent);
}
},
/**
* @method _onClick
* @description "click" event handler for the menu.
* @protected
* @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.
*/
_onClick: function (p_sType, p_aArgs) {
var oEvent = p_aArgs[0],
oItem = p_aArgs[1],
bInMenuAnchor = false,
oSubmenu,
oMenu,
oRoot,
sId,
sURL,
nHashPos,
nLen;
var hide = function () {
/*
There is an inconsistency between Firefox for Mac OS X and Firefox Windows
regarding the triggering of the display of the browser's context menu and the
subsequent firing of the "click" event. In Firefox for Windows, when the user
triggers the display of the browser's context menu the "click" event also fires
for the document object, even though the "click" event did not fire for the
element that was the original target of the "contextmenu" event. This is unique
to Firefox on Windows. For all other A-Grade browsers, including Firefox for
Mac OS X, the "click" event doesn't fire for the document object.
This bug in Firefox for Windows affects Menu as Menu instances listen for
events at the document level and have an internal "click" event handler they
use to hide themselves when clicked. As a result, in Firefox for Windows a
Menu will hide when the user right clicks on a MenuItem to raise the browser's
default context menu, because its internal "click" event handler ends up
getting called. The following line fixes this bug.
*/
if (!((UA.gecko && this.platform == _WINDOWS) && oEvent.button > 0)) {
oRoot = this.getRoot();
if (oRoot instanceof YAHOO.widget.MenuBar ||
oRoot.cfg.getProperty(_POSITION) == _STATIC) {
oRoot.clearActiveItem();
}
else {
oRoot.hide();
}
}
};
if (oItem) {
if (oItem.cfg.getProperty(_DISABLED)) {
Event.preventDefault(oEvent);
hide.call(this);
}
else {
oSubmenu = oItem.cfg.getProperty(_SUBMENU);
/*
Check if the URL of the anchor is pointing to an element that is
a child of the menu.
*/
sURL = oItem.cfg.getProperty(_URL);
if (sURL) {
nHashPos = sURL.indexOf(_HASH);
nLen = sURL.length;
if (nHashPos != -1) {
sURL = sURL.substr(nHashPos, nLen);
nLen = sURL.length;
if (nLen > 1) {
sId = sURL.substr(1, nLen);
oMenu = YAHOO.widget.MenuManager.getMenu(sId);
if (oMenu) {
bInMenuAnchor =
(this.getRoot() === oMenu.getRoot());
}
}
else if (nLen === 1) {
bInMenuAnchor = true;
}
}
}
if (bInMenuAnchor && !oItem.cfg.getProperty(_TARGET)) {
Event.preventDefault(oEvent);
if (UA.webkit) {
oItem.focus();
}
else {
oItem.focusEvent.fire();
}
}
if (!oSubmenu && !this.cfg.getProperty(_KEEP_OPEN)) {
hide.call(this);
}
}
}
},
/**
* @method _onKeyDown
* @description "keydown" event handler for the menu.
* @protected
* @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.
*/
_onKeyDown: function (p_sType, p_aArgs) {
var oEvent = p_aArgs[0],
oItem = p_aArgs[1],
oSubmenu,
oItemCfg,
oParentItem,
oRoot,
oNextItem,
oBody,
nBodyScrollTop,
nBodyOffsetHeight,
aItems,
nItems,
nNextItemOffsetTop,
nScrollTarget,
oParentMenu;
if (this._useHideDelay) {
this._cancelHideDelay();
}
/*
This function is called to prevent a bug in Firefox. In Firefox,
moving a DOM element into a stationary mouse pointer will cause the
browser to fire mouse events. This can result in the menu mouse
event handlers being called uncessarily, especially when menus are
moved into a stationary mouse pointer as a result of a
key event handler.
*/
function stopMouseEventHandlers() {
this._bStopMouseEventHandlers = true;
Lang.later(10, this, function () {
this._bStopMouseEventHandlers = false;
});
}
if (oItem && !oItem.cfg.getProperty(_DISABLED)) {
oItemCfg = oItem.cfg;
oParentItem = this.parent;
switch(oEvent.keyCode) {
case 38: // Up arrow
case 40: // Down arrow
oNextItem = (oEvent.keyCode == 38) ?
oItem.getPreviousEnabledSibling() :
oItem.getNextEnabledSibling();
if (oNextItem) {
this.clearActiveItem();
oNextItem.cfg.setProperty(_SELECTED, true);
oNextItem.focus();
if (this.cfg.getProperty(_MAX_HEIGHT) > 0) {
oBody = this.body;
nBodyScrollTop = oBody.scrollTop;
nBodyOffsetHeight = oBody.offsetHeight;
aItems = this.getItems();
nItems = aItems.length - 1;
nNextItemOffsetTop = oNextItem.element.offsetTop;
if (oEvent.keyCode == 40 ) { // Down
if (nNextItemOffsetTop >= (nBodyOffsetHeight + nBodyScrollTop)) {
oBody.scrollTop = nNextItemOffsetTop - nBodyOffsetHeight;
}
else if (nNextItemOffsetTop <= nBodyScrollTop) {
oBody.scrollTop = 0;
}
if (oNextItem == aItems[nItems]) {
oBody.scrollTop = oNextItem.element.offsetTop;
}
}
else { // Up
if (nNextItemOffsetTop <= nBodyScrollTop) {
oBody.scrollTop = nNextItemOffsetTop - oNextItem.element.offsetHeight;
}
else if (nNextItemOffsetTop >= (nBodyScrollTop + nBodyOffsetHeight)) {
oBody.scrollTop = nNextItemOffsetTop;
}
if (oNextItem == aItems[0]) {
oBody.scrollTop = 0;
}
}
nBodyScrollTop = oBody.scrollTop;
nScrollTarget = oBody.scrollHeight - oBody.offsetHeight;
if (nBodyScrollTop === 0) {
this._disableScrollHeader();
this._enableScrollFooter();
}
else if (nBodyScrollTop == nScrollTarget) {
this._enableScrollHeader();
this._disableScrollFooter();
}
else {
this._enableScrollHeader();
this._enableScrollFooter();
}
}
}
Event.preventDefault(oEvent);
stopMouseEventHandlers();
break;
case 39: // Right arrow
oSubmenu = oItemCfg.getProperty(_SUBMENU);
if (oSubmenu) {
if (!oItemCfg.getProperty(_SELECTED)) {
oItemCfg.setProperty(_SELECTED, true);
}
oSubmenu.show();
oSubmenu.setInitialFocus();
oSubmenu.setInitialSelection();
}
else {
oRoot = this.getRoot();
if (oRoot instanceof YAHOO.widget.MenuBar) {
oNextItem = oRoot.activeItem.getNextEnabledSibling();
if (oNextItem) {
oRoot.clearActiveItem();
oNextItem.cfg.setProperty(_SELECTED, true);
oSubmenu = oNextItem.cfg.getProperty(_SUBMENU);
if (oSubmenu) {
oSubmenu.show();
oSubmenu.setInitialFocus();
}
else {
oNextItem.focus();
}
}
}
}
Event.preventDefault(oEvent);
stopMouseEventHandlers();
break;
case 37: // Left arrow
if (oParentItem) {
oParentMenu = oParentItem.parent;
if (oParentMenu instanceof YAHOO.widget.MenuBar) {
oNextItem =
oParentMenu.activeItem.getPreviousEnabledSibling();
if (oNextItem) {
oParentMenu.clearActiveItem();
oNextItem.cfg.setProperty(_SELECTED, true);
oSubmenu = oNextItem.cfg.getProperty(_SUBMENU);
if (oSubmenu) {
oSubmenu.show();
oSubmenu.setInitialFocus();
}
else {
oNextItem.focus();
}
}
}
else {
this.hide();
oParentItem.focus();
}
}
Event.preventDefault(oEvent);
stopMouseEventHandlers();
break;
}
}
if (oEvent.keyCode == 27) { // Esc key
if (this.cfg.getProperty(_POSITION) == _DYNAMIC) {
this.hide();
if (this.parent) {
this.parent.focus();
}
}
else if (this.activeItem) {
oSubmenu = this.activeItem.cfg.getProperty(_SUBMENU);
if (oSubmenu && oSubmenu.cfg.getProperty(_VISIBLE)) {
oSubmenu.hide();
this.activeItem.focus();
}
else {
this.activeItem.blur();
this.activeItem.cfg.setProperty(_SELECTED, false);
}
}
Event.preventDefault(oEvent);
}
},
/**
* @method _onKeyPress
* @description "keypress" event handler for a Menu instance.
* @protected
* @param {String} p_sType The name of the event that was fired.
* @param {Array} p_aArgs Collection of arguments sent when the event
* was fired.
*/
_onKeyPress: function (p_sType, p_aArgs) {
var oEvent = p_aArgs[0];
if (oEvent.keyCode == 40 || oEvent.keyCode == 38) {
Event.preventDefault(oEvent);
}
},
/**
* @method _onBlur
* @description "blur" event handler for a Menu instance.
* @protected
* @param {String} p_sType The name of the event that was fired.
* @param {Array} p_aArgs Collection of arguments sent when the event
* was fired.
*/
_onBlur: function (p_sType, p_aArgs) {
if (this._hasFocus) {
this._hasFocus = false;
}
},
/**
* @method _onYChange
* @description "y" event handler for a Menu instance.
* @protected
* @param {String} p_sType The name of the event that was fired.
* @param {Array} p_aArgs Collection of arguments sent when the event
* was fired.
*/
_onYChange: function (p_sType, p_aArgs) {
var oParent = this.parent,
nScrollTop,
oIFrame,
nY;
if (oParent) {
nScrollTop = oParent.parent.body.scrollTop;
if (nScrollTop > 0) {
nY = (this.cfg.getProperty(_Y) - nScrollTop);
Dom.setY(this.element, nY);
oIFrame = this.iframe;
if (oIFrame) {
Dom.setY(oIFrame, nY);
}
this.cfg.setProperty(_Y, nY, true);
}
}
},
/**
* @method _onScrollTargetMouseOver
* @description "mouseover" event handler for the menu's "header" and "footer"
* elements. Used to scroll the body of the menu up and down when the
* menu's "maxheight" configuration property is set to a value greater than 0.
* @protected
* @param {Event} p_oEvent Object representing the DOM event object passed
* back by the event utility (YAHOO.util.Event).
* @param {YAHOO.widget.Menu} p_oMenu Object representing the menu that
* fired the event.
*/
_onScrollTargetMouseOver: function (p_oEvent, p_oMenu) {
var oBodyScrollTimer = this._bodyScrollTimer;
if (oBodyScrollTimer) {
oBodyScrollTimer.cancel();
}
this._cancelHideDelay();
var oTarget = Event.getTarget(p_oEvent),
oBody = this.body,
nScrollIncrement = this.cfg.getProperty(_SCROLL_INCREMENT),
nScrollTarget,
fnScrollFunction;
function scrollBodyDown() {
var nScrollTop = oBody.scrollTop;
if (nScrollTop < nScrollTarget) {
oBody.scrollTop = (nScrollTop + nScrollIncrement);
this._enableScrollHeader();
}
else {
oBody.scrollTop = nScrollTarget;
this._bodyScrollTimer.cancel();
this._disableScrollFooter();
}
}
function scrollBodyUp() {
var nScrollTop = oBody.scrollTop;
if (nScrollTop > 0) {
oBody.scrollTop = (nScrollTop - nScrollIncrement);
this._enableScrollFooter();
}
else {
oBody.scrollTop = 0;
this._bodyScrollTimer.cancel();
this._disableScrollHeader();
}
}
if (Dom.hasClass(oTarget, _HD)) {
fnScrollFunction = scrollBodyUp;
}
else {
nScrollTarget = oBody.scrollHeight - oBody.offsetHeight;
fnScrollFunction = scrollBodyDown;
}
this._bodyScrollTimer = Lang.later(10, this, fnScrollFunction, null, true);
},
/**
* @method _onScrollTargetMouseOut
* @description "mouseout" event handler for the menu's "header" and "footer"
* elements. Used to stop scrolling the body of the menu up and down when the
* menu's "maxheight" configuration property is set to a value greater than 0.
* @protected
* @param {Event} p_oEvent Object representing the DOM event object passed
* back by the event utility (YAHOO.util.Event).
* @param {YAHOO.widget.Menu} p_oMenu Object representing the menu that
* fired the event.
*/
_onScrollTargetMouseOut: function (p_oEvent, p_oMenu) {
var oBodyScrollTimer = this._bodyScrollTimer;
if (oBodyScrollTimer) {
oBodyScrollTimer.cancel();
}
this._cancelHideDelay();
},
// Private methods
/**
* @method _onInit
* @description "init" event handler for the 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.
*/
_onInit: function (p_sType, p_aArgs) {
this.cfg.subscribeToConfigEvent(_VISIBLE, this._onVisibleChange);
var bRootMenu = !this.parent,
bLazyLoad = this.lazyLoad;
/*
Automatically initialize a menu's subtree if:
1) This is the root menu and lazyload is off
2) This is the root menu, lazyload is on, but the menu is
already visible
3) This menu is a submenu and lazyload is off
*/
if (((bRootMenu && !bLazyLoad) ||
(bRootMenu && (this.cfg.getProperty(_VISIBLE) ||
this.cfg.getProperty(_POSITION) == _STATIC)) ||
(!bRootMenu && !bLazyLoad)) && this.getItemGroups().length === 0) {
if (this.srcElement) {
this._initSubTree();
}
if (this.itemData) {
this.addItems(this.itemData);
}
}
else if (bLazyLoad) {
this.cfg.fireQueue();
}
},
/**
* @method _onBeforeRender
* @description "beforerender" event handler for the menu. Appends all of the
* <code><ul></code>, <code><li></code> and their accompanying
* title elements to the body element of the 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.
*/
_onBeforeRender: function (p_sType, p_aArgs) {
var oEl = this.element,
nListElements = this._aListElements.length,
bFirstList = true,
i = 0,
oUL,
oGroupTitle;
if (nListElements > 0) {
do {
oUL = this._aListElements[i];
if (oUL) {
if (bFirstList) {
Dom.addClass(oUL, _FIRST_OF_TYPE);
bFirstList = false;
}
if (!Dom.isAncestor(oEl, oUL)) {
this.appendToBody(oUL);
}
oGroupTitle = this._aGroupTitleElements[i];
if (oGroupTitle) {
if (!Dom.isAncestor(oEl, oGroupTitle)) {
oUL.parentNode.insertBefore(oGroupTitle, oUL);
}
Dom.addClass(oUL, _HAS_TITLE);
}
}
i++;
}
while (i < nListElements);
}
},
/**
* @method _onRender
* @description "render" event handler for the 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.
*/
_onRender: function (p_sType, p_aArgs) {
if (this.cfg.getProperty(_POSITION) == _DYNAMIC) {
if (!this.cfg.getProperty(_VISIBLE)) {
this.positionOffScreen();
}
}
},
/**
* @method _onBeforeShow
* @description "beforeshow" event handler for the 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.
*/
_onBeforeShow: function (p_sType, p_aArgs) {
var nOptions,
n,
oSrcElement,
oContainer = this.cfg.getProperty(_CONTAINER);
if (this.lazyLoad && this.getItemGroups().length === 0) {
if (this.srcElement) {
this._initSubTree();
}
if (this.itemData) {
if (this.parent && this.parent.parent &&
this.parent.parent.srcElement &&
this.parent.parent.srcElement.tagName.toUpperCase() ==
_SELECT) {
nOptions = this.itemData.length;
for(n=0; n<nOptions; n++) {
if (this.itemData[n].tagName) {
this.addItem((new this.ITEM_TYPE(this.itemData[n])));
}
}
}
else {
this.addItems(this.itemData);
}
}
oSrcElement = this.srcElement;
if (oSrcElement) {
if (oSrcElement.tagName.toUpperCase() == _SELECT) {
if (Dom.inDocument(oSrcElement)) {
this.render(oSrcElement.parentNode);
}
else {
this.render(oContainer);
}
}
else {
this.render();
}
}
else {
if (this.parent) {
this.render(this.parent.element);
}
else {
this.render(oContainer);
}
}
}
var oParent = this.parent,
aAlignment;
if (!oParent && this.cfg.getProperty(_POSITION) == _DYNAMIC) {
this.cfg.refireEvent(_XY);
}
if (oParent) {
aAlignment = oParent.parent.cfg.getProperty(_SUBMENU_ALIGNMENT);
this.cfg.setProperty(_CONTEXT, [oParent.element, aAlignment[0], aAlignment[1]]);
this.align();
}
},
getConstrainedY: function (y) {
var oMenu = this,
aContext = oMenu.cfg.getProperty(_CONTEXT),
nInitialMaxHeight = oMenu.cfg.getProperty(_MAX_HEIGHT),
nMaxHeight,
oOverlapPositions = {
"trbr": true,
"tlbl": true,
"bltl": true,
"brtr": true
},
bPotentialContextOverlap = (aContext && oOverlapPositions[aContext[1] + aContext[2]]),
oMenuEl = oMenu.element,
nMenuOffsetHeight = oMenuEl.offsetHeight,
nViewportOffset = Overlay.VIEWPORT_OFFSET,
viewPortHeight = Dom.getViewportHeight(),
scrollY = Dom.getDocumentScrollTop(),
bCanConstrain =
(oMenu.cfg.getProperty(_MIN_SCROLL_HEIGHT) + nViewportOffset < viewPortHeight),
nAvailableHeight,
oContextEl,
nContextElY,
nContextElHeight,
bFlipped = false,
nTopRegionHeight,
nBottomRegionHeight,
topConstraint = scrollY + nViewportOffset,
bottomConstraint = scrollY + viewPortHeight - nMenuOffsetHeight - nViewportOffset,
yNew = y;
var flipVertical = function () {
var nNewY;
// The Menu is below the context element, flip it above
if ((oMenu.cfg.getProperty(_Y) - scrollY) > nContextElY) {
nNewY = (nContextElY - nMenuOffsetHeight);
}
else { // The Menu is above the context element, flip it below
nNewY = (nContextElY + nContextElHeight);
}
oMenu.cfg.setProperty(_Y, (nNewY + scrollY), true);
return nNewY;
};
/*
Uses the context element's position to calculate the availble height
above and below it to display its corresponding Menu.
*/
var getDisplayRegionHeight = function () {
// The Menu is below the context element
if ((oMenu.cfg.getProperty(_Y) - scrollY) > nContextElY) {
return (nBottomRegionHeight - nViewportOffset);
}
else { // The Menu is above the context element
return (nTopRegionHeight - nViewportOffset);
}
};
/*
Sets the Menu's "y" configuration property to the correct value based on its
current orientation.
*/
var alignY = function () {
var nNewY;
if ((oMenu.cfg.getProperty(_Y) - scrollY) > nContextElY) {
nNewY = (nContextElY + nContextElHeight);
}
else {
nNewY = (nContextElY - oMenuEl.offsetHeight);
}
oMenu.cfg.setProperty(_Y, (nNewY + scrollY), true);
};
// Resets the maxheight of the Menu to the value set by the user
var resetMaxHeight = function () {
oMenu._setScrollHeight(this.cfg.getProperty(_MAX_HEIGHT));
oMenu.hideEvent.unsubscribe(resetMaxHeight);
};
/*
Trys to place the Menu in the best possible position (either above or
below its corresponding context element).
*/
var setVerticalPosition = function () {
var nDisplayRegionHeight = getDisplayRegionHeight(),
bMenuHasItems = (oMenu.getItems().length > 0),
nMenuMinScrollHeight,
fnReturnVal;
if (nMenuOffsetHeight > nDisplayRegionHeight) {
nMenuMinScrollHeight =
bMenuHasItems ? oMenu.cfg.getProperty(_MIN_SCROLL_HEIGHT) : nMenuOffsetHeight;
if ((nDisplayRegionHeight > nMenuMinScrollHeight) && bMenuHasItems) {
nMaxHeight = nDisplayRegionHeight;
}
else {
nMaxHeight = nInitialMaxHeight;
}
oMenu._setScrollHeight(nMaxHeight);
oMenu.hideEvent.subscribe(resetMaxHeight);
// Re-align the Menu since its height has just changed
// as a result of the setting of the maxheight property.
alignY();
if (nDisplayRegionHeight < nMenuMinScrollHeight) {
if (bFlipped) {
/*
All possible positions and values for the "maxheight"
configuration property have been tried, but none were
successful, so fall back to the original size and position.
*/
flipVertical();
}
else {
flipVertical();
bFlipped = true;
fnReturnVal = setVerticalPosition();
}
}
}
else if (nMaxHeight && (nMaxHeight !== nInitialMaxHeight)) {
oMenu._setScrollHeight(nInitialMaxHeight);
oMenu.hideEvent.subscribe(resetMaxHeight);
// Re-align the Menu since its height has just changed
// as a result of the setting of the maxheight property.
alignY();
}
return fnReturnVal;
};
// Determine if the current value for the Menu's "y" configuration property will
// result in the Menu being positioned outside the boundaries of the viewport
if (y < topConstraint || y > bottomConstraint) {
// The current value for the Menu's "y" configuration property WILL
// result in the Menu being positioned outside the boundaries of the viewport
if (bCanConstrain) {
if (oMenu.cfg.getProperty(_PREVENT_CONTEXT_OVERLAP) && bPotentialContextOverlap) {
// SOLUTION #1:
// If the "preventcontextoverlap" configuration property is set to "true",
// try to flip and/or scroll the Menu to both keep it inside the boundaries of the
// viewport AND from overlaping its context element (MenuItem or MenuBarItem).
oContextEl = aContext[0];
nContextElHeight = oContextEl.offsetHeight;
nContextElY = (Dom.getY(oContextEl) - scrollY);
nTopRegionHeight = nContextElY;
nBottomRegionHeight = (viewPortHeight - (nContextElY + nContextElHeight));
setVerticalPosition();
yNew = oMenu.cfg.getProperty(_Y);
}
else if (!(oMenu instanceof YAHOO.widget.MenuBar) &&
nMenuOffsetHeight >= viewPortHeight) {
// SOLUTION #2:
// If the Menu exceeds the height of the viewport, introduce scroll bars
// to keep the Menu inside the boundaries of the viewport
nAvailableHeight = (viewPortHeight - (nViewportOffset * 2));
if (nAvailableHeight > oMenu.cfg.getProperty(_MIN_SCROLL_HEIGHT)) {
oMenu._setScrollHeight(nAvailableHeight);
oMenu.hideEvent.subscribe(resetMaxHeight);
alignY();
yNew = oMenu.cfg.getProperty(_Y);
}
}
else {
// SOLUTION #3:
if (y < topConstraint) {
yNew = topConstraint;
} else if (y > bottomConstraint) {
yNew = bottomConstraint;
}
}
}
else {
// The "y" configuration property cannot be set to a value that will keep
// entire Menu inside the boundary of the viewport. Therefore, set
// the "y" configuration property to scrollY to keep as much of the
// Menu inside the viewport as possible.
yNew = nViewportOffset + scrollY;
}
}
return yNew;
},
/**
* @method _onHide
* @description "hide" event handler for the 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.
*/
_onHide: function (p_sType, p_aArgs) {
if (this.cfg.getProperty(_POSITION) === _DYNAMIC) {
this.positionOffScreen();
}
},
/**
* @method _onShow
* @description "show" event handler for the 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.
*/
_onShow: function (p_sType, p_aArgs) {
var oParent = this.parent,
oParentMenu,
oElement,
nOffsetWidth,
sWidth;
function disableAutoSubmenuDisplay(p_oEvent) {
var oTarget;
if (p_oEvent.type == _MOUSEDOWN || (p_oEvent.type == _KEYDOWN && p_oEvent.keyCode == 27)) {
/*
Set the "autosubmenudisplay" to "false" if the user
clicks outside the menu bar.
*/
oTarget = Event.getTarget(p_oEvent);
if (oTarget != oParentMenu.element || !Dom.isAncestor(oParentMenu.element, oTarget)) {
oParentMenu.cfg.setProperty(_AUTO_SUBMENU_DISPLAY, false);
Event.removeListener(document, _MOUSEDOWN, disableAutoSubmenuDisplay);
Event.removeListener(document, _KEYDOWN, disableAutoSubmenuDisplay);
}
}
}
function onSubmenuHide(p_sType, p_aArgs, p_sWidth) {
this.cfg.setProperty(_WIDTH, _EMPTY_STRING);
this.hideEvent.unsubscribe(onSubmenuHide, p_sWidth);
}
if (oParent) {
oParentMenu = oParent.parent;
if (!oParentMenu.cfg.getProperty(_AUTO_SUBMENU_DISPLAY) &&
(oParentMenu instanceof YAHOO.widget.MenuBar ||
oParentMenu.cfg.getProperty(_POSITION) == _STATIC)) {
oParentMenu.cfg.setProperty(_AUTO_SUBMENU_DISPLAY, true);
Event.on(document, _MOUSEDOWN, disableAutoSubmenuDisplay);
Event.on(document, _KEYDOWN, disableAutoSubmenuDisplay);
}
// The following fixes an issue with the selected state of a MenuItem
// not rendering correctly when a submenu is aligned to the left of
// its parent Menu instance.
if ((this.cfg.getProperty("x") < oParentMenu.cfg.getProperty("x")) &&
(UA.gecko && UA.gecko < 1.9) && !this.cfg.getProperty(_WIDTH)) {
oElement = this.element;
nOffsetWidth = oElement.offsetWidth;
/*
Measuring the difference of the offsetWidth before and after
setting the "width" style attribute allows us to compute the
about of padding and borders applied to the element, which in
turn allows us to set the "width" property correctly.
*/
oElement.style.width = nOffsetWidth + _PX;
sWidth = (nOffsetWidth - (oElement.offsetWidth - nOffsetWidth)) + _PX;
this.cfg.setProperty(_WIDTH, sWidth);
this.hideEvent.subscribe(onSubmenuHide, sWidth);
}
}
},
/**
* @method _onBeforeHide
* @description "beforehide" event handler for the 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.
*/
_onBeforeHide: function (p_sType, p_aArgs) {
var oActiveItem = this.activeItem,
oRoot = this.getRoot(),
oConfig,
oSubmenu;
if (oActiveItem) {
oConfig = oActiveItem.cfg;
oConfig.setProperty(_SELECTED, false);
oSubmenu = oConfig.getProperty(_SUBMENU);
if (oSubmenu) {
oSubmenu.hide();
}
}
/*
Focus can get lost in IE when the mouse is moving from a submenu back to its parent Menu.
For this reason, it is necessary to maintain the focused state in a private property
so that the _onMouseOver event handler is able to determined whether or not to set focus
to MenuItems as the user is moving the mouse.
*/
if (UA.ie && this.cfg.getProperty(_POSITION) === _DYNAMIC && this.parent) {
oRoot._hasFocus = this.hasFocus();
}
if (oRoot == this) {
oRoot.blur();
}
},
/**
* @method _onParentMenuConfigChange
* @description "configchange" event handler for a submenu.
* @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_oSubmenu Object representing the submenu that
* subscribed to the event.
*/
_onParentMenuConfigChange: function (p_sType, p_aArgs, p_oSubmenu) {
var sPropertyName = p_aArgs[0][0],
oPropertyValue = p_aArgs[0][1];
switch(sPropertyName) {
case _IFRAME:
case _CONSTRAIN_TO_VIEWPORT:
case _HIDE_DELAY:
case _SHOW_DELAY:
case _SUBMENU_HIDE_DELAY:
case _CLICK_TO_HIDE:
case _EFFECT:
case _CLASSNAME:
case _SCROLL_INCREMENT:
case _MAX_HEIGHT:
case _MIN_SCROLL_HEIGHT:
case _MONITOR_RESIZE:
case _SHADOW:
case _PREVENT_CONTEXT_OVERLAP:
p_oSubmenu.cfg.setProperty(sPropertyName, oPropertyValue);
break;
case _SUBMENU_ALIGNMENT:
if (!(this.parent.parent instanceof YAHOO.widget.MenuBar)) {
p_oSubmenu.cfg.setProperty(sPropertyName, oPropertyValue);
}
break;
}
},
/**
* @method _onParentMenuRender
* @description "render" event handler for a submenu. Renders a
* submenu in response to the firing of its parent's "render" event.
* @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_oSubmenu Object representing the submenu that
* subscribed to the event.
*/
_onParentMenuRender: function (p_sType, p_aArgs, p_oSubmenu) {
var oParentMenu = p_oSubmenu.parent.parent,
oParentCfg = oParentMenu.cfg,
oConfig = {
constraintoviewport: oParentCfg.getProperty(_CONSTRAIN_TO_VIEWPORT),
xy: [0,0],
clicktohide: oParentCfg.getProperty(_CLICK_TO_HIDE),
effect: oParentCfg.getProperty(_EFFECT),
showdelay: oParentCfg.getProperty(_SHOW_DELAY),
hidedelay: oParentCfg.getProperty(_HIDE_DELAY),
submenuhidedelay: oParentCfg.getProperty(_SUBMENU_HIDE_DELAY),
classname: oParentCfg.getProperty(_CLASSNAME),
scrollincrement: oParentCfg.getProperty(_SCROLL_INCREMENT),
maxheight: oParentCfg.getProperty(_MAX_HEIGHT),
minscrollheight: oParentCfg.getProperty(_MIN_SCROLL_HEIGHT),
iframe: oParentCfg.getProperty(_IFRAME),
shadow: oParentCfg.getProperty(_SHADOW),
preventcontextoverlap: oParentCfg.getProperty(_PREVENT_CONTEXT_OVERLAP),
monitorresize: oParentCfg.getProperty(_MONITOR_RESIZE)
},
oLI;
if (!(oParentMenu instanceof YAHOO.widget.MenuBar)) {
oConfig[_SUBMENU_ALIGNMENT] = oParentCfg.getProperty(_SUBMENU_ALIGNMENT);
}
p_oSubmenu.cfg.applyConfig(oConfig);
if (!this.lazyLoad) {
oLI = this.parent.element;
if (this.element.parentNode == oLI) {
this.render();
}
else {
this.render(oLI);
}
}
},
/**
* @method _onMenuItemDestroy
* @description "destroy" event handler for the menu's items.
* @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.
*/
_onMenuItemDestroy: function (p_sType, p_aArgs, p_oItem) {
this._removeItemFromGroupByValue(p_oItem.groupIndex, p_oItem);
},
/**
* @method _onMenuItemConfigChange
* @description "configchange" event handler for the menu's items.
* @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.
*/
_onMenuItemConfigChange: function (p_sType, p_aArgs, p_oItem) {
var sPropertyName = p_aArgs[0][0],
oPropertyValue = p_aArgs[0][1],
oSubmenu;
switch(sPropertyName) {
case _SELECTED:
if (oPropertyValue === true) {
this.activeItem = p_oItem;
}
break;
case _SUBMENU:
oSubmenu = p_aArgs[0][1];
if (oSubmenu) {
this._configureSubmenu(p_oItem);
}
break;
}
},
// Public event handlers for configuration properties
/**
* @method configVisible
* @description Event handler for when the "visible" configuration property
* the menu changes.
* @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.
*/
configVisible: function (p_sType, p_aArgs, p_oMenu) {
var bVisible,
sDisplay;
if (this.cfg.getProperty(_POSITION) == _DYNAMIC) {
Menu.superclass.configVisible.call(this, p_sType, p_aArgs, p_oMenu);
}
else {
bVisible = p_aArgs[0];
sDisplay = Dom.getStyle(this.element, _DISPLAY);
Dom.setStyle(this.element, _VISIBILITY, _VISIBLE);
if (bVisible) {
if (sDisplay != _BLOCK) {
this.beforeShowEvent.fire();
Dom.setStyle(this.element, _DISPLAY, _BLOCK);
this.showEvent.fire();
}
}
else {
if (sDisplay == _BLOCK) {
this.beforeHideEvent.fire();
Dom.setStyle(this.element, _DISPLAY, _NONE);
this.hideEvent.fire();
}
}
}
},
/**
* @method configPosition
* @description Event handler for when the "position" configuration property
* of the menu changes.
* @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.
*/
configPosition: function (p_sType, p_aArgs, p_oMenu) {
var oElement = this.element,
sCSSPosition = p_aArgs[0] == _STATIC ? _STATIC : _ABSOLUTE,
oCfg = this.cfg,
nZIndex;
Dom.setStyle(oElement, _POSITION, sCSSPosition);
if (sCSSPosition == _STATIC) {
// Statically positioned menus are visible by default
Dom.setStyle(oElement, _DISPLAY, _BLOCK);
oCfg.setProperty(_VISIBLE, true);
}
else {
/*
Even though the "visible" property is queued to
"false" by default, we need to set the "visibility" property to
"hidden" since Overlay's "configVisible" implementation checks the
element's "visibility" style property before deciding whether
or not to show an Overlay instance.
*/
Dom.setStyle(oElement, _VISIBILITY, _HIDDEN);
}
if (sCSSPosition == _ABSOLUTE) {
nZIndex = oCfg.getProperty(_ZINDEX);
if (!nZIndex || nZIndex === 0) {
oCfg.setProperty(_ZINDEX, 1);
}
}
},
/**
* @method configIframe
* @description Event handler for when the "iframe" configuration property of
* the menu changes.
* @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.
*/
configIframe: function (p_sType, p_aArgs, p_oMenu) {
if (this.cfg.getProperty(_POSITION) == _DYNAMIC) {
Menu.superclass.configIframe.call(this, p_sType, p_aArgs, p_oMenu);
}
},
/**
* @method configHideDelay
* @description Event handler for when the "hidedelay" configuration property
* of the menu changes.
* @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.
*/
configHideDelay: function (p_sType, p_aArgs, p_oMenu) {
var nHideDelay = p_aArgs[0];
this._useHideDelay = (nHideDelay > 0);
},
/**
* @method configContainer
* @description Event handler for when the "container" configuration property
* of the menu changes.
* @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.
*/
configContainer: function (p_sType, p_aArgs, p_oMenu) {
var oElement = p_aArgs[0];
if (Lang.isString(oElement)) {
this.cfg.setProperty(_CONTAINER, Dom.get(oElement), true);
}
},
/**
* @method _clearSetWidthFlag
* @description Change event listener for the "width" configuration property. This listener is
* added when a Menu's "width" configuration property is set by the "_setScrollHeight" method, and
* is used to set the "_widthSetForScroll" property to "false" if the "width" configuration property
* is changed after it was set by the "_setScrollHeight" method. If the "_widthSetForScroll"
* property is set to "false", and the "_setScrollHeight" method is in the process of tearing down
* scrolling functionality, it will maintain the Menu's new width rather than reseting it.
* @private
*/
_clearSetWidthFlag: function () {
this._widthSetForScroll = false;
this.cfg.unsubscribeFromConfigEvent(_WIDTH, this._clearSetWidthFlag);
},
/**
* @method _setScrollHeight
* @description
* @param {String} p_nScrollHeight Number representing the scrolling height of the Menu.
* @private
*/
_setScrollHeight: function (p_nScrollHeight) {
var nScrollHeight = p_nScrollHeight,
bRefireIFrameAndShadow = false,
bSetWidth = false,
oElement,
oBody,
oHeader,
oFooter,
fnMouseOver,
fnMouseOut,
nMinScrollHeight,
nHeight,
nOffsetWidth,
sWidth;
if (this.getItems().length > 0) {
oElement = this.element;
oBody = this.body;
oHeader = this.header;
oFooter = this.footer;
fnMouseOver = this._onScrollTargetMouseOver;
fnMouseOut = this._onScrollTargetMouseOut;
nMinScrollHeight = this.cfg.getProperty(_MIN_SCROLL_HEIGHT);
if (nScrollHeight > 0 && nScrollHeight < nMinScrollHeight) {
nScrollHeight = nMinScrollHeight;
}
Dom.setStyle(oBody, _HEIGHT, _EMPTY_STRING);
Dom.removeClass(oBody, _YUI_MENU_BODY_SCROLLED);
oBody.scrollTop = 0;
// Need to set a width for the Menu to fix the following problems in
// Firefox 2 and IE:
// #1) Scrolled Menus will render at 1px wide in Firefox 2
// #2) There is a bug in gecko-based browsers where an element whose
// "position" property is set to "absolute" and "overflow" property is
// set to "hidden" will not render at the correct width when its
// offsetParent's "position" property is also set to "absolute." It is
// possible to work around this bug by specifying a value for the width
// property in addition to overflow.
// #3) In IE it is necessary to give the Menu a width before the
// scrollbars are rendered to prevent the Menu from rendering with a
// width that is 100% of the browser viewport.
bSetWidth = ((UA.gecko && UA.gecko < 1.9) || UA.ie);
if (nScrollHeight > 0 && bSetWidth && !this.cfg.getProperty(_WIDTH)) {
nOffsetWidth = oElement.offsetWidth;
/*
Measuring the difference of the offsetWidth before and after
setting the "width" style attribute allows us to compute the
about of padding and borders applied to the element, which in
turn allows us to set the "width" property correctly.
*/
oElement.style.width = nOffsetWidth + _PX;
sWidth = (nOffsetWidth - (oElement.offsetWidth - nOffsetWidth)) + _PX;
this.cfg.unsubscribeFromConfigEvent(_WIDTH, this._clearSetWidthFlag);
YAHOO.log("Setting the \"width\" configuration property to " + sWidth + " for srolling.",
"info", this.toString());
this.cfg.setProperty(_WIDTH, sWidth);
/*
Set a flag (_widthSetForScroll) to maintain some history regarding how the
"width" configuration property was set. If the "width" configuration property
is set by something other than the "_setScrollHeight" method, it will be
necessary to maintain that new value and not clear the width if scrolling
is turned off.
*/
this._widthSetForScroll = true;
this.cfg.subscribeToConfigEvent(_WIDTH, this._clearSetWidthFlag);
}
if (nScrollHeight > 0 && (!oHeader && !oFooter)) {
YAHOO.log("Creating header and footer for scrolling.", "info", this.toString());
this.setHeader(_NON_BREAKING_SPACE);
this.setFooter(_NON_BREAKING_SPACE);
oHeader = this.header;
oFooter = this.footer;
Dom.addClass(oHeader, _TOP_SCROLLBAR);
Dom.addClass(oFooter, _BOTTOM_SCROLLBAR);
oElement.insertBefore(oHeader, oBody);
oElement.appendChild(oFooter);
}
nHeight = nScrollHeight;
if (oHeader && oFooter) {
nHeight = (nHeight - (oHeader.offsetHeight + oFooter.offsetHeight));
}
if ((nHeight > 0) && (oBody.offsetHeight > nScrollHeight)) {
YAHOO.log("Setting up styles and event handlers for scrolling.",
"info", this.toString());
Dom.addClass(oBody, _YUI_MENU_BODY_SCROLLED);
Dom.setStyle(oBody, _HEIGHT, (nHeight + _PX));
if (!this._hasScrollEventHandlers) {
Event.on(oHeader, _MOUSEOVER, fnMouseOver, this, true);
Event.on(oHeader, _MOUSEOUT, fnMouseOut, this, true);
Event.on(oFooter, _MOUSEOVER, fnMouseOver, this, true);
Event.on(oFooter, _MOUSEOUT, fnMouseOut, this, true);
this._hasScrollEventHandlers = true;
}
this._disableScrollHeader();
this._enableScrollFooter();
bRefireIFrameAndShadow = true;
}
else if (oHeader && oFooter) {
YAHOO.log("Removing styles and event handlers for scrolling.", "info", this.toString());
/*
Only clear the the "width" configuration property if it was set the
"_setScrollHeight" method and wasn't changed by some other means after it was set.
*/
if (this._widthSetForScroll) {
YAHOO.log("Clearing width used for scrolling.", "info", this.toString());
this._widthSetForScroll = false;
this.cfg.unsubscribeFromConfigEvent(_WIDTH, this._clearSetWidthFlag);
this.cfg.setProperty(_WIDTH, _EMPTY_STRING);
}
this._enableScrollHeader();
this._enableScrollFooter();
if (this._hasScrollEventHandlers) {
Event.removeListener(oHeader, _MOUSEOVER, fnMouseOver);
Event.removeListener(oHeader, _MOUSEOUT, fnMouseOut);
Event.removeListener(oFooter, _MOUSEOVER, fnMouseOver);
Event.removeListener(oFooter, _MOUSEOUT, fnMouseOut);
this._hasScrollEventHandlers = false;
}
oElement.removeChild(oHeader);
oElement.removeChild(oFooter);
this.header = null;
this.footer = null;
bRefireIFrameAndShadow = true;
}
if (bRefireIFrameAndShadow) {
this.cfg.refireEvent(_IFRAME);
this.cfg.refireEvent(_SHADOW);
}
}
},
/**
* @method _setMaxHeight
* @description "renderEvent" handler used to defer the setting of the
* "maxheight" configuration property until the menu is rendered in lazy
* load scenarios.
* @param {String} p_sType The name of the event that was fired.
* @param {Array} p_aArgs Collection of arguments sent when the event
* was fired.
* @param {Number} p_nMaxHeight Number representing the value to set for the
* "maxheight" configuration property.
* @private
*/
_setMaxHeight: function (p_sType, p_aArgs, p_nMaxHeight) {
this._setScrollHeight(p_nMaxHeight);
this.renderEvent.unsubscribe(this._setMaxHeight);
},
/**
* @method configMaxHeight
* @description Event handler for when the "maxheight" configuration property of
* a Menu changes.
* @param {String} p_sType The name of the event that was fired.
* @param {Array} p_aArgs Collection of arguments sent when the event
* was fired.
* @param {YAHOO.widget.Menu} p_oMenu The Menu instance fired
* the event.
*/
configMaxHeight: function (p_sType, p_aArgs, p_oMenu) {
var nMaxHeight = p_aArgs[0];
if (this.lazyLoad && !this.body && nMaxHeight > 0) {
this.renderEvent.subscribe(this._setMaxHeight, nMaxHeight, this);
}
else {
this._setScrollHeight(nMaxHeight);
}
},
/**
* @method configClassName
* @description Event handler for when the "classname" configuration property of
* a menu changes.
* @param {String} p_sType The name of the event that was fired.
* @param {Array} p_aArgs Collection of arguments sent when the event was fired.
* @param {YAHOO.widget.Menu} p_oMenu The Menu instance fired the event.
*/
configClassName: function (p_sType, p_aArgs, p_oMenu) {
var sClassName = p_aArgs[0];
if (this._sClassName) {
Dom.removeClass(this.element, this._sClassName);
}
Dom.addClass(this.element, sClassName);
this._sClassName = sClassName;
},
/**
* @method _onItemAdded
* @description "itemadded" event handler for a Menu instance.
* @private
* @param {String} p_sType The name of the event that was fired.
* @param {Array} p_aArgs Collection of arguments sent when the event
* was fired.
*/
_onItemAdded: function (p_sType, p_aArgs) {
var oItem = p_aArgs[0];
if (oItem) {
oItem.cfg.setProperty(_DISABLED, true);
}
},
/**
* @method configDisabled
* @description Event handler for when the "disabled" configuration property of
* a menu changes.
* @param {String} p_sType The name of the event that was fired.
* @param {Array} p_aArgs Collection of arguments sent when the event was fired.
* @param {YAHOO.widget.Menu} p_oMenu The Menu instance fired the event.
*/
configDisabled: function (p_sType, p_aArgs, p_oMenu) {
var bDisabled = p_aArgs[0],
aItems = this.getItems(),
nItems,
i;
if (Lang.isArray(aItems)) {
nItems = aItems.length;
if (nItems > 0) {
i = nItems - 1;
do {
aItems[i].cfg.setProperty(_DISABLED, bDisabled);
}
while (i--);
}
if (bDisabled) {
this.clearActiveItem(true);
Dom.addClass(this.element, _DISABLED);
this.itemAddedEvent.subscribe(this._onItemAdded);
}
else {
Dom.removeClass(this.element, _DISABLED);
this.itemAddedEvent.unsubscribe(this._onItemAdded);
}
}
},
/**
* @method configShadow
* @description Event handler for when the "shadow" configuration property of
* a menu changes.
* @param {String} p_sType The name of the event that was fired.
* @param {Array} p_aArgs Collection of arguments sent when the event was fired.
* @param {YAHOO.widget.Menu} p_oMenu The Menu instance fired the event.
*/
configShadow: function (p_sType, p_aArgs, p_oMenu) {
var sizeShadow = function () {
var oElement = this.element,
oShadow = this._shadow;
if (oShadow && oElement) {
// Clear the previous width
if (oShadow.style.width && oShadow.style.height) {
oShadow.style.width = _EMPTY_STRING;
oShadow.style.height = _EMPTY_STRING;
}
oShadow.style.width = (oElement.offsetWidth + 6) + _PX;
oShadow.style.height = (oElement.offsetHeight + 1) + _PX;
}
};
var replaceShadow = function () {
this.element.appendChild(this._shadow);
};
var addShadowVisibleClass = function () {
Dom.addClass(this._shadow, _YUI_MENU_SHADOW_VISIBLE);
};
var removeShadowVisibleClass = function () {
Dom.removeClass(this._shadow, _YUI_MENU_SHADOW_VISIBLE);
};
var createShadow = function () {
var oShadow = this._shadow,
oElement;
if (!oShadow) {
oElement = this.element;
if (!m_oShadowTemplate) {
m_oShadowTemplate = document.createElement(_DIV_LOWERCASE);
m_oShadowTemplate.className = _YUI_MENU_SHADOW_YUI_MENU_SHADOW_VISIBLE;
}
oShadow = m_oShadowTemplate.cloneNode(false);
oElement.appendChild(oShadow);
this._shadow = oShadow;
this.beforeShowEvent.subscribe(addShadowVisibleClass);
this.beforeHideEvent.subscribe(removeShadowVisibleClass);
if (UA.ie) {
/*
Need to call sizeShadow & syncIframe via setTimeout for
IE 7 Quirks Mode and IE 6 Standards Mode and Quirks Mode
or the shadow and iframe shim will not be sized and
positioned properly.
*/
Lang.later(0, this, function () {
sizeShadow.call(this);
this.syncIframe();
});
this.cfg.subscribeToConfigEvent(_WIDTH, sizeShadow);
this.cfg.subscribeToConfigEvent(_HEIGHT, sizeShadow);
this.cfg.subscribeToConfigEvent(_MAX_HEIGHT, sizeShadow);
this.changeContentEvent.subscribe(sizeShadow);
Module.textResizeEvent.subscribe(sizeShadow, this, true);
this.destroyEvent.subscribe(function () {
Module.textResizeEvent.unsubscribe(sizeShadow, this);
});
}
this.cfg.subscribeToConfigEvent(_MAX_HEIGHT, replaceShadow);
}
};
var onBeforeShow = function () {
if (this._shadow) {
// If called because the "shadow" event was refired - just append again and resize
replaceShadow.call(this);
if (UA.ie) {
sizeShadow.call(this);
}
}
else {
createShadow.call(this);
}
this.beforeShowEvent.unsubscribe(onBeforeShow);
};
var bShadow = p_aArgs[0];
if (bShadow && this.cfg.getProperty(_POSITION) == _DYNAMIC) {
if (this.cfg.getProperty(_VISIBLE)) {
if (this._shadow) {
// If the "shadow" event was refired - just append again and resize
replaceShadow.call(this);
if (UA.ie) {
sizeShadow.call(this);
}
}
else {
createShadow.call(this);
}
}
else {
this.beforeShowEvent.subscribe(onBeforeShow);
}
}
},
// Public methods
/**
* @method initEvents
* @description Initializes the custom events for the menu.
*/
initEvents: function () {
Menu.superclass.initEvents.call(this);
// Create custom events
var i = EVENT_TYPES.length - 1,
aEventData,
oCustomEvent;
do {
aEventData = EVENT_TYPES[i];
oCustomEvent = this.createEvent(aEventData[1]);
oCustomEvent.signature = CustomEvent.LIST;
this[aEventData[0]] = oCustomEvent;
}
while (i--);
},
/**
* @method positionOffScreen
* @description Positions the menu outside of the boundaries of the browser's
* viewport. Called automatically when a menu is hidden to ensure that
* it doesn't force the browser to render uncessary scrollbars.
*/
positionOffScreen: function () {
var oIFrame = this.iframe,
oElement = this.element,
sPos = this.OFF_SCREEN_POSITION;
oElement.style.top = _EMPTY_STRING;
oElement.style.left = _EMPTY_STRING;
if (oIFrame) {
oIFrame.style.top = sPos;
oIFrame.style.left = sPos;
}
},
/**
* @method getRoot
* @description Finds the menu's root menu.
*/
getRoot: function () {
var oItem = this.parent,
oParentMenu,
returnVal;
if (oItem) {
oParentMenu = oItem.parent;
returnVal = oParentMenu ? oParentMenu.getRoot() : this;
}
else {
returnVal = this;
}
return returnVal;
},
/**
* @method toString
* @description Returns a string representing the menu.
* @return {String}
*/
toString: function () {
var sReturnVal = _MENU,
sId = this.id;
if (sId) {
sReturnVal += (_SPACE + sId);
}
return sReturnVal;
},
/**
* @method setItemGroupTitle
* @description Sets the title of a group of menu items.
* @param {String} p_sGroupTitle String specifying the title of the group.
* @param {Number} p_nGroupIndex Optional. Number specifying the group to which
* the title belongs.
*/
setItemGroupTitle: function (p_sGroupTitle, p_nGroupIndex) {
var nGroupIndex,
oTitle,
i,
nFirstIndex;
if (Lang.isString(p_sGroupTitle) && p_sGroupTitle.length > 0) {
nGroupIndex = Lang.isNumber(p_nGroupIndex) ? p_nGroupIndex : 0;
oTitle = this._aGroupTitleElements[nGroupIndex];
if (oTitle) {
oTitle.innerHTML = p_sGroupTitle;
}
else {
oTitle = document.createElement(this.GROUP_TITLE_TAG_NAME);
oTitle.innerHTML = p_sGroupTitle;
this._aGroupTitleElements[nGroupIndex] = oTitle;
}
i = this._aGroupTitleElements.length - 1;
do {
if (this._aGroupTitleElements[i]) {
Dom.removeClass(this._aGroupTitleElements[i], _FIRST_OF_TYPE);
nFirstIndex = i;
}
}
while (i--);
if (nFirstIndex !== null) {
Dom.addClass(this._aGroupTitleElements[nFirstIndex],
_FIRST_OF_TYPE);
}
this.changeContentEvent.fire();
}
},
/**
* @method addItem
* @description Appends an item to the menu.
* @param {YAHOO.widget.MenuItem} p_oItem Object reference for the MenuItem
* instance to be added to the menu.
* @param {String} p_oItem String specifying the text of the item to be added
* to the menu.
* @param {Object} p_oItem Object literal containing a set of menu item
* configuration properties.
* @param {Number} p_nGroupIndex Optional. Number indicating the group to
* which the item belongs.
* @return {YAHOO.widget.MenuItem}
*/
addItem: function (p_oItem, p_nGroupIndex) {
return this._addItemToGroup(p_nGroupIndex, p_oItem);
},
/**
* @method addItems
* @description Adds an array of items to the menu.
* @param {Array} p_aItems Array of items to be added to the menu. The array
* can contain strings specifying the text for each item to be created, object
* literals specifying each of the menu item configuration properties,
* or MenuItem instances.
* @param {Number} p_nGroupIndex Optional. Number specifying the group to
* which the items belongs.
* @return {Array}
*/
addItems: function (p_aItems, p_nGroupIndex) {
var nItems,
aItems,
oItem,
i,
returnVal;
if (Lang.isArray(p_aItems)) {
nItems = p_aItems.length;
aItems = [];
for(i=0; i<nItems; i++) {
oItem = p_aItems[i];
if (oItem) {
if (Lang.isArray(oItem)) {
aItems[aItems.length] = this.addItems(oItem, i);
}
else {
aItems[aItems.length] = this._addItemToGroup(p_nGroupIndex, oItem);
}
}
}
if (aItems.length) {
returnVal = aItems;
}
}
return returnVal;
},
/**
* @method insertItem
* @description Inserts an item into the menu at the specified index.
* @param {YAHOO.widget.MenuItem} p_oItem Object reference for the MenuItem
* instance to be added to the menu.
* @param {String} p_oItem String specifying the text of the item to be added
* to the menu.
* @param {Object} p_oItem Object literal containing a set of menu item
* configuration properties.
* @param {Number} p_nItemIndex Number indicating the ordinal position at which
* the item should be added.
* @param {Number} p_nGroupIndex Optional. Number indicating the group to which
* the item belongs.
* @return {YAHOO.widget.MenuItem}
*/
insertItem: function (p_oItem, p_nItemIndex, p_nGroupIndex) {
return this._addItemToGroup(p_nGroupIndex, p_oItem, p_nItemIndex);
},
/**
* @method removeItem
* @description Removes the specified item from the menu.
* @param {YAHOO.widget.MenuItem} p_oObject Object reference for the MenuItem
* instance to be removed from the menu.
* @param {Number} p_oObject Number specifying the index of the item
* to be removed.
* @param {Number} p_nGroupIndex Optional. Number specifying the group to
* which the item belongs.
* @return {YAHOO.widget.MenuItem}
*/
removeItem: function (p_oObject, p_nGroupIndex) {
var oItem,
returnVal;
if (!Lang.isUndefined(p_oObject)) {
if (p_oObject instanceof YAHOO.widget.MenuItem) {
oItem = this._removeItemFromGroupByValue(p_nGroupIndex, p_oObject);
}
else if (Lang.isNumber(p_oObject)) {
oItem = this._removeItemFromGroupByIndex(p_nGroupIndex, p_oObject);
}
if (oItem) {
oItem.destroy();
YAHOO.log("Item removed." +
" Text: " + oItem.cfg.getProperty("text") + ", " +
" Index: " + oItem.index + ", " +
" Group Index: " + oItem.groupIndex, "info", this.toString());
returnVal = oItem;
}
}
return returnVal;
},
/**
* @method getItems
* @description Returns an array of all of the items in the menu.
* @return {Array}
*/
getItems: function () {
var aGroups = this._aItemGroups,
nGroups,
returnVal,
aItems = [];
if (Lang.isArray(aGroups)) {
nGroups = aGroups.length;
returnVal = ((nGroups == 1) ? aGroups[0] : (Array.prototype.concat.apply(aItems, aGroups)));
}
return returnVal;
},
/**
* @method getItemGroups
* @description Multi-dimensional Array representing the menu items as they
* are grouped in the menu.
* @return {Array}
*/
getItemGroups: function () {
return this._aItemGroups;
},
/**
* @method getItem
* @description Returns the item at the specified index.
* @param {Number} p_nItemIndex Number indicating the ordinal position of the
* item to be retrieved.
* @param {Number} p_nGroupIndex Optional. Number indicating the group to which
* the item belongs.
* @return {YAHOO.widget.MenuItem}
*/
getItem: function (p_nItemIndex, p_nGroupIndex) {
var aGroup,
returnVal;
if (Lang.isNumber(p_nItemIndex)) {
aGroup = this._getItemGroup(p_nGroupIndex);
if (aGroup) {
returnVal = aGroup[p_nItemIndex];
}
}
return returnVal;
},
/**
* @method getSubmenus
* @description Returns an array of all of the submenus that are immediate
* children of the menu.
* @return {Array}
*/
getSubmenus: function () {
var aItems = this.getItems(),
nItems = aItems.length,
aSubmenus,
oSubmenu,
oItem,
i;
if (nItems > 0) {
aSubmenus = [];
for(i=0; i<nItems; i++) {
oItem = aItems[i];
if (oItem) {
oSubmenu = oItem.cfg.getProperty(_SUBMENU);
if (oSubmenu) {
aSubmenus[aSubmenus.length] = oSubmenu;
}
}
}
}
return aSubmenus;
},
/**
* @method clearContent
* @description Removes all of the content from the menu, including the menu
* items, group titles, header and footer.
*/
clearContent: function () {
var aItems = this.getItems(),
nItems = aItems.length,
oElement = this.element,
oBody = this.body,
oHeader = this.header,
oFooter = this.footer,
oItem,
oSubmenu,
i;
if (nItems > 0) {
i = nItems - 1;
do {
oItem = aItems[i];
if (oItem) {
oSubmenu = oItem.cfg.getProperty(_SUBMENU);
if (oSubmenu) {
this.cfg.configChangedEvent.unsubscribe(
this._onParentMenuConfigChange, oSubmenu);
this.renderEvent.unsubscribe(this._onParentMenuRender,
oSubmenu);
}
this.removeItem(oItem, oItem.groupIndex);
}
}
while (i--);
}
if (oHeader) {
Event.purgeElement(oHeader);
oElement.removeChild(oHeader);
}
if (oFooter) {
Event.purgeElement(oFooter);
oElement.removeChild(oFooter);
}
if (oBody) {
Event.purgeElement(oBody);
oBody.innerHTML = _EMPTY_STRING;
}
this.activeItem = null;
this._aItemGroups = [];
this._aListElements = [];
this._aGroupTitleElements = [];
this.cfg.setProperty(_WIDTH, null);
},
/**
* @method destroy
* @description Removes the menu's <code><div></code> element
* (and accompanying child nodes) from the document.
*/
destroy: function () {
// Remove all items
this.clearContent();
this._aItemGroups = null;
this._aListElements = null;
this._aGroupTitleElements = null;
// Continue with the superclass implementation of this method
Menu.superclass.destroy.call(this);
YAHOO.log("Destroyed.", "info", this.toString());
},
/**
* @method setInitialFocus
* @description Sets focus to the menu's first enabled item.
*/
setInitialFocus: function () {
var oItem = this._getFirstEnabledItem();
if (oItem) {
oItem.focus();
}
},
/**
* @method setInitialSelection
* @description Sets the "selected" configuration property of the menu's first
* enabled item to "true."
*/
setInitialSelection: function () {
var oItem = this._getFirstEnabledItem();
if (oItem) {
oItem.cfg.setProperty(_SELECTED, true);
}
},
/**
* @method clearActiveItem
* @description Sets the "selected" configuration property of the menu's active
* item to "false" and hides the item's submenu.
* @param {Boolean} p_bBlur Boolean indicating if the menu's active item
* should be blurred.
*/
clearActiveItem: function (p_bBlur) {
if (this.cfg.getProperty(_SHOW_DELAY) > 0) {
this._cancelShowDelay();
}
var oActiveItem = this.activeItem,
oConfig,
oSubmenu;
if (oActiveItem) {
oConfig = oActiveItem.cfg;
if (p_bBlur) {
oActiveItem.blur();
this.getRoot()._hasFocus = true;
}
oConfig.setProperty(_SELECTED, false);
oSubmenu = oConfig.getProperty(_SUBMENU);
if (oSubmenu) {
oSubmenu.hide();
}
this.activeItem = null;
}
},
/**
* @method focus
* @description Causes the menu to receive focus and fires the "focus" event.
*/
focus: function () {
if (!this.hasFocus()) {
this.setInitialFocus();
}
},
/**
* @method blur
* @description Causes the menu to lose focus and fires the "blur" event.
*/
blur: function () {
var oItem;
if (this.hasFocus()) {
oItem = MenuManager.getFocusedMenuItem();
if (oItem) {
oItem.blur();
}
}
},
/**
* @method hasFocus
* @description Returns a boolean indicating whether or not the menu has focus.
* @return {Boolean}
*/
hasFocus: function () {
return (MenuManager.getFocusedMenu() == this.getRoot());
},
/**
* Adds the specified CustomEvent subscriber to the menu and each of
* its submenus.
* @method subscribe
* @param p_type {string} the type, or name of the event
* @param p_fn {function} the function to exectute when the event fires
* @param p_obj {Object} An object to be passed along when the event
* fires
* @param p_override {boolean} If true, the obj passed in becomes the
* execution scope of the listener
*/
subscribe: function () {
function onItemAdded(p_sType, p_aArgs, p_oObject) {
var oItem = p_aArgs[0],
oSubmenu = oItem.cfg.getProperty(_SUBMENU);
if (oSubmenu) {
oSubmenu.subscribe.apply(oSubmenu, p_oObject);
}
}
function onSubmenuAdded(p_sType, p_aArgs, p_oObject) {
var oSubmenu = this.cfg.getProperty(_SUBMENU);
if (oSubmenu) {
oSubmenu.subscribe.apply(oSubmenu, p_oObject);
}
}
Menu.superclass.subscribe.apply(this, arguments);
Menu.superclass.subscribe.call(this, _ITEM_ADDED, onItemAdded, arguments);
var aItems = this.getItems(),
nItems,
oItem,
oSubmenu,
i;
if (aItems) {
nItems = aItems.length;
if (nItems > 0) {
i = nItems - 1;
do {
oItem = aItems[i];
oSubmenu = oItem.cfg.getProperty(_SUBMENU);
if (oSubmenu) {
oSubmenu.subscribe.apply(oSubmenu, arguments);
}
else {
oItem.cfg.subscribeToConfigEvent(_SUBMENU, onSubmenuAdded, arguments);
}
}
while (i--);
}
}
},
/**
* @description Initializes the class's configurable properties which can be
* changed using the menu's Config object ("cfg").
* @method initDefaultConfig
*/
initDefaultConfig: function () {
Menu.superclass.initDefaultConfig.call(this);
var oConfig = this.cfg;
// Module documentation overrides
/**
* @config effect
* @description Object or array of objects representing the ContainerEffect
* classes that are active for animating the container. When set this
* property is automatically applied to all submenus.
* @type Object
* @default null
*/
// Overlay documentation overrides
/**
* @config x
* @description Number representing the absolute x-coordinate position of
* the Menu. This property is only applied when the "position"
* configuration property is set to dynamic.
* @type Number
* @default null
*/
/**
* @config y
* @description Number representing the absolute y-coordinate position of
* the Menu. This property is only applied when the "position"
* configuration property is set to dynamic.
* @type Number
* @default null
*/
/**
* @description Array of the absolute x and y positions of the Menu. This
* property is only applied when the "position" configuration property is
* set to dynamic.
* @config xy
* @type Number[]
* @default null
*/
/**
* @config context
* @description Array of context arguments for context-sensitive positioning.
* The format is: [id or element, element corner, context corner].
* For example, setting this property to ["img1", "tl", "bl"] would
* align the Mnu's top left corner to the context element's
* bottom left corner. This property is only applied when the "position"
* configuration property is set to dynamic.
* @type Array
* @default null
*/
/**
* @config fixedcenter
* @description Boolean indicating if the Menu should be anchored to the
* center of the viewport. This property is only applied when the
* "position" configuration property is set to dynamic.
* @type Boolean
* @default false
*/
/**
* @config iframe
* @description Boolean indicating whether or not the Menu should
* have an IFRAME shim; used to prevent SELECT elements from
* poking through an Overlay instance in IE6. When set to "true",
* the iframe shim is created when the Menu instance is intially
* made visible. This property is only applied when the "position"
* configuration property is set to dynamic and is automatically applied
* to all submenus.
* @type Boolean
* @default true for IE6 and below, false for all other browsers.
*/
// Add configuration attributes
/*
Change the default value for the "visible" configuration
property to "false" by re-adding the property.
*/
/**
* @config visible
* @description Boolean indicating whether or not the menu is visible. If
* the menu's "position" configuration property is set to "dynamic" (the
* default), this property toggles the menu's <code><div></code>
* element's "visibility" style property between "visible" (true) or
* "hidden" (false). If the menu's "position" configuration property is
* set to "static" this property toggles the menu's
* <code><div></code> element's "display" style property
* between "block" (true) or "none" (false).
* @default false
* @type Boolean
*/
oConfig.addProperty(
VISIBLE_CONFIG.key,
{
handler: this.configVisible,
value: VISIBLE_CONFIG.value,
validator: VISIBLE_CONFIG.validator
}
);
/*
Change the default value for the "constraintoviewport" configuration
property (inherited by YAHOO.widget.Overlay) to "true" by re-adding the property.
*/
/**
* @config constraintoviewport
* @description Boolean indicating if the menu will try to remain inside
* the boundaries of the size of viewport. This property is only applied
* when the "position" configuration property is set to dynamic and is
* automatically applied to all submenus.
* @default true
* @type Boolean
*/
oConfig.addProperty(
CONSTRAIN_TO_VIEWPORT_CONFIG.key,
{
handler: this.configConstrainToViewport,
value: CONSTRAIN_TO_VIEWPORT_CONFIG.value,
validator: CONSTRAIN_TO_VIEWPORT_CONFIG.validator,
supercedes: CONSTRAIN_TO_VIEWPORT_CONFIG.supercedes
}
);
/*
Change the default value for the "preventcontextoverlap" configuration
property (inherited by YAHOO.widget.Overlay) to "true" by re-adding the property.
*/
/**
* @config preventcontextoverlap
* @description Boolean indicating whether or not a submenu should overlap its parent MenuItem
* when the "constraintoviewport" configuration property is set to "true".
* @type Boolean
* @default true
*/
oConfig.addProperty(PREVENT_CONTEXT_OVERLAP_CONFIG.key, {
value: PREVENT_CONTEXT_OVERLAP_CONFIG.value,
validator: PREVENT_CONTEXT_OVERLAP_CONFIG.validator,
supercedes: PREVENT_CONTEXT_OVERLAP_CONFIG.supercedes
});
/**
* @config position
* @description String indicating how a menu should be positioned on the
* screen. Possible values are "static" and "dynamic." Static menus are
* visible by default and reside in the normal flow of the document
* (CSS position: static). Dynamic menus are hidden by default, reside
* out of the normal flow of the document (CSS position: absolute), and
* can overlay other elements on the screen.
* @default dynamic
* @type String
*/
oConfig.addProperty(
POSITION_CONFIG.key,
{
handler: this.configPosition,
value: POSITION_CONFIG.value,
validator: POSITION_CONFIG.validator,
supercedes: POSITION_CONFIG.supercedes
}
);
/**
* @config submenualignment
* @description Array defining how submenus should be aligned to their
* parent menu item. The format is: [itemCorner, submenuCorner]. By default
* a submenu's top left corner is aligned to its parent menu item's top
* right corner.
* @default ["tl","tr"]
* @type Array
*/
oConfig.addProperty(
SUBMENU_ALIGNMENT_CONFIG.key,
{
value: SUBMENU_ALIGNMENT_CONFIG.value,
suppressEvent: SUBMENU_ALIGNMENT_CONFIG.suppressEvent
}
);
/**
* @config autosubmenudisplay
* @description Boolean indicating if submenus are automatically made
* visible when the user mouses over the menu's items.
* @default true
* @type Boolean
*/
oConfig.addProperty(
AUTO_SUBMENU_DISPLAY_CONFIG.key,
{
value: AUTO_SUBMENU_DISPLAY_CONFIG.value,
validator: AUTO_SUBMENU_DISPLAY_CONFIG.validator,
suppressEvent: AUTO_SUBMENU_DISPLAY_CONFIG.suppressEvent
}
);
/**
* @config showdelay
* @description Number indicating the time (in milliseconds) that should
* expire before a submenu is made visible when the user mouses over
* the menu's items. This property is only applied when the "position"
* configuration property is set to dynamic and is automatically applied
* to all submenus.
* @default 250
* @type Number
*/
oConfig.addProperty(
SHOW_DELAY_CONFIG.key,
{
value: SHOW_DELAY_CONFIG.value,
validator: SHOW_DELAY_CONFIG.validator,
suppressEvent: SHOW_DELAY_CONFIG.suppressEvent
}
);
/**
* @config hidedelay
* @description Number indicating the time (in milliseconds) that should
* expire before the menu is hidden. This property is only applied when
* the "position" configuration property is set to dynamic and is
* automatically applied to all submenus.
* @default 0
* @type Number
*/
oConfig.addProperty(
HIDE_DELAY_CONFIG.key,
{
handler: this.configHideDelay,
value: HIDE_DELAY_CONFIG.value,
validator: HIDE_DELAY_CONFIG.validator,
suppressEvent: HIDE_DELAY_CONFIG.suppressEvent
}
);
/**
* @config submenuhidedelay
* @description Number indicating the time (in milliseconds) that should
* expire before a submenu is hidden when the user mouses out of a menu item
* heading in the direction of a submenu. The value must be greater than or
* equal to the value specified for the "showdelay" configuration property.
* This property is only applied when the "position" configuration property
* is set to dynamic and is automatically applied to all submenus.
* @default 250
* @type Number
*/
oConfig.addProperty(
SUBMENU_HIDE_DELAY_CONFIG.key,
{
value: SUBMENU_HIDE_DELAY_CONFIG.value,
validator: SUBMENU_HIDE_DELAY_CONFIG.validator,
suppressEvent: SUBMENU_HIDE_DELAY_CONFIG.suppressEvent
}
);
/**
* @config clicktohide
* @description Boolean indicating if the menu will automatically be
* hidden if the user clicks outside of it. This property is only
* applied when the "position" configuration property is set to dynamic
* and is automatically applied to all submenus.
* @default true
* @type Boolean
*/
oConfig.addProperty(
CLICK_TO_HIDE_CONFIG.key,
{
value: CLICK_TO_HIDE_CONFIG.value,
validator: CLICK_TO_HIDE_CONFIG.validator,
suppressEvent: CLICK_TO_HIDE_CONFIG.suppressEvent
}
);
/**
* @config container
* @description HTML element reference or string specifying the id
* attribute of the HTML element that the menu's markup should be
* rendered into.
* @type <a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
* level-one-html.html#ID-58190037">HTMLElement</a>|String
* @default document.body
*/
oConfig.addProperty(
CONTAINER_CONFIG.key,
{
handler: this.configContainer,
value: document.body,
suppressEvent: CONTAINER_CONFIG.suppressEvent
}
);
/**
* @config scrollincrement
* @description Number used to control the scroll speed of a menu. Used to
* increment the "scrollTop" property of the menu's body by when a menu's
* content is scrolling. When set this property is automatically applied
* to all submenus.
* @default 1
* @type Number
*/
oConfig.addProperty(
SCROLL_INCREMENT_CONFIG.key,
{
value: SCROLL_INCREMENT_CONFIG.value,
validator: SCROLL_INCREMENT_CONFIG.validator,
supercedes: SCROLL_INCREMENT_CONFIG.supercedes,
suppressEvent: SCROLL_INCREMENT_CONFIG.suppressEvent
}
);
/**
* @config minscrollheight
* @description Number defining the minimum threshold for the "maxheight"
* configuration property. When set this property is automatically applied
* to all submenus.
* @default 90
* @type Number
*/
oConfig.addProperty(
MIN_SCROLL_HEIGHT_CONFIG.key,
{
value: MIN_SCROLL_HEIGHT_CONFIG.value,
validator: MIN_SCROLL_HEIGHT_CONFIG.validator,
supercedes: MIN_SCROLL_HEIGHT_CONFIG.supercedes,
suppressEvent: MIN_SCROLL_HEIGHT_CONFIG.suppressEvent
}
);
/**
* @config maxheight
* @description Number defining the maximum height (in pixels) for a menu's
* body element (<code><div class="bd"<</code>). Once a menu's body
* exceeds this height, the contents of the body are scrolled to maintain
* this value. This value cannot be set lower than the value of the
* "minscrollheight" configuration property.
* @default 0
* @type Number
*/
oConfig.addProperty(
MAX_HEIGHT_CONFIG.key,
{
handler: this.configMaxHeight,
value: MAX_HEIGHT_CONFIG.value,
validator: MAX_HEIGHT_CONFIG.validator,
suppressEvent: MAX_HEIGHT_CONFIG.suppressEvent,
supercedes: MAX_HEIGHT_CONFIG.supercedes
}
);
/**
* @config classname
* @description String representing the CSS class to be applied to the
* menu's root <code><div></code> element. The specified class(es)
* are appended in addition to the default class as specified by the menu's
* CSS_CLASS_NAME constant. When set this property is automatically
* applied to all submenus.
* @default null
* @type String
*/
oConfig.addProperty(
CLASS_NAME_CONFIG.key,
{
handler: this.configClassName,
value: CLASS_NAME_CONFIG.value,
validator: CLASS_NAME_CONFIG.validator,
supercedes: CLASS_NAME_CONFIG.supercedes
}
);
/**
* @config disabled
* @description Boolean indicating if the menu should be disabled.
* Disabling a menu disables each of its items. (Disabled menu items are
* dimmed and will not respond to user input or fire events.) Disabled
* menus have a corresponding "disabled" CSS class applied to their root
* <code><div></code> element.
* @default false
* @type Boolean
*/
oConfig.addProperty(
DISABLED_CONFIG.key,
{
handler: this.configDisabled,
value: DISABLED_CONFIG.value,
validator: DISABLED_CONFIG.validator,
suppressEvent: DISABLED_CONFIG.suppressEvent
}
);
/**
* @config shadow
* @description Boolean indicating if the menu should have a shadow.
* @default true
* @type Boolean
*/
oConfig.addProperty(
SHADOW_CONFIG.key,
{
handler: this.configShadow,
value: SHADOW_CONFIG.value,
validator: SHADOW_CONFIG.validator
}
);
/**
* @config keepopen
* @description Boolean indicating if the menu should remain open when clicked.
* @default false
* @type Boolean
*/
oConfig.addProperty(
KEEP_OPEN_CONFIG.key,
{
value: KEEP_OPEN_CONFIG.value,
validator: KEEP_OPEN_CONFIG.validator
}
);
}
}); // END YAHOO.lang.extend
})();