/** * $Id: zpmenu.js 4894 2006-10-24 19:24:24Z alex $ * @fileoverview Zapatec DHTML Menu Widget. * *
* Copyright (c) 2004-2006 by Zapatec, Inc. * http://www.zapatec.com * 1700 MLK Way, Berkeley, California, * 94709, U.S.A. * All rights reserved. **/ /** * Extends base Zapatec Widget class (utils/zpwidget.js). * *
* In addition to config options defined in base Zapatec.Widget class
* provides following config options:
*
* container [object or string] Element or id of element that will hold
* the menu. Required when sourceType is other than "html". If sourceType is
* "html" and "container" is not specified, menu "source" element will be
* replaced with the menu.
*
* dynamic [boolean] If true is passed, the tree will use the
* "dynamic initialization" technique which greatly improves generation time.
* Some functionality is not available in this mode until all the tree was
* generated. In "dynamic" mode the tree is initially collapsed and levels are
* generated "on the fly" as the end user expands them. You can't retrieve nodes
* by ID (which implies you can't synchronize to certain nodes) until they have
* been generated.
*
* showDelay [number] Delay before a submenu is shown, in milliseconds.
*
* hideDelay [number] Delay before a submenu is hidden, in milliseconds.
*
* onClick [boolean] Top menu drops on click not on hover.
*
* vertical [boolean] Make it a vertical menu.
*
* scrollWithWindow [boolean]
*
* dropShadow [number]
*
* drag [boolean]
*
* slide [boolean]
*
* glide [boolean]
*
* fade [boolean]
*
* wipe [boolean]
*
* unfurl [boolean]
*
* animSpeed [number] percentage animation per frame.
*
* defaultIcons [string] If set, all tree items will get an additional TD
* element containing that string in the class attribute. This helps you
* to include custom default icons without specifying them as IMG tags in
* the tree.
*
* zIndex [number] Can be used for two menus on the same page. Use higher
* value for menu which must be in front of other menus.
*
* rememberPath [boolean or string] Used to keep track of previous menu
* location. Optional if pathCookie flag value differs from "__zp_item".
* Possible values:
* 1) true: keep track.
* 2) false: do not keep track.
* 3) "expand": the menu will open expanded to this previously location.
*
* pathCookie [string] Used to keep track of previous menu location. Use
* this option with or instead of "rememberPath" when you need to specify which
* cookie will contain path information. This is needed e.g. when you have
* several menus on a page. If "rememberPath" option is not false and
* "pathCookie" option is not set, cookie name "__zp_item" will be used by
* default.
*
* triggerEvent [string] Event that will trigger showing of the menu.
* Possible values:
* 1) For mouse click: 'mousedown' or 'mouseup' or 'click' (no matter which, all
* values treated the same).
* 2) For keyboard: 'keydown' or 'keyup' or 'keypress' (no matter which, all
* values treated the same).
*
* triggerKey [number or string] Decimal keyboard scan code or mouse
* button: "left" or "both". Default: right mouse button.
* Requires "triggerEvent" to be set.
* See keyboard scan codes at:
* http://techwww.in.tu-clausthal.de/Dokumentation/Standards_Bussysteme/ASCII-Tabelle/
* http://www.nthelp.com/charts.htm
*
* triggerObject [string or object] Element id or HTMLElement object
* associated with the menu. E.g. div inside which user must click to open the
* menu. Default: window.document.
* Requires triggerEvent to be set.
* Also can be following array (to set trigger on several elements):
* [
* [object or string] HTMLElement object or element id ||
* {
* triggerObject: [object or string] HTMLElement object or element id,
* triggerArgs: [any] args that should be available for external scripts
* },
* ...
* ]
* When trigger menu is shown, its "triggerObject" property contains reference
* to trigger object that last invoked the menu, "triggerArgs" property contains
* corresponding arguments. External scripts can access those properties.
* This gives ability to attach menu to several objects and to pass through some
* piece of data from those objects to external scripts. E.g. to determine,
* which cell of the grid was clicked, etc.
* If array is empty (triggerObject: []), trigger objects are not set initially
* and can be set later using setTriggerObject() method.
*
* top [string] menu initial top offset.
* right [string] menu initial right offset.
* bottom [string] menu initial bottom offset.
* left [string] menu initial left offset.
* If set, top_parent div will be absolute positioned. Their values will be
* assigned to corresponding CSS properties of top_parent div.
* Important: For drag and scroll menus set either "top" or "bottom" and
* "right" or "left" options instead of putting menu inside absolute positioned
* div. Otherwise menu can be wrong positioned.
*
* onInit [function] function reference to call when menu is initialized.
* Can be used e.g. to disable certain items, etc.
*
*
* @constructor
* @extends Zapatec.Widget
* @param {object} objArgs User configuration
*/
Zapatec.Menu = function(objArgs) {
// For backward compatibility with v1.1
if (arguments.length > 1) {
var objConfig = arguments[1];
objConfig.source = arguments[0];
objArgs = objConfig;
}
// Call constructor of superclass
Zapatec.Menu.SUPERconstructor.call(this, objArgs);
};
// Inherit Widget
Zapatec.inherit(Zapatec.Menu, Zapatec.Widget);
/**
* Initializes menu.
*
* @param {object} objArgs User configuration
*/
Zapatec.Menu.prototype.init = function(objArgs) {
// Define config options
this.config.container = null;
this.config.dynamic = false;
this.config.showDelay = 0;
this.config.hideDelay = 500;
this.config.onClick = false;
this.config.vertical = false;
this.config.scrollWithWindow = false;
this.config.dropShadow = 0;
this.config.drag = false;
this.config.slide = false;
this.config.glide = false;
this.config.fade = false;
this.config.wipe = false;
this.config.unfurl = false;
this.config.animSpeed = 10;
this.config.defaultIcons = null;
this.config.zIndex = 0;
this.config.rememberPath = false;
this.config.pathCookie = '__zp_item';
this.config.triggerEvent = null;
this.config.triggerKey = null;
this.config.triggerObject = null;
this.config.top = null;
this.config.right = null;
this.config.bottom = null;
this.config.left = null;
this.config.onInit = null;
// Call parent init
Zapatec.Menu.SUPERclass.init.call(this, objArgs);
// Continue initialization after theme is loaded
};
/**
* Extends parent method.
* @private
*/
Zapatec.Menu.prototype.addStandardEventListeners = function() {
// Call parent method
Zapatec.Menu.SUPERclass.addStandardEventListeners.call(this);
// Add menu specific event listeners
this.addEventListener('loadThemeEnd', function() {
if (Zapatec.windowLoaded) {
this.onThemeLoad();
} else {
var objMenu = this;
Zapatec.Utils.addEvent(window, 'load', function() {
objMenu.onThemeLoad()
});
}
});
};
/**
* Holds reference to menu object that is currently on top. When menu is
* mouseovered, its top_parent zIndex is changed to max to put it over the rest
* of elements. This variable is needed to be able to restore zIndex of previous
* top menu.
* @private
*/
Zapatec.Menu.onTop = null;
/**
* Restores zIndex of this menu.
* @private
*/
Zapatec.Menu.prototype.restoreZIndex = function() {
this.top_parent.style.zIndex = this.config.zIndex;
Zapatec.Menu.onTop = null;
};
/**
* Puts this menu on top.
* @private
*/
Zapatec.Menu.prototype.putOnTop = function() {
// Restore zIndex of previous top menu
var objOnTop = Zapatec.Menu.onTop;
if (objOnTop) {
objOnTop.restoreZIndex();
}
// Put this menu over the rest of elements
// Max zIndex in IE and FF: 10737418239, in Opera: 2147483583
this.top_parent.style.zIndex = 2147483583;
Zapatec.Menu.onTop = this;
};
/**
* Called when theme is loaded.
* @private
*/
Zapatec.Menu.prototype.onThemeLoad = function() {
// Current trigger object that launched menu (menu can be attached to several
// objects).
this.triggerObject = null;
// Arguments received from current trigger object. Those arguments can be
// accessed from external script, e.g. to determine, which cell of the grid
// was clicked, etc.
this.triggerArgs = null;
this.animations = [];
// Menu container
this.container = Zapatec.Widget.getElementById(this.config.container);
// Call parent method to load data from the specified source
this.loadData();
this.openMenus = [];
this.clickDone = false;
var objMenu = this;
// Setup triggers
if (this.config.triggerEvent) {
this.setTriggerObject(this.config.triggerObject || window.document);
// Hide menu on click
Zapatec.Utils.addEvent(window.document, 'mouseup',
function() {
objMenu.hideMenu()
}
);
// Prevent hiding on click inside menu
Zapatec.Utils.addEvent(this.top_parent, 'mouseup',
function(objEvent) {
return Zapatec.Utils.stopEvent(objEvent);
}
);
// Hide menu on ESC
Zapatec.Utils.addEvent(window.document, 'keypress',
function(objEvent) {
objEvent || (objEvent = window.event);
if (objEvent.keyCode == 27) {
for (var i = 0; i < Zapatec.Menu.selectedItemsStack.length; i++) {
if (Zapatec.Menu.all[Zapatec.Menu.selectedItemsStack[i].__zp_tree]
== objMenu) {
return;
}
}
// No more selected items in this menu
objMenu.hideMenu();
}
}
);
} else {
// Dragging and scrolling can't work correctly together with triggers
if (this.config.scrollWithWindow) {
Zapatec.ScrollWithWindow.register(this.rootMenu);
}
if (this.config.drag) {
this.dragging = false;
Zapatec.Utils.addEvent(window.document, "mousedown",
function(ev) { return Zapatec.Menu.dragStart(ev, objMenu) });
Zapatec.Utils.addEvent(window.document, "mousemove",
function(ev) { return Zapatec.Menu.dragMove(ev, objMenu) });
Zapatec.Utils.addEvent(window.document, "mouseup",
function(ev) { return Zapatec.Menu.dragEnd(ev, objMenu) });
}
}
// Enforce animation mixing rules: fade + any 1 other.
if (this.config.fade) {
this.addAnimation('fade');
}
if (this.config.slide) {
this.addAnimation('slide');
} else if (this.config.glide) {
this.addAnimation('glide');
} else if (this.config.wipe) {
this.addAnimation('wipe');
} else if (this.config.unfurl) {
this.addAnimation('unfurl');
}
// Oninit callback
if (typeof this.config.onInit == 'function') {
// To be able to reference to this object from onInit function
setTimeout(function() {
objMenu.config.onInit();
}, 0);
}
};
/**
* Initializes a menu from the HTML source.
*
* @private
* @param {object} objSource Source HTMLElement object
*/
Zapatec.Menu.prototype.loadDataHtml = function(objSource) {
// Check arguments
if (!objSource) {
return;
}
this.list = objSource;
this.items = {};
this.trees = {};
this.selectedItem = null;
// Sub-menu container counter
this.lastContainerNumber = 0;
this.menuId = objSource.id || Zapatec.Utils.generateID("tree");
var objContainer = this.top_parent = Zapatec.Utils.createElement("div");
// Initially menu is hidden and will be shown on triggerEvent
objContainer.style.display = 'none';
// In order to work correctly when menu is put inside * This global variable keeps a "hash table" (that is, a plain JavaScript * object) mapping ID-s to references to Zapatec.Menu objects. It's helpful if * you want to operate on a tree but you don't want to keep a reference to it. * Example: * * // the following makes a tree for the
* Counter that is increased by 1 before each item added. Next menu item will * have tabIndex property value = current value of Zapatec.Menu.tabIndex + 1. * * Note: * in Opera tabIndex property value of node must be > 0, otherwise it will be * ignored; * Mozilla starts travelling from nodes with tabIndex > 0; * IE starts travelling from nodes with tabIndex == 0; * all nodes without tabIndex set explicitly have tabIndex == 0 ** @private */ Zapatec.Menu.tabIndex = 1000; /** * Walks through a LI element and creates the HTML elements associated with that * tree item. When it encounters an UL element it calls createTree() in order to * create the item's subtree. This function may also call item_addIcon() in * order to add the +/- buttons or icons present in the item definition as IMG * tags, or item_addDefaultIcon() if the tree configuration specifies * "defaultIcons" and no IMG tag was present. * * @private * @param {object} li LI element * @param {object} objContainer Parent element where the HTML elements should be * created * @param {object} next_li Next LI element, if this is not the last one * @param {number} level Level of this item in the main tree * @param {number} intItem Nth item for this sub-tree * @return DIV element holding the HTML elements of the created item * @type object */ Zapatec.Menu.prototype.createItem = function(li, objContainer, next_li, level, intItem) { if (!li.firstChild) { return; } var id = li.id || Zapatec.Utils.generateID("tree.item"); var item = this.items[id] = Zapatec.Utils.createElement("div", objContainer.__zp_menu); var t = Zapatec.Utils.createElement("table", item); var tb = Zapatec.Utils.createElement("tbody", t); var tr = Zapatec.Utils.createElement("tr", tb); var td = Zapatec.Utils.createElement("td", tr); var has_icon = false; if (!level) { // This will allow to have correct item offsetWidth value in Opera td.style.whiteSpace = 'nowrap'; } t.className = "zpMenu-table"; t.cellSpacing = 0; t.cellPadding = 0; td.className = "zpMenu-label" //If there's a title attribute to the LI var title = li.getAttribute('title'); if (title) { //apply it to the menu item td.setAttribute('title', title); } // add the LI's classname to the item.className = "zpMenu-item" + (li.className ? ' ' + li.className : ''); Zapatec.Utils.addClass(item, "zpMenu-level-" + (level+1)); // Define the Nth level of a sub-menu, 1 based item.__zp_item = id; item.__zp_tree = this.menuId; item.__zp_parent = objContainer.__zp_treeid; item.onmouseover = Zapatec.Menu.onItemMouseOver; item.onmouseout = Zapatec.Menu.onItemMouseOut; item.onclick = Zapatec.Menu.onItemClick; Zapatec.Utils.addClass(item, "zpMenu-item-" + (intItem % 2==1 ? "odd" : "even")); // Parse li var fc, subtree = false, accessKey = null; var getAccessKey = function(node) { var key = null; if (node.nodeType == 1) { // ELEMENT_NODE if (key = node.getAttribute('accesskey')) { // Remove accesskey attribute because it will cause duplicate onclick event node.removeAttribute('accesskey', false); if (/^[a-z0-9]$/i.test(key)) { return key; } else { key = null; } } var childNodes = node.childNodes; for (var i = 0; i < childNodes.length; i++) { if (key = getAccessKey(childNodes[i])) { break; } } } else if (node.nodeType == 3) { // TEXT_NODE var label = node.data.replace(/(^\s+|\s+$)/g, ''); if (/_([a-z0-9])/i.test(label)) { label = label.replace(/_([a-z0-9])/i, '$1'); key = RegExp.$1; var span = Zapatec.Utils.createElement("span"); span.innerHTML = label; var objParentNode = node.parentNode; objParentNode.insertBefore(span, node); objParentNode.removeChild(node); } } return key; }; while (fc = li.firstChild) { if (fc.nodeType == 1 && (/^[ou]l$/i.test(fc.tagName.toLowerCase()))) { // Subtree if (!subtree) { this.item_addIcon(item, null); var np = Zapatec.Utils.createElement("div", objContainer); // The following to be able to position menu at the bottom right corner // of the screen without appearing of scrollbars // Also Opera zIndex requires absolute positioning np.style.position = 'absolute'; if (!this.config.triggerEvent) { np.style.left = '-9999px'; np.style.top = '-9999px'; } if (this.config.dropShadow) { var ds = np.__zp_dropshadow = Zapatec.Utils.createElement('div'); objContainer.insertBefore(ds, np); ds.style.position = 'absolute'; if (!this.config.triggerEvent) { ds.style.left = '-9999px'; ds.style.top = '-9999px'; } ds.style.backgroundColor = '#000'; if (window.opera) { ds.style.backgroundColor = '#666'; // opacity doesn't work in Opera } else { ds.style.filter = 'alpha(opacity=' + this.config.dropShadow + ')'; } ds.style.opacity = this.config.dropShadow / 100; } np.__zp_item = id; np.__zp_menu = Zapatec.Utils.createElement("div", np); np.__zp_menu.className = 'zpMenu' + (fc.className ? ' ' + fc.className : ''); np.className = 'zpMenuContainer'; np.__zp_menu.onmouseover = Zapatec.Menu.onItemMouseOver; np.__zp_menu.onmouseout = Zapatec.Menu.onItemMouseOut; if (next_li) { np.__zp_menu.className += " zpMenu-lined"; } // Will hold sub-menu icons np.__zp_icons = []; // Build submenu item.__zp_subtree = this.createTree(fc, np, level+1); // Align captions in the sub-menu if (np.__zp_icons.length) { this.alignSubMenu(np); } // We don't need this any more np.__zp_icons = null; item.className += " zpMenu-item-collapsed"; this.toggleItem(id); if (/(^|\s)selected(\s|$)/i.test(li.className)) { this.selectedItem = item; } subtree = true; } li.removeChild(fc); } else { // Label li.removeChild(fc); if (fc.nodeType == 3) { // Text var label = fc.data.replace(/(^\s+|\s+$)/g, ''); if (label) { var strInnerHtml = label; if (Zapatec.Menu.onDocumentKeyDown && !accessKey) { strInnerHtml = label.replace(/_([a-z0-9])/i, '$1'); accessKey = RegExp.$1; } var span = Zapatec.Utils.createElement("span", td); // IE 6.0 doesn't escape correctly plain text when it is assigned to // innerHTML property if (strInnerHtml == label) { // Plain text span.appendChild(document.createTextNode(strInnerHtml)); } else { // Contains span.innerHTML = strInnerHtml; } if (title) span.setAttribute('title', title); // To make title work in Opera } } else if (fc.tagName) { // Skip comments, etc. if (fc.tagName.toLowerCase() == 'img') { // Icon this.item_addIcon(item, fc); has_icon = true; if (objContainer.__zp_icons instanceof Array) { objContainer.__zp_icons.push(fc); } } else { // Other stuff if (fc.tagName.toLowerCase() == 'hr') { Zapatec.Utils.addClass(item, "zpMenu-item-hr"); } else if (fc.tagName.toLowerCase() == 'input' && fc.getAttribute('type') == 'checkbox') { fc.onmousedown = function(ev){ if (this.checked) { this.checked = false; } else { this.checked = true; } return Zapatec.Utils.stopEvent(ev); }; } else if (fc.tagName.toLowerCase() == 'input' && fc.getAttribute('type') == 'radio') { fc.onmousedown = function(ev){ this.checked = true; return Zapatec.Utils.stopEvent(ev); }; } else if (fc.tagName.toLowerCase() == 'a') { if (Zapatec.Menu.onDocumentKeyDown && !accessKey) { accessKey = getAccessKey(fc); } // Tab navigation support fc.tabIndex = ++Zapatec.Menu.tabIndex; fc.onfocus = Zapatec.Menu.onItemMouseOver; fc.onblur = Zapatec.Menu.onItemMouseOut; } td.appendChild(fc); if (title && !fc.getAttribute('title')) fc.setAttribute('title', title); // To make title work in Opera } } } } if (accessKey) { accessKey = accessKey.toUpperCase().charCodeAt(0); objContainer.__zp_keymap[accessKey] = item; } if (!has_icon && !/zpMenu-item-hr/i.test(item.className)) // No icons for this non-HR menu item if (this.config.defaultIcons) // Use user config setting defaultIcons className this.item_addDefaultIcon(item, this.config.defaultIcons); else // No icons default className this.item_addDefaultIcon(item, "zpMenu-noicon"); return item; }; /** * Aligns captions in the sub-menu. * * @private * @param {object} objSubMenu Sub-menu container */ Zapatec.Menu.prototype.alignSubMenu = function(objSubMenu) { // Preload icons if (Zapatec.StyleSheet) { this.alignSubMenuWithStyleSheet(objSubMenu); } else { // Load StyleSheet class var objMenu = this; Zapatec.Transport.loadJS({ module: 'stylesheet', onLoad: function() { objMenu.alignSubMenuWithStyleSheet(objSubMenu); } }); } }; /** * Aligns captions in the sub-menu. Zapatec.StyleSheet must exist. * * @private * @param {object} objSubMenu Sub-menu container */ Zapatec.Menu.prototype.alignSubMenuWithStyleSheet = function(objSubMenu) { // Get icons array var arrIcons = objSubMenu.__zp_icons; var arrIconsSrc = []; for (var iIcon = 0; iIcon < arrIcons.length; iIcon++) { arrIconsSrc.push(arrIcons[iIcon].src); } // Wait while icons are loading var objMenu = this; Zapatec.Transport.preloadImages({ urls: arrIconsSrc, onLoad: function() { // Get max icon width var iMaxIconWidth = 0; for (var iIcon = 0; iIcon < arrIcons.length; iIcon++) { var iIconWidth = arrIcons[iIcon].width; if (iIconWidth && iMaxIconWidth < iIconWidth) { iMaxIconWidth = iIconWidth; } } // Get stylesheet if (!objMenu.styleSheet) { objMenu.styleSheet = new Zapatec.StyleSheet(); } // Set caption width objMenu.styleSheet.addRule('#' + objSubMenu.id + ' .icon div', 'width:' + iMaxIconWidth + 'px'); }, timeout: 60000 // 1 minute }); }; /** *
* Adds a TD element having a certain class attribute which helps having a tree
* containing icons without defining IMG tags for each item. The class name will
* be "tgb icon className" (where "className" is the specified parameter).
* Further, in order to customize the icons, one should add some CSS lines like
* this:
*
* div.tree-item td.customIcon {
* background: url("themes/img/fs/document2.png") no-repeat 0 50%;
* }
* div.tree-item-expanded td.customIcon {
* background: url("themes/img/fs/folder-open.png") no-repeat 0 50%;
* }
* div.tree-item-collapsed td.customIcon {
* background: url("themes/img/fs/folder.png") no-repeat 0 50%;
* }
*
* As you can see, it's very easy to customize the default icons for a normal
* tree item (that has no subtrees) or for expanded or collapsed items. For
* the above example to work, one has to pass { defaultIcons: "customIcon" } in
* the tree configuration object.
*
* This function does nothing if the className parameter has a false logical
* value (i.e. is null).
*
*
* @private
* @param {object} item DIV element holding the item
* @param {string} className Additional class name
*/
Zapatec.Menu.prototype.item_addDefaultIcon = function(item, className) {
if (!className) {
return;
}
var last_td = item.firstChild.firstChild.firstChild.lastChild, td;
var td = Zapatec.Utils.createElement("td");
td.className = "tgb icon " + className;
last_td.parentNode.insertBefore(td, last_td);
// To be able to set table cell width
Zapatec.Utils.createElement('div', td);
};
/**
* If img is passed, adds it as an icon for the given item. If not passed,
* creates a "+/-" button for the given item.
*
* @private
* @param {object} item DIV holding the item elements
* @param {object} img Optional. IMG element; normally one found in the
* element id [string] ||
* HTMLElement object [object] ||
* [
* element id [string] ||
* HTMLElement object [object] ||
* {
* triggerObject: element id [string] || HTMLElement object [object],
* triggerArgs: any args that should be available to external scripts [any]
* },
* ...
* ]
*
*/
Zapatec.Menu.prototype.setTriggerObject = function(triggerObject) {
if (!this.config.triggerEvent) {
// This method is applicable only to trigger menus
return;
}
var strTriggerEvent = this.config.triggerEvent;
var strTriggerKey = this.config.triggerKey;
// Get trigger objects
var objTriggerElements = [];
if (triggerObject) {
if (typeof triggerObject == 'string') {
// Element id
var objElement = document.getElementById(triggerObject);
if (objElement) {
objTriggerElements.push({
triggerObject: objElement,
triggerArgs: null
});
}
} else if (typeof triggerObject == 'object') {
if (triggerObject == window.document ||
typeof triggerObject.length == 'undefined') {
// HTMLElement object
objTriggerElements.push({
triggerObject: triggerObject,
triggerArgs: null
});
} else {
// Array
for (var iObj = 0; iObj < triggerObject.length; iObj++) {
var triggerElement = triggerObject[iObj];
if (triggerElement) {
if (typeof triggerElement == 'string') {
// Element id
var objElement = document.getElementById(triggerElement);
if (objElement) {
objTriggerElements.push({
triggerObject: objElement,
triggerArgs: null
});
}
} else if (typeof triggerElement == 'object') {
if (typeof triggerElement.triggerObject != 'undefined' &&
typeof triggerElement.triggerArgs != 'undefined') {
// Arguments passed
if (typeof triggerElement.triggerObject == 'string') {
// Element id
var objElement =
document.getElementById(triggerElement.triggerObject);
if (objElement) {
objTriggerElements.push({
triggerObject: objElement,
triggerArgs: triggerElement.triggerArgs
});
}
} else if (typeof triggerElement.triggerObject == 'object') {
// HTMLElement object
objTriggerElements.push(triggerElement);
}
} else {
// HTMLElement object
objTriggerElements.push({
triggerObject: triggerElement,
triggerArgs: null
});
}
}
}
}
}
}
}
if (objTriggerElements.length == 0) {
// Nothing to set up
return;
}
// Set up trigger objects
var objMenu = this;
if (strTriggerEvent == 'mousedown' || strTriggerEvent == 'mouseup' ||
strTriggerEvent == 'click') {
// Mouse trigger
// Need this function to be able to set current trigger object and arguments
var funcSetupTriggerEvent = function(objTriggerElement) {
Zapatec.Utils.addEvent(objTriggerElement.triggerObject, 'mouseup',
function(objEvent) {
objEvent || (objEvent = window.event);
// Get mouse position
var objMousePos = Zapatec.Utils.getMousePos(objEvent);
// Get mouse button
var button;
if (objEvent.button) {
button = objEvent.button;
} else {
button = objEvent.which;
}
if (window.opera) {
// Button 1 is used for both showing and hiding menu in Opera
// because Opera doesn't allow to disable context menu
if (button == 1 && objMenu.top_parent.style.display == 'none') {
setTimeout(function() {
// Set current trigger object
objMenu.triggerObject = objTriggerElement.triggerObject;
// Set arguments received from current trigger object
objMenu.triggerArgs = objTriggerElement.triggerArgs;
// Show menu at mouse position
objMenu.popupMenu(objMousePos.pageX, objMousePos.pageY);
}, 100);
return Zapatec.Utils.stopEvent(objEvent);
}
} else {
// In Safari Meta (Alt) Key + left click is used because it doesn't
// react on right mouse button
if (strTriggerKey == 'both' ||
(strTriggerKey == 'left' && button == 1) ||
((!strTriggerKey || strTriggerKey == 'right') &&
(button > 1 || objEvent.metaKey))) {
setTimeout(function() {
// Set current trigger object
objMenu.triggerObject = objTriggerElement.triggerObject;
// Set arguments received from current trigger object
objMenu.triggerArgs = objTriggerElement.triggerArgs;
// Show menu at mouse position
objMenu.popupMenu(objMousePos.pageX, objMousePos.pageY);
}, 100);
Zapatec.Utils.stopEvent(objEvent);
// Safari bug workaround
objEvent.returnValue = true;
return false;
}
}
}
);
};
for (var iEl = 0; iEl < objTriggerElements.length; iEl++) {
funcSetupTriggerEvent(objTriggerElements[iEl])
}
// Disable context menu
window.document.oncontextmenu = function() {return false};
} else if (strTriggerEvent == 'keydown' || strTriggerEvent == 'keyup' ||
strTriggerEvent == 'keypress') {
// Keyboard trigger
// Need this function to be able to set current trigger object and arguments
var funcSetupTriggerEvent = function(objTriggerElement) {
Zapatec.Utils.addEvent(objTriggerElement.triggerObject, 'keydown',
function(objEvent) {
objEvent || (objEvent = window.event);
if (objEvent.keyCode == strTriggerKey) {
// Set current trigger object
objMenu.triggerObject = objTriggerElement.triggerObject;
// Set arguments received from current trigger object
objMenu.triggerArgs = objTriggerElement.triggerArgs;
// Show menu
objMenu.popupMenu();
return Zapatec.Utils.stopEvent(objEvent);
}
}
);
};
for (var iEl = 0; iEl < objTriggerElements.length; iEl++) {
funcSetupTriggerEvent(objTriggerElements[iEl])
}
}
};
//Constants
Zapatec.Menu.MOUSEOUT = 0;
Zapatec.Menu.MOUSEOVER = 1;
Zapatec.Menu.CLICK = 2;
/**
* Collection of animations (function references).
* These are called to progressively style the DOM elements as menus show
* and hide. They do not have to set item visibility, but may want to set DOM
* properties like clipping, opacity and position to create custom effects.
*
* @param {object} ref HTMLElement object that contains the menu items
* @param {number} counter Animation progress value, from 0 (start) to 100 (end)
*/
Zapatec.Menu.animations = {};
Zapatec.Menu.animations.fade = function(ref, counter) {
var f = ref.filters, done = (counter==100);
if (f) {
if (!done && ref.style.filter.indexOf("alpha") == -1) {
ref.style.filter += ' alpha(opacity=' + counter + ')';
}
else if (f.length && f.alpha) with (f.alpha) {
if (done) enabled = false;
else { opacity = counter; enabled=true }
}
}
else {
ref.style.opacity = ref.style.MozOpacity = counter/100.1;
}
};
Zapatec.Menu.animations.slide = function(ref, counter) {
if (counter != 100) {
var cP = Math.pow(Math.sin(Math.PI * counter / 200), 0.75);
if (typeof ref.__zp_origmargintop == 'undefined') {
ref.__zp_origmargintop = ref.style.marginTop;
}
ref.style.marginTop = '-' + parseInt(ref.offsetHeight * (1 - cP)) + 'px';
ref.style.clip = 'rect(' + parseInt(ref.offsetHeight * (1 - cP)) + 'px,' +
ref.offsetWidth + 'px,' + ref.offsetHeight + 'px,0)';
} else {
if (typeof ref.__zp_origmargintop != 'undefined') {
ref.style.marginTop = ref.__zp_origmargintop;
}
try {
ref.style.clip = '';
} catch (objException) {
// IE 6.0
ref.style.clip = 'rect(auto,auto,auto,0)';
}
}
};
Zapatec.Menu.animations.glide = function(ref, counter) {
if (counter != 100) {
var cP = Math.pow(Math.sin(Math.PI * counter / 200), 0.75);
ref.style.clip = 'rect(0,' + ref.offsetWidth + 'px,' +
parseInt(ref.offsetHeight * cP) + 'px,0)';
} else {
try {
ref.style.clip = '';
} catch (objException) {
// IE 6.0
ref.style.clip = 'rect(0,auto,auto,0)';
}
}
};
Zapatec.Menu.animations.wipe = function(ref, counter) {
if (counter != 100) {
ref.style.clip = 'rect(0,' + parseInt(ref.offsetWidth * (counter / 100)) +
'px,' + parseInt(ref.offsetHeight * (counter / 100)) + 'px,0)';
} else {
try {
ref.style.clip = '';
} catch (objException) {
// IE 6.0
ref.style.clip = 'rect(0,auto,auto,0)';
}
}
};
Zapatec.Menu.animations.unfurl = function(ref, counter) {
if (counter <= 50) {
ref.style.clip = 'rect(0,' + parseInt(ref.offsetWidth * (counter / 50)) +
'px,10px,0)';
} else if (counter < 100) {
ref.style.clip = 'rect(0,' + ref.offsetWidth + 'px,' +
parseInt(ref.offsetHeight * ((counter - 50) / 50)) + 'px,0)';
} else {
try {
ref.style.clip = '';
} catch (objException) {
// IE 6.0
ref.style.clip = 'rect(0,auto,auto,0)';
}
}
};
/**
* Called with the name of an animation (in the Zapatec.Menu.animations[] array)
* to apply that animation to this menu object.
*
* @param {string} animation Name of the animation
*/
Zapatec.Menu.prototype.addAnimation = function(animation) {
this.animations[this.animations.length] = Zapatec.Menu.animations[animation];
};
/**
* Sets the display/visibility of a specified menu, calling defined animation
* functions and repeatedly calling itself.
*
* @private
* @param {object} menu HTMLElement object
* @param {boolean} show True shows, false hides
*/
Zapatec.Menu.prototype.treeSetDisplay = function(menu, show) {
// First pass on menu creation: just hide.
if (!menu.__zp_initialised) {
menu.style.visibility = 'hidden';
menu.style.left = '-9999px';
menu.style.top = '-9999px';
if (menu.__zp_dropshadow) {
menu.__zp_dropshadow.style.visibility = 'hidden';
menu.__zp_dropshadow.style.left = '-9999px';
menu.__zp_dropshadow.style.top = '-9999px';
}
menu.__zp_initialised = true;
return;
}
var treeId = menu.__zp_tree || menu.__zp_menu.firstChild.__zp_tree;
var tree;
if (treeId) {
tree = Zapatec.Menu.all[treeId];
}
if (!tree) {
return;
}
if (tree.animations.length == 0) {
if (show) {
menu.style.visibility = 'inherit';
if (menu.__zp_dropshadow) {
menu.__zp_dropshadow.style.visibility = 'inherit';
}
} else {
menu.style.visibility = 'hidden';
menu.style.left = '-9999px';
menu.style.top = '-9999px';
if (menu.__zp_dropshadow) {
menu.__zp_dropshadow.style.visibility = 'hidden';
menu.__zp_dropshadow.style.left = '-9999px';
menu.__zp_dropshadow.style.top = '-9999px';
}
}
return;
}
// Otherwise animate.
menu.__zp_anim_timer |= 0;
clearTimeout(menu.__zp_anim_timer);
menu.__zp_anim_counter |= 0;
if (show && !menu.__zp_anim_counter) {
menu.style.visibility = 'inherit';
if (menu.__zp_dropshadow) {
menu.__zp_dropshadow.style.visibility = 'inherit';
}
}
for (var ii = 0; ii < tree.animations.length; ii++) {
tree.animations[ii](menu, menu.__zp_anim_counter);
if (menu.__zp_dropshadow
&& tree.animations[ii] != Zapatec.Menu.animations.fade) {
tree.animations[ii](menu.__zp_dropshadow, menu.__zp_anim_counter);
}
}
// Iterate
if (!(show && menu.__zp_anim_counter == 100)) { // Prevent infinite loop
menu.__zp_anim_counter += tree.config.animSpeed * (show ? 1 : -1);
if (menu.__zp_anim_counter > 100) {
// Correction to show menu properly
menu.__zp_anim_counter = 100;
menu.__zp_anim_timer = setTimeout(function() {
tree.treeSetDisplay(menu, show);
}, 50);
} else if (menu.__zp_anim_counter <= 0) {
// Hide menu
menu.__zp_anim_counter = 0;
menu.style.visibility = 'hidden';
menu.style.left = '-9999px';
menu.style.top = '-9999px';
if (menu.__zp_dropshadow) {
menu.__zp_dropshadow.style.visibility = 'hidden';
menu.__zp_dropshadow.style.left = '-9999px';
menu.__zp_dropshadow.style.top = '-9999px';
}
} else {
// Next iteration
menu.__zp_anim_timer = setTimeout(function() {
tree.treeSetDisplay(menu, show);
}, 50);
}
}
};
// GLOBAL EVENT HANDLERS (to workaround the stupid Microsoft memory leak)
/**
* Global event handler that gets called when a tree item is moused over.
* @private
*/
Zapatec.Menu.onItemMouseOver = function() {
// Loop up the DOM, dispatch event to correct source item.
var item = this, tree = null;
while (item && item != document.body) {
var t_id = item.__zp_tree || item.firstChild.__zp_tree;
if (t_id) tree = Zapatec.Menu.all[t_id];
var itemClassName = item.className;
if (/zpMenu-item/.test(itemClassName) && !/zpMenu-item-hr/.test(itemClassName)) {
tree.itemMouseHandler(item.__zp_item, Zapatec.Menu.MOUSEOVER);
}
item = tree && item.__zp_treeid ?
tree.items[item.__zp_item] : item.parentNode;
}
return true; // To make tooltips work in Opera
};
/**
* Global event handler that gets called when a tree item is moused out.
* @private
*/
Zapatec.Menu.onItemMouseOut = function() {
var item = this, tree = null;
while (item && item != document.body) {
var t_id = item.__zp_tree || item.firstChild.__zp_tree;
if (t_id) tree = Zapatec.Menu.all[t_id];
var itemClassName = item.className;
if (
/zpMenu-item/.test(itemClassName) && !/zpMenu-item-hr/.test(itemClassName) &&
// Top item was not unselected with Esc button
!(/zpMenu-level-1/.test(itemClassName) && !/zpMenu-item-selected/.test(itemClassName))
) {
tree.itemMouseHandler(item.__zp_item, Zapatec.Menu.MOUSEOUT);
}
item = tree && item.__zp_treeid ?
tree.items[item.__zp_item] : item.parentNode;
}
return false;
};
/**
* Global event handler that gets called when a tree item is clicked, to make
* the whole item clickable.
* @private
*/
Zapatec.Menu.onItemClick = function(ev) {
var item = this;
if (!/zpMenuDisabled/.test(item.className)) {
while (item && item != document.body) {
if (item.nodeName && item.nodeName.toLowerCase() == 'a') {
return true;
}
if (/zpMenu-item/.test(item.className)) {
var objMenu = Zapatec.Menu.all[item.__zp_tree];
// Show-on-click mode
if (objMenu.config.onClick && item.__zp_subtree &&
(/zpMenu-top/.test(objMenu.trees[item.__zp_parent].className))) {
objMenu.itemMouseHandler(item.__zp_item, Zapatec.Menu.CLICK);
return Zapatec.Utils.stopEvent(ev);
}
// Otherwise navigate the page
var itemLink = item.getElementsByTagName('a');
var itemInput = item.getElementsByTagName('input');
var itemSelect = item.getElementsByTagName('select');
if (itemLink && itemLink.item(0)
&& itemLink.item(0).getAttribute('href')
&& itemLink.item(0).getAttribute('href') != '#'
&& itemLink.item(0).getAttribute('href') != window.document.location.href + '#'
&& itemLink.item(0).getAttribute('href') != 'javascript:void(0)') {
var href = itemLink.item(0).getAttribute('href');
var target = itemLink.item(0).getAttribute('target');
if (objMenu.config.rememberPath || objMenu.config.pathCookie != '__zp_item') {
// Save path in cookies
Zapatec.Utils.writeCookie(objMenu.config.pathCookie, item.__zp_item);
}
try {
if (target) {
window.open(href, target);
} else {
window.location.href = href; // may raise exception in Mozilla
}
} catch(e) {};
if (objMenu.config.triggerEvent) {
objMenu.hideMenu();
}
} else if (itemInput && itemInput.item(0)) {
var inp = itemInput.item(0);
var type = inp.getAttribute('type');
if (type == 'checkbox') {
if (inp.checked) {
inp.checked = false;
} else {
inp.checked = true;
}
} else if (type == 'radio') {
inp.checked = true;
}
} else if (itemSelect && itemSelect.item(0)) {
return true; // Pass through
} else if (item.__zp_subtree) {
objMenu.itemMouseHandler(item.__zp_item, Zapatec.Menu.CLICK);
} else if (objMenu.config.triggerEvent) {
objMenu.hideMenu();
}
return Zapatec.Utils.stopEvent(ev);
}
item = item.parentNode;
}
}
return false;
};
/**
* Called from the mouse over/out event handlers to process the mouse event and
* correctly manage timers.
*
* @private
* @param {string} item_id Item ID
* @param {number} type 0 = mouseout, 1 = mouseover, 2 = click
*/
Zapatec.Menu.prototype.itemMouseHandler = function(item_id, type) {
if (type) {
// Mouseover or click
// Put this menu on top
this.putOnTop();
} else {
// Mouseout
// Restore zIndex
this.restoreZIndex();
}
var item = this.items[item_id];
if (!item) return;
var menu = this._getTree(item.__zp_parent);
// If slide animation and Opera, skip mouseover and mouseout events while
// animating subtree because current item may receive fake mouseout even when
// mouse pointer is over it and loose cursor. Other solution is needed because
// submenu may stay expanded due to missing real mouseout event.
if (type < 2 && window.opera && this.config.slide) {
var objSubtree = this._getTree(item.__zp_subtree);
if (objSubtree && objSubtree.__zp_anim_counter &&
objSubtree.__zp_anim_counter < 100) {
return;
}
}
// Record an item as lit/shown, and dim/hide any previously lit items.
if (menu && menu.__zp_activeitem != item_id) {
if (menu.__zp_activeitem) {
var lastItem = this.items[menu.__zp_activeitem];
clearTimeout(lastItem.__zp_dimtimer);
clearTimeout(lastItem.__zp_mousetimer);
var objMenu = this;
setTimeout(function() {
Zapatec.Menu.unselectItem(lastItem);
// Threading bugfix for some menus remaining visible.
if (lastItem.__zp_state) objMenu.toggleItem(lastItem.__zp_item, false);
Zapatec.Menu.selectItem(item);
}, 0);
} else {
setTimeout(function() {
Zapatec.Menu.selectItem(item);
}, 0);
}
menu.__zp_activeitem = item_id;
}
// Set a timer to dim this item when the whole menu hides.
clearTimeout(item.__zp_dimtimer);
if (type == Zapatec.Menu.MOUSEOUT) {
item.__zp_dimtimer = setTimeout(function() {
Zapatec.Menu.unselectItem(item);
if (menu.__zp_activeitem == item_id) menu.__zp_activeitem = '';
}, this.config.hideDelay);
}
// Stop any pending show/hide action.
clearTimeout(item.__zp_mousetimer);
// Check if this is a click on a first-level menu item.
if (this.config.onClick && !this.clickDone) {
if (/zpMenu-top/.test(this.trees[item.__zp_parent].className) &&
(type == Zapatec.Menu.MOUSEOVER)) return;
// Set the flag that enables further onmouseover activity.
if (type == Zapatec.Menu.CLICK) this.clickDone = true;
}
// Setup show/hide timers.
if (!item.__zp_state && type) {
item.__zp_mousetimer = setTimeout('Zapatec.Menu.all["' +
item.__zp_tree + '"].itemShow("' + item.__zp_item + '")',
(this.config.showDelay || 1));
} else if (item.__zp_state && !type) {
item.__zp_mousetimer = setTimeout('Zapatec.Menu.all["' +
item.__zp_tree + '"].itemHide("' + item.__zp_item + '")',
(this.config.hideDelay || 1));
}
};
/**
* Called from the itemMouseHandler() after a timeout; positions and shows
* a designated item's branch of the tree.
*
* @private
* @param {string} item_id Item ID to show
*/
Zapatec.Menu.prototype.itemShow = function(item_id) {
var item = this.items[item_id];
if (/zpMenuDisabled/.test(item.className)) {
return;
}
var subMenu = this._getTree(item.__zp_subtree);
if (!subMenu) {
return;
}
var parMenu = this._getTree(item.__zp_parent);
// Setting visible here works around MSIE bug where
// offsetWidth/Height are initially zero.
if (!subMenu.offsetHeight) {
subMenu.style.visibility = 'visible';
}
// In Opera z-index is not inherited by default
if (subMenu.style.zIndex === '') {
subMenu.style.zIndex = 'inherit';
}
var subMenuBorderLeft, subMenuBorderTop;
if (typeof subMenu.clientLeft != 'undefined') { // IE & Opera
subMenuBorderLeft = subMenu.clientLeft;
subMenuBorderTop = subMenu.clientTop;
} else { // Mozilla
subMenuBorderLeft = (subMenu.offsetWidth - subMenu.clientWidth) / 2;
subMenuBorderTop = (subMenu.offsetHeight - subMenu.clientHeight) / 2;
}
var fc = subMenu.firstChild;
var subMenuMarginLeft = fc.offsetLeft;
var subMenuMarginTop = fc.offsetTop;
// Acquire browser dimensions
var scrollX = window.pageXOffset || document.body.scrollLeft ||
document.documentElement.scrollLeft || 0;
var scrollY = window.pageYOffset || document.body.scrollTop ||
document.documentElement.scrollTop || 0;
var objWindowSize = Zapatec.Utils.getWindowSize();
var winW = objWindowSize.width;
var winH = objWindowSize.height;
// Adjust sub-menu width and height
if (!subMenu.style.width || !subMenu.style.height) {
var maxHeight = winH - 7;
if (subMenu.offsetHeight > maxHeight) {
// Need scrolling
// iSubMenuOffsetHeight is needed because IE 5.0 delays with offsetHeight
// calculation after element is changed
var iSubMenuOffsetHeight = subMenu.offsetHeight;
fc.__zp_first = fc.firstChild;
fc.__zp_last = fc.lastChild;
var objUp = Zapatec.Utils.createElement("div");
objUp.__zp_tree = fc.firstChild.__zp_tree;
objUp.className = 'zpMenuScrollUpInactive';
objUp.__zp_mouseover = false;
objUp.__zp_timer = null;
// Up arrow handler
var funcMoveUp = function() {
var objContainer = objUp.parentNode;
var iContainerHeight = objContainer.parentNode.clientHeight;
var objUpArrow = objContainer.firstChild;
var objDownArrow = objContainer.lastChild;
// Check if we can move up
if (objContainer.__zp_first.previousSibling != objUpArrow) {
// Show first item
if (objContainer.__zp_first.style.height) {
// Partly hidden
objContainer.__zp_first.style.height = '';
objContainer.__zp_first.style.overflow = '';
} else {
// Completely hidden
objContainer.__zp_first = objContainer.__zp_first.previousSibling;
objContainer.__zp_first.style.display = 'block';
}
var iNewHeight = objContainer.offsetHeight;
// Hide last item
while (iNewHeight > iContainerHeight) {
objContainer.__zp_last.style.display = 'none';
if (objContainer.__zp_last.style.height) {
objContainer.__zp_last.style.height = '';
objContainer.__zp_last.style.overflow = '';
}
objContainer.__zp_last = objContainer.__zp_last.previousSibling;
iNewHeight = objContainer.offsetHeight;
}
// Correct height
var iSpace = iContainerHeight - iNewHeight;
if (iSpace > 0) {
// Return last item back and cut it off
objContainer.__zp_last = objContainer.__zp_last.nextSibling;
objContainer.__zp_last.style.display = 'block';
var iItemHeight = iSpace - (objContainer.__zp_last.offsetHeight -
objContainer.__zp_last.clientHeight);
if (iItemHeight >= 0) {
objContainer.__zp_last.style.display = 'none';
objContainer.__zp_last.style.height = iItemHeight + 'px';
objContainer.__zp_last.style.overflow = 'hidden';
objContainer.__zp_last.style.display = 'block';
iNewHeight = objContainer.offsetHeight;
// Check height
if (iNewHeight != iContainerHeight) {
// May be non-standards-compliant mode
iItemHeight -= iNewHeight - iContainerHeight;
if (iItemHeight > 0) {
objContainer.__zp_last.style.height = iItemHeight + 'px';
} else {
objContainer.__zp_last.style.display = 'none';
objContainer.__zp_last.style.height = '';
objContainer.__zp_last.style.overflow = '';
objContainer.__zp_last = objContainer.__zp_last.previousSibling;
}
}
} else {
objContainer.__zp_last.style.display = 'none';
objContainer.__zp_last = objContainer.__zp_last.previousSibling;
}
}
// Show down arrow
objDownArrow.className = 'zpMenuScrollDownActive';
// Hide up arrow if needed
if (objContainer.__zp_first.previousSibling == objUpArrow) {
objUpArrow.className = 'zpMenuScrollUpInactive';
}
// Continue scrolling
if (objUp.__zp_timer) clearTimeout(objUp.__zp_timer);
if (objUp.__zp_mouseover) {
objUp.__zp_timer = setTimeout(funcMoveUp, 50);
}
}
return true;
};
objUp.onmouseover = function() {
objUp.__zp_mouseover = true;
return funcMoveUp();
}
objUp.onmouseout = function() {
objUp.__zp_mouseover = false;
if (objUp.__zp_timer) {
clearTimeout(objUp.__zp_timer);
objUp.__zp_timer = null;
}
};
fc.insertBefore(objUp, fc.firstChild);
var objDown = Zapatec.Utils.createElement("div");
objDown.__zp_tree = fc.firstChild.__zp_tree;
objDown.className = 'zpMenuScrollDownActive';
objDown.__zp_mouseover = false;
objDown.__zp_timer = null;
// Down arrow handler
var funcMoveDown = function() {
var objContainer = objDown.parentNode;
var iContainerHeight = objContainer.parentNode.clientHeight;
var objUpArrow = objContainer.firstChild;
var objDownArrow = objContainer.lastChild;
// Check if we can move down
if (objContainer.__zp_last.nextSibling != objDownArrow) {
// Show last item
if (objContainer.__zp_last.style.height) {
// Partly hidden
objContainer.__zp_last.style.height = '';
objContainer.__zp_last.style.overflow = '';
} else {
// Completely hidden
objContainer.__zp_last = objContainer.__zp_last.nextSibling;
objContainer.__zp_last.style.display = 'block';
}
var iNewHeight = objContainer.offsetHeight;
// Hide first item
while (iNewHeight > iContainerHeight) {
objContainer.__zp_first.style.display = 'none';
if (objContainer.__zp_first.style.height) {
objContainer.__zp_first.style.height = '';
objContainer.__zp_first.style.overflow = '';
}
objContainer.__zp_first = objContainer.__zp_first.nextSibling;
iNewHeight = objContainer.offsetHeight;
}
// Correct height
var iSpace = iContainerHeight - iNewHeight;
if (iSpace > 0) {
// Return first item back and cut it off
objContainer.__zp_first = objContainer.__zp_first.previousSibling;
objContainer.__zp_first.style.display = 'block';
var iItemHeight = iSpace - (objContainer.__zp_first.offsetHeight -
objContainer.__zp_first.clientHeight);
if (iItemHeight > 0) {
objContainer.__zp_first.style.display = 'none';
objContainer.__zp_first.style.height = iItemHeight + 'px';
objContainer.__zp_first.style.overflow = 'hidden';
objContainer.__zp_first.style.display = 'block';
iNewHeight = objContainer.offsetHeight;
// Check height
if (iNewHeight != iContainerHeight) {
// May be non-standards-compliant mode
iItemHeight -= iNewHeight - iContainerHeight;
if (iItemHeight > 0) {
objContainer.__zp_first.style.height = iItemHeight + 'px';
} else {
objContainer.__zp_first.style.display = 'none';
objContainer.__zp_first.style.height = '';
objContainer.__zp_first.style.overflow = '';
objContainer.__zp_first = objContainer.__zp_first.nextSibling;
}
}
} else {
objContainer.__zp_first.style.display = 'none';
objContainer.__zp_first = objContainer.__zp_first.nextSibling;
}
}
// Show up arrow
objUpArrow.className = 'zpMenuScrollUpActive';
// Hide down arrow if needed
if (objContainer.__zp_last.nextSibling == objDownArrow) {
objDownArrow.className = 'zpMenuScrollDownInactive';
}
// Continue scrolling
if (objDown.__zp_timer) clearTimeout(objDown.__zp_timer);
if (objDown.__zp_mouseover) {
objDown.__zp_timer = setTimeout(funcMoveDown, 50);
}
}
return true;
};
objDown.onmouseover = function() {
objDown.__zp_mouseover = true;
return funcMoveDown();
}
objDown.onmouseout = function() {
objDown.__zp_mouseover = false;
if (objDown.__zp_timer) {
clearTimeout(objDown.__zp_timer);
objDown.__zp_timer = null;
}
};
fc.appendChild(objDown);
var lc = fc.__zp_last;
iSubMenuOffsetHeight += objUp.offsetHeight + objDown.offsetHeight;
while (iSubMenuOffsetHeight > maxHeight) {
iSubMenuOffsetHeight -= lc.offsetHeight;
lc.style.display = 'none';
lc = lc.previousSibling;
fc.__zp_last = lc;
}
}
var width = fc.offsetWidth;
var height = fc.offsetHeight;
if (typeof subMenu.clientLeft != 'undefined' && !window.opera &&
!(document.compatMode && document.compatMode == 'CSS1Compat')) {
// IE in non-standards-compliant mode
width += subMenuBorderLeft * 2 + subMenuMarginLeft * 2;
height += subMenuBorderTop * 2 + subMenuMarginTop * 2;
}
subMenu.style.width = width + 'px';
subMenu.style.height = height + 'px';
if (subMenu.__zp_dropshadow) {
subMenu.__zp_dropshadow.style.width = subMenu.offsetWidth + 'px';
subMenu.__zp_dropshadow.style.height = subMenu.offsetHeight + 'px';
}
fc.style.position = 'absolute';
fc.style.visibility = 'inherit';
}
// Calculate new menu position & check document boundaries.
var newLeft = 0, newTop = 0;
var menuPos = Zapatec.Utils.getAbsolutePos(parMenu);
if ((/zpMenu-top/.test(this.trees[item.__zp_parent].className)) && (!(this.config.vertical))) {
// Drop Down menus
newLeft = item.offsetLeft;
newTop = item.offsetHeight;
// Adjust menu direction if it will display outside visible area
if (menuPos.x + newLeft + subMenu.offsetWidth + subMenuMarginLeft + 7 > scrollX + winW) {
newLeft += item.offsetWidth - subMenu.offsetWidth - subMenuMarginLeft;
if (subMenu.__zp_dropshadow) newLeft -= 6;
} else {
newLeft -= subMenuBorderLeft;
}
if (menuPos.y + newTop + subMenu.offsetHeight + subMenuMarginTop + 7 > scrollY + winH) {
newTop = -subMenu.offsetHeight;
if (subMenu.__zp_dropshadow) newTop -= 5;
}
} else {
// Vertical menus
newLeft = item.offsetWidth;
newTop = item.offsetTop;
// Adjust menu direction if it will display outside visible area
if (menuPos.x + newLeft + subMenu.offsetWidth + subMenuMarginLeft + 7 > scrollX + winW) {
newLeft = -subMenu.offsetWidth;
if (subMenu.__zp_dropshadow) newLeft -= 5;
}
if (menuPos.y + newTop + subMenu.offsetHeight + subMenuMarginTop + 7 > scrollY + winH) {
newTop -= subMenu.offsetHeight - item.offsetHeight;
if (subMenu.__zp_dropshadow) newTop -= 5;
} else {
newTop -= subMenuBorderTop;
}
}
if (menuPos.x + newLeft < 0) {
newLeft = 0 - menuPos.x;
}
if (menuPos.y + newTop < 0) {
newTop = 0 - menuPos.y;
}
subMenu.style.left = newLeft + 'px';
subMenu.style.top = newTop + 'px';
if (subMenu.__zp_dropshadow) {
subMenu.__zp_dropshadow.style.left = newLeft + 5 + 'px';
subMenu.__zp_dropshadow.style.top = newTop + 5 + 'px';
}
// Apply MSIE 5.5+ Select Box fix last, so it corrects the dropshadow.
if (Zapatec.is_ie && !Zapatec.is_ie5) {
if (!subMenu.__zp_wch) {
subMenu.__zp_wch = Zapatec.Utils.createWCH(subMenu);
}
subMenu.__zp_wch.style.zIndex = -1;
if (this.config.dropShadow) {
Zapatec.Utils.setupWCH(subMenu.__zp_wch, -subMenuBorderLeft, -subMenuBorderTop, subMenu.offsetWidth + 6, subMenu.offsetHeight + 5);
} else {
Zapatec.Utils.setupWCH(subMenu.__zp_wch, -subMenuBorderLeft, -subMenuBorderTop, subMenu.offsetWidth, subMenu.offsetHeight);
}
}
this.toggleItem(item_id, true);
};
/**
* Called from the itemMouseHandler() after a timeout; hides a designated item's
* branch of the tree.
*
* @private
* @param {string} item_id Item ID to hide
*/
Zapatec.Menu.prototype.itemHide = function(item_id) {
var item = this.items[item_id];
var subMenu = this._getTree(item.__zp_subtree);
var parMenu = this._getTree(item.__zp_parent);
if (subMenu) {
this.toggleItem(item_id, false);
parMenu.__zp_activeitem = '';
subMenu.__zp_activeitem = '';
// Go no further if some items are still expanded.
for (var i in this.items) {
if (this.items[i].__zp_state) return;
}
// Another click is necessary to activate menu again.
this.clickDone = false;
}
};
/*
* dndmove Drag'n'drop (move menu) functions
*
* Contains some functions that implement menu "drag'n'drop" facility which
* allows one to move the menu around the browser's view.
*
*/
//@{
/**
* Starts dragging the element.
*
* @private
* @param {object} ev Event object
* @param {object} menu Zapatec.Menu object
* @return Always true
* @type boolean
*/
Zapatec.Menu.dragStart = function (ev, menu) {
ev || (ev = window.event);
if (menu.dragging) {
return true;
}
var rootMenu = menu.rootMenu;
if (!(/(absolute|fixed)/).test(rootMenu.style.position)) {
rootMenu.style.position = 'absolute';
var pos = Zapatec.Utils.getAbsolutePos(rootMenu);
rootMenu.style.left = pos.x + 'px';
rootMenu.style.top = pos.y + 'px';
}
var testElm = ev.srcElement || ev.target;
while (1) {
if (testElm == rootMenu) break;
else testElm = testElm.parentNode;
if (!testElm) return true;
}
menu.dragging = true;
var posX = ev.pageX || ev.clientX + window.document.body.scrollLeft || 0;
var posY = ev.pageY || ev.clientY + window.document.body.scrollTop || 0;
var L = parseInt(rootMenu.style.left) || 0;
var T = parseInt(rootMenu.style.top) || 0;
menu.xOffs = (posX - L);
menu.yOffs = (posY - T);
// Unregister from scroll
if (menu.config.scrollWithWindow) {
Zapatec.ScrollWithWindow.unregister(menu.rootMenu);
}
};
/**
* Called at mouseover and/or mousemove on document, this function repositions
* the menu according to the current mouse position.
*
* @private
* @param {object} ev Event object
* @param {object} menu Zapatec.Menu object
* @return Always false
* @type boolean
*/
Zapatec.Menu.dragMove = function (ev, menu) {
ev || (ev = window.event);
var rootMenu = menu.rootMenu;
if (!(menu && menu.dragging)) {
return false;
}
var posX = ev.pageX || ev.clientX + window.document.body.scrollLeft || 0;
var posY = ev.pageY || ev.clientY + window.document.body.scrollTop || 0;
var st = rootMenu.style, L = posX - menu.xOffs, T = posY - menu.yOffs;
st.left = L + "px";
st.top = T + "px";
return Zapatec.Utils.stopEvent(ev);
};
/**
* Gets called when the drag and drop operation is finished; thus, at onmouseup
*
* @private
* @param {object} ev Event object
* @param {object} menu Zapatec.Menu object
*/
Zapatec.Menu.dragEnd = function (ev, menu) {
if (!menu) {
return false;
}
if (menu.dragging) {
menu.dragging = false;
// Adjust menu position if it will display outside visible area
var rootMenu = menu.rootMenu;
var st = rootMenu.style, L = parseInt(st.left), T = parseInt(st.top);
var scrollX = window.pageXOffset || document.body.scrollLeft ||
document.documentElement.scrollLeft || 0;
var scrollY = window.pageYOffset || document.body.scrollTop ||
document.documentElement.scrollTop || 0;
var objWindowSize = Zapatec.Utils.getWindowSize();
var winW = objWindowSize.width;
var winH = objWindowSize.height;
if (L < 0) {
st.left = '0px';
} else if (L + rootMenu.offsetWidth > scrollX + winW) {
st.left = scrollX + winW - rootMenu.offsetWidth + 'px';
}
if (T < 0) {
st.top = '0px';
} else if (T + rootMenu.offsetHeight > scrollY + winH) {
st.top = scrollY + winH - rootMenu.offsetHeight + 'px';
}
// Restore to scroll
if (menu.config.scrollWithWindow) {
Zapatec.ScrollWithWindow.register(rootMenu);
}
}
};
//@}
/**
* Disables item from an external script.
*
*