Version: 1.0
Type: Full Script
Category: HTML
License: GNU General Public License
Description: GMenu is a highly customizable multilevel drop down menu. GMenu is written in JavaScript and is customized using CSS style sheets.
Using GMenu, you can quickly create a dynamic drop down menu that:
1. Organizes your website hierarchy
2. Is stylish and interactive
3. Works in IE, Firefox, and Netscape
4. Can even drop down over form fields in IE
and much more!
/* ** v1.0.20050304 ** ** gmenu is a customizable collection of javascript routines which can be used ** to quickly create platform independent drop down menus. ** ** gmenu has been tested on IE 6.0, Netscape 7.1, and FireFox 1.0 ** ** Copyright (C) 2004-2005 Geoffrey Derek Cox (formfields.com, [email protected]) ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the GNU General Public License ** as published by the Free Software Foundation; either version 2 ** of the License, or (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ** ** See http://www.gnu.org/ for an up-to-date copy of the GPL. */ // ENHANCEMENTS: // 1. GMenu blinks when web browser caching is disabled. This could be fixed // by optimizing some of the GMenu highlighting routines. var isIe = (document.all ? true : false); // Needed because the mouse coordinates in IE and NN are not exact var TOLERANCE_TOP = (isIe ? 2 : -2); var TOLERANCE_RIGHT = (isIe ? 2 : 0); var TOLERANCE_BOTTOM = (isIe ? 2 : 1); var TOLERANCE_LEFT = (isIe ? 2 : 0); // The number of pixels to offset the top-left corner of the top menu from a // root menu item. var TOP_MENU_LEFT_OFFSET = 0; // The maximum depth of sub menus (not including the root menu) var MAX_MENUS = 5; function stripPx(x) { if (x.length <= 2) return x; return eval(x.substr(0,x.length-2)); } function addDimensions(x1, x2) { return (stripPx(x1) + stripPx(x2)) + 'px'; } function getHighlightedClass(curClass) { if (curClass.substr(0, 12) == 'subMenuItemL') return 'subMenuItemLeafHighlight'; else return 'subMenuItemHighlight'; } function getNotHighlightedClass(curClass) { if (curClass.substr(0, 12) == 'subMenuItemL') return 'subMenuItemLeaf'; else return 'subMenuItem'; } // Highlight current item and "unhighlight" other items in current menu function setHighlighting(selectedNode) { //alert('start setHighlighting'); for (i = 0; i < (selectedNode.parentNode).childNodes.length; i++) { var curNode = (selectedNode.parentNode).childNodes[i]; if (curNode.id == selectedNode.id) { curNode.className = getHighlightedClass(curNode.className); } else { curNode.className = getNotHighlightedClass(curNode.className); } } //alert('end setHighlighting'); } function createLink(text, url, cssClass) { if (url == null) return text; return '<a class="' + cssClass + '" href="' + url + '" style="height:100%;width:100%;">' + text + '</a>'; } // This is needed so that a user doesn't cause a js error by doing a mouse over on the menu // before the menu has completely loaded. function enableRootMenus() { var i = 1; while ( (rootMenu = document.getElementById("rootMenu" + (i++))) ) { rootMenu.disabled = false; } } // Prepare the menus // Note: IE doesn't allow one to cover a form field with a div, however it does permit // an iframe to cover a form field. Therefore, we place an iframe under each menu div. function initGMenu() { html = ''; for (i = 1; i <= MAX_MENUS; i++) { html += '<div id="menuBase' + i + '" class="subMenuBase" style="position:absolute;top:0px;left:0px;visibility:hidden;">' + '<iframe frameborder="0" width="100%"></iframe></div>'; html += '<div id="menu' + i + '" class="subMenu" style="position:absolute;top:0px;left:0px;visibility:hidden;" onMouseOut="hideMenus(event);">' + '</div>'; } document.body.innerHTML += html; enableRootMenus(); } // Fill in the sub menus. This is done every time a menu pops up. function drawSubMenu(menuId, menuArray) { var menu = document.getElementById(menuId); var html = ''; for (i = 0; i < menuArray.length; i++) { html += '<div id="' + menuId + "-" + i + '" class="' + (menuArray[i][2] == null ? 'subMenuItemLeaf' : 'subMenuItem"') + '"' + ' onMouseOver="' +(menuArray[i][2] == null ? '' : 'showMenu('' + menuId + '', ' + menuArray[i][2] + ', this);') + 'setHighlighting(this);">' + createLink(menuArray[i][0], menuArray[i][1], 'subMenuItem') + '</div>'; } menu.innerHTML = html; return menu; } // Shows a level 1 menu, right below the root menus. // Had to adjust the top location for netscape????????? function showTopMenu(curMenu, menuArray) { curMenu.className = curMenu.id + 'Highlight'; var subMenu = drawSubMenu('menu1', menuArray); subMenu.style.top = curMenu.offsetParent.offsetTop + curMenu.offsetParent.offsetHeight; subMenu.style.left = curMenu.offsetParent.offsetLeft + curMenu.offsetLeft + TOP_MENU_LEFT_OFFSET; //subMenu.style.top = curMenu.offsetTop + curMenu.offsetHeight; //subMenu.style.left = curMenu.offsetLeft + TOP_MENU_LEFT_OFFSET; var subMenuBase = document.getElementById('menuBase1'); subMenuBase.childNodes[0].style.height = subMenu.offsetHeight; subMenuBase.style.top = subMenu.style.top; subMenuBase.style.left = subMenu.style.left; subMenu.style.visibility = 'visible'; subMenuBase.style.visibility = 'visible'; } // Shows a level2..leveln menu, directly to the right of the selected item. function showMenu(curMenuId, menuArray, curItem) { var curMenu = document.getElementById(curMenuId); var subMenuId = 'menu' + (eval((curMenuId).substr(4,1)) + 1); var subMenuBaseId = 'menuBase' + (eval((curMenuId).substr(4,1)) + 1); var subMenuBase = document.getElementById(subMenuBaseId); var subMenu = drawSubMenu(subMenuId, menuArray); subMenu.style.top = curItem.offsetParent.offsetTop + curItem.offsetTop; //stripPx(curMenu.style.top); subMenu.style.left = stripPx(curMenu.style.left) + curMenu.offsetWidth - 4; subMenuBase.style.top = subMenu.style.top; subMenuBase.childNodes[0].style.height = subMenu.offsetHeight; subMenuBase.style.left = subMenu.style.left; subMenu.style.visibility = 'visible'; subMenuBase.style.visibility = 'visible'; } function unhighlightRootMenus() { var i = 1; while ( (rootMenu = document.getElementById("rootMenu" + (i++))) ) { rootMenu.className = rootMenu.id; } } // Recursively check and then hide all menus arropriately. This is done // during a mouseOut event. // ENHANCEMENT: Ideally the menus should be hidden a second after the mouse leaves // the menu hierarchy. Unfortunately, I was unable to implement this // using setTimeout because setTimeout doesn't play nicely with mouse coordinates. function hideMenus(e) { if (hideMenu('menu1', e)) { unhighlightRootMenus(); } } // Recursively check all sub menus of the current menu. // If the mouse has left a menu and all its sub menus then hide that menu. function hideMenu(curMenuId, e) { var curMenu = document.getElementById(curMenuId); if (isIe) { // using IE? //xMousePos = window.event.x + document.body.scrollLeft + stripPx(curMenu.style.left); // for IE xMousePos = event.clientX + document.body.scrollLeft; //yMousePos = window.event.y + document.body.scrollTop + stripPx(curMenu.style.top); yMousePos = event.clientY + document.body.scrollTop; } else { xMousePos = e.clientX; yMousePos = e.clientY; } var hideChild = true; var childId = 'menu' + (eval((curMenu.id).substr(4,1)) + 1); var curMenuBaseId = 'menuBase' + (eval((curMenu.id).substr(4,1))); var curMenuBase = document.getElementById(curMenuBaseId); var child = document.getElementById(childId); if (child != null) { hideChild = hideMenu(child.id, e); } //alert(curMenu.style.left + " " + curMenu.offsetWidth + " " + curMenu.offsetLeft + " " + xMousePos); //alert(curMenu.style.left + " " + curMenu.offsetWidth + " " + curMenu.offsetLeft + " " + xMousePos); if (xMousePos < stripPx(curMenu.style.left) + TOLERANCE_LEFT || xMousePos > stripPx(curMenu.style.left) + curMenu.offsetWidth - TOLERANCE_RIGHT || yMousePos < stripPx(curMenu.style.top) + TOLERANCE_TOP || yMousePos > stripPx(curMenu.style.top) + curMenu.offsetHeight - TOLERANCE_BOTTOM || curMenu.style.visibility == 'hidden') { if (hideChild == true) { curMenu.style.visibility = 'hidden'; curMenuBase.style.visibility = 'hidden'; return true; } return false; } else { return false; } }