// Globals
var _cmIDCount = 0;
var _cmIDName = 'cmSubMenuID'; // for creating submenu id
var _cmTimeOut = null; // how long the menu would stay
var _cmCurrentItem = null; // the current menu item being selected;
var _cmNoAction = new Object (); // indicate that the item cannot be hovered.
var _cmNoClick = new Object (); // similar to _cmNoAction but does not respond to mouseup/mousedown events
var _cmSplit = new Object (); // indicate that the item is a menu split
var _cmItemList = new Array (); // a simple list of items
// default node properties
var _cmNodeProperties =
{
// main menu display attributes
//
// Note. When the menu bar is horizontal,
// mainFolderLeft and mainFolderRight are
// put in . When the menu
// bar is vertical, they would be put in
// a separate TD cell.
// HTML code to the left of the folder item
mainFolderLeft: '',
// HTML code to the right of the folder item
mainFolderRight: '',
// HTML code to the left of the regular item
mainItemLeft: '',
// HTML code to the right of the regular item
mainItemRight: '',
// sub menu display attributes
// HTML code to the left of the folder item
folderLeft: '',
// HTML code to the right of the folder item
folderRight: '',
// HTML code to the left of the regular item
itemLeft: '',
// HTML code to the right of the regular item
itemRight: '',
//mine
itemLeftEmpty: '',
// cell spacing for main menu
mainSpacing: 0,
// cell spacing for sub menus
subSpacing: 0,
// auto disappear time for submenus in milli-seconds
delay: 500,
// act on click to open sub menu
// not yet implemented
// 0 : use default behavior
// 1 : hover open in all cases
// 2 : click on main, hover on sub
// 3 : click open in all cases
clickOpen: 1
};
//////////////////////////////////////////////////////////////////////
//
// Drawing Functions and Utility Functions
//
//////////////////////////////////////////////////////////////////////
//
// produce a new unique id
//
function cmNewID ()
{
return _cmIDName + (++_cmIDCount);
}
//
// return the property string for the menu item
//
function cmActionItem (item, prefix, isMain, idSub, orient, nodeProperties)
{
var clickOpen = _cmNodeProperties.clickOpen;
if (nodeProperties.clickOpen)
clickOpen = nodeProperties.clickOpen;
// var index = _cmItemList.push (item) - 1;
_cmItemList[_cmItemList.length] = item;
var index = _cmItemList.length - 1;
idSub = (!idSub) ? 'null' : ('\'' + idSub + '\'');
orient = '\'' + orient + '\'';
prefix = '\'' + prefix + '\'';
var onClick = (clickOpen == 3) || (clickOpen == 2 && isMain);
var returnStr;
var cursorHand = ( item[2] != null && item[2] != '' ? ' style="cursor:pointer;" ' : '' );
if (onClick)
returnStr = cursorHand + ' onmouseover="cmItemMouseOver (this,' + prefix + ',' + isMain + ',' + idSub + ',' + index + ')" onmousedown="cmItemMouseDownOpenSub (this,' + index + ',' + prefix + ',' + orient + ',' + idSub + ');"';
else
returnStr = cursorHand + ' onmouseover="cmItemMouseOverOpenSub (this,' + prefix + ',' + isMain + ',' + idSub + ',' + orient + ',' + index + ');" onmousedown="cmItemMouseDown (this,' + index + ');"';
return returnStr + cursorHand + ' onmouseout="cmItemMouseOut (this,' + nodeProperties.delay + ')" onmouseup="cmItemMouseUp (this,' + index + ');"';
}
//
// this one is used by _cmNoClick to only take care of onmouseover and onmouseout
// events which are associated with menu but not actions associated with menu clicking/closing
//
function cmNoClickItem (item, prefix, isMain, idSub, orient, nodeProperties)
{
// var index = _cmItemList.push (item) - 1;
_cmItemList[_cmItemList.length] = item;
var index = _cmItemList.length - 1;
idSub = (!idSub) ? 'null' : ('\'' + idSub + '\'');
orient = '\'' + orient + '\'';
prefix = '\'' + prefix + '\'';
return ' onmouseover="cmItemMouseOver (this,' + prefix + ',' + isMain + ',' + idSub + ',' + index + ')" onmouseout="cmItemMouseOut (this,' + nodeProperties.delay + ')"';
}
function cmNoActionItem (item, prefix)
{
return item[1];
}
function cmSplitItem (prefix, isMain, vertical)
{
var classStr = 'cm' + prefix;
if (isMain)
{
classStr += 'Main';
if (vertical)
classStr += 'HSplit';
else
classStr += 'VSplit';
}
else
classStr += 'HSplit';
return eval (classStr);
}
//
// draw the sub menu recursively
//
function cmDrawSubMenu (subMenu, prefix, id, orient, nodeProperties, againDoughter)
{
var str = '
' + strSub;
//alert("..." + str);
return str;
}
//
// The function that builds the menu inside the specified element id.
//
// @param id id of the element
// orient orientation of the menu in [hv][ab][lr] format
// menu the menu object to be drawn
// nodeProperties properties for each menu node
//
function cmDraw (id, menu, orient, nodeProperties, prefix)
{
var obj = cmGetObject (id);
if (!nodeProperties)
nodeProperties = _cmNodeProperties;
if (!prefix)
prefix = '';
var str = '' + strSub;
obj.innerHTML = str;
/* document.write ("" + str + ""); */
}
//
// The function builds the menu inside the specified element id.
//
// This function is similar to cmDraw except that menu is taken from HTML node
// rather a javascript tree. This feature allows links to be scanned by search
// bots.
//
// This function basically converts HTML node to a javascript tree, and then calls
// cmDraw to draw the actual menu, replacing the hidden menu tree.
//
// Format:
//
//
function cmDrawFromText (id, orient, nodeProperties, prefix)
{
var domMenu = cmGetObject (id);
var menu = null;
for (var currentDomItem = domMenu.firstChild; currentDomItem; currentDomItem = currentDomItem.nextSibling)
{
if (!currentDomItem.tagName || currentDomItem.tagName.toLowerCase () != 'ul')
continue;
menu = cmDrawFromTextSubMenu (currentDomItem);
break;
}
if (menu)
cmDraw (id, menu, orient, nodeProperties, prefix);
}
//
// a recursive function that build menu tree structure
//
function cmDrawFromTextSubMenu (domMenu)
{
var items = new Array ();
for (var currentDomItem = domMenu.firstChild; currentDomItem; currentDomItem = currentDomItem.nextSibling)
{
if (!currentDomItem.tagName || currentDomItem.tagName.toLowerCase () != 'li')
continue;
if (currentDomItem.firstChild == null)
{
items[items.length] = _cmSplit;
continue;
}
var item = new Array ();
var currentItem = currentDomItem.firstChild;
for (; currentItem; currentItem = currentItem.nextSibling)
{
// scan for span tag
if (!currentItem.tagName || currentItem.tagName.toLowerCase () != 'span')
continue;
if (!currentItem.firstChild)
item[0] = null;
else
item[0] = currentItem.innerHTML;
break;
}
if (!currentItem)
continue;
for (; currentItem; currentItem = currentItem.nextSibling)
{
// scan for span tag
if (!currentItem.tagName || currentItem.tagName.toLowerCase () != 'a')
continue;
item[1] = currentItem.innerHTML;
item[2] = currentItem.href;
item[3] = currentItem.target;
item[4] = currentItem.title;
if (item[4] == '')
item[4] = null;
break;
}
for (; currentItem; currentItem = currentItem.nextSibling)
{
// scan for span tag
if (!currentItem.tagName || currentItem.tagName.toLowerCase () != 'ul')
continue;
var subMenuItems = cmDrawFromTextSubMenu (currentItem);
for (i = 0; i < subMenuItems.length; ++i)
item[i + 5] = subMenuItems[i];
break;
}
items[items.length] = item;
}
return items;
}
//////////////////////////////////////////////////////////////////////
//
// Mouse Event Handling Functions
//
//////////////////////////////////////////////////////////////////////
//
// action should be taken for mouse moving in to the menu item
//
// Here we just do things concerning this menu item, w/o opening sub menus.
//
function cmItemMouseOver (obj, prefix, isMain, idSub, index)
{
clearTimeout (_cmTimeOut);
if (!obj.cmPrefix)
{
obj.cmPrefix = prefix;
obj.cmIsMain = isMain;
}
var thisMenu = cmGetThisMenu (obj, prefix);
// insert obj into cmItems if cmItems doesn't have obj
if (!thisMenu.cmItems)
thisMenu.cmItems = new Array ();
var i;
for (i = 0; i < thisMenu.cmItems.length; ++i)
{
if (thisMenu.cmItems[i] == obj)
break;
}
if (i == thisMenu.cmItems.length)
{
//thisMenu.cmItems.push (obj);
thisMenu.cmItems[i] = obj;
}
// hide the previous submenu that is not this branch
if (_cmCurrentItem)
{
// occationally, we get this case when user
// move the mouse slowly to the border
if (_cmCurrentItem == obj || _cmCurrentItem == thisMenu)
{
var item = _cmItemList[index];
cmSetStatus (item);
return;
}
var thatPrefix = _cmCurrentItem.cmPrefix;
var thatMenu = cmGetThisMenu (_cmCurrentItem, thatPrefix);
if (thatMenu != thisMenu.cmParentMenu)
{
if (_cmCurrentItem.cmIsMain)
_cmCurrentItem.className = thatPrefix + 'MainItem';
else
_cmCurrentItem.className = thatPrefix + 'MenuItem';
if (thatMenu.id != idSub)
cmHideMenu (thatMenu, thisMenu, thatPrefix);
}
}
// okay, set the current menu to this obj
_cmCurrentItem = obj;
// just in case, reset all items in this menu to MenuItem
cmResetMenu (thisMenu, prefix);
var item = _cmItemList[index];
var isDefaultItem = cmIsDefaultItem (item);
if (isDefaultItem)
{
if (isMain)
obj.className = prefix + 'MainItemHover';
else
obj.className = prefix + 'MenuItemHover';
}
cmSetStatus (item);
}
//
// action should be taken for mouse moving in to the menu item
//
// This function also opens sub menu
//
function cmItemMouseOverOpenSub (obj, prefix, isMain, idSub, orient, index)
{
cmItemMouseOver (obj, prefix, isMain, idSub, index);
if (idSub)
{
var subMenu = cmGetObject (idSub);
cmShowSubMenu (obj, prefix, subMenu, orient);
}
}
//
// action should be taken for mouse moving out of the menu item
//
function cmItemMouseOut (obj, delayTime)
{
if (!delayTime)
delayTime = _cmNodeProperties.delay;
_cmTimeOut = window.setTimeout ('cmHideMenuTime ()', delayTime);
window.defaultStatus = '';
}
//
// action should be taken for mouse button down at a menu item
//
function cmItemMouseDown (obj, index)
{
if (cmIsDefaultItem (_cmItemList[index]))
{
if (obj.cmIsMain)
obj.className = obj.cmPrefix + 'MainItemActive';
else
obj.className = obj.cmPrefix + 'MenuItemActive';
}
}
//
// action should be taken for mouse button down at a menu item
// this is one also opens submenu if needed
//
function cmItemMouseDownOpenSub (obj, index, prefix, orient, idSub)
{
cmItemMouseDown (obj, index);
if (idSub)
{
var subMenu = cmGetObject (idSub);
cmShowSubMenu (obj, prefix, subMenu, orient);
}
}
//
// action should be taken for mouse button up at a menu item
//
function cmItemMouseUp (obj, index)
{
var item = _cmItemList[index];
var link = null, target = '_self';
if (item.length > 2)
link = item[2];
if (item.length > 3 && item[3])
target = item[3];
if (link != null)
{
window.open (link, target);
}
var prefix = obj.cmPrefix;
var thisMenu = cmGetThisMenu (obj, prefix);
var hasChild = (item.length > 5);
if (!hasChild)
{
if (cmIsDefaultItem (item))
{
if (obj.cmIsMain)
obj.className = prefix + 'MainItem';
else
obj.className = prefix + 'MenuItem';
}
cmHideMenu (thisMenu, null, prefix);
}
else
{
if (cmIsDefaultItem (item))
{
if (obj.cmIsMain)
obj.className = prefix + 'MainItemHover';
else
obj.className = prefix + 'MenuItemHover';
}
}
}
//////////////////////////////////////////////////////////////////////
//
// Mouse Event Support Utility Functions
//
//////////////////////////////////////////////////////////////////////
//
// move submenu to the appropriate location
//
// @param obj the menu item that opens up the subMenu
// subMenu the sub menu to be shown
// orient the orientation of the subMenu
//
function cmMoveSubMenu (obj, subMenu, orient)
{
var mode = String (orient);
var p = subMenu.offsetParent;
var subMenuWidth = cmGetWidth (subMenu);
var horiz = cmGetHorizontalAlign (obj, mode, p, subMenuWidth);
if (mode.charAt (0) == 'h')
{
if (mode.charAt (1) == 'b')
subMenu.style.top = (cmGetYAt (obj, p) + cmGetHeight (obj)) + 'px';
else
subMenu.style.top = (cmGetYAt (obj, p) - cmGetHeight (subMenu)) + 'px';
if (horiz == 'r')
subMenu.style.left = (cmGetXAt (obj, p)) + 'px';
else
subMenu.style.left = (cmGetXAt (obj, p) + cmGetWidth (obj) - subMenuWidth) + 'px';
}
else
{
if (horiz == 'r')
subMenu.style.left = (cmGetXAt (obj, p) + cmGetWidth (obj)) + 'px';
else
subMenu.style.left = (cmGetXAt (obj, p) - subMenuWidth) + 'px';
if (mode.charAt (1) == 'b')
subMenu.style.top = (cmGetYAt (obj, p)) + 'px';
else
subMenu.style.top = (cmGetYAt (obj, p) + cmGetHeight (obj) - cmGetHeight (subMenu)) + 'px';
}
}
//
// automatically re-adjust the menu position based on available screen size.
//
function cmGetHorizontalAlign (obj, mode, p, subMenuWidth)
{
var horiz = mode.charAt (2);
if (!(document.body))
return horiz;
var body = document.body;
var browserLeft;
var browserRight;
if (window.innerWidth)
{
// DOM window attributes
browserLeft = window.pageXOffset;
browserRight = window.innerWidth + browserLeft;
}
else if (body.clientWidth)
{
// IE attributes
browserLeft = body.clientLeft;
browserRight = body.clientWidth + browserLeft;
}
else
return horiz;
if (mode.charAt (0) == 'h')
{
if (horiz == 'r' && (cmGetXAt (obj) + subMenuWidth) > browserRight)
horiz = 'l';
if (horiz == 'l' && (cmGetXAt (obj) + cmGetWidth (obj) - subMenuWidth) < browserLeft)
horiz = 'r';
return horiz;
}
else
{
if (horiz == 'r' && (cmGetXAt (obj, p) + cmGetWidth (obj) + subMenuWidth) > browserRight)
horiz = 'l';
if (horiz == 'l' && (cmGetXAt (obj, p) - subMenuWidth) < browserLeft)
horiz = 'r';
return horiz;
}
}
//
// show the subMenu w/ specified orientation
// also move it to the correct coordinates
//
// @param obj the menu item that opens up the subMenu
// subMenu the sub menu to be shown
// orient the orientation of the subMenu
//
function cmShowSubMenu (obj, prefix, subMenu, orient)
{
if (!subMenu.cmParentMenu)
{
// establish the tree w/ back edge
var thisMenu = cmGetThisMenu (obj, prefix);
subMenu.cmParentMenu = thisMenu;
if (!thisMenu.cmSubMenu)
thisMenu.cmSubMenu = new Array ();
//thisMenu.cmSubMenu.push (subMenu);
thisMenu.cmSubMenu[thisMenu.cmSubMenu.length] = subMenu;
}
// position the sub menu
cmMoveSubMenu (obj, subMenu, orient);
subMenu.style.visibility = 'visible';
//
// On IE, controls such as SELECT, OBJECT, IFRAME (before 5.5)
// are window based controls. So, if the sub menu and these
// controls overlap, sub menu would be hidden behind them. Thus
// one needs to turn the visibility of these controls off when the
// sub menu is showing, and turn their visibility back on
// when the sub menu is hiding.
//
if (document.all) // it is IE
{
/* part of Felix Zaslavskiy's fix on hiding controls
not really sure if this part is necessary, but shouldn't
hurt. */
if (!subMenu.cmOverlap)
subMenu.cmOverlap = new Array ();
/*@cc_on @*/
/*@if (@_jscript_version >= 5.5)
@else @*/
cmHideControl ("IFRAME", subMenu);
/*@end @*/
cmHideControl ("SELECT", subMenu);
cmHideControl ("OBJECT", subMenu);
}
}
//
// reset all the menu items to class MenuItem in thisMenu
//
function cmResetMenu (thisMenu, prefix)
{
if (thisMenu.cmItems)
{
var i;
var str;
var items = thisMenu.cmItems;
for (i = 0; i < items.length; ++i)
{
if (items[i].cmIsMain)
str = prefix + 'MainItem';
else
str = prefix + 'MenuItem';
if (items[i].className != str)
items[i].className = str;
}
}
}
//
// called by the timer to hide the menu
//
function cmHideMenuTime ()
{
if (_cmCurrentItem)
{
var prefix = _cmCurrentItem.cmPrefix;
cmHideMenu (cmGetThisMenu (_cmCurrentItem, prefix), null, prefix);
_cmCurrentItem = null;
}
}
//
// hide thisMenu, children of thisMenu, as well as the ancestor
// of thisMenu until currentMenu is encountered. currentMenu
// will not be hidden
//
function cmHideMenu (thisMenu, currentMenu, prefix)
{
var str = prefix + 'SubMenu';
// hide the down stream menus
if (thisMenu.cmSubMenu)
{
var i;
for (i = 0; i < thisMenu.cmSubMenu.length; ++i)
{
cmHideSubMenu (thisMenu.cmSubMenu[i], prefix);
}
}
// hide the upstream menus
while (thisMenu && thisMenu != currentMenu)
{
cmResetMenu (thisMenu, prefix);
if (thisMenu.className == str)
{
thisMenu.style.visibility = 'hidden';
cmShowControl (thisMenu);
}
else
break;
thisMenu = cmGetThisMenu (thisMenu.cmParentMenu, prefix);
}
}
//
// hide thisMenu as well as its sub menus if thisMenu is not
// already hidden
//
function cmHideSubMenu (thisMenu, prefix)
{
if (thisMenu.style.visibility == 'hidden')
return;
if (thisMenu.cmSubMenu)
{
var i;
for (i = 0; i < thisMenu.cmSubMenu.length; ++i)
{
cmHideSubMenu (thisMenu.cmSubMenu[i], prefix);
}
}
cmResetMenu (thisMenu, prefix);
thisMenu.style.visibility = 'hidden';
cmShowControl (thisMenu);
}
//
// hide a control such as IFRAME
//
function cmHideControl (tagName, subMenu)
{
var x = cmGetX (subMenu);
var y = cmGetY (subMenu);
var w = subMenu.offsetWidth;
var h = subMenu.offsetHeight;
var i;
for (i = 0; i < document.all.tags(tagName).length; ++i)
{
var obj = document.all.tags(tagName)[i];
if (!obj || !obj.offsetParent)
continue;
// check if the object and the subMenu overlap
var ox = cmGetX (obj);
var oy = cmGetY (obj);
var ow = obj.offsetWidth;
var oh = obj.offsetHeight;
if (ox > (x + w) || (ox + ow) < x)
continue;
if (oy > (y + h) || (oy + oh) < y)
continue;
// if object is already made hidden by a different
// submenu then we dont want to put it on overlap list of
// of a submenu a second time.
// - bug fixed by Felix Zaslavskiy
if(obj.style.visibility == "hidden")
continue;
//subMenu.cmOverlap.push (obj);
subMenu.cmOverlap[subMenu.cmOverlap.length] = obj;
obj.style.visibility = "hidden";
}
}
//
// show the control hidden by the subMenu
//
function cmShowControl (subMenu)
{
if (subMenu.cmOverlap)
{
var i;
for (i = 0; i < subMenu.cmOverlap.length; ++i)
subMenu.cmOverlap[i].style.visibility = "";
}
subMenu.cmOverlap = null;
}
//
// returns the main menu or the submenu table where this obj (menu item)
// is in
//
function cmGetThisMenu (obj, prefix)
{
var str1 = prefix + 'SubMenu';
var str2 = prefix + 'Menu';
while (obj)
{
if (obj.className == str1 || obj.className == str2)
return obj;
obj = obj.parentNode;
}
return null;
}
//
// return true if this item is handled using default handlers
//
function cmIsDefaultItem (item)
{
if (item == _cmSplit || item[0] == _cmNoAction || item[0] == _cmNoClick)
return false;
return true;
}
//
// returns the object baring the id
//
function cmGetObject (id)
{
if (document.all)
return document.all[id];
return document.getElementById (id);
}
//
// functions that obtain the width of an HTML element.
//
function cmGetWidth (obj)
{
var width = obj.offsetWidth;
if (width > 0 || !cmIsTRNode (obj))
return width;
if (!obj.firstChild)
return 0;
// use TABLE's length can cause an extra pixel gap
//return obj.parentNode.parentNode.offsetWidth;
// use the left and right child instead
return obj.lastChild.offsetLeft - obj.firstChild.offsetLeft + cmGetWidth (obj.lastChild);
}
//
// functions that obtain the height of an HTML element.
//
function cmGetHeight (obj)
{
var height = obj.offsetHeight;
if (height > 0 || !cmIsTRNode (obj))
return height;
if (!obj.firstChild)
return 0;
// use the first child's height
return obj.firstChild.offsetHeight;
}
//
// functions that obtain the coordinates of an HTML element
//
function cmGetX (obj)
{
var x = 0;
do
{
x += obj.offsetLeft;
obj = obj.offsetParent;
}
while (obj);
return x;
}
function cmGetXAt (obj, elm)
{
var x = 0;
while (obj && obj != elm)
{
x += obj.offsetLeft;
obj = obj.offsetParent;
}
if (obj == elm)
return x;
return x - cmGetX (elm);
}
function cmGetY (obj)
{
var y = 0;
do
{
y += obj.offsetTop;
obj = obj.offsetParent;
}
while (obj);
return y;
}
function cmIsTRNode (obj)
{
var tagName = obj.tagName;
return tagName == "TR" || tagName == "tr" || tagName == "Tr" || tagName == "tR";
}
//
// get the Y position of the object. In case of TR element though,
// we attempt to adjust the value.
//
function cmGetYAt (obj, elm)
{
var y = 0;
if (!obj.offsetHeight && cmIsTRNode (obj))
{
var firstTR = obj.parentNode.firstChild;
obj = obj.firstChild;
y -= firstTR.firstChild.offsetTop;
}
while (obj && obj != elm)
{
y += obj.offsetTop;
obj = obj.offsetParent;
}
if (obj == elm)
return y;
return y - cmGetY (elm);
}
//
// extract description from the menu item and set the status text
// @param item the menu item
//
function cmSetStatus (item)
{
var descript = '';
if (item.length > 4)
descript = (item[4] != null) ? item[4] : (item[2] ? item[2] : descript);
else if (item.length > 2)
descript = (item[2] ? item[2] : descript);
window.defaultStatus = descript;
}
//
// debug function, ignore :)
//
function cmGetProperties (obj)
{
if (obj == undefined)
return 'undefined';
if (obj == null)
return 'null';
var msg = obj + ':\n';
var i;
for (i in obj)
msg += i + ' = ' + obj[i] + '; ';
return msg;
}
var cmthisThemeBase = '/common/javascript/jscookmenu/thisTheme/';
// the follow block allows user to re-define theme base directory
// before it is loaded.
try
{
if (mythisThemeBase)
{
cmthisThemeBase = mythisThemeBase;
}
}
catch (e)
{
}
var cmthisTheme =
{
mainFolderLeft: '',
mainFolderRight: '',
mainItemLeft: '',
mainItemRight: '',
folderLeft: '
',
folderRight: '
',
itemLeft: '
',
itemRight: '
',
itemLeftEmpty: '
',
mainSpacing: 0,
subSpacing: 0,
delay: 100
};
var cmthisThemeHSplit = [_cmNoClick, ' | '];
var cmthisThemeMainVSplit = [_cmNoClick, ''];
var cmthisThemeMainHSplit = [_cmNoClick, ' | '];