/** * Introducing Menu.js * * Menu.js is a free, small JavaScript (drop-down) menu for developers. It's * unobtrusive and only requires a HTML unordered list and your own CSS styles. * http://www.menujs.net/ * * Requires Prototype JS version 1.5.0 or greater. * (Also supports version 1.6.0.*, avoiding all deprecated methods) * http://www.prototypejs.org/ * _____________________________________________________________________________ * * As of version 1.2, released under the MIT License: * * Copyright (c) 2007-2009 Charming Design, Niek Kouwenberg * http://www.charmingdesign.net/ * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * _____________________________________________________________________________ * * Special thanks to CARE Internet Services B.V. * http://www.care.nl/ */ Menu = { /* Version of the Menu class */ Version: "1.3.1", /* * CONSTANTS */ /* Constant for a horizontal menu */ HORIZONTAL: 1, /* Constant for a vertical menu */ VERTICAL: 2, /* * MENU */ /* Hold the ID of the menu */ _menuId: null, /* Hold the menu UL node */ _menuNode: null, /* * TEMPORARY VARIABLES */ /* Hold the show timer */ _showTimeout: null, /* Hold the hide timer */ _hideTimeout: null, /* Will hold the link, submenu and level of the active menu node */ _activeNodes: new Array(), /* * OPTIONS */ /* Orientation of the menu (horizontal or vertical) */ _orientation: 1, /* Time in milliseconds before showing the sub menu */ _showPause: 0, /* Time in milliseconds before hiding the sub menu */ _hidePause: 1000, /* Opacity (0 = transparent, 1 = opaque) */ _opacity: 1, /* * METHODS */ /** * Sets the time to wait before hiding the sub menu. * * @param int secs * * @deprecated Please use the options argument for Menu.init() instead. */ setHidePause: function(secs) { Menu._hidePause = secs * 1000; /* deprecation warning */ alert("Deprecated method Menu.setHidePause() used: Please use the options argument for Menu.init()."); }, // function setHidePause /** * Initializes the dynamic menu. * * @param string menuId * @param object options * * Available options: * - orientation (int; one of: Menu.HORIZONTAL, Menu.VERTICAL) * - showPause (float; in seconds; default: 0.0) * - hidePause (float; in seconds; default: 1.0) * - opacity (float; 0 = transparent, 1 = opaque; default: 1; transparency of the sub menu) * * Example usage: * * * This method can be called after document load, but it is preferred to be * called directly from your page (HTML , before document load). This * way the menu loads faster and can be interacted with much sooner. */ init: function(menuId, options) { /* Save menu ID (fall back to the default ID "menu") */ Menu._menuId = (typeof menuId == "string") ? menuId : "menu"; /* Save options */ if (options) { /* Orientation */ if (options.orientation != undefined) { Menu._orientation = options.orientation; } /* Show timeout in seconds */ if (options.showPause != undefined) { Menu._showPause = options.showPause * 1000; } /* Hide timeout in seconds */ if (options.hidePause != undefined) { Menu._hidePause = options.hidePause * 1000; } /* Sub menu opacity */ if (options.opacity != undefined) { Menu._opacity = options.opacity; } } /* Check if the document is already loaded. Prototype 1.6.0 introduces * the document.loaded boolean, for 1.5*, check if we can retrieve an * element from the DOM (fails when document is not loaded). */ if (document.loaded === true || $(Menu._menuId)) { Menu._doInit(); } /* This is how it should work (init called before document load) */ else { /* Do the actual initialization on document load. The "dom:loaded" * event is preffered, but only available since 1.6.0 (with * document.observe construction). Fall back to the window onload * when not available. */ if (document.observe) { document.observe("dom:loaded", Menu._doInit); } else { Element.observe(window, "load", Menu._doInit); } } }, // function init /** * Initializes the drop down menu. * * Should be called on page load. */ _doInit: function() { /* After the DOM is loaded, save the menu node */ Menu._menuNode = $(Menu._menuId); /* Add events to each first level menu node (if any with a submenu). The * Menu._addEvents() method will add events recursively. */ Menu._addEvents(Menu._menuNode); }, // function _doInit /** * Recursively attaches events to the menu UL. * * @param HTMLUListElement ulElement * @param int level */ _addEvents: function(ulElement, level) { /* If level argument is not given, */ if (isNaN(level)) { level = 1; } /* Find all menu nodes */ var elements = (Element.select) ? ulElement.select("li") : ulElement.getElementsBySelector("li") for (var i = 0; i < elements.length; i++) { /* Only use the direct descendants (we're using recursion) */ if (elements[i].parentNode == ulElement) { /* Check if it has a sub menu (should return 1 or zero nodes) */ var subMenus = (Element.select) ? elements[i].select("ul") : elements[i].getElementsBySelector("ul"); if (subMenus.length > 0) { /* Add expand listener to the node */ elements[i].observe("mouseover", Menu._showSubMenu.bindAsEventListener(elements[i], level)); /* Add collapse listener to the node if it's the first level * (because the LI contains all other submenu's, therefore * any other listeners are over kill). */ if (level == 1) { elements[i].observe("mouseout", Menu._hideSubMenu.bindAsEventListener(elements[i], level)); } /* Add "submenu" class for this node link */ var a = (Element.select) ? elements[i].select("a")[0] : elements[i].getElementsBySelector("a")[0]; a.addClassName("submenu"); /* Recursion: check if the sub menu has nodes as well */ Menu._addEvents(subMenus[0], (level + 1)); } /* No sub menu */ else { /* Only hide any expanded sub menu when hovering this node */ elements[i].observe("mouseover", Menu._quickHideSubMenu.bindAsEventListener(elements[i], level)); } } } }, // function _addEvents /** * Shows the sub menu. * * @param Event e * @param int level */ _showSubMenu: function(e, level) { /* Don't bubble up */ Event.stop(e); /* is this the first menu node to be opened (need to check if we should apply the show timeout ) */ var isFirst = (Menu._activeNodes.length == 0); /* Hide previous opened sub menu */ Menu._quickHideSubMenu(e, level); /* Is this the first menu node and do we have a show delay? */ if (isFirst && Menu._showPause > 0) { /* Show in x (mili)seconds */ Menu._showTimeout = window.setTimeout(function() { Menu._doShowSubMenu(this, level); }.bind(this), Menu._showPause); } else { Menu._doShowSubMenu(this, level); } }, // function _showSubMenu /** * Shows the sub menu. * * @param Event e * @param int level */ _doShowSubMenu: function(node, level) { /* Clear possible timeout */ if (Menu._showTimeout) { window.clearTimeout(Menu._showTimeout); } /* Get node link and sub menu */ var a = (Element.select) ? node.select("a")[0] : node.getElementsBySelector("a")[0]; var subMenu = (Element.select) ? node.select("ul")[0] : node.getElementsBySelector("ul")[0]; /* Keep hover style as long as opened */ a.addClassName("menu_open"); /* Since 1.3, the class is also applied to the parent LI */ a.parentNode.addClassName("menu_open"); /* Show sub menu */ subMenu.style.display = "block"; subMenu.style.position = "absolute"; /* Set correct position. (Note: the horizontal/vertical properties only * apply to the first level nodes. All other levels are fixed vertical. */ var pos = (Element.positionedOffset) ? node.positionedOffset() : Position.positionedOffset(node); if (level == 1 && Menu._orientation == Menu.HORIZONTAL) { subMenu.style.left = pos[0] + "px"; subMenu.style.top = (pos[1] + node.getHeight()) + "px"; } else { subMenu.style.left = (pos[0] + node.getWidth()) + "px"; subMenu.style.top = pos[1] + "px"; } /* Apply opacity if not fully opaque. (Apply for sub menu of first level * only, because otherwise opacity would be doubled.) */ if (level == 1 && Menu._opacity > 0 && Menu._opacity < 1) { subMenu.setOpacity(Menu._opacity); } /* Save submenu */ Menu._activeNodes.push({"level": level, "link": a, "subMenu": subMenu}); }, // function _doShowSubMenu /** * Immediately hides the active menu. * * @param Event e * @param int level */ _quickHideSubMenu: function(e, level) { /* Don't bubble up */ Event.stop(e); /* Clear possible timeout */ if (Menu._hideTimeout) { window.clearTimeout(Menu._hideTimeout); } /* And hide the open menus from the given level up */ Menu._doHideSubMenusFromLevel(level); }, // function _quickHideSubMenu /** * Method for hiding all sub menus. * * Triggered onmouseout of first sub menu (level 2). * * @param Event e * @param int level */ _hideSubMenu: function(e, level) { /* Don't bubble up */ Event.stop(e); /* if hiding (lost focus on first level), do not show! */ if (Menu._showTimeout) { window.clearTimeout(Menu._showTimeout); } /* No pause? Don't use the timeout */ if (Menu._hidePause <= 0) { /* Hide all sub menus */ Menu._doHideSubMenusFromLevel(1); } else { /* Hide in x (mili)seconds */ Menu._hideTimeout = window.setTimeout(function() { Menu._doHideSubMenusFromLevel(1); }, Menu._hidePause); } }, // function _hideSubMenu /** * Hides all active sub menus from the given level. * * @param int level (Default: 1) * * @return boolean */ _doHideSubMenusFromLevel: function(level) { /* If level argument is not given, default to 1 */ if (isNaN(level)) { level = 1; } /* Remove these sub menus from the array */ Menu._activeNodes = Menu._activeNodes.findAll(function(node) { /* Hide all sub menus with a level equal or higher than the given * level, and return false to remove these from the array. */ if (node.level >= level) { /* Remove hover style */ if (node.link) { node.link.removeClassName("menu_open"); node.link.parentNode.removeClassName("menu_open"); } /* Hide sub menu */ if (node.subMenu) { node.subMenu.style.display = "none"; } /* Return false to remove node from the array */ return false; } /* Return true for the other nodes, keeping them in the array */ return true; }); } // function _doHideSubMenusFromLevel }; // class Menu