1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 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, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * @overview 26 * 27 * This file defines the zimbra application chooser. 28 * 29 */ 30 31 /** 32 * @class 33 * This class represents a zimbra application chooser. The chooser is the "tab application" toolbar shown 34 * in the Zimbra Web Client. The toolbar buttons are represented as "tabs". 35 * 36 * @param {Hash} params a hash of parameters 37 * 38 * @extends ZmToolBar 39 */ 40 ZmAppChooser = function(params) { 41 42 params.className = params.className || "ZmAppChooser"; 43 params.width = appCtxt.getSkinHint("appChooser", "fullWidth") ? "100%" : null; 44 45 ZmToolBar.call(this, params); 46 47 Dwt.setLocation(this.getHtmlElement(), Dwt.LOC_NOWHERE, Dwt.LOC_NOWHERE); 48 49 this.setScrollStyle(Dwt.CLIP); 50 51 this._buttonListener = new AjxListener(this, this._handleButton); 52 this._initOverFlowTabs(); 53 var buttons = params.buttons; 54 for (var i = 0; i < buttons.length; i++) { 55 var id = buttons[i]; 56 if (id == ZmAppChooser.SPACER) { 57 this.addSpacer(ZmAppChooser.SPACER_HEIGHT); 58 } else { 59 this._createButton(id); 60 } 61 } 62 63 this._createPrecedenceList(); 64 this._inited = true; 65 }; 66 67 ZmAppChooser.prototype = new ZmToolBar; 68 ZmAppChooser.prototype.constructor = ZmAppChooser; 69 ZmAppChooser.prototype.role = "tablist"; 70 71 ZmAppChooser.prototype._initOverFlowTabs = 72 function(){ 73 this._leftOverflow = document.getElementById("moreTabsLeftContainer"); 74 this._rightOverflow = document.getElementById("moreTabsRightContainer"); 75 if (this._leftOverflow) { 76 this._leftOverflow.onclick = this._showLeftTab.bind(this); 77 } 78 if (this._rightOverflow) { 79 this._rightOverflow.onclick = this._showRightTab.bind(this); 80 } 81 this._leftBtnIndex = -1; 82 this._deletedButtons = []; 83 }; 84 85 ZmAppChooser.prototype._showLeftTab = 86 function(){ 87 var items = this.getItems(); 88 if(this._leftBtnIndex > -1){ 89 items[this._leftBtnIndex].setVisible(true); 90 this._leftBtnIndex--; 91 } 92 this._checkArrowVisibility(); 93 }; 94 95 ZmAppChooser.prototype._showRightTab = 96 function(){ 97 var items = this.getItems(); 98 this._leftBtnIndex++; 99 items[this._leftBtnIndex].setVisible(false); 100 this._checkArrowVisibility(); 101 }; 102 103 ZmAppChooser.prototype._showTab = 104 function(id, ev){ 105 var button = this._buttons[id]; 106 this._moreTabsBtn.getMenu().popdown(); 107 if (!button) return; 108 if (!button.getVisible()){ // Left side 109 var found = false; 110 for (var index in this._buttons){ 111 if (!found && this._buttons[index].getHTMLElId() == button.getHTMLElId()) 112 found = true; 113 else if (this._buttons[index].getVisible()) 114 break; 115 if (found){ 116 this._buttons[index].setVisible(true) 117 this._leftBtnIndex--; 118 } 119 } 120 }else { // Right side 121 while(this._isTabOverflow(button.getHtmlElement())){ 122 this._showRightTab(); 123 } 124 } 125 this._checkArrowVisibility(); 126 appCtxt.getAppController()._appButtonListener(ev); 127 128 129 }; 130 131 ZmAppChooser.prototype._attachMoreTabMenuItems = 132 function(menu){ 133 134 for (var deletedIndex=0; deletedIndex < this._deletedButtons.length; deletedIndex++){ 135 var mi = menu.getItemById(ZmOperation.MENUITEM_ID, this._deletedButtons[deletedIndex] + "_menu"); 136 if (mi) { 137 menu.removeChild(mi); 138 mi.dispose(); 139 } 140 } 141 142 this._deletedButtons = []; 143 for(var index in this._buttons){ 144 var item = menu.getItemById(ZmOperation.MENUITEM_ID, index + "_menu"); 145 if (item){ 146 if (item.getText() != this._buttons[index].getText()){ 147 item.setText(this._buttons[index].getText()); 148 } 149 } else { 150 var mi = new DwtMenuItem({parent:menu, style:DwtMenuItem.CASCADE_STYLE, id: index + "_menu"}); 151 mi.setData(ZmOperation.MENUITEM_ID, index + "_menu" ); 152 mi.setData(Dwt.KEY_ID, index); 153 mi.addSelectionListener(this._showTab.bind(this, index)); 154 mi.setText(this._buttons[index].getText()); 155 } 156 } 157 158 if(menu.getHtmlElement().style.width == "0px"){ 159 this._moreTabsBtn.popup(); 160 } 161 }; 162 163 ZmAppChooser.prototype._showOverflowTabsMenu = 164 function(){ 165 var menu = new DwtMenu({parent:this._moreTabsBtn}); 166 menu.addPopupListener(new AjxListener(this, this._attachMoreTabMenuItems,[menu])); 167 return menu; 168 }; 169 170 171 ZmAppChooser.prototype._checkArrowVisibility = 172 function(){ 173 var items = this.getItems(); 174 if (this._leftBtnIndex < 0) 175 this._setArrowVisibility(this._leftOverflow, "none"); 176 else 177 this._setArrowVisibility(this._leftOverflow, ""); 178 179 if (!this._isTabOverflow(items[items.length -1].getHtmlElement())){ 180 this._setArrowVisibility(this._rightOverflow, "none"); 181 }else{ 182 this._setArrowVisibility(this._rightOverflow, ""); 183 } 184 this._adjustWidth(); 185 }; 186 187 188 189 ZmAppChooser.prototype._setArrowVisibility = 190 function(element, option){ 191 if (!element) { 192 return; 193 } 194 element.style.display = option|| ""; 195 var display = ((this._leftOverflow && this._leftOverflow.style.display == "none") && 196 (this._rightOverflow && this._rightOverflow.style.display == "none")) ? "none" : ""; 197 var moreTabsMenu = document.getElementById("moreTabsMenu"); 198 if (moreTabsMenu) { 199 moreTabsMenu.style.display = display; 200 } 201 if (display != "none" && !this._moreTabsBtn && moreTabsMenu ){ 202 var containerEl = moreTabsMenu; 203 var button = new DwtToolBarButton({parent:DwtShell.getShell(window), id: "moreTabsMenuBtn", style:"background:none no-repeat scroll 0 0 transparent; border: none"}); 204 button.setToolTipContent(ZmMsg.more, true); 205 button.setText(""); 206 button.reparentHtmlElement(moreTabsMenu); 207 button.setMenu(new AjxListener(this, this._showOverflowTabsMenu)); 208 this._moreTabsBtn = button; 209 } 210 }; 211 212 /** 213 * Returns a string representation of the object. 214 * 215 * @return {String} a string representation of the object 216 */ 217 ZmAppChooser.prototype.toString = 218 function() { 219 return "ZmAppChooser"; 220 }; 221 222 // 223 // Constants 224 // 225 226 ZmAppChooser.SPACER = "spacer"; 227 ZmAppChooser.B_HELP = "Help"; 228 ZmAppChooser.B_LOGOUT = "Logout"; 229 230 ZmApp.CHOOSER_SORT[ZmAppChooser.SPACER] = 160; 231 ZmApp.CHOOSER_SORT[ZmAppChooser.B_HELP] = 170; 232 ZmApp.CHOOSER_SORT[ZmAppChooser.B_LOGOUT] = 190; 233 234 // hard code help/logout since they are not real "apps" 235 ZmApp.ICON[ZmAppChooser.B_HELP] = "Help"; 236 ZmApp.ICON[ZmAppChooser.B_LOGOUT] = "Logoff"; 237 ZmApp.CHOOSER_TOOLTIP[ZmAppChooser.B_HELP] = "goToHelp"; 238 ZmApp.CHOOSER_TOOLTIP[ZmAppChooser.B_LOGOUT] = "logOff"; 239 240 ZmAppChooser.SPACER_HEIGHT = 10; 241 242 // 243 // Data 244 // 245 246 ZmAppChooser.prototype.TEMPLATE = "share.Widgets#ZmAppChooser"; 247 ZmAppChooser.prototype.ITEM_TEMPLATE = "share.Widgets#ZmAppChooserItem"; 248 ZmAppChooser.prototype.SPACER_TEMPLATE = "dwt.Widgets#ZmAppChooserSpacer"; 249 250 // 251 // Public methods 252 // 253 /** 254 * Adds a selection listener. 255 * 256 * @param {AjxListener} listener the listener 257 */ 258 ZmAppChooser.prototype.addSelectionListener = 259 function(listener) { 260 this.addListener(DwtEvent.SELECTION, listener); 261 }; 262 263 ZmAppChooser.prototype._checkTabOverflowAdd = 264 function(button) { 265 var display = "none"; 266 if (this._isTabOverflow(button)){ 267 display = ""; 268 } 269 this._setArrowVisibility(this._rightOverflow, display); 270 this._adjustWidth(); 271 }; 272 273 ZmAppChooser.prototype._isTabOverflow = 274 function(tab){ 275 var tabPos = tab.offsetLeft + tab.clientWidth + 30; 276 if (!this._refElement){ 277 this._refElement = document.getElementById(this._refElementId); 278 } 279 var container = this._refElement && this._refElement.parentNode; 280 281 if (!container) return false; 282 var offsetWidth = container.offsetWidth; 283 return (offsetWidth < tabPos); 284 }; 285 286 ZmAppChooser.prototype._adjustWidth = 287 function(){ 288 var container = this._refElement && this._refElement.parentNode; 289 if (container && container.offsetWidth >= 30) { 290 this._refElement.style.maxWidth = this._refElement.style.width = container.offsetWidth - 30 + "px"; 291 this._refElement.style.overflow = "hidden"; 292 } 293 294 }; 295 296 ZmAppChooser.prototype._checkTabOverflowDelete = 297 function(index){ 298 var items = this.getItems(); 299 if(this._isTabOverflow(items[items.length - 1])){ 300 return; 301 } 302 this._showLeftTab(); 303 } 304 305 306 /** 307 * Adds a button to the toolbar. 308 * 309 * @param {String} id the button id 310 * @param {Hash} params a hash of parameters 311 * @param {String} params.text the text 312 * @param {String} params.image the image 313 * @param {int} params.index the index 314 * @param {String} params.tooltip the tool top 315 * @param {String} params.textPrecedence the image precedence 316 * @param {String} params.imagePrecedence the image precedence 317 * 318 * @return {ZmAppButton} the newly created button 319 */ 320 ZmAppChooser.prototype.addButton = 321 function(id, params) { 322 323 var buttonParams = { 324 parent: this, 325 id: ZmId.getButtonId(ZmId.APP, id), 326 text: params.text, 327 image: params.image, 328 leftImage: params.leftImage, 329 rightImage: params.rightImage, 330 index: params.index 331 }; 332 buttonParams.style = params.style ? params.style : DwtLabel.IMAGE_LEFT; 333 var button = new ZmAppButton(buttonParams); 334 button.setToolTipContent(params.tooltip, true); 335 button.textPrecedence = params.textPrecedence; 336 button.imagePrecedence = params.imagePrecedence; 337 button.setData(Dwt.KEY_ID, id); 338 button.addSelectionListener(this._buttonListener); 339 this._buttons[id] = button; 340 this._checkTabOverflowAdd(button.getHtmlElement()); 341 return button; 342 }; 343 344 /** 345 * Removes a button. 346 * 347 * @param {String} id the id of the button to remove 348 */ 349 ZmAppChooser.prototype.removeButton = 350 function(id) { 351 var button = this._buttons[id]; 352 if (button) { 353 var index = this.__getButtonIndex(id); 354 button.dispose(); 355 this._buttons[id] = null; 356 delete this._buttons[id]; 357 if (this._moreTabsBtn && 358 this._moreTabsBtn.getMenu() && 359 this._moreTabsBtn.getMenu().getItemCount() > 0){ 360 this._deletedButtons.push(id); 361 } 362 this._checkTabOverflowDelete(index); 363 } 364 }; 365 366 /** 367 * Replaces a button. 368 * 369 * @param {String} oldId the old button id 370 * @param {String} newId the new button id 371 * @param {Hash} params a hash of parameters 372 * @param {String} params.text the text 373 * @param {String} params.image the image 374 * @param {int} params.index the index 375 * @param {String} params.tooltip the tool tip 376 * @param {String} params.textPrecedence the text display precedence 377 * @param {String} params.imagePrecedence the image display precedence 378 * 379 * @return {ZmAppButton} the newly created button 380 */ 381 ZmAppChooser.prototype.replaceButton = 382 function(oldId, newId, params) { 383 if (!this._buttons[oldId]) { return null; } 384 params.index = this.__getButtonIndex(oldId); 385 this.removeButton(oldId); 386 return this.addButton(newId, params); 387 }; 388 389 ZmAppChooser.prototype.getButton = 390 function(id) { 391 return this._buttons[id]; 392 }; 393 394 /** 395 * Sets the specified button as selected. 396 * 397 * @param {String} id the button id 398 */ 399 ZmAppChooser.prototype.setSelected = 400 function(id) { 401 var oldBtn = this._buttons[this._selectedId]; 402 if (this._selectedId && oldBtn) { 403 this.__markPrevNext(this._selectedId, false); 404 oldBtn.setSelected(false); 405 } 406 407 var newBtn = this._buttons[id]; 408 if (newBtn) { 409 newBtn.setSelected(true); 410 this.setAttribute('aria-activedescendant', newBtn.getHTMLElId()); 411 412 if (newBtn._toggleText != null && newBtn._toggleText != "") { 413 // hide text for previously selected button first 414 if (oldBtn) { 415 oldBtn._toggleText = (oldBtn._toggleText != null && oldBtn._toggleText != "") 416 ? oldBtn._toggleText : oldBtn.getText(); 417 oldBtn.setText(""); 418 } 419 420 // reset original text for newly selected button 421 newBtn.setText(newBtn._toggleText); 422 newBtn._toggleText = null; 423 } 424 } 425 426 this._selectedId = id; 427 }; 428 429 /** 430 * @private 431 */ 432 ZmAppChooser.prototype._createButton = 433 function(id) { 434 this.addButton(id, {text:ZmMsg[ZmApp.NAME[id]] || ZmApp.NAME[id], image:ZmApp.ICON[id], tooltip:ZmMsg[ZmApp.CHOOSER_TOOLTIP[id]], 435 textPrecedence:ZmApp.TEXT_PRECEDENCE[id], imagePrecedence:ZmApp.IMAGE_PRECEDENCE[id]}); 436 }; 437 438 /** 439 * @private 440 */ 441 ZmAppChooser.prototype._handleButton = 442 function(evt) { 443 this.notifyListeners(DwtEvent.SELECTION, evt); 444 }; 445