1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2013, 2014, 2015, 2016 Synacor, Inc. 5 * 6 * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at: https://www.zimbra.com/license 9 * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15 10 * have been added to cover use of software over a computer network and provide for limited attribution 11 * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B. 12 * 13 * Software distributed under the License is distributed on an "AS IS" basis, 14 * WITHOUT WARRANTY OF ANY KIND, either express or implied. 15 * See the License for the specific language governing rights and limitations under the License. 16 * The Original Code is Zimbra Open Source Web Client. 17 * The Initial Developer of the Original Code is Zimbra, Inc. All rights to the Original Code were 18 * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015. 19 * 20 * All portions of the code are Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 25 /** 26 * Creates a menu item. 27 * @constructor 28 * @class 29 * Menu items can be part of a radio group, or can be checked style menu items. 30 * 31 * @author Ross Dargahi 32 * 33 * @param {hash} params a hash of parameters 34 * @param {DwtComposite} params.parent the parent widget 35 * @param {constant} params.style the menu item style 36 * @param {string} params.radioGroupId the radio group that the menu item is part of 37 * @param {number} params.index the position in menu 38 * @param {string} params.className the CSS class 39 * @param {constant} params.posStyle the positioning style (see {@link DwtControl}) 40 * @param {string} params.id an explicit ID to use for the control's HTML element 41 * 42 * @extends DwtButton 43 */ 44 DwtMenuItem = function(params) { 45 if (arguments.length == 0) { return; } 46 params = Dwt.getParams(arguments, DwtMenuItem.PARAMS); 47 48 // check parameters 49 var parent = params.parent; 50 if (!(parent && parent.isDwtMenu)) { 51 throw new DwtException("Parent must be a DwtMenu object", DwtException.INVALIDPARENT, "DwtMenuItem"); 52 } 53 54 var style = params.style = params.style || DwtMenuItem.NO_STYLE; 55 if (parent._style == DwtMenu.BAR_STYLE && style != DwtMenuItem.PUSH_STYLE) { 56 throw new DwtException("DwtMenuItemInit: invalid style", DwtException.INVALID_PARAM, "DwtMenuItem"); 57 } 58 59 // call super constructor 60 params.className = params.className || "ZMenuItem"; 61 style &= ~DwtLabel.IMAGE_RIGHT; // remove image right style 62 style |= DwtButton.ALWAYS_FLAT | DwtLabel.IMAGE_LEFT; // set default styles 63 var isSeparator = (style & DwtMenuItem.SEPARATOR_STYLE); 64 if (isSeparator) { 65 params.className = "ZMenuItemSeparator"; 66 this.isFocusable = false; 67 this.noTab = true; 68 this.role = null; 69 } 70 params.listeners = DwtMenuItem._listeners; 71 DwtButton.call(this, params); 72 73 this.setDropDownImages("Cascade", "Cascade", "Cascade", "Cascade"); 74 this._radioGroupId = params.radioGroupId; 75 76 // add this item at the specified index 77 if (parent._addItem) { 78 parent._addItem(this, params.index); 79 } 80 81 // add listeners if not menu item separator 82 if (!isSeparator) { 83 this.addSelectionListener(this.__handleItemSelect.bind(this)); 84 } 85 }; 86 87 DwtMenuItem.PARAMS = ["parent", "style", "radioGroupId", "index", "className", "posStyle", "id"]; 88 89 DwtMenuItem.prototype = new DwtButton; 90 DwtMenuItem.prototype.constructor = DwtMenuItem; 91 92 DwtMenuItem.prototype.isDwtMenuItem = true; 93 DwtMenuItem.prototype.toString = function() { return "DwtMenuItem"; }; 94 DwtMenuItem.prototype.role = "menuitem"; 95 96 // 97 // Constants 98 // 99 100 DwtMenuItem.CHECKED = 1; 101 DwtMenuItem.UNCHECKED = 2; 102 103 /** 104 * Defines the "no" style. 105 */ 106 DwtMenuItem.NO_STYLE = 0; 107 /** 108 * Defines the "check" style. 109 */ 110 DwtMenuItem.CHECK_STYLE = DwtButton._LAST_STYLE * 2; 111 /** 112 * Defines the "radio" style. 113 */ 114 DwtMenuItem.RADIO_STYLE = DwtButton._LAST_STYLE * 4; 115 /** 116 * Defines the "separator" style. 117 */ 118 DwtMenuItem.SEPARATOR_STYLE = DwtButton._LAST_STYLE * 8; 119 /** 120 * Defines the "cascade" style. 121 */ 122 DwtMenuItem.CASCADE_STYLE = DwtButton._LAST_STYLE * 16; 123 /** 124 * Defines the "push" style. 125 */ 126 DwtMenuItem.PUSH_STYLE = DwtButton._LAST_STYLE * 32; 127 /** 128 * Defines the "select" style. 129 */ 130 DwtMenuItem.SELECT_STYLE = DwtButton._LAST_STYLE * 64; 131 DwtMenuItem._LAST_STYLE = DwtMenuItem.SELECT_STYLE; 132 133 DwtMenuItem._MENU_POPUP_DELAY = 250; 134 DwtMenuItem._MENU_POPDOWN_DELAY = 250; 135 136 // 137 // Data 138 // 139 140 DwtMenuItem.prototype.TEMPLATE = "dwt.Widgets#ZMenuItem"; 141 DwtMenuItem.prototype.SEPARATOR_TEMPLATE = "dwt.Widgets#ZMenuItemSeparator"; 142 DwtMenuItem.prototype.BLANK_CHECK_TEMPLATE = "dwt.Widgets#ZMenuItemBlankCheck"; 143 DwtMenuItem.prototype.BLANK_ICON_TEMPLATE = "dwt.Widgets#ZMenuItemBlankIcon"; 144 DwtMenuItem.prototype.BLANK_CASCADE_TEMPLATE = "dwt.Widgets#ZMenuItemBlankCascade"; 145 146 // 147 // Public methods 148 // 149 DwtMenuItem.prototype.dispose = 150 function() { 151 delete this._checkEl; 152 DwtButton.prototype.dispose.call(this); 153 }; 154 155 /** 156 * Creates the menu item. 157 * 158 * @param {hash} params a hash of parameters 159 */ 160 DwtMenuItem.create = 161 function(params) { 162 var mi = new DwtMenuItem(params); 163 if (params.imageInfo) { 164 mi.setImage(params.imageInfo); 165 } 166 if (params.text) { 167 mi.setText(params.text); 168 } 169 mi.setEnabled(params.enabled !== false); 170 return mi; 171 }; 172 173 /** 174 * Gets the checked flag. 175 * 176 * @return {boolean} <code>true</code> if the item is checked 177 */ 178 DwtMenuItem.prototype.getChecked = 179 function() { 180 return this._itemChecked; 181 }; 182 183 /** 184 * Sets the checked flag. 185 * 186 * @param {boolean} checked if <code>true</code>, check the item 187 * @param {boolean} skipNotify if <code>true</code>, do not notify listeners 188 */ 189 DwtMenuItem.prototype.setChecked = 190 function(checked, skipNotify) { 191 this._setChecked(checked, null, skipNotify); 192 this.parent._checkItemAdded(this); 193 }; 194 195 DwtMenuItem.prototype.setImage = 196 function(imageInfo) { 197 DwtButton.prototype.setImage.call(this, imageInfo); 198 this.parent._iconItemAdded(this); 199 }; 200 201 DwtMenuItem.prototype.setText = 202 function(text) { 203 DwtButton.prototype.setText.call(this, text); 204 if (this.parent.isPoppedUp()) { 205 // resize menu if we reset text on the fly 206 this.parent.render(); 207 } 208 }; 209 210 DwtMenuItem.prototype.setMenu = 211 function(params) { 212 var params = Dwt.getParams(arguments, DwtButton.setMenuParams); 213 DwtButton.prototype.setMenu.call(this, params); 214 this.parent._submenuItemAdded(this); 215 }; 216 217 DwtMenuItem.prototype.setHoverDelay = 218 function(delay) { 219 this._hoverDelay = delay; 220 }; 221 222 DwtMenuItem.prototype.setShortcut = 223 function(shortcut) { 224 if (shortcut && this._dropDownEl) { 225 this._dropDownEl.innerHTML = shortcut; 226 } 227 }; 228 229 // Set whether the item is selectable even when it has an open submenu 230 DwtMenuItem.prototype.setSelectableWithSubmenu = 231 function(selectable) { 232 this._selectableWithSubmenu = selectable; 233 }; 234 235 DwtMenuItem.prototype.isSeparator = 236 function() { 237 return Boolean(this._style & DwtMenuItem.SEPARATOR_STYLE); 238 }; 239 240 DwtMenuItem.prototype.handleKeyAction = 241 function(actionCode, ev) { 242 if (this.parent) { 243 return this.parent.handleKeyAction(actionCode, ev) 244 } else { 245 return DwtButton.prototype.handleKeyAction.call(this, actionCode, ev); 246 } 247 }; 248 249 DwtMenuItem.prototype.getKeyMapName = 250 function() { 251 return DwtKeyMap.MAP_MENU; 252 }; 253 254 // 255 // Protected methods 256 // 257 258 DwtMenuItem.prototype._createHtml = 259 function(templateId) { 260 var defaultTemplate = this.isSeparator() ? this.SEPARATOR_TEMPLATE : this.TEMPLATE; 261 DwtButton.prototype._createHtml.call(this, templateId || defaultTemplate); 262 }; 263 264 DwtMenuItem.prototype._createHtmlFromTemplate = 265 function(templateId, data) { 266 DwtButton.prototype._createHtmlFromTemplate.call(this, templateId, data); 267 this._checkEl = document.getElementById(data.id+"_check"); 268 }; 269 270 DwtMenuItem.prototype._setChecked = 271 function(checked, ev, skipNotify) { 272 var isCheck = this._style & DwtMenuItem.CHECK_STYLE; 273 var isRadio = this._style & DwtMenuItem.RADIO_STYLE; 274 if ((isCheck || isRadio) && this._itemChecked != checked) { 275 this._itemChecked = checked; 276 277 if (this._checkEl) { 278 this._checkEl.innerHTML = ""; 279 var icon = checked ? (isCheck ? "MenuCheck" : "MenuRadio") : "Blank_9"; 280 AjxImg.setImage(this._checkEl, icon); 281 if (checked) { 282 // deselect currently selected radio button 283 if (isRadio) { 284 this.parent._radioItemSelected(this, skipNotify); 285 } 286 } 287 } 288 } 289 }; 290 291 DwtMenuItem.prototype._checkItemAdded = function(className) {}; 292 DwtMenuItem.prototype._checkedItemsRemoved = function() {}; 293 294 DwtMenuItem.prototype._submenuItemAdded = 295 function() { 296 if (this.isSeparator()) { return; } 297 298 if (this._cascCell == null) { 299 this._cascCell = this._row.insertCell(-1); 300 this._cascCell.noWrap = true; 301 } 302 }; 303 304 DwtMenuItem.prototype._submenuItemRemoved = 305 function() { 306 if (this._dropDownEl) { 307 this._dropDownEl.innerHTML = ""; 308 } 309 }; 310 311 DwtMenuItem.prototype._popupMenu = 312 function(delay, kbGenerated) { 313 var menu = this.getMenu(); 314 var pp = this.parent.parent; 315 var pb = this.getBounds(); 316 var ws = menu.shell.getSize(); 317 var s = menu.getSize(); 318 var x; 319 var y; 320 var vBorder; 321 var hBorder; 322 var ppHtmlElement = pp.getHtmlElement(); 323 if (pp._style == DwtMenu.BAR_STYLE) { 324 vBorder = (ppHtmlElement.style.borderLeftWidth == "") ? 0 : parseInt(ppHtmlElement.style.borderLeftWidth); 325 x = pb.x + vBorder; 326 hBorder = (ppHtmlElement.style.borderTopWidth == "") ? 0 : parseInt(ppHtmlElement.style.borderTopWidth); 327 hBorder += (ppHtmlElement.style.borderBottomWidth == "") ? 0 : parseInt(ppHtmlElement.style.borderBottonWidth); 328 y = pb.y + pb.height + hBorder; 329 x = ((x + s.x) >= ws.x) ? x - (x + s.x - ws.x): x; 330 } 331 else { // Drop Down 332 vBorder = (ppHtmlElement.style.borderLeftWidth == "") ? 0 : parseInt(ppHtmlElement.style.borderLeftWidth); 333 vBorder += (ppHtmlElement.style.borderRightWidth == "") ? 0 : parseInt(ppHtmlElement.style.borderRightWidth); 334 x = pb.x + pb.width + vBorder; 335 hBorder = (ppHtmlElement.style.borderTopWidth == "") ? 0 : parseInt(ppHtmlElement.style.borderTopWidth); 336 y = pb.y + hBorder; 337 if (menu.centerOnParentVertically()) { 338 y += pb.height / 2; 339 } 340 //x = ((x + s.x) >= ws.x) ? pb.x - s.x - vBorder : x; 341 } 342 menu.popup(delay, x, y, kbGenerated); 343 }; 344 345 DwtMenuItem.prototype._popdownMenu = 346 function() { 347 var menu = this.getMenu(); 348 if (menu) { 349 menu.popdown(); 350 } 351 }; 352 353 DwtMenuItem.prototype._isMenuPoppedUp = 354 function() { 355 var menu = this.getMenu(); 356 return (menu && menu.isPoppedUp()); 357 }; 358 359 DwtMenuItem.prototype._blur = 360 function() { 361 var menu = this.parent; 362 var mev = new DwtMouseEvent(); 363 364 this._setMouseEvent(mev, { 365 dwtObj: this, 366 ersatz: true, 367 type: DwtEvent.ONMOUSEOUT 368 }); 369 this.notifyListeners(DwtEvent.ONMOUSEOUT, mev); 370 menu.__currentItem = null; 371 372 DwtButton.prototype._blur.call(this); 373 }; 374 375 DwtMenuItem.prototype._focus = function() { 376 377 var menu = this.parent; 378 var currItem = menu.__currentItem; 379 var mev = new DwtMouseEvent(); 380 381 this._setMouseEvent(mev, {dwtObj:this, ersatz: true}); 382 this.notifyListeners(AjxEnv.isIE ? DwtEvent.ONMOUSEENTER : DwtEvent.ONMOUSEOVER, mev); // mouseover selects a menu item 383 menu.__currentItem = this; 384 385 DwtButton.prototype._focus.call(this); 386 } 387 388 // 389 // Private methods 390 // 391 392 DwtMenuItem.prototype.__handleItemSelect = 393 function(event) { 394 this.setDisplayState(DwtControl.NORMAL); 395 if (this.isStyle(DwtMenuItem.CHECK_STYLE)) { 396 this._setChecked(!this._itemChecked, null, true); 397 event.detail = this.getChecked() ? DwtMenuItem.CHECKED : DwtMenuItem.UNCHECKED; 398 } 399 else if (this.isStyle(DwtMenuItem.RADIO_STYLE)) { 400 this._setChecked(true, true); 401 this.parent._radioItemSelected(this, true); 402 event.detail = this.getChecked() ? DwtMenuItem.CHECKED : DwtMenuItem.UNCHECKED; 403 } 404 else if (this.isStyle(DwtMenuItem.PUSH_STYLE)) { 405 if (this._menu) { 406 if (this._isMenuPoppedUp()) { 407 DwtMenu.closeActiveMenu(event); 408 } 409 else { 410 this._popupMenu(DwtKeyEvent.isKeyEvent(ev)); 411 } 412 } 413 return; 414 } 415 if (!this.isStyle(DwtMenuItem.CASCADE_STYLE)) { 416 if (this._selectableWithSubmenu || !this._menu || !this._menu.isPoppedUp || !this._menu.isPoppedUp()) { 417 DwtMenu.closeActiveMenu(event); 418 } 419 } 420 }; 421 422 DwtMenuItem._mouseOverListener = 423 function(ev) { 424 var menuItem = ev.dwtObj; 425 if (!menuItem) { return false; } 426 var menu = menuItem.parent; 427 if (menuItem.isSeparator()) { return false; } 428 DwtButton._mouseOverListener(ev, menuItem); 429 if (!ev.ersatz) { 430 menu._popdownSubmenus(); 431 } 432 if (!menuItem.hasFocus() && menuItem.getEnabled()) { 433 menuItem.focus(); 434 } 435 436 if (menuItem._menu && !ev.ersatz) { 437 menuItem._popupMenu(menuItem._hoverDelay); 438 } 439 }; 440 441 DwtMenuItem._mouseOutListener = 442 function(ev) { 443 var menuItem = ev.dwtObj; 444 var submenu = menuItem && menuItem.getMenu(); 445 if (submenu && submenu.isPoppedUp()) { return; } 446 DwtButton._mouseOutListener(ev); 447 }; 448 449 /* 450 * returns menu item table row element 451 */ 452 DwtMenuItem.prototype.getRowElement = 453 function() { 454 var el = this._textEl || 455 this._dropDownEl || 456 (this._iconEl && this._iconEl.left) || 457 (this._iconEl && this._iconEl.right); 458 if (el) { 459 return el.parentNode; 460 } 461 }; 462 463 DwtMenuItem._listeners = {}; 464 DwtMenuItem._listeners[DwtEvent.ONMOUSEOVER] = DwtMenuItem._mouseOverListener.bind(); 465 DwtMenuItem._listeners[DwtEvent.ONMOUSEOUT] = DwtMenuItem._mouseOutListener.bind(); 466 DwtMenuItem._listeners[DwtEvent.ONMOUSEDOWN] = DwtButton._mouseDownListener.bind(); 467 DwtMenuItem._listeners[DwtEvent.ONMOUSEUP] = DwtButton._mouseUpListener.bind(); 468 DwtMenuItem._listeners[DwtEvent.ONMOUSEENTER] = DwtMenuItem._mouseOverListener.bind(); 469 DwtMenuItem._listeners[DwtEvent.ONMOUSELEAVE] = DwtButton._mouseOutListener.bind(); 470