/* * ***** BEGIN LICENSE BLOCK ***** * Version: ZAPL 1.1 * * The contents of this file are subject to the Zimbra AJAX Public * License Version 1.1 ("License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.zimbra.com/license * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * The Original Code is: Zimbra AJAX Toolkit. * * The Initial Developer of the Original Code is Zimbra, Inc. * Portions created by Zimbra are Copyright (C) 2005 Zimbra, Inc. * All Rights Reserved. * * Contributor(s): * * ***** END LICENSE BLOCK ***** */ /** * Creates a menu object to menu items can be added. Menus can be created in various styles as * follows: * * DwtMenu.BAR_STYLE - Traditional menu bar. * DwtMenu.POPUP_STYLE - Popup menu * DwtMenu.DROPDOWN_STYLE - Used when a menu is a drop down (e.g. parent is a button or another menu item); * DwtMenu.COLOR_PICKER_STYLE - Menu is hosting a single color picker; * DwtMenu.CALENDAR_PICKER_STYLE - Menu is hostng a single calendar; * * @constructor * @class * * @author Ross Dargahi * @param parent the parent widget * @param style menu's style * @param className a CSS class * @param posStyle positioning style * @param dialog Dialog that this menu is a part of (if any) */ function DwtMenu(parent, style, className, posStyle, dialog) { if (arguments.length == 0) return; if (parent) { if (parent instanceof DwtMenuItem || parent instanceof DwtButton) this._style = DwtMenu.DROPDOWN_STYLE; else this._style = style || DwtMenu.POPUP_STYLE; if (!posStyle) posStyle = (this._style == DwtMenu.BAR_STYLE) ? DwtControl.STATIC_STYLE : DwtControl.ABSOLUTE_STYLE; } className = className || "DwtMenu"; // Hack to force us to hang off of the shell for positioning. DwtComposite.call(this, (parent instanceof DwtShell) ? parent : parent.shell, className, posStyle); this.parent = parent; if (parent == null) return; this._dialog = dialog; var htmlElement = this.getHtmlElement(); this._menuListeners = new AjxVector(); // Don't need to create table for color picker and calendar picker styles if (this._style != DwtMenu.COLOR_PICKER_STYLE && this._style != DwtMenu.CALENDAR_PICKER_STYLE) { this._table = this.getDocument().createElement("table"); this._table.border = 0; this._table.cellPadding = 0; this._table.cellSpacing = 0; htmlElement.appendChild(this._table); this._table.backgroundColor = DwtCssStyle.getProperty(htmlElement, "background-color"); } if (style != DwtMenu.BAR_STYLE) { this.setZIndex(Dwt.Z_HIDDEN); this._isPoppedup = false; } else { DwtMenu._activeMenuIds.add(htmlElement.id); this._isPoppedup = true; } this._popdownAction = new AjxTimedAction(); this._popdownAction.method = DwtMenu.prototype._doPopdown; this._popdownAction.obj = this; this._popdownActionId = -1; this._popupAction = new AjxTimedAction(); this._popupAction.method = DwtMenu.prototype._doPopup; this._popupAction.obj = this; this._popupActionId = -1; if ((this.parent instanceof DwtMenuItem && this.parent.parent._style == DwtMenu.BAR_STYLE) || !(this.parent instanceof DwtMenuItem)){ this._outsideListener = true; } this._numCheckedStyleItems = 0; } DwtMenu.prototype = new DwtComposite; DwtMenu.prototype.constructor = DwtMenu; DwtMenu.prototype.toString = function() { return "DwtMenu"; } DwtMenu.BAR_STYLE = 1; DwtMenu.POPUP_STYLE = 2; DwtMenu.DROPDOWN_STYLE = 3; DwtMenu.COLOR_PICKER_STYLE = 4; DwtMenu.CALENDAR_PICKER_STYLE = 5; DwtMenu._activeMenuUp = false; DwtMenu._activeMenuIds = new AjxVector(); DwtMenu.prototype.addMenuListener = function(listener) { if (!this._menuListeners.contains(listener)) { this._menuListeners.add(listener); } } DwtMenu.prototype.removeMenuListener = function(listener) { this._menuListeners.remove(listener); } DwtMenu.prototype.addPopdownListener = function(listener) { this.addListener(DwtEvent.POPDOWN, listener); } DwtMenu.prototype.removePopdownListener = function(listener) { this.removeListener(DwtEvent.POPDOWN, listener); } DwtMenu.prototype.getItem = function(index) { return this._children.get(index); } DwtMenu.prototype.getItemById = function(key, id) { var items = this.getItems(); for (var i = 0; i < items.length; i++) { var itemId = items[i].getData(key); if (itemId == id) return items[i]; } return null; } DwtMenu.prototype.getItemCount = function() { return this._children.size(); } DwtMenu.prototype.getItems = function() { return this._children.getArray(); } DwtMenu.prototype.getSelectedItem = function(style) { var a = this._children.getArray(); for (var i = 0; i < a.length; i++) { var mi = a[i]; if ((!style || (mi._style == style)) && mi.getChecked()) return mi; } return null; } DwtMenu.prototype.isPoppedup = function() { return this._isPoppedup; } DwtMenu.prototype.popup = function(msec, x, y) { if (this._style == DwtMenu.BAR_STYLE) return; if (this._popdownActionId != -1) { AjxTimedAction.cancelAction(this._popdownActionId); this._popdownActionId = -1; } else { if (this._isPoppedup || (this._popupActionId != -1 && msec && msec > 0)) { return; } else if (this._popupActionId != -1){ AjxTimedAction.cancelAction(this._popupActionId); this._popupActionId = -1; } if (!msec) { this._doPopup({x : x, y : y}); } else { this._popupAction.params.add(x); this._popupAction.params.add(y); this._popupActionId = AjxTimedAction.scheduleAction(this._popupAction, msec); } } } DwtMenu.prototype.popdown = function(msec) { if (this._style == DwtMenu.BAR_STYLE) return; if (this._popupActionId != -1) { AjxTimedAction.cancelAction(this._popupActionId); this._popupActionId = -1; } else { if (!this._isPoppedup || this._popdownActionId != -1) return; if (msec == null || msec == 0) this._doPopdown(); else this._popdownActionId = AjxTimedAction.scheduleAction(this._popdownAction, msec); } } /** * This allows the caller to associate one object with the menu. Association * means, for events, treat the menu, and this object as one. If I click on * elements pertaining to this object, we will think of them as part of the * menu. * @see _outsideMouseListener. */ DwtMenu.prototype.setAssociatedObj = function (dwtObj) { this._associatedObj = dwtObj; }; DwtMenu.prototype.setAssociatedElementId = function (id){ this._associatedElId = id; } /* * Checks a menu item (the menu must be radio or checkbox style). The menu item * is identified through the given field/value pair. * * @param field a key for menu item data * @param value value for the data of the menu item to check */ DwtMenu.prototype.checkItem = function(field, value, skipNotify) { var items = this._children.getArray(); for (var i = 0; i < items.length; i++) { var item = items[i]; if (item._style != DwtMenuItem.CHECK_STYLE && item._style != DwtMenuItem.RADIO_STYLE) continue; var val = item.getData(field); if (val == value) item.setChecked(true, skipNotify); } } DwtMenu.prototype.setSelectedItem = function (index){ var mi = this._children.get(index); mi.setSelectedStyle(); this._externallySelected = mi; }; DwtMenu.prototype.clearExternallySelectedItems = function () { if (this._externallySelected != null){ this._externallySelected._deselect(); this._externallySelected = null; } }; DwtMenu.prototype._removeChild = function(child) { if (this._style == DwtMenu.BAR_STYLE) { this._table.rows[0].deleteCell(child.getHtmlElement().parentNode.cellIndex); } else { /* If the item we are removing is check/radio style, and it is the last such item in the menu, then we * must instruct our other children to delete a "checked column" to ensure that things line up */ if (child._style == DwtMenuItem.CHECK_STYLE || child._style == DwtMenuItem.RADIO_STYLE) { if (this._numCheckedStyleItems == 1) { var sz = this._children.size(); if (sz > 0) { var a = this._children.getArray(); for (var i = 0; i < sz; i++) { if (a[i]._style != DwtMenuItem.CHECK_STYLE && a[i]._style != DwtMenuItem.RADIO_STYLE) a[i]._checkedItemsRemoved(); } } } this._numCheckedStyleItems--; } this._table.deleteRow(child.getHtmlElement().parentNode.parentNode.rowIndex); } this._children.remove(child); } // Override DwtComposite._addChild to do nothing DwtMenu.prototype._addChild = function(child) { // Color pickers and calendars are not menu aware so we have to deal with // them acordingly if ((child instanceof DwtColorPicker) || (child instanceof DwtCalendar)) this._addItem(child); } DwtMenu.prototype._addItem = function(item, index) { if (this._style == DwtMenu.COLOR_PICKER_STYLE || this._style == DwtMenu.CALENDAR_PICKER_STYLE) { // Item better be a color picker & we better not have any children if (this._children.size() > 0 || !(item.parent instanceof DwtMenu) || ((this._style == DwtMenu.COLOR_PICKER_STYLE && !(item instanceof DwtColorPicker)) || (this._style == DwtMenu.CALENDAR_PICKER_STYLE && !(item instanceof DwtCalendar)))) new DwtException("Invalid child", DwtException.INVALID_PARAM, "DwtMenu.prototype._addItem"); this._children.add(item); this.getHtmlElement().appendChild(item.getHtmlElement); } else { var row; var col; if (this._style == DwtMenu.BAR_STYLE){ var rows = this._table.rows; row = (rows.length != 0) ? rows[0]: this._table.insertRow(0); if (index == null || index > row.cells.length) index = rows.cells.length; col = row.insertCell(index); col.align = "center"; col.vAlign = "middle"; var spc = row.insertCell(row.cells.length); spc.nowrap = true; spc.width = "7px" } else { /* If the item we are adding is check/radio style, and it is the first such item in the menu, then we * must instruct our other children to add a "checked column" to ensure that things line up */ if (item._style == DwtMenuItem.CHECK_STYLE || item._style == DwtMenuItem.RADIO_STYLE) { if (this._numCheckedStyleItems == 0) { var sz = this._children.size(); if (sz > 0) { var a = this._children.getArray(); for (var i = 0; i < sz; i++) { if (a[i]._style != DwtMenuItem.CHECK_STYLE && a[i]._style != DwtMenuItem.RADIO_STYLE) a[i]._createCheckedStyle(); } } } this._numCheckedStyleItems++; } var rows = this._table.rows; if (index == null || index > rows.length) index = rows.length; var row = this._table.insertRow(index); col = row.insertCell(0); } col.noWrap = true; col.appendChild(item.getHtmlElement()); this._children.add(item, index); } } DwtMenu.prototype._radioItemSelected = function(child, skipNotify) { var radioGroupId = child._radioGroupId; var sz = this._children.size(); var a = this._children.getArray(); for (var i = 0; i < sz; i++) { if (a[i] != child && a[i]._style == DwtMenuItem.RADIO_STYLE && a[i]._radioGroupId == radioGroupId && a[i]._itemChecked) { a[i].setChecked(false, skipNotify); break; } } } DwtMenu.prototype._menuHasCheckedItems = function() { return (this._numCheckedStyleItems > 0) ? true : false; } DwtMenu.prototype._doPopup = function(args) { var pb = this.parent.getBounds(); var ws = this.shell.getSize(); var s = this.getSize(); var x; var y; var vBorder; var hBorder; if (this.parent instanceof DwtMenuItem) { var pp = this.parent.parent; var ppHtmlElement = pp.getHtmlElement(); if (pp._style == DwtMenu.BAR_STYLE) { vBorder = (ppHtmlElement.style.borderLeftWidth == "") ? 0 : parseInt(ppHtmlElement.style.borderLeftWidth); x = pb.x + vBorder; hBorder = (ppHtmlElement.style.borderTopWidth == "") ? 0 : parseInt(ppHtmlElement.style.borderTopWidth); hBorder += (ppHtmlElement.style.borderBottomWidth == "") ? 0 : parseInt(ppHtmlElement.style.borderBottonWidth); y = pb.y + pb.height + hBorder; x = ((x + s.x) >= ws.x) ? x - (x + s.x - ws.x): x; y = ((y + s.y) >= ws.y) ? y - (y + s.y - ws.y) : y; } else { // Drop Down vBorder = (ppHtmlElement.style.borderLeftWidth == "") ? 0 : parseInt(ppHtmlElement.style.borderLeftWidth); vBorder += (ppHtmlElement.style.borderRightWidth == "") ? 0 : parseInt(ppHtmlElement.style.borderRightWidth); x = pb.x + pb.width + vBorder; hBorder = (ppHtmlElement.style.borderTopWidth == "") ? 0 : parseInt(ppHtmlElement.style.borderTopWidth); y = pb.y + hBorder; x = ((x + s.x) >= ws.x) ? pb.x - s.x - vBorder: x; y = ((y + s.y) >= ws.y) ? y - (y + s.y - ws.y) : y; } this.setLocation(x, y); } else if (this.parent instanceof DwtSelect) { var p = this.parent; var pHtmlElement = p.getHtmlElement(); // since buttons are often absolutely positioned, and menus aren't, we need x,y relative to window var ptw = Dwt.toWindow(pHtmlElement, 0, 0); vBorder = (pHtmlElement.style.borderLeftWidth == "") ? 0 : parseInt(pHtmlElement.style.borderLeftWidth); x = pb.x + vBorder; hBorder = (pHtmlElement.style.borderTopWidth == "") ? 0 : parseInt(pHtmlElement.style.borderTopWidth); hBorder += (pHtmlElement.style.borderBottomWidth == "") ? 0 : parseInt(pHtmlElement.style.borderBottonWidth); y = pb.y + pb.height + hBorder; x = ((x + s.x) >= (ws.x - 5 )) ? x - (x + s.x - ws.x): x; if ( (y + s.y) >= (ws.y - 5 )) { var myEl = this.getHtmlElement(); myEl.style.height = ws.y - y - 30; myEl.style.overflow = "auto"; } //y = ((y + s.y) >= (ws.y - 30 )) ? y - (y + s.y - ws.y) : y; this.setLocation(x, y); } else if (this.parent instanceof DwtButton) { // Parent is DwtButton var p = this.parent; var pHtmlElement = p.getHtmlElement(); // since buttons are often absolutely positioned, and menus aren't, we need x,y relative to window var ptw = Dwt.toWindow(pHtmlElement, 0, 0); vBorder = (pHtmlElement.style.borderLeftWidth == "") ? 0 : parseInt(pHtmlElement.style.borderLeftWidth); x = ptw.x + vBorder; hBorder = (pHtmlElement.style.borderTopWidth == "") ? 0 : parseInt(pHtmlElement.style.borderTopWidth); hBorder += (pHtmlElement.style.borderBottomWidth == "") ? 0 : parseInt(pHtmlElement.style.borderBottonWidth); y = ptw.y + pb.height + hBorder; x = ((x + s.x) >= ws.x) ? x - (x + s.x - ws.x): x; y = ((y + s.y) >= ws.y) ? y - (y + s.y - ws.y) : y; this.setLocation(x, y); } else { // Popup menu type x = args.x; y = args.y; var newX = ((x + s.x) >= ws.x) ? x - (x + s.x - ws.x): x; var newY = ((y + s.y) >= ws.y) ? y - (y + s.y - ws.y) : y; this.setLocation(newX, newY); } // 5/2/2005 // EMC -- changed this to Z_DIALOG_MENU so that you don't have to pass // dialog object. This helps if you are adding and object to a dialog -- // where the object doesn't know anything about its container. // var zIndex = this._dialog ? this._dialog.getZIndex() + Dwt.Z_INC : Dwt.Z_MENU; var zIndex = this._dialog ? Dwt.Z_DIALOG_MENU : Dwt.Z_MENU; this.setZIndex(zIndex); this._popupActionId = -1; this._isPoppedup = true; if (this._outsideListener) { AjxCore.addListener(document.body, "onmousedown", DwtMenu._outsideMouseDownListener); } if (!DwtMenu._activeMenu) { DwtMenu._activeMenu = this; DwtMenu._activeMenuUp = true; DwtEventManager.addListener(DwtEvent.ONMOUSEDOWN, DwtMenu._outsideMouseDownListener); } DwtMenu._activeMenuIds.add(this._htmlElId); DwtMenu._activeMenuIds.sort(); } DwtMenu.prototype._doPopdown = function() { // Notify all sub menu's to pop themselves down var a = this._children.getArray(); var s = this._children.size(); for (var i = 0; i < s; i++) { if ((a[i] instanceof DwtMenuItem) && a[i]._style != DwtMenuItem.SEPARATOR_STYLE) a[i]._popdownMenu(); } this.setZIndex(Dwt.Z_HIDDEN); this.notifyListeners(DwtEvent.POPDOWN, this); // TODO release capture if you have it if (this._outsideListener) { AjxCore.removeListener(document.body, "onmousedown", DwtMenu._outsideMouseDownListener); } if (DwtMenu._activeMenu == this) { DwtMenu._activeMenu = null; DwtMenu._activeMenuUp = false; DwtEventManager.removeListener(DwtEvent.ONMOUSEDOWN, DwtMenu._outsideMouseDownListener); } DwtMenu._activeMenuIds.remove(this._htmlElId); this._popdownActionId = -1; this._isPoppedup = false; } DwtMenu.prototype._getActiveItem = function(){ var a = this._children.getArray(); var s = this._children.size(); for (var i = 0; i < s; i++) { if (a[i]._isMenuPoppedup()) return a[i]; } return null; } DwtMenu._mouseDownListener = function(ev) { if (!DwtMenu._activeMenuUp) return; var obj = DwtMenu._activeMenu; var mi = null; if (ev) { var mouseEv = DwtShell.mouseEvent; mouseEv.setFromDhtmlEvent(ev); mouseEv._stopPropagation = false; mouseEv._returnValue = true; mouseEv.setToDhtmlEvent(ev); mi = mouseEv.dwtObj; } // If we are dealing with a menu item that is istself contained in a menu that is // a menu bar, then don't do a popdown, else if the menuItem has a menu with a selection // listener, then continue with a popdown, else let the menuITem deal with it if (obj.parent instanceof DwtMenuItem && obj.parent.parent._style == DwtMenu.BAR_STYLE) { obj.parent.parent._getActiveItem()._deselect(); return true; } else if (mi && mi instanceof DwtMenuItem && !mi.isListenerRegistered(DwtEvent.SELECTION) && mi.getMenu() != null) { return true; } obj.popdown(0); return true; } /* Note that a hack has been added to DwtHtmlEditor to call this method when the editor get's focus. The reason * for this is that the editor uses an Iframe whose events are independent of the menu's document. In this case * event will be null */ DwtMenu._outsideMouseDownListener = function(ev) { if (DwtMenu._activeMenuUp){ ev = ev || window.event; if (ev) { // figure out if we are over the menu that is up var htmlEl = DwtUiEvent.getTarget(ev); var aM = DwtMenu._activeMenu; var nearestDwtObj = DwtUiEvent.getDwtObjFromEvent(ev); var id = aM._htmlElId; if (aM._associatedObj && aM._associatedObj == nearestDwtObj){ return true; } // assuming that the active menu is the parent of all other menus // that are up, search through the array of child menus dom ids as // well as our own. while (htmlEl != null) { if (htmlEl.id && htmlEl.id != "" && (htmlEl.id == id || htmlEl.id == aM._associatedElId || DwtMenu._activeMenuIds.binarySearch(htmlEl.id) != -1 )) { return true; } htmlEl = htmlEl.parentNode; } } DwtMenu._mouseDownListener(ev); } }; DwtMenu.closeActiveMenu = function() { if (DwtMenu._activeMenuUp){ DwtMenu._activeMenu.popdown(0); } };