1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2007, 2008, 2009, 2010, 2013, 2014, 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) 2007, 2008, 2009, 2010, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 /** 24 * Creates an empty accordion widget. 25 * @constructor 26 * @class 27 * This class implements an accordion widget, which is a stack of expandable 28 * accordion headers. Clicking on an accordion header's button expands it in 29 * place. 30 * 31 * @author Parag Shah 32 * 33 * @param {DwtControl} parent the parent widget 34 * @param {string} className the CSS class 35 * @param {Dwt.STATIC_STYLE|Dwt.ABSOLUTE_STYLE|Dwt.RELATIVE_STYLE} posStyle the positioning style 36 * 37 * @extends DwtComposite 38 */ 39 DwtAccordion = function(parent, className, posStyle) { 40 41 if (arguments.length == 0) return; 42 DwtComposite.call(this, {parent:parent, className:className, posStyle:(posStyle || Dwt.ABSOLUTE_STYLE)}); 43 44 this._initialize(className); 45 }; 46 47 DwtAccordion.prototype = new DwtComposite; 48 DwtAccordion.prototype.constructor = DwtAccordion; 49 50 51 // Public Methods 52 53 /** 54 * Returns a string representation of the object. 55 * 56 * @return {string} a string representation of the object 57 */ 58 DwtAccordion.prototype.toString = 59 function() { 60 return "DwtAccordion"; 61 }; 62 63 /** 64 * Adds an item to the accordion, in the form of a table row. 65 * 66 * @param {hash} params a hash of parameters 67 * @param {string} params.title the text for accordion header 68 * @param {hash} params.data the item data 69 * @param {string} params.icon the icon 70 * @param {boolean} params.hideHeader if <code>true</code>, do not show header (ideal when there's only one visible header item) 71 */ 72 DwtAccordion.prototype.addAccordionItem = 73 function(params) { 74 75 if (!this.isListenerRegistered(DwtEvent.CONTROL)) { 76 this.addControlListener(new AjxListener(this, this._controlListener)); 77 } 78 79 var itemNum = this.__ITEMCOUNT++; 80 var item = new DwtAccordionItem(itemNum, params.title, params.data, this); 81 var subs = { 82 id: this._htmlElId, 83 itemNum: itemNum, 84 title: params.title, 85 icon: params.icon 86 }; 87 88 // append new accordion item 89 var row = this._table.insertRow(-1); 90 var cell = row.insertCell(-1); 91 cell.id = this._htmlElId + "_cell_" + itemNum; 92 cell.className = "ZAccordionCell"; 93 cell.innerHTML = AjxTemplate.expand("dwt.Widgets#ZAccordionItem", subs); 94 95 // add onclick event handler to header DIV 96 var headerDiv = document.getElementById(this._htmlElId + "_header_" + itemNum); 97 if (params.hideHeader) { 98 Dwt.setVisible(headerDiv, false); 99 } else { 100 headerDiv.onclick = AjxCallback.simpleClosure(this._handleOnClickHeader, this, item); 101 headerDiv.oncontextmenu = AjxCallback.simpleClosure(this._handleOnRightClickHeader, this, item); 102 headerDiv.onmouseover = AjxCallback.simpleClosure(this._handleOnMouseoverHeader, this, item); 103 headerDiv.onmouseout = AjxCallback.simpleClosure(this._handleOnMouseoutHeader, this, item); 104 } 105 this._items.push(item); 106 107 return item; 108 }; 109 110 /** 111 * Gets the ordered list of accordion items. 112 * 113 * @return {array} an array of {@link DwtAccordionItem} objects 114 */ 115 DwtAccordion.prototype.getItems = 116 function() { 117 return this._items; 118 }; 119 120 /** 121 * Gets the accordion item with the given ID. 122 * 123 * @param {number} id the accordion item ID 124 * @return {DwtAccordionItem} the item or <code>null</code> if not found 125 */ 126 DwtAccordion.prototype.getItem = 127 function(id) { 128 for (var i = 0; i < this._items.length; i++) { 129 if (this._items[i].id == id) { 130 return this._items[i]; 131 } 132 } 133 return null; 134 }; 135 136 /** 137 * Gets the item by index. 138 * 139 * @param {number} id the accordion item index 140 * @return {DwtAccordionItem} the item or <code>null</code> if not found 141 */ 142 DwtAccordion.prototype.getItemByIndex = 143 function(index) { 144 return (index >=0 && index < this._items.length) ? this._items[index] : null; 145 } 146 147 /** 148 * Hides all accordion items. 149 */ 150 DwtAccordion.prototype.hideAccordionItems = 151 function() { 152 for (var i = 0; i < this._items.length; i++) { 153 var header = document.getElementById(this._htmlElId + "_header_" + this._items[i].id); 154 if (header) { 155 Dwt.setVisible(header, false); 156 } 157 } 158 }; 159 160 /** 161 * Shows single accordion item based on given id. 162 * 163 * @param {number} id the accordion item ID 164 */ 165 DwtAccordion.prototype.showAccordionItem = 166 function(id) { 167 var header = document.getElementById(this._htmlElId + "_header_" + id); 168 if (header) { 169 Dwt.setVisible(header, true); 170 } 171 }; 172 173 /** 174 * Allows the accordion items to be clickable or not. If disabled, the label of 175 * each accordion item will be grayed out. 176 * 177 * @param {boolean} enabled if <code>true</code>, enabled. 178 */ 179 DwtAccordion.prototype.setEnabled = 180 function(enabled) { 181 if (enabled == this._enabled) { return; } 182 183 this._enabled = enabled; 184 185 for (var i in this._items) { 186 var item = this._items[i]; 187 if (this._currentItemId != item.id) { 188 item._setEnabled(enabled); 189 } 190 } 191 }; 192 193 /** 194 * Resizes the accordion. This override applies accordion size changes to accordion items as well. 195 * 196 * @param {number} width the new width for accordion 197 * @param {number} height the new height for accordion 198 */ 199 DwtAccordion.prototype.resize = 200 function(width, height) { 201 if (width) { 202 // if width changed, resize all header items 203 for (var i = 0; i < this._items.length; i++) { 204 var id = this._items[i].id; 205 var title = document.getElementById(this._htmlElId + "_title_" + id); 206 var fudge = 30; 207 208 var iconCell = document.getElementById(this._htmlElId + "_icon_" + id); 209 if (iconCell && iconCell.className && iconCell.className != "") { 210 fudge += 16; // the default width of an icon 211 } 212 Dwt.setSize(title, width - fudge); 213 214 } 215 } 216 217 var newHeight; 218 if (height) { 219 var hdr = document.getElementById(this._htmlElId + "_header_" + this._currentItemId); 220 if (hdr) { 221 var hdrHeightSum = Dwt.getSize(hdr).y * this._getVisibleHeaderCount(); 222 newHeight = (height - hdrHeightSum); // force min. height of 100px? 223 } 224 } 225 226 var body = document.getElementById(this._htmlElId + "_body_" + this._currentItemId); 227 if (body) { 228 Dwt.setSize(body, width, newHeight); 229 if (body.firstChild) { 230 Dwt.setSize(body.firstChild, width, newHeight); 231 } 232 } 233 }; 234 235 DwtAccordion.prototype.setBounds = 236 function(x, y, width, height) { 237 DwtComposite.prototype.setBounds.call(this, x, y, width, height); 238 239 this.resize(width, height); 240 }; 241 242 /** 243 * Gets the expanded accordion item. 244 * 245 * @return {DwtAccordionItem} the item 246 */ 247 DwtAccordion.prototype.getExpandedItem = 248 function() { 249 return this._items[this._currentItemId || 0]; 250 }; 251 252 /** 253 * Expands the accordion item with the given ID by making its body visible. The bodies of 254 * other items are hidden. 255 * 256 * @param {number} id the accordion item ID 257 * @param {boolean} notify if <code>true</code>, selection listeners are to be notified 258 */ 259 DwtAccordion.prototype.expandItem = 260 function(id, notify) { 261 var selectedItem; 262 for (var i = 0; i < this._items.length; i++) { 263 var itemId = this._items[i].id; 264 var header = document.getElementById(this._htmlElId + "_header_" + itemId); 265 var body = document.getElementById(this._htmlElId + "_body_" + itemId); 266 var cell = document.getElementById(this._htmlElId + "_cell_" + itemId); 267 var status = document.getElementById(this._htmlElId + "_status_" + itemId); 268 269 if (id == itemId) { 270 Dwt.setVisible(body, true); 271 header.className = "ZAccordionHeader ZWidget ZSelected"; 272 status.className = "ImgAccordionOpened"; 273 cell.style.height = "100%"; 274 this._currentItemId = id; 275 selectedItem = this._items[i]; 276 } else { 277 Dwt.setVisible(body, false); 278 header.className = "ZAccordionHeader ZWidget"; 279 status.className = "ImgAccordionClosed"; 280 cell.style.height = "0px"; 281 } 282 } 283 284 if (selectedItem && notify && this.isListenerRegistered(DwtEvent.SELECTION)) { 285 this.notifySelectionListeners(selectedItem); 286 } 287 }; 288 289 DwtAccordion.prototype.notifySelectionListeners = 290 function(selectedItem) { 291 var selEv = DwtShell.selectionEvent; 292 selEv.item = this; 293 selEv.detail = selectedItem; 294 this.notifyListeners(DwtEvent.SELECTION, selEv); 295 }; 296 297 /** 298 * Attaches the HTML content of the given control to the accordion item with 299 * the given ID. 300 * 301 * @param {number} id the accordion item ID 302 * @param {DwtControl} contentObject the control that contains this item's content 303 */ 304 DwtAccordion.prototype.setItemContent = 305 function(id, contentObject) { 306 var aiBody = this.getBody(id); 307 if (aiBody) { 308 this._items[id].control = contentObject; 309 contentObject.reparentHtmlElement(aiBody); 310 var size = contentObject.getSize(); 311 this.resize(size.x, size.y); 312 } 313 }; 314 315 /** 316 * Gets the <code><body></code> element of the accordion item with the given ID. 317 * 318 * @param {number} id the accordion item ID 319 * @return {Element} the element 320 */ 321 DwtAccordion.prototype.getBody = 322 function(id) { 323 return document.getElementById(this._htmlElId + "_body_" + id); 324 }; 325 326 /** 327 * Gets the <code><header></code> element of the accordion item with the given ID. 328 * 329 * @param {number} id the accordion item ID 330 * @return {Element} the element 331 */ 332 DwtAccordion.prototype.getHeader = 333 function(id) { 334 return document.getElementById(this._htmlElId + "_header_" + id); 335 }; 336 337 /** 338 * Shows or hides the accordion. 339 * 340 * @param {boolean} show if <code>true</code>, show the accordion; otherwise hide it 341 */ 342 DwtAccordion.prototype.show = 343 function(show) { 344 var div = document.getElementById(this._htmlElId + "_div"); 345 if (div) { 346 Dwt.setVisible(div, show); 347 } 348 }; 349 350 /** 351 * Adds a listener to be notified when the button is pressed. 352 * 353 * @param {AjxListener} listener a listener 354 */ 355 DwtAccordion.prototype.addSelectionListener = 356 function(listener) { 357 this.addListener(DwtEvent.SELECTION, listener); 358 }; 359 360 /** 361 * Shows or hides an alert (aka orange background) on the accordion header 362 * 363 * @param {number} id the accordion item ID 364 * @param {boolean} show if <code>true</code>, show the alert 365 */ 366 DwtAccordion.prototype.showAlert = 367 function(id, show) { 368 var header = document.getElementById(this._htmlElId + "_header_" + id); 369 if (show) { 370 Dwt.delClass(header, null, "ZAlert"); 371 } else { 372 Dwt.delClass(header, "ZAlert", null); 373 } 374 }; 375 376 // Private Methods 377 378 /** 379 * Creates the HTML skeleton for the accordion. 380 * 381 * @private 382 */ 383 DwtAccordion.prototype._initialize = 384 function() { 385 this._items = []; 386 this.__ITEMCOUNT = 0; 387 388 this.getHtmlElement().innerHTML = AjxTemplate.expand("dwt.Widgets#ZAccordion", {id: this._htmlElId}); 389 this._table = document.getElementById(this._htmlElId + "_accordion_table"); 390 391 this._setMouseEventHdlrs(); 392 }; 393 394 /** 395 * Returns the number of accordion items which have visible headers. 396 * 397 * @private 398 */ 399 DwtAccordion.prototype._getVisibleHeaderCount = 400 function() { 401 var count = 0; 402 for (var i = 0; i < this._items.length; i++) { 403 var hdr = document.getElementById(this._htmlElId + "_header_" + this._items[i].id); 404 if (hdr && Dwt.getVisible(hdr)) { 405 count++; 406 } 407 } 408 return count; 409 }; 410 411 412 // Listeners 413 414 /** 415 * When a header button is clicked, the item is expanded. Also, any listeners 416 * are notified. 417 * 418 * @param {DwtAccordionItem} item the accordion item whose header was clicked 419 * @param {DwtUiEvent} ev the click event 420 * 421 * @private 422 */ 423 DwtAccordion.prototype._handleOnClickHeader = 424 function(item, ev) { 425 if (!this._enabled) { return; } 426 427 this.expandItem(item.id, true); 428 }; 429 430 /** 431 * When a header button is right-clicked, any listeners will be notified so a 432 * context menu can be shown, for example. 433 * 434 * @param {DwtAccordionItem} item the accordion item whose header was clicked 435 * @param {DwtUiEvent} ev the click event 436 * 437 * @private 438 */ 439 DwtAccordion.prototype._handleOnRightClickHeader = 440 function(item, ev) { 441 ev = ev || window.event; 442 443 if (this.isListenerRegistered(DwtEvent.ONCONTEXTMENU)) { 444 var selEv = DwtShell.selectionEvent; 445 DwtUiEvent.copy(selEv, ev); 446 selEv.item = this; 447 selEv.detail = item; 448 this.notifyListeners(DwtEvent.ONCONTEXTMENU, selEv); 449 } 450 }; 451 452 DwtAccordion.prototype._handleOnMouseoverHeader = 453 function(item, ev) { 454 ev = ev || window.event; 455 456 if (this.isListenerRegistered(DwtEvent.ONMOUSEOVER)) { 457 var selEv = DwtShell.selectionEvent; 458 DwtUiEvent.copy(selEv, ev); 459 selEv.item = this; 460 selEv.detail = item; 461 this.notifyListeners(DwtEvent.ONMOUSEOVER, selEv); 462 } 463 }; 464 465 DwtAccordion.prototype._handleOnMouseoutHeader = 466 function(ev) { 467 this.setToolTipContent(null); 468 }; 469 470 /** 471 * Handles a resize event. 472 * 473 * @param {DwtEvent} ev the control event 474 * 475 * @private 476 */ 477 DwtAccordion.prototype._controlListener = 478 function(ev) { 479 if (this.getScrollStyle() != Dwt.CLIP) { return; } 480 481 var newWidth = (ev.oldWidth != ev.newWidth) ? ev.newWidth : null; 482 var newHeight = (ev.oldHeight != ev.newHeight) ? ev.newHeight : null; 483 484 if ((!newWidth && !newHeight) || ev.newWidth < 0 || ev.newHeight < 0) { return; } 485 486 this.resize(newWidth, newHeight); 487 }; 488 489 /** 490 * Creates an accordion item. 491 * @class 492 * This class represents a single expandable accordion item. 493 * 494 * @param {string} id the unique ID for this item 495 * @param {string} title the text for the item header 496 * @param {hash} data a hash of arbitrary data for this item 497 * @param {DwtAccordion} accordion the owning accordion 498 */ 499 DwtAccordionItem = function(id, title, data, accordion) { 500 this.id = id; 501 this.title = title; 502 this.data = data; 503 this.accordion = accordion; 504 }; 505 506 /** 507 * Returns a string representation of the object. 508 * 509 * @return {string} a string representation of the object 510 */ 511 DwtAccordionItem.prototype.toString = 512 function() { 513 return "DwtAccordionItem"; 514 }; 515 516 /** 517 * Sets the icon. 518 * 519 * @param {string} icon the icon 520 */ 521 DwtAccordionItem.prototype.setIcon = 522 function(icon) { 523 var iconCell = document.getElementById(this.accordion._htmlElId + "_icon_" + this.id); 524 if (iconCell) { 525 iconCell.className = icon; 526 } 527 }; 528 529 /** 530 * Gets the icon cell. 531 * 532 * @return {Element} the cell 533 */ 534 DwtAccordionItem.prototype.getIconCell = 535 function() { 536 return document.getElementById(this.accordion._htmlElId + "_icon_" + this.id); 537 }; 538 539 /** 540 * Sets the title. 541 * 542 * @param {string} title the title 543 */ 544 DwtAccordionItem.prototype.setTitle = 545 function(title) { 546 var titleCell = document.getElementById(this.accordion._htmlElId + "_title_" + this.id); 547 if (titleCell) { 548 titleCell.innerHTML = title; 549 } 550 }; 551 552 /** 553 * @private 554 */ 555 DwtAccordionItem.prototype._setEnabled = 556 function(enabled) { 557 var titleCell = document.getElementById(this.accordion._htmlElId + "_title_" + this.id); 558 if (titleCell) { 559 if (enabled) { 560 Dwt.delClass(titleCell, "ZDisabled"); 561 } else { 562 Dwt.addClass(titleCell, "ZDisabled"); 563 } 564 } 565 566 var status = document.getElementById(this.accordion._htmlElId + "_status_" + this.id); 567 if (status) { 568 if (enabled) { 569 Dwt.delClass(status, "ZDisabledImage ZDisabled"); 570 } else { 571 Dwt.addClass(status, "ZDisabledImage ZDisabled"); 572 } 573 } 574 }; 575