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 * This file contains classes for a Dwt dialog pop-up. 27 */ 28 29 /** 30 * @class 31 * This class represents a popup dialog with a title and standard buttons. 32 * A client or subclass sets the dialog content. Dialogs always hang-off the main shell 33 * since their stacking order is managed through z-index. 34 * 35 * @author Ross Dargahi 36 * @author Conrad Damon 37 * 38 * @param {hash} params a hash of parameters 39 * @param {DwtComposite} params.parent the parent widget (the shell) 40 * @param {string} params.className the CSS class 41 * @param {string} params.title the title of dialog 42 * @param {array|constant} params.standardButtons an array of standard buttons to include. Defaults to {@link DwtDialog.OK_BUTTON} and {@link DwtDialog.CANCEL_BUTTON}. 43 * @param {array} params.extraButtons a list of {@link DwtDialog_ButtonDescriptor} objects describing custom buttons to add to the dialog 44 * @param {number} params.zIndex the z-index to set for this dialog when it is visible. Defaults to {@link Dwt.Z_DIALOG}. 45 * @param {DwtDialog.MODELESS|DwtDialog.MODAL} params.mode the modality of the dialog. Defaults to {@link DwtDialog.MODAL}. 46 * @param {boolean} params.disposeOnPopDown destroy the content of dialog on popdown, Defaults to false 47 * @param {DwtPoint} params.loc the location at which to popup the dialog. Defaults to centered within its parent. 48 * 49 * @see DwtDialog.CANCEL_BUTTON 50 * @see DwtDialog.OK_BUTTON 51 * @see DwtDialog.DISMISS_BUTTON 52 * @see DwtDialog.NO_BUTTON 53 * @see DwtDialog.YES_BUTTON 54 * @see DwtDialog.ALL_BUTTONS 55 * @see DwtDialog.NO_BUTTONS 56 * 57 * @extends DwtBaseDialog 58 */ 59 DwtDialog = function(params) { 60 if (arguments.length == 0) { return; } 61 params = Dwt.getParams(arguments, DwtDialog.PARAMS); 62 params.className = params.className || "DwtDialog"; 63 this._title = params.title = params.title || ""; 64 65 // standard buttons default to OK / Cancel 66 var standardButtons = params.standardButtons; 67 var extraButtons = params.extraButtons; 68 if (!standardButtons) { 69 standardButtons = [DwtDialog.OK_BUTTON, DwtDialog.CANCEL_BUTTON]; 70 } else if (standardButtons == DwtDialog.NO_BUTTONS) { 71 standardButtons = null; 72 } else if (standardButtons && !standardButtons.length) { 73 standardButtons = [standardButtons]; 74 } 75 76 // assemble the list of button IDs, and the list of button descriptors 77 this._buttonList = []; 78 var buttonOrder = {}; 79 buttonOrder[DwtDialog.ALIGN_LEFT] = []; 80 buttonOrder[DwtDialog.ALIGN_CENTER] = []; 81 buttonOrder[DwtDialog.ALIGN_RIGHT] = []; 82 if (standardButtons || extraButtons) { 83 this._buttonDesc = {}; 84 if (standardButtons && standardButtons.length) { 85 this._initialEnterButtonId = this._enterButtonId = standardButtons[0]; 86 for (var i = 0; i < standardButtons.length; i++) { 87 var buttonId = standardButtons[i]; 88 this._buttonList.push(buttonId); 89 var align = DwtDialog.ALIGN[buttonId]; 90 if (align) { 91 buttonOrder[align].push(buttonId); 92 } 93 // creating standard button descriptors on file read didn't work, so we create them here 94 this._buttonDesc[buttonId] = new DwtDialog_ButtonDescriptor(buttonId, AjxMsg[DwtDialog.MSG_KEY[buttonId]], align); 95 } 96 // set standard callbacks 97 this._resetCallbacks(); 98 } 99 if (extraButtons && extraButtons.length) { 100 if (!this._enterButtonId) { 101 this._initialEnterButtonId = this._enterButtonId = extraButtons[0]; 102 } 103 for (var i = 0; i < extraButtons.length; i++) { 104 var buttonId = extraButtons[i].id; 105 this._buttonList.push(buttonId); 106 var align = extraButtons[i].align; 107 if (align) { 108 buttonOrder[align].push(buttonId); 109 } 110 this._buttonDesc[buttonId] = extraButtons[i]; 111 } 112 } 113 } 114 115 // get button IDs 116 this._buttonElementId = {}; 117 for (var i = 0; i < this._buttonList.length; i++) { 118 var buttonId = this._buttonList[i]; 119 //this._buttonElementId[this._buttonList[i]] = params.id + "_button" + buttonId + "_cell"; 120 this._buttonElementId[buttonId] = this._buttonDesc[buttonId].label? this._buttonDesc[buttonId].label + "_" + Dwt.getNextId():Dwt.getNextId(); 121 } 122 123 DwtBaseDialog.call(this, params); 124 125 // set up buttons 126 this._button = {}; 127 for (var i = 0; i < this._buttonList.length; i++) { 128 var buttonId = this._buttonList[i]; 129 var b = this._button[buttonId] = new DwtButton({parent:this,id:this._htmlElId+"_button"+buttonId}); 130 b.setText(this._buttonDesc[buttonId].label); 131 b.buttonId = buttonId; 132 b.addSelectionListener(new AjxListener(this, this._buttonListener)); 133 var el = document.getElementById(this._buttonElementId[buttonId]); 134 if (el) { 135 el.appendChild(b.getHtmlElement()); 136 } 137 } 138 // add to tab group, in order 139 var list = buttonOrder[DwtDialog.ALIGN_LEFT].concat(buttonOrder[DwtDialog.ALIGN_CENTER], buttonOrder[DwtDialog.ALIGN_RIGHT]); 140 for (var i = 0; i < list.length; i++) { 141 var button = this._button[list[i]]; 142 this._tabGroup.addMember(button); 143 } 144 }; 145 146 DwtDialog.PARAMS = ["parent", "className", "title", "standardButtons", "extraButtons", "zIndex", "mode", "loc", "id"]; 147 148 DwtDialog.prototype = new DwtBaseDialog; 149 DwtDialog.prototype.constructor = DwtDialog; 150 151 DwtDialog.prototype.isDwtDialog = true; 152 DwtDialog.prototype.toString = function() { return "DwtDialog"; }; 153 154 // 155 // Constants 156 // 157 158 /** 159 * Defines the "left" align. 160 */ 161 DwtDialog.ALIGN_LEFT = 1; 162 /** 163 * Defines the "right" align. 164 */ 165 DwtDialog.ALIGN_RIGHT = 2; 166 /** 167 * Defines the "center" align. 168 */ 169 DwtDialog.ALIGN_CENTER = 3; 170 171 // standard buttons, their labels, and their positioning 172 173 /** 174 * Defines the "Cancel" button. 175 */ 176 DwtDialog.CANCEL_BUTTON = 1; 177 /** 178 * Defines the "OK" button. 179 */ 180 DwtDialog.OK_BUTTON = 2; 181 /** 182 * Defines the "Dismiss" button. 183 */ 184 DwtDialog.DISMISS_BUTTON = 3; 185 /** 186 * Defines the "No" button. 187 */ 188 DwtDialog.NO_BUTTON = 4; 189 /** 190 * Defines the "Yes" button. 191 */ 192 DwtDialog.YES_BUTTON = 5; 193 194 DwtDialog.LAST_BUTTON = 5; 195 196 /** 197 * Defines "no" buttons. This constant is used to show no buttons. 198 */ 199 DwtDialog.NO_BUTTONS = 256; 200 /** 201 * Defines "all" buttons. This constant is used to show all buttons. 202 */ 203 DwtDialog.ALL_BUTTONS = [DwtDialog.CANCEL_BUTTON, DwtDialog.OK_BUTTON, 204 DwtDialog.DISMISS_BUTTON, DwtDialog.NO_BUTTON, 205 DwtDialog.YES_BUTTON]; 206 207 DwtDialog.MSG_KEY = {}; 208 DwtDialog.MSG_KEY[DwtDialog.CANCEL_BUTTON] = "cancel"; 209 DwtDialog.MSG_KEY[DwtDialog.OK_BUTTON] = "ok"; 210 DwtDialog.MSG_KEY[DwtDialog.DISMISS_BUTTON] = "close"; 211 DwtDialog.MSG_KEY[DwtDialog.NO_BUTTON] = "no"; 212 DwtDialog.MSG_KEY[DwtDialog.YES_BUTTON] = "yes"; 213 214 DwtDialog.ALIGN = {}; 215 DwtDialog.ALIGN[DwtDialog.CANCEL_BUTTON] = DwtDialog.ALIGN_RIGHT; 216 DwtDialog.ALIGN[DwtDialog.OK_BUTTON] = DwtDialog.ALIGN_RIGHT; 217 DwtDialog.ALIGN[DwtDialog.DISMISS_BUTTON] = DwtDialog.ALIGN_RIGHT; 218 DwtDialog.ALIGN[DwtDialog.NO_BUTTON] = DwtDialog.ALIGN_RIGHT; 219 DwtDialog.ALIGN[DwtDialog.YES_BUTTON] = DwtDialog.ALIGN_RIGHT; 220 221 /** 222 * Defines a "modeless" dialog. 223 * 224 * @see DwtBaseDialog.MODELESS 225 */ 226 DwtDialog.MODELESS = DwtBaseDialog.MODELESS; 227 228 /** 229 * Defines a "modal" dialog. 230 * 231 * @see DwtBaseDialog.MODAL 232 */ 233 DwtDialog.MODAL = DwtBaseDialog.MODAL; 234 235 // 236 // Data 237 // 238 /** 239 * @private 240 */ 241 DwtDialog.prototype.CONTROLS_TEMPLATE = "dwt.Widgets#DwtDialogControls"; 242 243 // 244 // Public methods 245 // 246 247 DwtDialog.prototype.popdown = 248 function() { 249 DwtBaseDialog.prototype.popdown.call(this); 250 if (!this._disposeOnPopDown) { 251 this.resetButtonStates(); 252 } 253 }; 254 255 /** 256 * This method will pop-up the dialog. 257 * 258 * @param {DwtPoint} loc the location 259 * @param {constant} focusButtonId the button Id 260 */ 261 DwtDialog.prototype.popup = 262 function(loc, focusButtonId) { 263 this._focusButtonId = focusButtonId; 264 DwtBaseDialog.prototype.popup.call(this, loc); 265 }; 266 267 /** 268 * @private 269 */ 270 DwtDialog.prototype._resetTabFocus = 271 function(){ 272 if (this._focusButtonId) { 273 var focusButton = this.getButton(this._focusButtonId); 274 this._tabGroup.setFocusMember(focusButton, true); 275 } else { 276 DwtBaseDialog.prototype._resetTabFocus.call(this); 277 } 278 }; 279 280 DwtDialog.prototype.reset = 281 function() { 282 this._resetCallbacks(); 283 this.resetButtonStates(); 284 DwtBaseDialog.prototype.reset.call(this); 285 }; 286 287 /** 288 * Sets all buttons back to inactive state. 289 * 290 */ 291 DwtDialog.prototype.resetButtonStates = 292 function() { 293 for (b in this._button) { 294 this._button[b].setEnabled(true); 295 this._button[b].setHovered(false); 296 } 297 this.associateEnterWithButton(this._initialEnterButtonId); 298 }; 299 300 /** 301 * Gets a button by the specified Id. 302 * 303 * @param {constant} buttonId the button Id 304 * @return {DwtButton} the button or <code>null</code> if not found 305 */ 306 DwtDialog.prototype.getButton = 307 function(buttonId) { 308 return this._button[buttonId]; 309 }; 310 311 /** 312 * Sets the button enabled state. 313 * 314 * @param {constant} buttonId the button Id 315 * @param {boolean} enabled if <code>true</code>, enable the button; <code>false</code> otherwise 316 */ 317 DwtDialog.prototype.setButtonEnabled = 318 function(buttonId, enabled) { 319 if (!this._button[buttonId]) { 320 return; 321 } 322 this._button[buttonId].setEnabled(enabled); 323 }; 324 325 /** 326 * Sets the button visible state. 327 * 328 * @param {constant} buttonId the button Id 329 * @param {boolean} enabled if <code>true</code>, make the button visible; <code>false</code> otherwise 330 */ 331 DwtDialog.prototype.setButtonVisible = 332 function(buttonId, visible) { 333 if (!this._button[buttonId]) { 334 return; 335 } 336 this._button[buttonId].setVisible(visible); 337 }; 338 339 /** 340 * Gets the button enabled state. 341 * 342 * @param {constant} buttonId the button Id 343 * @return {boolean} <code>true</code> if the button is enabled; <code>false</code> otherwise 344 */ 345 DwtDialog.prototype.getButtonEnabled = 346 function(buttonId) { 347 return this._button[buttonId].getEnabled(); 348 }; 349 350 /** 351 * Registers a callback for a given button. Can be passed an AjxCallback, 352 * the params needed to create one, or as a bound function. 353 * 354 * @param {constant} buttonId one of the standard dialog buttons 355 * @param {AjxCallback} func the callback method 356 * @param {Object} obj the callback object 357 * @param {array} args the callback args 358 */ 359 DwtDialog.prototype.registerCallback = 360 function(buttonId, func, obj, args) { 361 this._buttonDesc[buttonId].callback = (func && (func.isAjxCallback || (!obj && !args))) ? func : (new AjxCallback(obj, func, args)); 362 }; 363 364 /** 365 * Unregisters a callback for a given button. 366 * 367 * @param {constant} buttonId one of the standard dialog buttons 368 */ 369 DwtDialog.prototype.unregisterCallback = 370 function(buttonId) { 371 this._buttonDesc[buttonId].callback = null; 372 }; 373 374 /** 375 * Sets the given listener as the only listener for the given button. 376 * 377 * @param {constant} buttonId one of the standard dialog buttons 378 * @param {AjxListener} listener a listener 379 */ 380 DwtDialog.prototype.setButtonListener = 381 function(buttonId, listener) { 382 this._button[buttonId].removeSelectionListeners(); 383 this._button[buttonId].addSelectionListener(listener); 384 }; 385 386 /** 387 * Sets the enter key listener. 388 * 389 * @param {AjxListener} listener a listener 390 */ 391 DwtDialog.prototype.setEnterListener = 392 function(listener) { 393 this.removeAllListeners(DwtEvent.ENTER); 394 this.addEnterListener(listener); 395 }; 396 397 /** 398 * Associates the "enter" key with a given button. 399 * 400 * @param {constant} buttonId one of the standard dialog buttons 401 */ 402 DwtDialog.prototype.associateEnterWithButton = 403 function(id) { 404 this._enterButtonId = id; 405 }; 406 407 DwtDialog.prototype.getKeyMapName = 408 function() { 409 return DwtKeyMap.MAP_DIALOG; 410 }; 411 412 DwtDialog.prototype.handleKeyAction = 413 function(actionCode, ev) { 414 switch (actionCode) { 415 416 case DwtKeyMap.ENTER: 417 this.notifyListeners(DwtEvent.ENTER, ev); 418 break; 419 420 case DwtKeyMap.CANCEL: 421 // hitting ESC should act as a cancel 422 //TODO: dialog should set ESC/Enter listeners so we don't have to guess the action to take 423 var handled = false; 424 handled = handled || this._runCallbackForButtonId(DwtDialog.CANCEL_BUTTON); 425 handled = handled || this._runCallbackForButtonId(DwtDialog.NO_BUTTON); 426 handled = handled || this._runCallbackForButtonId(DwtDialog.DISMISS_BUTTON); 427 428 //don't let OK act as cancel if there are other buttons 429 if (!handled && this._buttonDesc[DwtDialog.OK_BUTTON] && this._buttonList.length == 1) { 430 handled = handled || this._runCallbackForButtonId(DwtDialog.OK_BUTTON); 431 } 432 this.popdown(); 433 return true; 434 435 case DwtKeyMap.YES: 436 if (this._buttonDesc[DwtDialog.YES_BUTTON]) { 437 this._runCallbackForButtonId(DwtDialog.YES_BUTTON); 438 } 439 break; 440 441 case DwtKeyMap.NO: 442 if (this._buttonDesc[DwtDialog.NO_BUTTON]) { 443 this._runCallbackForButtonId(DwtDialog.NO_BUTTON); 444 } 445 break; 446 447 default: 448 return false; 449 } 450 return true; 451 }; 452 453 // 454 // Protected methods 455 // 456 457 /** 458 * @private 459 */ 460 DwtDialog.prototype._createHtmlFromTemplate = 461 function(templateId, data) { 462 DwtBaseDialog.prototype._createHtmlFromTemplate.call(this, templateId, data); 463 464 var focusId = data.id+"_focus"; 465 if (document.getElementById(focusId)) { 466 this._focusElementId = focusId; 467 } 468 this._buttonsEl = document.getElementById(data.id+"_buttons"); 469 if (this._buttonsEl) { 470 var html = []; 471 var idx = 0; 472 this._addButtonsHtml(html,idx); 473 this._buttonsEl.innerHTML = html.join(""); 474 } 475 }; 476 477 // TODO: Get rid of these button template methods! 478 /** 479 * @private 480 */ 481 DwtDialog.prototype._getButtonsContainerStartTemplate = 482 function () { 483 return "<table role='presentation' width='100%'><tr>"; 484 }; 485 486 /** 487 * @private 488 */ 489 DwtDialog.prototype._getButtonsAlignStartTemplate = 490 function () { 491 return "<td align=\"{0}\"><table role='presentation'><tr>"; 492 }; 493 494 /** 495 * @private 496 */ 497 DwtDialog.prototype._getButtonsAlignEndTemplate = 498 function () { 499 return "</tr></table></td>"; 500 }; 501 502 /** 503 * @private 504 */ 505 DwtDialog.prototype._getButtonsCellTemplate = 506 function () { 507 return "<td id=\"{0}\"></td>"; 508 }; 509 510 /** 511 * @private 512 */ 513 DwtDialog.prototype._getButtonsContainerEndTemplate = 514 function () { 515 return "</tr></table>"; 516 }; 517 518 /** 519 * @private 520 */ 521 DwtDialog.prototype._addButtonsHtml = 522 function(html, idx) { 523 if (this._buttonList && this._buttonList.length) { 524 var leftButtons = new Array(); 525 var rightButtons = new Array(); 526 var centerButtons = new Array(); 527 for (var i = 0; i < this._buttonList.length; i++) { 528 var buttonId = this._buttonList[i]; 529 switch (this._buttonDesc[buttonId].align) { 530 case DwtDialog.ALIGN_RIGHT: rightButtons.push(buttonId); break; 531 case DwtDialog.ALIGN_LEFT: leftButtons.push(buttonId); break; 532 case DwtDialog.ALIGN_CENTER: centerButtons.push(buttonId); break; 533 } 534 } 535 html[idx++] = this._getButtonsContainerStartTemplate(); 536 537 if (leftButtons.length) { 538 html[idx++] = AjxMessageFormat.format( 539 this._getButtonsAlignStartTemplate(), 540 ["left"]); 541 for (var i = 0; i < leftButtons.length; i++) { 542 var buttonId = leftButtons[i]; 543 var cellTemplate = this._buttonDesc[buttonId].cellTemplate ? 544 this._buttonDesc[buttonId].cellTemplate : this._getButtonsCellTemplate(); 545 html[idx++] = AjxMessageFormat.format( 546 cellTemplate, 547 [this._buttonElementId[buttonId]]); 548 } 549 html[idx++] = this._getButtonsAlignEndTemplate(); 550 } 551 if (centerButtons.length){ 552 html[idx++] = AjxMessageFormat.format( 553 this._getButtonsAlignStartTemplate(), 554 ["center"]); 555 for (var i = 0; i < centerButtons.length; i++) { 556 var buttonId = centerButtons[i]; 557 var cellTemplate = this._buttonDesc[buttonId].cellTemplate ? 558 this._buttonDesc[buttonId].cellTemplate : this._getButtonsCellTemplate(); 559 html[idx++] = AjxMessageFormat.format( 560 cellTemplate, 561 [this._buttonElementId[buttonId]]); 562 } 563 html[idx++] = this._getButtonsAlignEndTemplate(); 564 } 565 if (rightButtons.length) { 566 html[idx++] = AjxMessageFormat.format( 567 this._getButtonsAlignStartTemplate(), 568 ["right"]); 569 for (var i = 0; i < rightButtons.length; i++) { 570 var buttonId = rightButtons[i]; 571 var cellTemplate = this._buttonDesc[buttonId].cellTemplate ? 572 this._buttonDesc[buttonId].cellTemplate : this._getButtonsCellTemplate(); 573 574 html[idx++] = AjxMessageFormat.format(cellTemplate, 575 [this._buttonElementId[buttonId]]); 576 } 577 html[idx++] = this._getButtonsAlignEndTemplate(); 578 } 579 html[idx++] = this._getButtonsContainerEndTemplate(); 580 } 581 return idx; 582 }; 583 584 /** 585 * Button listener that checks for callbacks. 586 * 587 * @private 588 */ 589 DwtDialog.prototype._buttonListener = 590 function(ev, args) { 591 var obj = DwtControl.getTargetControl(ev); 592 var buttonId = (obj && obj.buttonId) || this._enterButtonId; 593 if (buttonId) { 594 this._runCallbackForButtonId(buttonId, args); 595 } 596 }; 597 598 /** 599 * @private 600 */ 601 DwtDialog.prototype._runCallbackForButtonId = 602 function(id, args) { 603 var buttonDesc = this._buttonDesc[id]; 604 var callback = buttonDesc && buttonDesc.callback; 605 if (!callback) { 606 return false; 607 } 608 args = (args instanceof Array) ? args : [args]; 609 callback.run.apply(callback, args); 610 return true; 611 }; 612 613 /** 614 * @private 615 */ 616 DwtDialog.prototype._runEnterCallback = 617 function(args) { 618 if (this._enterButtonId && this.getButtonEnabled(this._enterButtonId)) { 619 this._runCallbackForButtonId(this._enterButtonId, args); 620 } 621 }; 622 623 /** 624 * Default callbacks for the standard buttons. 625 * 626 * @private 627 */ 628 DwtDialog.prototype._resetCallbacks = 629 function() { 630 if (this._buttonDesc) { 631 for (var i = 0; i < DwtDialog.ALL_BUTTONS.length; i++) { 632 var id = DwtDialog.ALL_BUTTONS[i]; 633 if (this._buttonDesc[id]) 634 this._buttonDesc[id].callback = new AjxCallback(this, this.popdown); 635 } 636 } 637 }; 638 639 // 640 // Classes 641 // 642 643 /** 644 * @class 645 * This class represents a button descriptor. 646 * 647 * @param {string} id the button Id 648 * @param {string} label the button label 649 * @param {constant} align the alignment 650 * @param {AjxCallback} callback the callback 651 * @param {string} cellTemplate the template 652 */ 653 DwtDialog_ButtonDescriptor = function(id, label, align, callback, cellTemplate) { 654 this.id = id; 655 this.label = label; 656 this.align = align; 657 this.callback = callback; 658 this.cellTemplate = cellTemplate; 659 }; 660