/* * AcmeMenu v2.7 * * Copyright (c) 2003-2005 Mackley F. Pexton. All rights reserved. * * This is open software and is licensed under the terms of the * GNU General Public License * Instructions and source code are available at www.acmebase.org/menu. * This software is part of the AcmeBase project at www.acmebase.org. * Send correspondence and feedback to: mack_pexton[at]acmebase.org. */ /****************************************************************************** AcmeMenu v2.7 -- Display pop-up menu and sub-menus. The functions below display pop-up menus using CSS positioning and DOM. They currently work on MS Internet Explorer 5+/PC, IE 5+/Mac, Opera 5+, Netscape 6+, Mozilla 1+, Konqueror 3+, and Safari. There are four primary functions: menu('menu_id','menu_header_id'); // display a menu below a header submenu('submenu_id'); // display submenu to the right make_menu('menu_id','menu_header_id'); // setup and activate menus hide_menus(); // hide all menus A fifth function: initialize_menus(); is used to initialize the menu configuration variables to their initial default values. It is needed when there are several of menus on a page, each with their own set of configuration settings. It is automatically invoked for the first menu. The make_menu() function returns a Menu object which has three methods and two properties of use: m.show_menu() // show a menu m.hide_menu() // hide a menu m.hide_submenu() // hide a menu and its submenus m.is_visible // true if menu is shown m.is_submenu // true if menu is a submenu The menu object can also be used to set localized values of configuration variables. In order to place menu panels next to their headers on the page, a few utility functions are used which might be of use elsewhere: // The following works with version 5+ browsers. // The mouse coordinates are used to locate the element. getEventElementLeft(evt,e); // get left coordinate of element e getEventElementTop(evt,e); // get top coordinate of element e // The following works with version 6+ browsers. // The mouse coordinates are not needed. getElementLeft(e); // get left coordinate of element e getElementTop(e); // get top coordinate of element e // The following works with all supported browsers. getElementWidth(e); // width of element including borders getElementHeight(e); // height of element including borders // The following reads the current style setting by first investigaing // the element style attribute followed by the stylesheet setting. getElementStyle(e,dom_property,css_attribute); // get current style setting // The following looks for both specific and general style settings. // For example: "border-right" (borderRight) and second value of "border". getBorderWidth(e,'right'); // top, bottom, left, right border width getPadding(e,'left'); // top, bottom, left, right padding getMargin(e,'top'); // top, bottom, left, right margin A menu as defined below is a pop-up display of links or any other kind of form input fields. Menu selections are contained by a
tag (or any block element) that has an id attribute set to the name of the menu. All menus and submenus need id attributes. Example menu: Menu ...
... form input tags ...
To set up a menu so that it automatically pops up the menus and their cascading submenus as the user's mouse hovers over them, call the make_menu() function like: By calling make_menu(), the user is not required to click on the menu buttons to activate them and the location of the submenus are automatically positioned to be to the right of the menu button that activated them. If the make_menu() function is not used to set up the menus to respond to the mouse, then the menu locations cannot be automatically determined. Therefore, the menu's x,y (left,top) location needs to be defined with CSS style settings. An tag with the href attribute calling the submenu() function pops up a submenu. A submenu is just the same as a main menu in that it has a
tag enclosing tags, and it has an id attribute naming the submenu. Its the function name "submenu()" in the href attribute of the tag that is sensed to "link" together the menus and their submenus. The hide_menus() function hides all the displayed menus. It is useful when performing an action that does not change the window display. By default, clicking in a menu freezes it open until clicking outside its borders. The display of a menu and its tag menu selections is controlled by CSS styles. To allow the selected path to remain highlighted as the mouse hovers over a submenu, the menu and submenu header elements have the string "-hilite" appended to their class name (if they have one) when their submenu is displayed. Those styles need to be defined in a stylesheet and they are usually similar to the appearance defined for the A:hover style. Example stylesheet: DIV.menu { position:absolute; display:none; width:150px; } DIV.submenu { position:absolute; display:none; width:150px; } DIV.menu A, DIV.submenu A { display:block; width:100%; } DIV.menu A:hover, DIV.submenu A:hover, A.menu-head-hilite, A.submenu-head-hilite { background-color:#DDDDDD; } A.submenu-head { background-image:url(/images/rtriangle.gif); background-position:right center; background-repeat:no-repeat; } Menus also work well as as pop-up data entry forms because when the user clicks inside a menu, its display is "frozen" on the screen until they click away outside the menus. That behavior can be turned off by setting the class variable Menu.AllowFreeze to "false". Submenus can only appear in one location in a menu chain. They cannot be reused with other menus. This software tries to preserve any events (onMouseOver, onMouseOut, onClick) defined by the web page author. Rollovers should work as always. An ancilliary file AcmeMenuEffects.js enhances the menus with fades, triggers, and auto-play. It has several configuration variables of its own. There is a debug function at the end that can be turned on or off by setting the variable debug.is_on to true or false. It produces a flood of output in a second window. Pausing the movement of the mouse over the menus causes blank lines to be inserted into the debug window to separate tests. The debug functions can also be turned on after a page is displayed by typing the following into the browser address bar: javascript:debug.on() History: v2.0 - Rewrite AcmeBase menues, change event model, IE5 and 6. v2.1 - Now gets dimensions from CSS stylesheets, Netscape. v2.2 - Added system menus, click start, click stop, Opera. v2.3 - Added menus, image links for menu items, IE Macintosh. v2.4 - Added fixed menus, auto hide, triggers, fades, and auto-play. v2.41 - Revised interface with menu effects and auto-play. v2.5 - Added Safari and Konqueror. v2.51 - Moved body onclick handler to html element. v2.52 - Added MenuAlign and SubmenuAlign constants. v2.6 - Added hooks for TearOffMenus. v2.61 - Check for element existance in getElementHeight/Width. v2.62 - Added extra checks for table margins and padding discovered with Firefox. v2.63 - Fixed bug allowing Safari to click and activate menu links. v2.64 - Fixed Safari v2 reading stylesheets. v2.7 - Added OnShowMenu and OnHideMenu event handler hooks. ******************************************************************************/ // Uncomment following to turn on debug. //debug.is_on = true; // Uncomment following to turn on mouse coordinates. //mouse_coord.is_on = true; /* * Detect browser version */ bv.isNS = (navigator.userAgent.indexOf("Netscape") >= 0) ? true : false; bv.isNS71 = (navigator.userAgent.indexOf("Netscape/7.1") >= 0) ? true : false; bv.isOpr = (navigator.userAgent.indexOf("Opera") >= 0) ? true : false; bv.isOpr5 = (navigator.userAgent.search(/Opera.5/) >= 0) ? true : false; bv.isOpr6 = (navigator.userAgent.search(/Opera.6/) >= 0) ? true : false; bv.isOpr7 = (navigator.userAgent.search(/Opera.7/) >= 0) ? true : false; bv.isSafari = (navigator.userAgent.search(/Safari/) >= 0) ? true : false; bv.isKonq = (navigator.vendor == "KDE") ? true : false; bv.isIE = (document.all && ! bv.isOpr && ! bv.isKonq); bv.isIE6 = (bv.isIE && document.compatMode && document.compatMode.indexOf("CSS1") >= 0) ? true : false; bv.isIE5Mac = (bv.isIE && navigator.userAgent.indexOf('Mac') >= 0) ? true : false; bv.isIE5 = (bv.isIE && ! bv.isIE5Mac && ! bv.isIE6) ? true : false; // default version bv.isMoz = (! bv.isNS && ! bv.isOpr && ! bv.isIE && ! bv.isSafari && ! bv.isKonq && navigator.userAgent.indexOf("Mozilla") == 0) ? true : false; function bv() { // Return all the browser version flags that match this browser. var v,s = ""; for (v in bv) if (v.substring(0,2) == "is" && bv[v]) s += (s ? " " : "") + v; return s; } // Uncomment the following if you have a non-menu page for old browsers. //if (! bv()) location.href = "http://www.your-web-site.com/nomenus.html"; /* * Main functions */ function make_menu(id, header_id, keep_open) { // Make a menu out of an HTML element and its header element by // setting up event handlers to respond to mouse events. // The id of the menu is required. // The header_id is usually required except for static (vertical) menus. // The seldom used keep_open flag keeps menus open after mouse clicks. debug("make_menu("+id+")"); var m; if (Menu.menus[id]) { // This menu has been previously made, possibly by the menu() // function if the user clicked the menu header before the // menus were loaded. Delete the menu chain and remake it. debug("make_menu("+id+"): deleting previously made menu '"+id+"'."); var menu_id; for (menu_id in Menu.menus) { m = Menu.menus[menu_id]; if (m.top.id == id) { m.hide_menu(); delete Menu.menus[menu_id]; } } } var manual = false; // we want event handlers to auto open menus keep_open = (keep_open != null && keep_open) ? true : Menu.KeepOpen; m = new Menu(id, header_id, manual, keep_open); m.configure_menu(); var header_e = m.header_e; if (header_e) { // Save current mouseover and mouseout event handlers. // Note: Original event definitions are normally saved in the // menu object, however, because of Netscape's thinking about // when to fire mouseout events, the onmouseover, onmouseout, // and onclick definitions need to be saved in the element's // object. See set_enclosed_elements_handler() function. if (header_e.onmouseover && header_e.onmouseover != Menu.onmouseover_handler) header_e.orig_onmouseover = header_e.onmouseover; if (header_e.onmouseout && header_e.onmouseout != Menu.onmouseout_handler) header_e.orig_onmouseout = header_e.onmouseout; // Assign event handlers to header element. header_e.onmouseover = Menu.onmouseover_handler; header_e.onmouseout = Menu.onmouseout_handler; if (bv.isNS) Menu.set_enclosed_elements_handler(header_e); } else { // Setup static menus. // Static menus are those menus without a header element. // They must always be visible. (If there isn't a header element, // how can they be shown?) Static menus never do get "shown" // in show_menu(), so they can't get setup there. Finish // setting up the menu here. m.setup_menu(); } // Setup handlers to hide all menus if mouse clicked outside of them. if (document.body.parentNode.onclick && document.body.parentNode.onclick != Menu.onclick_body_handler) Menu.orig_onclick_body_handler = document.body.parentNode.onclick; document.body.parentNode.onclick = Menu.onclick_body_handler; // Setup handlers to recompute menu positions if window resized. if (window.onresize && window.onresize != Menu.onresize_handler) Menu.orig_onresize_handler = window.onresize; window.onresize = Menu.onresize_handler; return m; // return menu object } function menu(id, header_id, keep_open) { // Display a menu. // The header id is optional. // The optional keep_open flag prevents the menu from being hidden // when the user clicks away. It works with only manual menus here. debug("menu("+id+")"); // Define Menu object if one is not already defined. var m = Menu.menus[id]; if (!m) { // Check if menus have been loaded into the browser. if (! getElementRef(id)) { debug("menu(): menu '"+id+"' not found: page not loaded yet?"); return; } var manual = true; keep_open = keep_open ? true : false; // change to boolean m = new Menu(id, header_id, manual, keep_open); m.configure_menu(); // Scan the menu node for any contained elements with a submenu() // function call in the href attribute. This is necessary in order // to find the header elements for the submenus. // Note: This is only performed on "manual" menus that have // not been setup with the make_menu() function. Menus setup // by make_menu() have mouseover and mouseout event handlers // defined that reveal the header elements of submenus so // that they can be built dynamically instead of calling // build_submenus() to set them all up at once. Menu.build_submenus(m, getElementRef(id)); } if (! m.header_e) { debug('Menu: ERROR: cannot find the menu header element for the menu "'+id+'"'); // Don't hide menu if there is not a header to re-display it. return; } // Note: the keep_open flag operates slightly different for manual // menus than with the auto menus. When set for auto menus, the menus // are never hidden, even if the menu header is clicked. Manual menus // toggle the display of the menu when the menu header is clicked. // Subvert this check for unrecognized browsers to allow menus to // degenerate as much as possible. This is a temporary fix. //###if (m.keep_open && ! m.manual && bv()) { if (m.keep_open && ! m.manual) { m.freeze(); return; } // Toggle display/non-display of menu. if (m.is_visible) { // Toggle ClickStop mode. This freezes the menu open, which // if there is a positioning error, allows the user to go // find the menu. This does not apply to manual menus. //if (m.manual || m.ClickStop) { //## click closes (!!), keeps hide time if (m.manual) { //## click ok, looses HideDelayTime if (m.click_stop_auto_set) m.click_stop_auto_set = m.ClickStop = false; m.hide_submenu(); } else { m.ClickStop = true; m.click_stop_auto_set = true; // Note: The body onclick handler has been triggered // by this time and has scheduled all menus -- including // this one -- to be hidden, so redisplay it again. m.show_menu(); if (Menu.is_effects_loaded && autoplay.is_activated) autoplay.pause(); // Track the currently displayed top menu. Menu.click_stop_menu = m; } } else { m.show_menu(); } } function submenu(id) { // Display a submenu. debug("submenu("+id+")"); var m = Menu.menus[id]; if (m) { // Ignore unless using manual menus. if (! m.manual) return; // Toggle display/non-display of submenu. if (m.is_visible) { m.hide_submenu(); } else { if (m.parent.child) m.parent.hide_submenu(); m.parent.show_submenu(id); } return; } debug("submenu(): ERROR: The menu for submenu '"+id+ "' has not been setup with make_menu()."); } function hide_menus(force_relocation) { // Hide all displayed menus. debug("hide_menus(): force_relocation="+ (force_relocation != null ? force_relocation : 'null')); var m,id; for (id in Menu.menus) { m = Menu.menus[id]; if (! m.keep_open) m.hide_menu(); if (force_relocation) m.is_located = false; } Menu.click_start_enabled = false; // reset } function reset_menus() { initialize_menus(); } // old name function initialize_menus() { // Initialize all class constants. /* * Menu settings */ // Class Constants (change as desired) // The following offset constants are used to position the menu or submenu // relative to their header elements. These are the initial default settings // that can be changed for each menu. New submenus receive their offsets from // their parent menu. The measurements are for dropdown menus and submenus to // the right, so positive x positions are to the right and positive y positions // go down. For menus going up or to the left, the offset numbers are // automatically reversed, thereby maintaining the "same offset" whether the // menu goes right or left, up or down. This allows the direction of the // menu to be changed simply by changing the MenuPostion and SubmenuPosition // constants below without having to change the offset constants. // Menu panels are positioned from the lower left corner of the header. Menu.MenuOffsetX = 0; // Offset from left side of header object Menu.MenuOffsetY = 0; // Offset from bottom side of header object // Submenu panels are positioned from the upper right corner of their header. Menu.SubmenuOffsetX = 0; // Offset from right side of header object Menu.SubmenuOffsetY = 0; // Offset from top of header object // Menus and submenus can project out in different directions from their header. // By default, menus drop "Down", and submenus pop "Right". The position codes // can be set to one of the following constants: // U (up), D (down), L (left), or R (right). Menu.MenuPosition = "D"; // Down Menu.SubmenuPosition = "R"; // Right // The edges of the menus and submenus are aligned to the MenuPosition or // SubmenuPosition. By default, menus are aligned using to their top edge and // submenus are aligned on their left edge. The alignment codes can be set // to one of the following constants: // T (top), B (bottom), L (left), or R (right). Menu.MenuAlign = "L"; // Left Menu.SubmenuAlign = "T"; // Top // Menus are usually repositioned if necessary to keep them within view. // Set AllowReposition variable to "false" to turn off the feature. Menu.AllowReposition = true; // Menus can be classified as vertical menus (the default) or horizontal menus. // The Y coordinate of vertical menus is adjusted slightly so that the first // menu item is straight across from the header element. Horizontal menus have // their placement adjusted slightly so the menu selections tend to line up // vertically. The MenuOrientation and SubmenuOrientation constants can be set // to one of the following constants: // V (vertical), H (horizontal), or N (neither). Menu.MenuOrientation = "V"; Menu.SubmenuOrientation = "V"; // Border dimensions and padding around menus and their headers need to be // known for accurate placement of the menus. The dimensions are read from the // element's style attribute or they are read from CSS stylesheet settings. // The units of measure must be pixel units "px" in order to be correctly // interpreted. If styles settings cannot be read by the browser (e.g. Opera), // the following constants are used. // Uncomment and define the following as needed. //Menu.MenuHeaderBorderWidth = null; //Menu.MenuHeaderPadding = null; //Menu.MenuHeaderPaddingHorz = null; //Menu.MenuHeaderPaddingVert = null; //Menu.MenuBorderWidth = null; //Menu.MenuPadding = null; //Menu.MenuPaddingHorz = null; //Menu.MenuPaddingVert = null; //Menu.SubmenuHeaderBorderWidth = null; //Menu.SubmenuHeaderPadding = null; //Menu.SubmenuHeaderPaddingHorz = null; //Menu.SubmenuHeaderPaddingVert = null; //Menu.SubmenuBorderWidth = null; //Menu.SubmenuPadding = null; //Menu.SubmenuPaddingHorz = null; //Menu.SubmenuPaddingVert = null; // Opera 6 needs to know the top margin of the document body in order // to correctly determine the Y coordinate of a menu header, but it cannot // read the style settings. This is only used for Opera 6. Menu.BodyMarginTop = 0; // Menus can be set to be dormant until the menu header is clicked. // This activates a behavior similar to Windows' system menus. Menu.ClickStart = false; // Menus can be set to stay open until a mouse click. // If ClickStart is true, ClickStop is also forced to be true. Menu.ClickStop = Menu.ClickStart || false; // Menus can be set to stay open, even if a mouse click. Menu.KeepOpen = false; // Menus can be set to stay open a set number of seconds after // the mouse wanders outside them. It is like the ClickStop // flag being set with an click event automatically scheduled // to occur after the specified number of seconds. Menu.HideDelayTime = 0; // Menus can be "frozen" open if the mouse is clicked inside them. // Set AllowFreeze variable to "false" to turn off that feature. Menu.AllowFreeze = true; // Menu locations are cached once they are computed. However, if the inline // position of the menus changes, the cached coordinates become invalid. // Set CacheLocation to false to force the menu's coordinates to be // computed each time they are shown. Alternately, you could call // hide_menus(true) with its argument set to true to force the relocation // of the menus the next time they are shown. Menu.CacheLocation = true; // Menu locations can be fixed in a predetermined location by setting the CSS // styles left and top, and by setting the MenuFixed configuration constant // to "true". The same is true for submenus, their location is fixed on the // page if SubmenuFixed is "true". Menu.MenuFixed = false; Menu.SubmenuFixed = false; // The menu z-index is used to ensure the menu is displayed on top of the // document. The submenu z-indexes are automatically incremented. Menu.ZIndex = 100; // The highlight suffix is appended to a menu header's class name -- if it // has one. This allows the selected path to stay highlighted as the mouse // moves over the submenu. Menu.HighlightSuffix = "-hilite"; // Functions can be assigned to OnShowMenu and OnHideMenu which are triggered // whenever a menu is displayed or hidden. The menu object is passed to the // functions as their single argument. Menu.OnShowMenu = null; Menu.OnHideMenu = null; // Timing delays are used before actually showing or hiding a menu. // Increase these numbers if menus items have large padding. The mouse needs // to go from a submenu header across the padding onto the submenu within // the TimeToHide time to ensure smooth operation. Menu.TimeToShow = 150; // Delay before showing menu (milliseconds) Menu.TimeToHide = 100; // Delay before hiding menu (milliseconds) // Each time the menus are initialized, the group number is incremented. // This allows a set of menus to be grouped together. Menu.Group++; // Initialize extra menu settings if ancillary file has been loaded. if (Menu.initialize_effects) Menu.initialize_effects(); } // Initialize the menu grouping before initializing menu configuration. Menu.Group = -1; initialize_menus(); // initialize class constants // Class Variables (do not change) Menu.menus = {}; // All the defined menus and their Menu objects Menu.click_start_enabled = false;// on/off flag Menu.click_stop_menu = null; // Current menu displayed in click stop mode. Menu.hide_delay_timer = null; // Timer for auto hiding click_stop_menu. Menu.keep_open_menu = []; // Current menu displayed in keep open mode. Menu.frozen_menu = null; // Current menu that is frozen open. Menu.max_z = 0; // Maximum zIndex of top menu displayed. Menu.is_effects_loaded = false; // Set to true in AcmeMenuEffects.js when loaded. // Regex to extract menu and header names from menu() and submenu() calls. Menu.menu_re = /menu\([ ]*(['"]?)([^'",)]+)/; Menu.submenu_re = /submenu\([ ]*(['"]?)([^'",)]+)/; /* * Menu object definition */ function Menu(id, header, manual, keep_open) { // Menu object constructor. // Required arguments: // id is set to the id attribute of the menu
tag. // Optional arguments: // header can be a string (e.g. id name) or an object. // manual is set to true for manual menus. // keep_open is set to true for manual (non)menu in auto environment. debug("new Menu("+id+")"); if (id) this.e = getElementRef(id); if (header) this.header_e = getElementRef(header); if (header && ! this.header_e) { debug('Menu: ERROR: cannot find the menu header "'+header+'"'); } this.id = id; this.is_submenu = false; this.is_setup = false; this.is_located = false; this.is_finalized = false; this.is_fixed = false; this.is_static = this.header_e ? false : true; this.is_visible = this.is_static ? true : false; this.parent = null; this.child = null; this.top = this; this.x = null; this.y = null; this.z = Menu.ZIndex; this.evt = null; this.frozen_e = null; this.frozen_onmouseout = null; this.saved_body_onclick = null; this.show_timer = 0; this.hide_timer = 0; this.manual = manual; this.keep_open = keep_open; // Register all defined menus. Menu.menus[id] = this; } /* * Event handlers */ Menu.get_evt = function(evt) { // Return DOM or Windows event object. return evt ? evt : window.event ? window.event : null; } Menu.get_evt_target = function(evt) { // Return DOM or Windows event target element. return evt.target ? evt.target : evt.srcElement ? evt.srcElement : null; } Menu.get_menu_id = function(href) { // Helper function for event handlers to parse the menu id from href string. // The match for a menu/submenu function in href has already been done. if (bv.isKonq) { // Konqueror 2.2 does not have RegExp.$1 ... properties. return href.match(Menu.menu_re).replace(/.*['"]/,""); } else { // Menu_id is quoted text or named variable. return RegExp.$1 ? RegExp.$2 : eval(RegExp.$2); } } Menu.onmouseover_handler = function(evt) { evt = Menu.get_evt(evt); var e = Menu.get_evt_target(evt); // Event handler attached to menu
tags. debug("Menu.onmouseover_handler(): e.id="+e.id, e); clearTimeout(Menu.hide_delay_timer); // reset // Find menu object name that belongs to this event. var menu_id, submenu_id, submenu_target; var m, target_e; for (target_e = e; target_e != null; target_e = target_e.parentNode) { // Execute original event if one was saved. // This applies to the menu header. In Netscape, // it also applies to all the elements processed by // set_enclosed_elements_handler(). if (target_e.orig_onmouseover) target_e.orig_onmouseover(evt); if (target_e.id && (m = Menu.menus[target_e.id])) { // Found the menu container (
) element. if (Menu.frozen_menu && Menu.frozen_menu.e != target_e) Menu.frozen_menu.thaw(); m.save_event(evt); // Hide existing submenu if visible. if (m.child && (! submenu_id || m.child.id != submenu_id)) { m.child.hide_submenu(); } if (submenu_id) { m.show_submenu(submenu_id,submenu_target); } else { m.show_menu(); } if (Menu.is_effects_loaded && autoplay.is_activated) autoplay.pause(); break; } if (target_e.href) { if (target_e.href.match(Menu.submenu_re)) { // Found submenu() function in href attribute. // Submenu_id is quoted text or named variable. submenu_id = Menu.get_menu_id(target_e.href); submenu_target = target_e; debug("Menu.onmouseover: found " + "submenu(" + submenu_id + ") in target."); // Parse the href attribute for more commands // after the submenu() function. Execute remaining // script in element's href attribute asynchronously // like a menu effects trigger. // Uncomment next to execute extra href statements. //if (target_e.href.match(/\);(.+)/)) setTimeout(RegExp.$1, 1); // Continue on to find menu containing this submenu header. continue; } if (target_e.href.match(Menu.menu_re)) { // Found menu() function in href attribute. if (Menu.is_effects_loaded && autoplay.is_activated) autoplay.pause(); menu_id = Menu.get_menu_id(target_e.href); debug("Menu.onmouseover: found " + "menu(" + menu_id + ") in target."); // Parse the href attribute for more commands // after the menu() function. Execute remaining // script in element's href attribute asynchronously // like a menu effects trigger. // Uncomment next to execute extra href statements. //if (target_e.href.match(/\);(.+)/)) setTimeout(RegExp.$1, 1); if (Menu.frozen_menu) Menu.frozen_menu.thaw(); if ((m = Menu.menus[menu_id])) { if (! m.is_located) m.save_event(evt); // Do not show menu if using ClickStart // and the user has not "clicked" yet. if (m.ClickStart && ! Menu.click_start_enabled) { debug("Menu.onmouseover:"+ " click start mode is not enabled."+ " Skip display of menu."); break; } m.show_menu(); } break; } } } } Menu.onmouseout_handler = function(evt) { evt = Menu.get_evt(evt); var e = Menu.get_evt_target(evt); // Event handler attached to menu
tags. // The following is a special debug statement for Safari. // Safari fires a mouseout event when writing to the debug window. (!!) if (debug.is_on && bv.isSafari) return; if (bv.isNS) { // Note: Instead of descending into all levels below an element // in set_enclosed_elements_handler() to ensure that all enclosed // objects that cause Netscape to trigger a mouseout event are // covered with a mouseover event handler, we simply filter some // of the elements causing spurious mouseout events here. (!!) if (e.nodeName == "TEXTAREA") return; if (e.nodeName == "OPTION") return; } debug("Menu.onmouseout_handler(): e.id="+e.id, e); // Find menu object name that belongs to this event. var m, top; var menu_id, submenu_id; var target_e; for (target_e = e; target_e != null; target_e = target_e.parentNode) { // Trigger original event if one was saved. // (This happens only with the menu header.) if (target_e.orig_onmouseout) target_e.orig_onmouseout(evt); if (target_e.id && (m = Menu.menus[target_e.id])) { // Found the menu container (
) element. // Restart autoplay if paused. if (Menu.is_effects_loaded && autoplay.is_activated && ! Menu.frozen_menu && ! Menu.click_stop_menu) autoplay.resume(); // Leave menus displayed if keep_open specified. if (m.keep_open) break; // Leave menus displayed if ClickStop specified. if (m.top.ClickStop) { if (m.top.click_stop_auto_set) m.top.click_stop_auto_set = m.top.ClickStop = false; if (m.top.HideDelayTime) { debug("Menu.onmouseout_handler():"+ " scheduling auto hide: "+m.top.HideDelayTime); Menu.hide_delay_timer = setTimeout( "hide_menus()", m.top.HideDelayTime); } break; } // Hide entire menu chain. m.top.hide_submenu(); break; } if (target_e.href) { if (target_e.href.match(Menu.submenu_re)) { // Found submenu() function in href attribute. // Submenu_id is quoted text or named variable. submenu_id = Menu.get_menu_id(target_e.href); debug("Menu.onmouseout: found " + "submenu(" + submenu_id + ") in target."); // Parse the href attribute for more commands // after the submenu() function. Execute remaining // script in element's href attribute asynchronously. // Uncomment next to execute extra href statements. //if (target_e.href.match(/\);(.+)/)) setTimeout(RegExp.$1, 1); // Continue on to find enclosing menu container. continue; } if (target_e.href.match(Menu.menu_re)) { // Found menu() function in href attribute. menu_id = Menu.get_menu_id(target_e.href); debug("Menu.onmouseout: found " + "menu(" + menu_id + ") in target."); // Parse the href attribute for more commands // after the menu() function. Execute remaining // script in element's href attribute asynchronously. // Uncomment next to execute extra href statements. //if (target_e.href.match(/\);(.+)/)) setTimeout(RegExp.$1, 1); if ((m = Menu.menus[menu_id])) { // Restart autoplay if paused. if (Menu.is_effects_loaded && autoplay.is_activated && ! Menu.frozen_menu && ! Menu.click_stop_menu) autoplay.resume(); // Leave menus displayed if ClickStart or ClickStop. if (m.ClickStop) { if (m.HideDelayTime) { debug("Menu.onmouseout_handler():"+ " scheduling auto hide: "+ m.HideDelayTime); Menu.hide_delay_timer = setTimeout( "hide_menus()", m.HideDelayTime); break; } else { debug("Menu.onmouseout_handler():"+ " Do not hide menu."+ " ClickStop="+m.ClickStop); break; } } // Leave menus displayed if keep_open is specified. if (m.keep_open) { // Cancel the hide timers if current // keep_open menu is scheduled to be hidden. if (Menu.keep_open_menu[m.Group] && Menu.keep_open_menu[m.Group] != m) { // Cancel the show timer if this menu // has not been already shown. clearTimeout(m.show_timer); // Ensure current keep_open menu // remains shown. Menu.keep_open_menu[m.Group].show_menu(); } debug("Menu.onmouseout_handler():"+ " Do not hide menu."+ " keep_open="+m.keep_open); break; } // Hide entire menu chain. m.hide_submenu(); } break; } } } } Menu.onclick_handler = function(evt) { evt = Menu.get_evt(evt); var e = Menu.get_evt_target(evt); // Event handler attached to menu
tags. debug("Menu.onclick_handler: e.id="+e.id, e); // Find menu object name that belongs to this event. var m, target_e; for (target_e = e; target_e != null; target_e = target_e.parentNode) { if (target_e.href && ! target_e.href.match(Menu.submenu_re)) { // A menu item was selected. Hide the menus. hide_menus(); if (Menu.is_effects_loaded && autoplay.is_activated) autoplay.pause(); break; } if (! target_e.onclick) continue; // find node with this event handler if (target_e.orig_onclick) target_e.orig_onclick(evt); if (target_e.id && (m = Menu.menus[target_e.id])) { // Found the menu container (
) element. // Keep menu displayed unless user clicks away. m.freeze(); break; } } if (! bv.isSafari) { evt.cancelBubble = true; if (evt.stopPropagation) evt.stopPropagation(); } return true; } Menu.onclick_handler_ignore = function(evt) { evt = Menu.get_evt(evt); var e = Menu.get_evt_target(evt); // Mouse click event handler attached to manual menu
tag. // This handler only gets attached to manual menus and only when // an onclick event handler is attached to the document body. It // captures and stops the click event bubbling. It allows "manual" // menus for entering form fields to exist on a page that also // uses "auto" menus -- menus that are to disappear when clicking // on the document body. debug("Menu.onclick_handler_ignore: e.id="+e.id, e); if (e.orig_onclick) e.orig_onclick(evt); evt.cancelBubble = true; if (evt.stopPropagation) evt.stopPropagation(); if (bv.isSafari || bv.isKonq) { while (e) { if (e.href && e.href.match(Menu.submenu_re)) { // Capturing clicks in Safari prevents element // from executing href. submenu(Menu.get_menu_id(e.href)); break; } e = e.parentNode; } } return true; } Menu.onclick_body_handler = function(evt) { // Mouse click event handler attached to the tag. debug("Menu.onclick_body_handler()"); hide_menus(); if (Menu.orig_onclick_body_handler) { debug("Menu.onclick_body_handler(): executing original handler"); return Menu.orig_onclick_body_handler(evt); } } Menu.onresize_handler = function(evt) { // Event handler called when the browser window is resized. // Resizing the window causes all the document inline element's // coordinates to change (e.g. the menu header elements) which // causes all the cached menu coordinates to be invalid. debug("Menu.onresize_handler()"); hide_menus('relocate'); if (Menu.orig_onresize_handler) return Menu.orig_onresize_handler(evt); } /* * Freezing methods */ // Note: instead of having a bunch of flags saving state when a menu is frozen // or not, we simply remove the element's onMouseOut event handler temporarily // and put it back when the user clicks on the document body. // Class Method Menu.onclick_body_handler_thaw = function(evt) { // Mouse click event handler attached to the tag. // It interfaces with the object methods. debug("Menu.onclick_body_handler_thaw(): menu="+ (Menu.frozen_menu ? Menu.frozen_menu.id : "NULL")); if (Menu.frozen_menu) Menu.frozen_menu.thaw(); } Menu.prototype.freeze = function() { debug("Menu.freeze("+this.id+")"); // Hide menus still open because of ClickStop mode. // This handles the case where menus are on top of menu panels. if (this.is_fixed) this.hide_opened_menus(); if (Menu.frozen_menu) { if (Menu.frozen_menu == this) { // Ignore repeated clicks over frozen element. return; } else { Menu.frozen_menu.thaw(); } } this.frozen_e = this.e; this.frozen_onmouseout = this.frozen_e.onmouseout; this.frozen_e.onmouseout = null; this.saved_body_onclick = document.body.parentNode.onclick; document.body.parentNode.onclick = Menu.onclick_body_handler_thaw; if (Menu.is_effects_loaded && autoplay.is_activated) autoplay.pause(); Menu.frozen_menu = this; // Mozilla fires a mouseout event just before processing a click event. if (bv.isMoz) this.show_menu(); // ensure menu is displayed } Menu.prototype.thaw = function() { debug("Menu.thaw("+this.frozen_e.id+")"); if (this.frozen_e) { // Put back onmouseout and the body onclick event handlers. this.frozen_e.onmouseout = this.frozen_onmouseout; document.body.parentNode.onclick = this.saved_body_onclick; } this.frozen_e = null; // clear for use as a flag Menu.frozen_menu = null; if (! this.top.keep_open) { // Hide menu chain from bottom up. if (this.child) this.hide_submenu(); var m = this; do { debug("Menu.thaw(): hiding menu "+m.id); m.hide_menu(); } while ((m = m.parent)); } // Restart autoplay if paused. if (Menu.is_effects_loaded && autoplay.is_activated) autoplay.resume(); } /* * Menu builder */ // Elements to ignore when trying to find submenus. Menu.filter_object = { "#text": true, "SELECT": true, "TEXTAREA": true}; Menu.build_submenus = function(m,e) { // Recursive function to traverse the nodes contained in the menu // element looking for submenus and building menu objects for each. debug("Menu.build_submenus: checking e.nodeName="+e.nodeName+", href="+e.href); if (e.href && e.href.match(Menu.submenu_re)) { // Found submenu() function in href attribute. var id = Menu.get_menu_id(e.href); var submenu = m.make_submenu(id,e); debug("Menu.build_submenus: found submenu("+id+") for "+m.id); // Look in submenu for more submenus. Menu.build_submenus(submenu, getElementRef(id)); debug("Menu.build_submenus: pop return"); return; } if (e.childNodes) { var n; for (n = 0; n < e.childNodes.length; n++) { if (! Menu.filter_object[e.childNodes[n].nodeName]) { debug("Menu.build_submenus: recursing to "+ e.childNodes[n].nodeName+", n="+n); Menu.build_submenus(m, e.childNodes[n]); } } } debug("Menu.build_submenus: pop"); } Menu.set_enclosed_elements_handler = function(e) { // Set onmouseover handler to all the child elements of e // because NS6+ fires the mouseout event when the mouse // hovers over an ENCLOSED element. (!!) // Note: this routine is not recursive, it only looks at the // first level of child nodes directly under the element. See // onmouseout_handler() for the filtering of spurious events from // hovering over text within a TEXTAREA element or for hovering // over the scroll bar of a SELECT element. var child_e; if (e.childNodes) { var n; for (n = 0; n < e.childNodes.length; n++) { child_e = e.childNodes[n]; if (child_e.nodeType != 1) continue; // Save original mouseover event. if (child_e.onmouseover && child_e.onmouseover != Menu.onmouseover_handler) child_e.orig_onmouseover = child_e.onmouseover; child_e.onmouseover = Menu.onmouseover_handler; debug("Menu.set_enclosed_elements_handler: child_e=",child_e); } } } Menu.prototype.make_submenu = function(menu_id, header) { // Attach a new menu as a submenu to this menu. var submenu = new Menu(menu_id, header, this.manual, this.keep_open); // Link parent objects of this menu. submenu.parent = this; submenu.top = this.top; // Set flag. submenu.is_submenu = true; // Set Z index so submenu is on top of parent. submenu.z = this.z + 10; return submenu; } Menu.prototype.configure_menu = function() { // Assign global class settings to object instance settings. // Configuration settings are only stored in the top menu object. // Note: Instead of having a bunch of assignment statements, // scan the components of the Menu class object and copy them // to this object. To filter out functions and class working // variables, we have cleverly named all the configuration // constants with capitalized names. All the other private // variables and function names in Menu begin with lowercase // characters. var v,c; for (v in Menu) { c = v.substr(0,1); if (c == c.toUpperCase()) { debug("Menu.configure_menu(): this."+v+" = "+Menu[v]); this[v] = Menu[v]; } } } Menu.prototype.setup_menu = function() { // Attach event handlers to menu element. // Note: to allow make_menu() to be called before menus are defined in the // document, we delay assigning the menu element till now where the user // is actually trying to show the menu. if (! this.e) this.e = getElementRef(this.id); if (! this.e) { debug('Menu: ERROR: cannot find the menu "'+this.id+'"'); return; } if (this.manual) { // Manual menus do not use event handlers, so header elements are // not known, which means auto-placement of submenus is not possible. this.is_located = true; this.is_finalized = true; // Manual menus do not get "located", so their Z indexes don't // get set. As a convenience, set the Z index here. this.e.style.zIndex = Menu.ZIndex; this.AllowReposition = false; // force off // Attach a click event handler to the menu to ignore clicks // if there is already a click event handler on the document // body. The document body click handler is used by auto menus // to hide all shown menus. This allows a manual menu used for // entering form fields to coexist on the same page as auto // menus. if (document.body.parentNode.onclick && ! this.e.onclick) { this.e.onclick = Menu.onclick_handler_ignore; } } else { if (! this.is_submenu) this.setup_top_menu(); // Simplify flags determining if this menu is a fixed menu // (a menu whose coordinates are predetermined). this.is_fixed = this.is_submenu ? (this.top.SubmenuFixed || false) : (this.top.MenuFixed || false); debug("Menu.setup_menu: menu is_fixed: "+this.is_fixed,this.e); // Setup auto menu event handlers. // Save current onmouseover and onmouseout event handlers. if (this.e.onmouseover && this.e.onmouseover != Menu.onmouseover_handler) this.e.orig_onmouseover = this.e.onmouseover; if (this.e.onmouseout && this.e.onmouseout != Menu.onmouseout_handler) this.e.orig_onmouseout = this.e.onmouseout; if (this.e.onclick && this.e.onclick != Menu.onclick_handler) this.e.orig_onclick = this.e.onclick; // Define event handlers. this.e.onmouseover = Menu.onmouseover_handler; this.e.onmouseout = Menu.onmouseout_handler; if (this.top.AllowFreeze) this.e.onclick = Menu.onclick_handler; if (bv.isNS) Menu.set_enclosed_elements_handler(this.e); } // Set flag to adjust the display style setting only if it // is initially "display:none". This is fluff. It is used // because setting the display style to none on fixed menus // causes some browsers to jiggle the display. this.set_display_none = (getElementStyle(this.e,"display") == "none") || bv.isSafari ? true : false; this.is_setup = true; } Menu.prototype.setup_top_menu = function() { // Finish setting up configuration variables. // Adjust settings that are dependent on others. // This is only done on top menus, not submenus. if (this.ClickStart) this.ClickStop = true; // force on if (this.HideDelayTime) { // Convert time to milliseconds this.HideDelayTime = parseFloat(this.HideDelayTime) * 1000.0; this.ClickStop = true; // force on debug("Menu.setup_top_menu: HideDelayTime="+this.HideDelayTime+ " setting ClickStop to true"); } if (Menu.is_effects_loaded) { this.setup_triggers(); this.setup_fade(); } } Menu.prototype.locate_menu = function() { // Locate menu on page. debug("Menu.locate_menu: this.evt="+this.evt); if (this.evt) { if (this.is_submenu) { this.compute_position_submenu(); } else { this.compute_position_menu(); } // Note: Give IE5 a left coordinate and it places it 2 pixels // to the right except for the left most column of a frameset. // Likewise, IE5 places an object 2 pixels down from the // coordinate specified except for the top row of a frameset. // I cannot find a way to determine if a frame is a top frame // or a left frame, so it is assumed that if there is a // frameset, the menus are in the top left frame so fudge factor // is set to 0. var fudge = bv.isIE5 && self.parent.frames.length == 0 ? -2 : 0; // Assign computed position to the menu element. this.e.style.left = (this.x + fudge) + "px"; this.e.style.top = (this.y + fudge) + "px"; if (bv.isOpr5) this.e.style.pixelTop = (this.y + fudge); debug("Menu.locate_menu: left="+this.e.style.left+", top="+this.e.style.top); } this.e.style.zIndex = this.z; if (this.top.CacheLocation && (! this.parent || this.parent.is_located)) this.is_located = true; // set this.is_finalized = false; // reset } Menu.prototype.locate_static_menu = function(submenu) { // A menu that does not have a header is an static menu. It is always // visible, It does not pop-up. Locating the menu on the page // needs to be delayed until the mouse activates one of its menu items. var p = this.top.SubmenuPosition; if (p == "R" || p == "L") { // Only the x coordinate is needed because submenus are placed // relative to the y coordinate of the header element, not the menu. this.x = getEventElementLeft(submenu.evt, this.e, this.get_border_width("left"), this.get_padding("left")); } else { this.y = getEventElementTop(submenu.evt, this.e, this.get_border_width("top"), this.get_padding("top")); } this.is_located = true; } Menu.prototype.finalize_menu = function() { // Compute final coordinates of menu. // Sometimes a menu has to be displayed before its width or // height can be determined so that it can be properly positioned. // This method computes those final placement coordinates. if (! this.e) return; this.is_finalized = true; if (this.manual) return; if (this.is_submenu) { this.finalize_position_submenu(); this.finalize_alignment_submenu(); } else { this.finalize_position_menu(); this.finalize_alignment_menu(); } // Note: see note in locate_menu() about IE5 placing objects in normal // windows and in framesets. var fudge = bv.isIE5 && self.parent.frames.length == 0 ? -2 : 0; // Assign recomputed position to the menu element. this.e.style.left = (this.x + fudge) + "px"; this.e.style.top = (this.y + fudge) + "px"; if (bv.isOpr5) this.e.style.pixelTop = this.y; debug("Menu.finalize_menu: left="+this.e.style.left+", top="+this.e.style.top); } Menu.prototype.save_event = function(evt) { // Save event properties needed later. // Mouse event properties are used to dynamically position menus and // submenus. Because of timing, we wait until actually showing the // menus to compute the location in the document, at which time // the event has gone out of scope. This method saves only the // properties that are needed later in a pseudo-event object. this.evt = { "target" : (evt.target ? evt.target : evt.srcElement ? evt.srcElement : null), "offsetX" : (evt.offsetX ? evt.offsetX : 0), "offsetY" : (evt.offsetY ? evt.offsetY : 0), "clientX" : (evt.clientX ? evt.clientX : 0), "clientY" : (evt.clientY ? evt.clientY : 0) }; } /* * Showing methods */ Menu.prototype.show_menu = function() { debug("Menu.show_menu("+this.id+"): is_visible="+this.is_visible); clearTimeout(this.hide_timer); clearTimeout(this.show_timer); if (this.is_submenu) this.show_parentmenu(); if (this.is_static) this.track_shown_keep_open_menus(); if (this.is_visible) return; // Schedule display of menu this.show_timer = setTimeout("Menu.menus['"+this.id+"'].do_show_menu()", this.top.TimeToShow); debug("Menu.show_menu("+this.id+"): schedule show: "+this.top.TimeToShow); } Menu.prototype.do_show_menu = function() { debug("Menu.do_show_menu("+this.id+"):"+ " is_visible="+this.is_visible+ ", is_static="+this.is_static); if (this.is_visible) return; // Last check for submenus rendered after parent hidden. (only in ClickStart mode) if (this.top.ClickStop && this.parent && ! this.parent.is_visible) return; if (! this.is_setup) this.setup_menu(); if (! this.is_fixed && (! this.is_located || (this.parent && ! this.parent.is_located))) this.locate_menu(); // Fading menus keep incrementing zIndex to keep new menus on top // to capture the mouse events first. This is especially useful // for fixed menus that overlap each other. if (Menu.is_effects_loaded && this.z > Menu.max_z) { Menu.max_z = this.z; } else { this.e.style.zIndex = this.z = ++Menu.max_z; } // Hide menus still open because of ClickStop mode. this.hide_opened_menus(); // Hide tear off menu before showing it again in its computed position. if (this.tear_off_menu) this.tear_off_menu.close(); // Show menu. if (Menu.is_effects_loaded) { this.show_menu_fade(); } else { if (this.set_display_none) this.e.style.display = "block"; this.e.style.visibility = "visible"; } if (! this.is_fixed) { if (! this.is_finalized) this.finalize_menu(); if (this.top.AllowReposition) this.reposition_menu(); } // Show select path by changing the menu header's class name. if (this.header_e.className) { this.header_e.className += this.top.HighlightSuffix; } this.is_visible = true; if (! this.is_submenu && ! this.manual) this.track_shown_keep_open_menus(); if (this.top.OnShowMenu) this.top.OnShowMenu(this); } /* * Hiding methods */ Menu.prototype.hide_menu = function() { debug("Menu.hide_menu("+this.id+"): is_visible="+this.is_visible); clearTimeout(this.hide_timer); clearTimeout(this.show_timer); if (this.is_static) this.track_hidden_keep_open_menus(); if (! this.is_visible) return; // Schedule the menu to be hidden. this.hide_timer = setTimeout("Menu.menus['"+this.id+"'].do_hide_menu()", this.top.TimeToHide); debug("Menu.hide_menu("+this.id+"): schedule hide: "+this.top.TimeToHide); } Menu.prototype.do_hide_menu = function() { debug("Menu.do_hide_menu("+this.id+"):"+ " is_visible="+this.is_visible+ ", is_static="+this.is_static); if (! this.is_visible || this.is_static) return; // Hide menu. if (Menu.is_effects_loaded) { this.hide_menu_fade(); } else { if (! this.tear_off_menu) { this.e.style.visibility = "hidden"; if (this.set_display_none) this.e.style.display = "none"; } } // Return menu header back to original class (unselect menu item). if (this.header_e.className && this.top.HighlightSuffix) { var re = new RegExp(this.top.HighlightSuffix); this.header_e.className = this.header_e.className.replace(re,""); } this.is_visible = false; if (! this.is_submenu && ! this.manual) this.track_hidden_keep_open_menus(); if (this.top.OnHideMenu) this.top.OnHideMenu(this); } /* * Sub-menu methods */ Menu.prototype.show_submenu = function(menu_id,header) { // Show a submenu. debug("Menu.show_submenu("+menu_id+")"); var submenu = Menu.menus[menu_id]; // Dynamically build a submenu if needed. if (! submenu) submenu = this.make_submenu(menu_id,header); // Link child object of menu that is displayed. this.child = submenu; // Pass along event object for positioning. submenu.evt = this.evt; submenu.show_menu(); } Menu.prototype.hide_submenu = function() { // Recursive function to traverse the objects contained in the menu. debug("Menu.hide_submenu("+this.id+")"); // Hide all descendant menus from bottom up. if (this.child) this.child.hide_submenu(); this.hide_menu(); } /* * Parent-menu methods */ Menu.prototype.show_parentmenu = function() { // Ensure parent menus are displayed. debug("Menu.show_parentmenu("+this.id+")"); var m, child = this; while ((m = child.parent)) { debug("Menu.show_parentmenu("+this.id+"): showing parent "+m.id); m.show_menu(); // If the parent menu already has a child menu displayed, // hide it first and reconnect this menu to be its child // menu. if (m.child && m.child != child) m.child.hide_submenu(); m.child = child; // connect links child = m; } } /* * Open menu methods */ Menu.prototype.hide_opened_menus = function() { // Hide any menus that have remained opened if they // do not match this current menu's top menu. debug("Menu.hide_opened_menus():"); // Hide currently displayed menu if in ClickStop mode. if (Menu.click_stop_menu && Menu.click_stop_menu != this.top) { debug("Menu.hide_opened_menus(): hiding click_stop_menu: "+ Menu.click_stop_menu.id); Menu.click_stop_menu.hide_submenu(); Menu.click_start_enabled = false; } // Hide currently displayed menu if keep_open. if (this.keep_open) { var m = Menu.keep_open_menu[this.top.Group]; if (m && m != this.top) { debug("Menu.hide_opened_menus: hiding keep_open_menu: "+m.id); m.hide_submenu(); } } } Menu.prototype.track_shown_keep_open_menus = function() { // Track the ClickStop and keep_open menus and their state flags. // This gets called when showing top level menus. debug("Menu.track_shown_keep_open_menus:"); if (this.ClickStop) { // Track the currently displayed top menu. // Because of timing delays showing menus, a top menu // might have just been displayed and assigned to the // click_stop_menu. Check one last time. if (Menu.click_stop_menu && Menu.click_stop_menu != this) { clearTimeout(Menu.hide_delay_timer); Menu.click_stop_menu.do_hide_menu(); } Menu.click_stop_menu = this; Menu.click_start_enabled = true; debug("Menu.track_shown_keep_open_menus:"+ " setting click_start_enabled",this.e); } if (this.keep_open) { // Track the currently displayed top menu. // As above, check one last time for keep_open_menu. var m = Menu.keep_open_menu[this.top.Group]; if (m && m != this) m.do_hide_menu(); Menu.keep_open_menu[this.top.Group] = this; Menu.click_start_enabled = false; // reset debug("Menu.track_shown_keep_open_menus:"+ " setting keep_open_menu",this.e); } } Menu.prototype.track_hidden_keep_open_menus = function() { // Track the ClickStop and keep_open menus and their state flags. // This gets called when hiding top level menus. debug("Menu.track_hidden_keep_open_menus:"); if (Menu.click_stop_menu && Menu.click_stop_menu == this) { debug("Menu.track_hidden_keep_open_menus:"+ " clear click_stop_menu:",Menu.click_stop_menu.e); if (this.click_stop_auto_set) this.click_stop_auto_set = this.ClickStop = false; Menu.click_stop_menu = null; } var m = Menu.keep_open_menu[this.top.Group]; if (m && m == this) { debug("Menu.track_hidden_keep_open_menus:"+ " clear keep_open_menu:",m.e); Menu.keep_open_menu[this.top.Group] = null; } } /* * Positioning methods */ Menu.prototype.compute_position_menu = function() { // Position menu relative to the lower left corner of the header // for a vertically oriented menu (V) positioned down (D). this.x = getEventElementLeft(this.evt, this.header_e, this.get_header_border_width("left"), this.get_header_padding("left")) + this.top.MenuOffsetX; this.y = this.get_header_top() + this.get_header_height() + this.top.MenuOffsetY; debug("Menu.compute_position_menu: x="+this.x+", y="+this.y); } Menu.prototype.compute_position_submenu = function() { // Position submenu relative to the upper right corner of the header. // for a vertically oriented submenu (V) positioned to the right (R). // Align submenu so first option is horizonal from header element. var parent = this.parent; if (parent.is_static && ! parent.is_located) parent.locate_static_menu(this); // If submenu orientation is vertical (the default), place menu outside // this menu's borders, otherwise place the menu next to the header's // upper right corner. this.x = (this.top.SubmenuOrientation == "V" && this.top.MenuOrientation == "V" ? parent.x + getElementWidth(parent.e) : getEventElementLeft(this.evt, this.header_e, this.get_header_border_width("left"), this.get_header_padding("left")) + getElementWidth(this.header_e) ) + this.top.SubmenuOffsetX; this.y = (this.top.SubmenuOrientation != "N" ? this.compute_vert_menu_y() : this.get_header_top() ) + this.top.SubmenuOffsetY; debug("Menu.compute_position_submenu: x="+this.x+", y="+this.y); } Menu.prototype.finalize_position_menu = function() { // Readjust coordinates for menus poping up or to the left. // The menu's height or width needs to be known first before adjusting // the coordinates, so the menu is rendered first and then queried to // find its size. // At this point, the default MenuPosition is "D" and the x,y coordinate // is currently the lower left corner of the header. debug("Menu.finalize_position_menu: MenuPosition="+this.top.MenuPosition); switch (this.top.MenuPosition) { case "U": // Up this.y -= getElementHeight(this.e) + this.get_header_height() + this.top.MenuOffsetY * 2; break; case "L": // Left this.x -= getElementWidth(this.e) + this.top.MenuOffsetX * 2; this.y = this.get_header_top() + this.top.MenuOffsetY; break; case "R": // Right this.x += getElementWidth(this.header_e); this.y = this.get_header_top() + this.top.MenuOffsetY; break; case "D": // Down default: // do nothing } } Menu.prototype.finalize_position_submenu = function() { // Readjust coordinates for submenus poping up or to the left. // This does the same function for submenus as // finalize_position_menu() does for menus above. // At this point, the default MenuPosition is "R" and the x,y coordinate // is currently the upper right corner of the header. debug("Menu.finalize_position_submenu: SubmenuPosition="+this.top.SubmenuPosition); switch (this.top.SubmenuPosition) { case "L": // Left this.x = (this.top.SubmenuOrientation == "V" && this.top.MenuOrientation == "V" ? this.parent.x : getEventElementLeft(this.evt, this.header_e, this.get_header_border_width("left"), this.get_header_padding("left")) ) - getElementWidth(this.e) - this.top.SubmenuOffsetX; break; case "D": // Down this.x = (this.top.SubmenuOrientation != "N" ? this.compute_horz_menu_x() : getEventElementLeft(this.evt, this.header_e, this.get_header_border_width("left"), this.get_header_padding("left")) ) + this.top.SubmenuOffsetX; this.y = (this.top.SubmenuOrientation == "H" && this.top.MenuOrientation == "H" ? this.parent.y + getElementHeight(this.parent.e, this.parent.getBorderHeight()) : this.get_header_top() + this.get_header_height() ) + this.top.SubmenuOffsetY; break; case "U": // Up this.x = (this.top.SubmenuOrientation != "N" ? this.compute_horz_menu_x() : getEventElementLeft(this.evt, this.header_e, this.get_header_border_width("left"), this.get_header_padding("left")) ) + this.top.SubmenuOffsetX; this.y = (this.top.SubmenuOrientation == "H" && this.top.MenuOrientation == "H" ? this.parent.y : this.get_header_top() ) - getElementHeight(this.e, this.getBorderHeight()) - this.top.SubmenuOffsetY; break; case "R": // Right default: // do nothing } } Menu.prototype.finalize_alignment_menu = function() { switch (this.top.MenuAlign) { case "R": this.x -= getElementWidth(this.e) - getElementWidth(this.header_e) + this.top.MenuOffsetX; break; case "B": this.y -= getElementHeight(this.e) - getElementHeight(this.header_e) + this.top.MenuOffsetY; break; case "L": case "T": default: } } Menu.prototype.finalize_alignment_submenu = function() { switch (this.top.SubmenuAlign) { case "R": this.x -= getElementWidth(this.e) - getElementWidth(this.parent.e) + this.top.SubmenuOffsetX; break; case "B": this.y -= getElementHeight(this.e) - getElementHeight(this.parent.e) + this.top.SubmenuOffsetY; break; case "L": case "T": default: } } Menu.prototype.reposition_menu = function() { // Recompute menu coordinates if its display is off the screen. var reposition = false; var e = this.e; var b = document.body; // Window dimensions. var wwidth = window.innerWidth ? window.innerWidth : b.clientWidth ? b.clientWidth : b.offsetWidth; var wheight = window.innerHeight ? window.innerHeight : b.clientHeight ? b.clientHeight : b.offsetHeight; var wleft = b.scrollLeft ? b.scrollLeft : (b.parentNode && b.parentNode.scrollLeft) ? b.parentNode.scrollLeft : 0; var wtop = b.scrollTop ? b.scrollTop : (b.parentNode && b.parentNode.scrollTop) ? b.parentNode.scrollTop : 0; var wright = wleft + wwidth; var wbottom = wtop + wheight; // Element dimensions. var ewidth = e.offsetWidth ? e.offsetWidth : getElementWidth(e); var eheight = e.offsetHeight ? e.offsetHeight : getElementHeight(e); var eleft = e.offsetLeft ? e.offsetLeft : getElementLeft(e); var etop = e.offsetTop ? e.offsetTop : getElementTop(e); var eright = eleft + ewidth; var ebottom = etop + eheight; debug("Menu.reposition_menu: eleft="+eleft+", eright="+eright+ ", etop="+etop+", ebottom="+ebottom+", ewidth="+ewidth+", eheight="+eheight); debug("Menu.reposition_menu: wleft="+wleft+", wright="+wright+ ", wtop="+wtop+", wbottom="+wbottom+", wwidth="+wwidth+", wheight="+wheight); if (eright > wright) { eleft = wright - ewidth; reposition = true; } if (ebottom > wbottom) { etop = wbottom - eheight; reposition = true; } if (eleft < wleft) { eleft = wleft; reposition = true; } if (etop < wtop) { etop = wtop; reposition = true; } if (reposition) { // Reposition the menu to be within the window. this.e.style.left = eleft + "px"; this.e.style.top = etop + "px"; if (bv.isOpr5) this.e.style.pixelTop = etop; this.is_located = false; // reset for next time debug("Menu.reposition_menu: new position is: x="+eleft+", y="+etop); } } Menu.prototype.compute_horz_menu_x = function() { // Compute X coordinate for a horizontally oriented menu so that it // lines up vertically to the header element. Used when menu position // is "U" or "D". // The horizontal alignment is from the left of the header border // to just inside the submenu's border and padding. var l = getEventElementLeft(this.evt, this.header_e, this.get_header_border_width("left"), this.get_header_padding("left")); var o = tableOffset(this.e); var p = this.get_padding("left"); var b = this.get_border_width("left"); debug("Menu.compute_horz_menu_x"+ ": getElementLeft="+l+ ", tableOffset="+o+ ", this.get_padding('left)="+p+ ", this.get_border_width('left)="+b+ ", returning: "+(l-o-p-b),this.e); return l - o - p - b; } Menu.prototype.compute_vert_menu_y = function() { // Compute Y coordinate for a vertically oriented menu so that it // lines up horizontally to the header element. Used when menu position // is "L" or "R". // The vertical alignment is from the top of the header border // to just inside the submenu's border and padding. var t = this.get_header_top(); var o = tableOffset(this.e); var p = this.get_padding("top"); var b = this.get_border_width("top"); debug("Menu.compute_vert_menu_y"+ ": this.get_header_top="+t+ ", tableOffset="+o+ ", this.get_padding('top')="+p+ ", this.get_border_width('top')="+b+ ", returning: "+(t-o-p-b),this.e); return t - o - p - b; } Menu.prototype.get_header_top = function() { // This routine deals with the "bug" (a DOM deficiency) in // Netscape, Opera and Safari where an tag encloses an tag and // the the tag doesn't have the CSS style setting display:block;, // which is common. The top coordinate returned is the top of // the tag's text (the line-height), not the top of the image. // See "Cascading Style Sheets, The Definitive Guide" by Eric A. Meyer, p300, // for a discussion of images as inline replaced elements and their heights. var header_e = this.header_e; var hy; if (bv.isIE) { // IE returns the correct top coordinate. hy = getEventElementTop(this.evt, header_e, this.get_header_border_width("top"), this.get_header_padding("top")); debug("Menu.get_header_top: returning getEventElementTop measurement: "+hy); return hy; } hy = getEventElementTop(this.evt, header_e); // Look for an image surrounded by the header tag that's not a // block element. Opera cannot retreive style settings, so it always // compares results. // Note: this does not work for Opera 7 as both the and tags // return the same offset, which are both wrong in table cells that have // their size automatically increased because of the content of the // surrounding cells. The work-around is to surround the tag // with a
tag on the web page. var is_aimg = false; if (this.evt.target.nodeName == "IMG") { var d = getElementStyle(header_e,"display"); if (d == "inline") is_aimg = true; // DOM if (d == "") is_aimg = true; // Safari } if ((bv.isOpr5 || bv.isOpr6) && this.evt.target.toString().indexOf("HTMLImage") > 0) is_aimg = true; if (is_aimg) { debug("Menu.get_header_top: Comparing top of IMG tag enclosed by header A tag."); var iy = getEventElementTop(this.evt); // get image's top coordinate debug("Menu.get_header_top: image y="+iy+", header y="+hy+", returning "+(iy < hy ? iy : hy)); return iy < hy ? iy : hy; } debug("Menu.get_header_top: returning getEventElementTop measurement: "+hy); return hy; } Menu.prototype.get_header_height = function() { // Like get_header_top() above, this routine deals with the bug in // Netscape and Safari of an tag enclosing an tag. Unless // the tag has a display:block CSS style setting, the height of // the element is the text height, not the image height. var header_e = this.header_e; if (bv.isIE) return getElementHeight(header_e); var is_aimg = false; if (this.evt.target.nodeName == "IMG") { var d = getElementStyle(header_e,"display"); if (d == "inline") is_aimg = true; // DOM if (d == "") is_aimg = true; // Safari } if ((bv.isOpr5 || bv.isOpr6) && this.evt.target.toString().indexOf("HTMLImage") > 0) is_aimg = true; if (bv.isOpr7) is_aimg = true; if (is_aimg) { debug("Menu.get_header_height: Comparing height of IMG tag with header tag."); var ih = getElementHeight(this.evt.target); var hh = getElementHeight(header_e); return ih > hh ? ih : hh; } return getElementHeight(header_e); } /* * Style properties */ Menu.prototype.get_border_width = function(position) { // Get border width // Look at style settings before menu configuration variables. var width = getBorderWidth(this.e,position); debug("Menu.get_border_width("+position+"): found="+getBorderWidth.found+", width="+width,this.e); if (getBorderWidth.found) return width; debug("Menu.get_border_width("+position+"): reading BorderWidth constants"); if (this.is_submenu && this.top.SubmenuBorderWidth != null) return this.top.SubmenuBorderWidth; if (this.top.MenuBorderWidth != null) return this.top.MenuBorderWidth; return 0; } Menu.prototype.get_header_border_width = function(position) { // Get header border width // Look at style settings before menu configuration variables. var width = getBorderWidth(this.header_e,position); debug("Menu.get_header_border_width("+position+"): found="+getBorderWidth.found+", width="+width,this.header_e); if (getBorderWidth.found) return width; debug("Menu.get_header_border_width("+position+"): reading BorderWidth constants"); if (this.is_submenu) { if (this.top.SubmenuHeaderBorderWidth != null) return this.top.SubmenuHeaderBorderWidth; } else { if (this.top.MenuHeaderBorderWidth != null) return this.top.MenuHeaderBorderWidth; } return 0; } Menu.prototype.get_padding = function(position) { // Get padding // Look at style settings before menu configuration variables. var p = getPadding(this.e,position); debug("Menu.get_padding("+position+"): found="+getPadding.found+", padding="+p,this.e); if (getPadding.found) return p; debug("Menu.get_padding("+position+"): reading Padding constants"); if (this.is_submenu) { if (this.top.SubmenuPaddingHorz != null && (position == "left" || position == "right")) return this.top.SubmenuPaddingHorz; if (this.top.SubmenuPaddingVert != null && (position == "top" || position == "bottom")) return this.top.SubmenuPaddingVert; if (this.top.SubmenuPadding != null) return this.top.SubmenuPadding; } if (this.top.MenuPaddingHorz != null && (position == "left" || position == "right")) return this.top.MenuPaddingHorz; if (this.top.MenuPaddingVert != null && (position == "top" || position == "bottom")) return this.top.MenuPaddingVert; if (this.top.MenuPadding != null) return this.top.MenuPadding; return 0; } Menu.prototype.get_header_padding = function(position) { // Get padding // Look at element style settings before menu configuration variables. var p = getPadding(this.header_e,position); debug("Menu.get_header_padding("+position+"): found="+getPadding.found+", padding="+p,this.header_e); if (getPadding.found) return p; debug("Menu.get_header_padding("+position+"): reading Padding constants"); if (this.is_submenu) { if (this.top.SubmenuHeaderPaddingHorz != null && (position == "left" || position == "right")) return this.top.SubmenuHeaderPaddingHorz; if (this.top.SubmenuHeaderPaddingVert != null && (position == "top" || position == "bottom")) return this.top.SubmenuHeaderPaddingVert; if (this.top.SubmenuHeaderPadding != null) return this.top.SubmenuHeaderPadding; } return 0; } Menu.prototype.getBorderWidth = function() { if (this.BorderWidth == null) this.BorderWidth = Math.floor((this.get_border_width("left") + this.get_border_width("right"))/2); return this.BorderWidth; } Menu.prototype.getBorderHeight = function() { if (this.BorderHeight == null) this.BorderHeight = Math.floor((this.get_border_width("top") + this.get_border_width("bottom"))/2); return this.BorderHeight; } /* ------------------------------------------------------------------------ */ /* * Utility functions */ function parseNum(s) { // Always return a parsed integer. No NaN! if (s) return parseInt("0"+s,10); return 0; } function getElementRef(id) { // Return a reference to the document element. // The id can be a string or an object. if (! id) return null; if (typeof id == "string") { if (document.getElementById) return document.getElementById(id); if (document.all) return document.all[id]; return null; } if (typeof id == "object") return id; return null; } function getEventElementLeft(evt,target_e ,border_width,padding_width) { // Get the left (x) coordinate of target element. // The coordinate includes padding and border but not the margin. // The specified target element can be a container element to the // element actually receiving the event. // The border_width argument is used only for IE4 or IE5. // The padding argument is used only for IE5Mac. // They allow constants to be used for specifying target border size // and padding. They are computed if missing. // Theoretically this routine should not be necessary to find // an element's coordinates, but some cases still use the mouse // coordinates to find an element's coordinate. if (! evt) evt = window.event; // IE default value if (! target_e) target_e = evt.target ? evt.target : evt.srcElement ? evt.srcElement : null; target_e = getElementRef(target_e); // can be string or object var x; // returned coordinate var e, ox; var target_offset = 0; if (bv.isIE5 || bv.isOpr5) { if (border_width == null) border_width = getBorderWidth(target_e,"left"); // Get element initially receiving the event. e = evt.target ? evt.target : evt.srcElement ? evt.srcElement : null; debug("getEventElementLeft(): IE5: begin evt.offsetX="+evt.offsetX+ ", border_width="+border_width,e); // Check for the case of: // Unless a width has been specified for an tag, it is not // in the chain of nodes traversed by the offsetParent link. if (! e.nodeName || e.nodeName != "IMG" || ! e.parentNode || e.parentNode.nodeName != "A" || ! e.offsetParent || e.offsetParent.nodeName == "A") { while (e) { // Add offset between event element and target element. if (e == target_e) break; debug("getEventElementLeft(): IE5: adding to"+ " target_offset "+e.offsetLeft+ ", target_e="+target_e.nodeName ,e); target_offset += e.offsetLeft; e = e.offsetParent; } } target_offset += border_width; x = evt.clientX - evt.offsetX - target_offset; if (bv.isIE5) x += document.body.scrollLeft; debug("getEventElementLeft(): IE5"+ " Computed x="+x+ ", (evt.offsetX="+evt.offsetX+ ", evt.clientX="+evt.clientX+ ", scrollLeft="+document.body.scrollLeft+ ", target_offset="+target_offset+ ", border_width="+border_width+ ", padding_width="+padding_width+ ")",target_e); return x; } else if (bv.isIE5Mac) { if (border_width == null) border_width = getBorderWidth(e,"left"); if (padding_width == null) padding_width = getPadding(e,"left"); // Get element initially receiving the event. e = evt.target ? evt.target : evt.srcElement ? evt.srcElement : null; debug("getEventElementLeft(): IE5Mac: begin "+ ", padding_width="+padding_width+", border="+border_width,e); // Check for the case of: if (e.nodeName && e.nodeName == "IMG" && e.parentNode && e.parentNode.nodeName == "A" && e.offsetParent && e.offsetParent.nodeName != "A") { x = evt.clientX - evt.offsetX - border_width - padding_width; debug("getEventElementLeft(): IE5Mac:"+ " Computed x="+x+", (ox="+ox+ ", evt.clientX="+evt.clientX+ ", evt.offsetX="+evt.offsetX+ ", border_width="+border_width+ ", padding_width="+padding_width+ ")",target_e); } else { ox = 0; target_found = false; while (e) { debug("getEventElementLeft(): IE5Mac: adding "+e.offsetLeft,e); ox += e.offsetLeft; if (e == target_e) target_found = true; // Add offset between event element and target element. if (! target_found) { target_offset += e.offsetLeft; } e = e.offsetParent; } target_offset += border_width + padding_width; x = getMargin(document.body,"left") + ox - target_offset; debug("getEventElementLeft(): IE5Mac:"+ " Computed x="+x+", (ox="+ox+ ", border_width="+border_width+ ", padding_width="+padding_width+ ", target_offset="+target_offset+ ")",target_e); } return x; } else if (bv.isOpr6) { x = getElementLeft(target_e); debug("getEventElementLeft(): Opr6: Computed x="+x); return x; } else { // (bv.isIE6 || bv.isNS || bv.isMoz || bv.isOpr7 || bv.isSafari) // Ignore TEXT nodes, go up to first ELEMENT node. e = target_e; while (e && e.nodeType != 1) { e = e.parentNode }; x = getElementLeft(e); debug("getEventElementLeft(): DOM: Computed x="+x); return x; } return 0; // cannot determine coordinate } function getEventElementTop(evt,target_e ,border_width,padding_width) { // Get the top (y) coordinate of target element. // The coordinate includes padding and border but not the margin. // The specified target element can be a container element to the // element actually receiving the event. // All arguments are optional except evt in Netscape (DOM). // The border_width argument is used only for IE4 or IE5. // The padding_width argument is used only for IE5Mac. // They allow constants to be used for specifying target border size // and padding. They are computed if missing. // Like getEventElementLeft(), this routine should not be // necessary, but mouse coordinates are still used to compute an // element's coordinate in older browsers. // Note: with the exception of Internet Explorer, the target element // must be a block type element (not inline) otherwise the Y // coordinate is incorrect. It is based on the text size, and if // the element is an tag enclosing an image, it is incorrect. if (! evt) evt = window.event; // IE default value if (! target_e) target_e = evt.target ? evt.target : evt.srcElement ? evt.srcElement : null; target_e = getElementRef(target_e); // can be string or object debug("getEventElementTop(): target_e is",target_e); var y; // returned coordinate var e, oy; var target_offset = 0; if (bv.isIE5 || bv.isIE5Mac || bv.isOpr5) { if (border_width == null) border_width = getBorderWidth(target_e,"top"); if (bv.isIE5Mac && padding_width == null) padding_width = getPadding(target_e,"top"); // Get element initially receiving the event. e = evt.target ? evt.target : evt.srcElement ? evt.srcElement : null; debug("getEventElementTop(): IE5: begin evt.offsetY="+evt.offsetY+ ", border_width="+border_width,e); // Get offset within element receiving the event. oy = evt.offsetY; // Check for the case of: // Unless a width has been specified for an tag, it is not // in the chain of nodes traversed by the offsetParent link. // This also applies to Opera 5 but e.nodeName is undefined // so we cannot check it or correct it. if (! e.nodeName || e.nodeName != "IMG" || ! e.parentNode || e.parentNode.nodeName != "A" || ! e.offsetParent || e.offsetParent.nodeName == "A") { while (e) { // Add offset between event element and target element. if (e == target_e) break; debug("getEventElementTop(): IE5: adding to"+ " target_offset "+e.offsetTop+ ", target="+target_e.nodeName ,e); target_offset += e.offsetTop; e = e.offsetParent; } } target_offset += border_width; if (bv.isIE5Mac) target_offset += padding_width; y = evt.clientY - oy - target_offset; if (bv.isIE5) y += document.body.scrollTop; debug("getEventElementTop(): IE5:"+ " Computed y="+y+ ", (oy="+oy+ ", scrollTop="+document.body.scrollTop+ ", evt.clientY="+evt.clientY+ ", target_offset="+target_offset+ ", border_width="+border_width+ ")",target_e); return y; } else if (bv.isOpr6) { y = getElementTop(target_e) + Menu.BodyMarginTop; debug("getEventElementTop(): Opr6: Computed y="+y); return y; } else { // (bv.isIE6 || bv.isNS || bv.isMoz || bv.isOpr7 || bv.isSafari) // Ignore TEXT nodes, go up to first ELEMENT node. e = target_e; while (e && e.nodeType != 1) { e = e.parentNode }; y = getElementTop(e); debug("getEventElementTop(): DOM: Computed y="+y); return y; } return 0; // cannot determine coordinate } function getElementLeft(e) { // Get the left (x) coordinate of an element. // The coordinate includes padding and border but not the margin. // This only works with version 6 browsers (IE6, NS6, Opr7). debug("getElementLeft(): begin e.offsetLeft="+e.offsetLeft,e); var x = e.offsetLeft; while ((e = e.offsetParent)) { debug("getElementLeft(): adding e.offsetLeft="+e.offsetLeft+ " and left border.",e); x += e.offsetLeft; if (bv.isSafari || bv.isKonq) { // Positioned elements should exclude body margins if (getElementStyle(e,'position') == 'absolute') break; } else { if (! ((bv.isNS71 || bv.isIE6 || bv.isMoz) && e.nodeName == 'TABLE') ) { x += getBorderWidth(e,"left"); } } // Note: Netscape adds an extra 1px to the border between // cells of tables that have a border defined. It gets // returned in the getBorderWidth() for the TD or TH // element when stepping through offsetParent nodes. // Internet Explorer 6 doesn't include its added pixel // in getBorderWidth(), so it needs to add that extra pixel. if (e.border && parseNum(e.border) > 0) { if (bv.isNS || bv.isMoz) x--; if (bv.isIE6 || bv.isOpr7) x++; } } debug("getElementLeft(): Computed x="+x,e); return x; } function getElementTop(e) { // Get the top (y) coordinate of an element. // The coordinate includes padding and border but not the margin. // This only works with version 6 browsers (IE6, NS6, Opr7). debug("getElementTop(): begin e.offsetTop="+e.offsetTop,e); var y = e.offsetTop; while ((e = e.offsetParent)) { debug("getElementTop(): adding e.offsetTop="+e.offsetTop+ " and top border.",e); y += e.offsetTop; if (bv.isSafari || bv.isKonq) { // Positioned elements should exclude body margins. if (getElementStyle(e,'position') == 'absolute') break; } else { if (! ((bv.isNS71 || bv.isIE6 || bv.isMoz) && e.nodeName == 'TABLE') ) { y += getBorderWidth(e,"top"); } } // Note: Netscape adds an extra 1px to the border between // cells of tables that have a border defined, and one on top. if (parseNum(e.border) > 0) { if (bv.isNS || bv.isMoz) y--; if (bv.isIE6 || bv.isOpr7) y++; } } debug("getElementTop(): Computed y="+y,e); return y; } function getElementStyle(e, dom_prop, css_attr, dom_gen_prop, css_gen_attr, position, dom_border_prop, css_border_attr, component, dom_border_gen_prop, css_border_gen_attr) { // Get the element's style settings. // Both the DOM property names (e.g. borderBottomWidth) and the // corresponding CSS attribute names (border-bottom-width) // needs to be specified. If css_attr not set, it is set to dom_prop. // // General properties and attributes for dom_prop and css_attr // are checked if specified. For example: a general property dom_gen_prop // for borderBottomWidth is borderWidth, and the css_gen_attr is // "border-width". The position argument can be one of: // "top", "bottom", "left", or "right". // // The dom_border_gen_prop and css_border_gen_attr are for the // special shortcut method of specifying borders like: // "border:solid black 1px" // // The last 5 arguments are only used for borders. Only the first two // arguments are mandatory. // // The multiple components of the "font" attribute are not parsed here // as they are for borders. if (! css_attr) css_attr = dom_prop; debug("getElementStyle(): checking element style, dom_prop="+dom_prop+", css_attr="+css_attr,e); var sty; if (e.style) { if ((sty = e.style[dom_prop])) { debug("getElementStyle(): e.style["+dom_prop+"]="+sty,e); } else if (dom_gen_prop != null && (sty = e.style[dom_gen_prop])) { debug("getElementStyle(): e.style["+dom_gen_prop+"],"+position+"="+sty,e); sty = parseStyleSetting(sty,position); } else if (dom_border_prop != null && (sty = e.style[dom_border_prop])) { debug("getElementStyle(): e.style["+dom_border_prop+"]="+sty,e); sty = parseBorderSetting(sty,component); } else if (dom_border_gen_prop != null && (sty = e.style[dom_border_gen_prop])) { debug("getElementStyle(): e.style["+dom_border_gen_prop+"]="+sty,e); sty = parseBorderSetting(sty,component); } if (sty) { if (component == null || component != "width") { return sty; } else { // IE5Mac returns "medium" for unset border widths. // Return style attribute setting only if it is a number, // otherwise look in style sheet settings below. if (sty.match(/\d/)) return sty; } } } debug("getElementStyle(): checking document.defaultView"); var vs, v = document.defaultView; if (v && v.getComputedStyle && (vs = v.getComputedStyle(e,""))) { // W3C DOM (Netscape) sty = vs.getPropertyValue(css_attr); if (sty != null) { debug("getElementStyle(): getComputedStyle("+css_attr+")="+sty,e); // Note: Firefox 1.0 (and others?) returns incorrect // margin and padding widths. if (sty == '0px' && e.nodeName == 'TABLE' && (css_gen_attr == 'padding' || css_gen_attr == 'margin')) { ; // fall through and look in style sheet. debug("getElementStyle(): falling through to check stylesheet",e); } else { return sty; } } if (css_gen_attr != null) { sty = parseStyleSetting(vs.getPropertyValue(css_gen_attr),position); if (sty != null) { debug("getElementStyle(): getComputedStyle("+css_gen_attr+ ","+position+")="+sty,e); return sty; } } if (css_border_attr != null) { sty = parseBorderSetting( vs.getPropertyValue(css_border_attr),component); if (sty != null) { debug("getElementStyle(): getComputedStyle("+ css_border_attr+")="+sty,e); return sty; } } if (css_border_gen_attr != null) { sty = parseBorderSetting( vs.getPropertyValue(css_border_gen_attr),component); if (sty != null) { debug("getElementStyle(): getComputedStyle("+ css_border_gen_attr+")="+sty,e); return sty; } } } debug("getElementStyle(): checking e.currentStyle"); if (e.currentStyle) { // IE5 not IE4 sty = e.currentStyle[dom_prop]; if (sty != null) { debug("getElementStyle(): e.currentStyle["+dom_prop+"]="+sty,e); return sty; } if (dom_gen_prop != null) { sty = parseStyleSetting(e.currentStyle[dom_gen_prop],position); if (sty != null) { debug("getElementStyle(): e.currentStyle["+dom_gen_prop+ "],"+position+"="+sty,e); return sty; } } if (dom_border_prop != null) { sty = parseBorderSetting(e.currentStyle[dom_border_prop],component); if (sty != null) { debug("getElementStyle(): e.currentStyle["+dom_border_prop+ "]="+sty,e); return sty; } } if (dom_border_gen_prop != null) { sty = parseBorderSetting(e.currentStyle[dom_border_gen_prop],component); if (sty != null) { debug("getElementStyle(): e.currentStyle["+dom_border_gen_prop+ "]="+sty,e); return sty; } } } debug("getElementStyle(): checking stylesheets rules"); // Brute force. Check all the rules in all the stylesheets. // This is from Apple Computer, plucked from O'Reilly Network. // Safari v1 gets styles like: // document.styleSheets[0].rules[0].selectorText // whereas v2 is like: // document.styleSheets[0].cssRules[0].styleSheet.cssRules[0].selectorText // Try styleSheets var sheets = document.styleSheets; var sheet, s; if(sheets && sheets.length > 0) { // loop over each sheet for(var x = 0; x < sheets.length; x++) { // grab stylesheet rules var rules = sheets[x].cssRules; if(rules && rules.length > 0) { // check each rule for(var y = 0; y < rules.length; y++) { if (rules[y].style) { // Safari v1 s = getElementStyleFromRule(e,css_attr,rules[y]); if (s != null) sty = s; } else { // Safari v2 sheet = rules[y].styleSheet; if (sheet) { for (var z = 0; z < sheet.cssRules.length; z++) { s = getElementStyleFromRule(e,css_attr,sheet.cssRules[z]); if (s != null) sty = s; } } } } } } } if (sty != null) { debug("getElementStyle(): returning stylesheet style: "+sty,e); return sty; } debug("getElementStyle(): Cannot find style for "+dom_prop+"("+css_attr+")",e); return null; } function getElementStyleFromRule(e, css_attr, rule) { var t = rule.selectorText; var z = rule.style; if (bv.isSafari) { // Remove attribute selector strings that Safari // automatically adds to selectorText. if (t.substr(0,1) == '*') { // Safari reports id selectors #myid as *[ID"myid"] t = '#' + t.substr(5,(t.length-7)); } else { // Safari reports class selectors DIV.myclass[CLASS~="myclass"] // The following weird syntax is to pass IE5 on Mac. t = t.replace((new RegExp("\\[.*?\\]")),""); } } // Uncomment the following to look at all selectors. //debug("getElementStyle(): looking at selectorText=" + // rule.selectorText+", ("+t+")"); var re = e.className ? new RegExp("\\."+e.className+"$") : null; if(z[css_attr] != '' && z[css_attr] != null && ((t == e.nodeName || t.match(re)) || (e.id && t == '#' + e.id))) { debug("getElementStyleFromRule(): found stylesheet rule: " + "rule.style["+css_attr+"]="+z[css_attr],e); return z[css_attr]; } // Uncomment the following to look at selectors not found. debug("getElementStyleFromRule(): "+t+" rule does not have a setting for: "+css_attr,e); return null; } function parseStyleSetting(setting,position) { // Parse a style setting for top, bottom, left, or right measurement settings. if (! setting) return null; if (! position) return setting; // Match one, two, three, or four space separated settings. setting.match(/(\S+)(\s+(\S+)(\s+(\S+)(\s+(\S+))?)?)?/); if (position == "top") { return RegExp.$1; } if (position == "right") { if (RegExp.$3) return RegExp.$3; return RegExp.$1; } if (position == "bottom") { if (RegExp.$5) return RegExp.$5; return RegExp.$1; } if (position == "left") { if (RegExp.$7) return RegExp.$7; if (RegExp.$3) return RegExp.$3; return RegExp.$1; } } function parseBorderSetting(setting,component) { // Parse number of pixels from a border setting like: "solid black 1px". // The component argument can be "style", "color", or "width". // Only "width" is parsed here for the menus. if (! component || component == "width") { if (setting.match(/(\d+px)/)) return RegExp.$1; return 0; } } function getBorderWidth(e,position) { // Get element's border width. // Optional position argument can be "top", "bottom", "left", or "right". // This routine always returns a number. A static flag getBorderWidth.found // is set to true if style setting was found. debug("getBorderWidth() begin: position="+position,e); var width; var dom_gen_prop = "borderWidth"; var css_gen_attr = "border-width"; var dom_border_gen_prop = "border"; var css_border_gen_attr = "border"; if (! position) { width = getElementStyle(e, dom_gen_prop, css_gen_attr, null, null, null, dom_border_gen_prop, css_border_gen_attr, "width"); } else { // Capitalize position to use to assemble DOM property names. var Position = position.substr(0,1).toUpperCase()+position.substr(1); var dom_prop = "border" + Position + "Width"; //borderLeftWidth var css_attr = "border-" + position + "-width"; //border-left-width var dom_border_prop = "border" + Position; //borderLeft var css_border_attr = "border-" + position; //border-left width = getElementStyle(e, dom_prop, css_attr, dom_gen_prop, css_gen_attr, position, dom_border_prop, css_border_attr, "width", dom_border_gen_prop, css_border_gen_attr); } // Ignore keywords "thin", "medium", and "thick". if (width == null || ! width.match(/\d/)) { getBorderWidth.found = false; debug("getBorderWidth(): position="+position+", border width="+ (width?width:"''")+" NOT FOUND",e); return 0; } else { getBorderWidth.found = true; debug("getBorderWidth(): position="+position+", width="+width,e); // Parse number of pixels. Assume units are "px". return parseNum(width); } } function getPadding(e,position) { // Get element's padding dimension. // Optional position argument can be "top", "bottom", "left", or "right". // This routine always returns a number. A static flag getPadding.found // is set to true if style setting was found. var p,s = "padding"; if (! position) { p = getElementStyle(e,s,s); } else { // Capitalize position to use to assemble DOM property names. var Position = position.substr(0,1).toUpperCase()+position.substr(1); p = getElementStyle(e, (s+Position), (s+"-"+position), s, s, position); } if (p == null) { getPadding.found = false; debug("getPadding(): position="+position+", padding NOT FOUND",e); return 0; } else { getPadding.found = true; debug("getPadding(): position="+position+", padding="+p,e); // Parse number of pixels. Assume units are "px". return parseNum(p); } } function getMargin(e,position) { // Get element's margin dimension. // Optional position argument can be "top", "bottom", "left", or "right". // This routine always returns a number. A static flag getMargin.found // is set to true if style setting was found. var m,s = "margin"; if (! position) { m = getElementStyle(e,s,s); } else { // Capitalize position to use to assemble DOM property names. var Position = position.substr(0,1).toUpperCase()+position.substr(1); m = getElementStyle(e, (s+Position), (s+"-"+position), s, s, position); } if (m == null) { getMargin.found = false; debug("getMargin(): position="+position+", margin NOT FOUND",e); return 0; } else { getMargin.found = true; debug("getMargin(): position="+position+", margin="+m,e); // Parse number of pixels. Assume units are "px". return parseNum(m); } } function tableOffset(e) { // Return the sum of the cellspacing, cellpadding, and border dimensions. // If cellSpacing or cellPadding is not set, their value is set to 1. var offset = 0; // Opera 5 and 6 do not have nodeName properties. Assume e is correct. if (e && e.nodeName == "TABLE") { debug("tableOffset: computing offset:"+ " cellPadding="+e.cellPadding+ ", cellSpacing="+e.cellSpacing+ ", border="+e.border,e); offset = (e.cellPadding == "" ? 1 : parseNum(e.cellPadding)) + (e.cellSpacing == "" ? 1 : parseNum(e.cellSpacing)) + parseNum(e.border); if (e.cellPadding == "" && e.cellSpacing == "") offset++; } else if (bv.isOpr6 || bv.isOpr5) { debug("tableOffset: computing offset for Opera 5/6 & Moz:"+ " cellPadding="+e.cellPadding+ ", cellSpacing="+e.cellSpacing+ ", border="+e.border,e); offset = (e.cellPadding == null ? 1 : parseNum(e.cellPadding)) + (e.cellSpacing == null ? 1 : parseNum(e.cellSpacing)) + parseNum(e.border); } debug("tableOffset: offset="+offset,e); return offset; } /* Based on "Dynamic HTML The Definitive Reference" 2nd. Ed. by Danny Goodman p92. */ function getElementWidth(e) { // Width including padding and borders, not margins. if (!e) return; var result = 0; if (e.offsetWidth) { // (bv.isIE && bv.isIE5Mac) result = e.offsetWidth; debug("getElementWidth(): offsetWidth="+result,e); } else if (e.clip && e.clip.width) { // (bv.isNS && bv.isMoz) result = e.clip.width; debug("getElementWidth(): clip.width="+result,e); } else if (e.style && e.style.pixelWidth) {// bv.isOpr result = e.style.pixelWidth; debug("getElementWidth(): pixelWidth="+result,e); } return parseInt(result); } function getElementHeight(e) { // Height including padding and borders, not margins. if (!e) return; var result = 0; if (e.offsetHeight) { // Note: in Netscape && Opera, if element e is an tag // enclosing an , and the CSS style for the tag // display attribute has not been set to "block", the height // returned is the tag's text box height, not the // tag's height. result = e.offsetHeight; debug("getElementHeight(): offsetHeight="+result,e); } else if (e.clip && e.clip.height) { result = e.clip.height; debug("getElementHeight(): clip.height="+result,e); } else if (e.style && e.style.pixelHeight) { result = e.style.pixelHeight; debug("getElementHeight(): pixelHeight="+result,e); } return parseInt(result); } /* ------------------------------------------------------------------------ */ /* * Debug */ function debug(msg,e) { if (! debug.is_on) return; clearTimeout(debug.break_time); // Turn off debug if debug message window has been closed. if (! debug.w || debug.w.closed) { debug.is_on = false; return; } // Note: This does not work well in Opera 7 as it auto-closes the debug // window document after a pause which causes the next debug message // to erase the previous message. // Safari v1 also has a hard time because it fires a mouseout event // whenever it writes to another window. debug.w.document.write("
"); debug.w.document.writeln(msg); if (e && e.nodeName) { debug.w.document.writeln("   <"+e.nodeName); if (e.href) debug.w.document.writeln(" href="+e.href); if (e.id) debug.w.document.writeln(" id="+e.id); debug.w.document.writeln(">"); } debug.w.scrollBy(0,100); // an arbitrarily high number if (! bv.isOpr) debug.break_time = setTimeout( 'debug.w.document.writeln("

");debug.w.scrollBy(0,100)', 1000); } debug.on = function() { debug.is_on = true; if (! debug.w || debug.w.closed) { debug.w = window.open(); //debug.w.blur(); } debug.w.document.writeln("Date: "+(new Date)+"
"); debug.w.document.writeln("Browser User Agent: "+navigator.userAgent+"
"); debug.w.document.writeln("Browser version: "+bv()); } debug.off = function() { debug.is_on = false; } if (! debug.on) debug.is_on = false; // set to true for debug output. if (debug.is_on) debug.on(); // open debug window /* * Display mouse coordinates. */ function mouse_coord(evt) { // Display mouse coordinates in status bar. var x,y; if (window.event) { x = document.body.scrollLeft + window.event.x; y = document.body.scrollTop + window.event.y; } else { x = evt.pageX; y = evt.pageY; } window.status = "X=" + x + " Y=" + y; // Uncomment following to output mouse coordinates in debug window too. debug("x="+x+", y="+y); } mouse_coord.on = function() { if (! document.body) { // Need to wait for document to load before attaching event handler. setTimeout("mouse_coord.on()",1000); return; } // Attach event handler. document.body.onmousemove = mouse_coord; } mouse_coord.off = function() { document.body.onmousemove = null; window.status = ""; } if (mouse_coord.is_on) mouse_coord.on();