1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 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, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * @overview 26 * This file contains a base Dwt dialog control. 27 * 28 */ 29 30 /** 31 * @class 32 * This is a base class for dialogs. Given content, this class will take care of 33 * showing and hiding the dialog, as well as dragging it. 34 * <p> 35 * Dialogs always hang off the main shell since their stacking order is managed through z-index. 36 * 37 * @author Ross Dargahi 38 * @author Conrad Damon 39 * 40 * @param {hash} params a hash of parameters 41 * @param {DwtComposite} params.parent the parent widget (the shell) 42 * @param {string} params.className the CSS class 43 * @param {string} params.title the title 44 * @param {number} [params.zIndex=Dwt.Z_DIALOG] the z-index to set for this dialog when it is visible 45 * @param {DwtBaseDialog.MODAL|DwtBaseDialog.MODELESS} [params.mode=DwtBaseDialog.MODAL] the modality of the dialog 46 * @param {DwtPoint} params.loc the location at which to popup the dialog. Defaults to being centered within its parent 47 * @param {boolean} params.disposeOnPopDown destroy the content of dialog on popdown, Defaults to false 48 * @param {DwtControl} params.view the the control whose element is to be re-parented 49 * @param {string} params.dragHandleId the the ID of element used as drag handle 50 * 51 * @extends DwtComposite 52 */ 53 DwtBaseDialog = function(params) { 54 if (arguments.length == 0) { return; } 55 56 params = Dwt.getParams(arguments, DwtBaseDialog.PARAMS); 57 var parent = params.parent; 58 if (!(parent instanceof DwtShell)) { 59 throw new DwtException("DwtBaseDialog parent must be a DwtShell", DwtException.INVALIDPARENT, "DwtDialog"); 60 } 61 params.className = params.className || "DwtBaseDialog"; 62 params.posStyle = DwtControl.ABSOLUTE_STYLE; 63 params.isFocusable = false; 64 65 this._title = params.title || ""; 66 67 DwtComposite.call(this, params); 68 this._disposeOnPopDown = params.disposeOnPopDown || false; 69 this._shell = parent; 70 this._zIndex = params.zIndex || Dwt.Z_DIALOG; 71 this._mode = params.mode || DwtBaseDialog.MODAL; 72 73 this._loc = new DwtPoint(); 74 if (params.loc) { 75 this._loc.x = params.loc.x; 76 this._loc.y = params.loc.y 77 } else { 78 this._loc.x = this._loc.y = Dwt.LOC_NOWHERE; 79 } 80 81 // Default dialog tab group. 82 this._tabGroup = new DwtTabGroup(this.toString()); 83 84 this._dragHandleId = params.dragHandleId || this._htmlElId + "_handle"; 85 this._createHtml(); 86 this._initializeDragging(this._dragHandleId); 87 88 if (params.view) { 89 this.setView(params.view); 90 } 91 92 // reset tab index 93 this.setZIndex(Dwt.Z_HIDDEN); // not displayed until popup() called 94 95 // Set visible to true now to allow for getting metrics. ZIndex hidden will prevent it 96 // from actually being visible until the dialog is popped up. 97 this.setVisible(true); 98 99 this._position(DwtBaseDialog.__nowhereLoc); 100 101 // Make sure mouse clicks propagate to the DwtDraggable handler (document.onMouseMove and onMouseUp) 102 this._propagateEvent[DwtEvent.ONMOUSEUP] = true; 103 }; 104 105 /** 106 * @private 107 */ 108 DwtBaseDialog.PARAMS = ["parent", "className", "title", "zIndex", "mode", "loc", "view", "dragHandleId", "id"]; 109 110 DwtBaseDialog.prototype = new DwtComposite; 111 DwtBaseDialog.prototype.constructor = DwtBaseDialog; 112 113 DwtBaseDialog.prototype.toString = function() { return "DwtBaseDialog"; }; 114 DwtBaseDialog.prototype.isDwtBaseDialog = true; 115 116 DwtBaseDialog.prototype.role = 'dialog'; 117 DwtBaseDialog.prototype.isFocusable = true; 118 119 120 // 121 // Constants 122 // 123 124 // modes 125 126 /** 127 * Defines a "modeless" dialog. 128 */ 129 DwtBaseDialog.MODELESS = 1; 130 131 /** 132 * Defines a "modal" dialog. 133 */ 134 DwtBaseDialog.MODAL = 2; 135 136 /** 137 * @private 138 */ 139 DwtBaseDialog.__nowhereLoc = new DwtPoint(Dwt.LOC_NOWHERE, Dwt.LOC_NOWHERE); 140 141 // 142 // Data 143 // 144 145 /** 146 * @private 147 */ 148 DwtBaseDialog.prototype.TEMPLATE = "dwt.Widgets#DwtBaseDialog"; 149 150 /** 151 * <strong>Note:</strong> 152 * This member variable will be set by sub-classes that want a control bar 153 * to appear below the dialog contents. 154 * 155 * @private 156 */ 157 DwtBaseDialog.prototype.CONTROLS_TEMPLATE = null; 158 159 // 160 // Public methods 161 // 162 163 /** 164 * Adds a popup listener. 165 * 166 * @param {AjxListener} listener the listener to add 167 */ 168 DwtBaseDialog.prototype.addPopupListener = 169 function(listener) { 170 this.addListener(DwtEvent.POPUP, listener); 171 } 172 173 /** 174 * Removes a popup listener. 175 * 176 * @param {AjxListener} listener the listener to remove 177 */ 178 DwtBaseDialog.prototype.removePopupListener = 179 function(listener) { 180 this.removeListener(DwtEvent.POPUP, listener); 181 } 182 183 /** 184 * Adds a popdown listener. 185 * 186 * @param {AjxListener} listener the listener to add 187 */ 188 DwtBaseDialog.prototype.addPopdownListener = 189 function(listener) { 190 this.addListener(DwtEvent.POPDOWN, listener); 191 } 192 193 /** 194 * Removes a popdown listener. 195 * 196 * @param {AjxListener} listener the listener to remove 197 */ 198 DwtBaseDialog.prototype.removePopdownListener = 199 function(listener) { 200 this.removeListener(DwtEvent.POPDOWN, listener); 201 } 202 203 /** 204 * Pops-up the dialog, makes the dialog visible in places. Everything under the dialog will 205 * become veiled if we are modal. Note: popping up a dialog will block 206 * keyboard actions from being delivered to the global key action handler (if one 207 * is registered). 208 * 209 * @param {DwtPoint} loc the desired location 210 */ 211 DwtBaseDialog.prototype.popup = 212 function(loc) { 213 if (this._poppedUp) { return; } 214 215 this.cleanup(true); 216 var thisZ = this._zIndex; 217 218 // if we're modal, setup the veil effect, and track which dialogs are open 219 if (this._mode == DwtBaseDialog.MODAL) { 220 thisZ = this._setModalEffect(thisZ); 221 } 222 223 this._shell._veilOverlay.activeDialogs.push(this); 224 this.setVisible(true); 225 226 // use whichever has a value, local has precedence 227 if (loc) { 228 this._loc.x = loc.x; 229 this._loc.y = loc.y; 230 } 231 this._position(loc); 232 233 //reset TAB focus before popup of dialog. 234 //method be over-written to focus a different member. 235 this._resetTabFocus(); 236 237 this.setZIndex(thisZ); 238 this._poppedUp = true; 239 240 // Push our tab group 241 var kbMgr = this._shell.getKeyboardMgr(); 242 kbMgr.pushTabGroup(this._tabGroup); 243 kbMgr.pushDefaultHandler(this); 244 245 DwtShell.getShell().addListener(DwtEvent.CONTROL, this._resizeHdlr.bind(this)); 246 247 this.notifyListeners(DwtEvent.POPUP, this); 248 }; 249 250 /** 251 * @private 252 */ 253 DwtBaseDialog.prototype._resetTabFocus = 254 function(){ 255 this._tabGroup.resetFocusMember(true); 256 }; 257 258 DwtBaseDialog.prototype.focus = 259 function () { 260 // if someone is listening for the focus to happen, give control to them, 261 // otherwise focus on this dialog. 262 if (this.isListenerRegistered(DwtEvent.ONFOCUS)) { 263 this.notifyListeners(DwtEvent.ONFOCUS); 264 } else if (this._focusElementId){ 265 var focEl = document.getElementById(this._focusElementId); 266 if (focEl) { 267 focEl.focus(); 268 return focEl; 269 } 270 } 271 }; 272 273 /** 274 * Checks if the dialog is popped-up. 275 * 276 * @return {boolean} <code>true</code> if the dialog is popped-up; <code>false</code> otherwise 277 */ 278 DwtBaseDialog.prototype.isPoppedUp = 279 function () { 280 return this._poppedUp; 281 }; 282 283 /** 284 * Pops-down and hides the dialog. 285 * 286 */ 287 DwtBaseDialog.prototype.popdown = 288 function() { 289 290 if (this._poppedUp) { 291 this._poppedUp = false; 292 this.cleanup(false); 293 294 //var myZIndex = this.getZIndex(); 295 var myZIndex = this._zIndex; 296 this.setZIndex(Dwt.Z_HIDDEN); 297 this.setVisible(false); 298 //TODO we should not create an object everytime we popdown a dialog (ditto w/popup) 299 this._position(DwtBaseDialog.__nowhereLoc); 300 if (this._mode == DwtBaseDialog.MODAL) { 301 this._undoModality(myZIndex); 302 } else { 303 this._shell._veilOverlay.activeDialogs.pop(); 304 } 305 //this.removeKeyListeners(); 306 307 //Dispose the dialog if _disposeOnPopDown is set to true 308 if (this._disposeOnPopDown === true) { 309 this.dispose(); 310 } 311 312 // Pop our tab group 313 var kbMgr = this._shell.getKeyboardMgr(); 314 kbMgr.popTabGroup(this._tabGroup); 315 kbMgr.popDefaultHandler(); 316 317 DwtShell.getShell().removeListener(DwtEvent.CONTROL, this._resizeHdlr.bind(this)); 318 319 this.notifyListeners(DwtEvent.POPDOWN, this); 320 } 321 }; 322 323 /** 324 * Sets the content of the dialog to a new view. Essentially re-parents 325 * the supplied control's HTML element to the dialogs HTML element 326 * 327 * @param {DwtControl} newView the control whose element is to be re-parented 328 */ 329 DwtBaseDialog.prototype.setView = 330 function(newView) { 331 this.reset(); 332 if (newView) { 333 this._getContentDiv().appendChild(newView.getHtmlElement()); 334 } 335 }; 336 337 /** 338 * Resets the dialog back to its original state. Subclasses should override this method 339 * to add any additional behavior, but should still call up into this method. 340 * 341 */ 342 DwtBaseDialog.prototype.reset = 343 function() { 344 this._loc.x = this._loc.y = Dwt.LOC_NOWHERE; 345 } 346 347 /** 348 * Cleans up the dialog so it can be used again later. 349 * 350 * @param {boolean} bPoppedUp if <code>true</code>, the dialog is popped-up; <code>false</code> otherwise 351 */ 352 DwtBaseDialog.prototype.cleanup = 353 function(bPoppedUp) { 354 //TODO handle different types of input fields e.g. checkboxes etc 355 var inputFields = this._getInputFields(); 356 357 if (inputFields) { 358 var len = inputFields.length; 359 for (var i = 0; i < len; i++) { 360 inputFields[i].disabled = !bPoppedUp; 361 if (bPoppedUp) 362 inputFields[i].value = ""; 363 } 364 } 365 } 366 367 /** 368 * Sets the title. 369 * 370 * @param {string} title the title 371 */ 372 DwtBaseDialog.prototype.setTitle = 373 function(title) { 374 if (this._titleEl) { 375 this._titleEl.innerHTML = title || ""; 376 } 377 378 this._title = title; 379 }; 380 381 /** 382 * Sets the dialog content (below the title, above the buttons). 383 * 384 * @param {string} text the dialog content 385 */ 386 DwtBaseDialog.prototype.setContent = 387 function(text) { 388 var d = this._getContentDiv(); 389 if (d) { 390 d.innerHTML = text || ""; 391 } 392 } 393 394 /** 395 * @private 396 */ 397 DwtBaseDialog.prototype._getContentDiv = 398 function() { 399 return this._contentEl; 400 }; 401 402 /** 403 * Adds an enter listener. 404 * 405 * @param {AjxListener} listener the listener to add 406 */ 407 DwtBaseDialog.prototype.addEnterListener = 408 function(listener) { 409 this.addListener(DwtEvent.ENTER, listener); 410 }; 411 412 /** 413 * Gets the active dialog. 414 * 415 * @return {DwtBaseDialog} the active dialog 416 */ 417 DwtBaseDialog.getActiveDialog = 418 function() { 419 var dialog = null; 420 var shellObj = DwtShell.getShell(window); 421 if (shellObj) { 422 var len = shellObj._veilOverlay.activeDialogs.length; 423 if (len > 0) { 424 dialog = shellObj._veilOverlay.activeDialogs[len - 1]; 425 } 426 } 427 return dialog; 428 }; 429 430 // 431 // Protected methods 432 // 433 434 DwtBaseDialog.prototype.getTabGroupMember = function() { 435 return this._tabGroup; 436 }; 437 438 /** 439 * @private 440 */ 441 DwtBaseDialog.prototype._initializeDragging = 442 function(dragHandleId) { 443 var dragHandle = document.getElementById(dragHandleId); 444 if (dragHandle) { 445 var control = DwtControl.fromElementId(window._dwtShellId); 446 if (control) { 447 var p = Dwt.getSize(control.getHtmlElement()); 448 var dragObj = document.getElementById(this._htmlElId); 449 var size = this.getSize(); 450 var dragEndCb = new AjxCallback(this, this._dragEnd); 451 var dragCb = new AjxCallback(this, this._duringDrag); 452 var dragStartCb = new AjxCallback(this, this._dragStart); 453 454 DwtDraggable.init(dragHandle, dragObj, 0, 455 document.body.offsetWidth - 10, 0, document.body.offsetHeight - 10, dragStartCb, dragCb, dragEndCb); 456 } 457 } 458 }; 459 460 /** 461 * @private 462 */ 463 DwtBaseDialog.prototype._getContentHtml = 464 function() { 465 return ""; 466 }; 467 468 /** 469 * @private 470 */ 471 DwtBaseDialog.prototype._createHtml = function(templateId) { 472 var data = { id: this._htmlElId }; 473 this._createHtmlFromTemplate(templateId || this.TEMPLATE, data); 474 }; 475 476 /** 477 * @private 478 */ 479 DwtBaseDialog.prototype._createHtmlFromTemplate = function(templateId, data) { 480 // set default params 481 data.dragId = this._dragHandleId; 482 data.title = this._title; 483 data.icon = ""; 484 data.closeIcon1 = ""; 485 data.closeIcon2 = ""; 486 data.controlsTemplateId = this.CONTROLS_TEMPLATE; 487 488 // expand template 489 DwtComposite.prototype._createHtmlFromTemplate.call(this, templateId, data); 490 491 // remember elements 492 this._titleBarEl = document.getElementById(data.id+"_titlebar"); 493 this._titleEl = document.getElementById(data.id+"_title"); 494 this._contentEl = document.getElementById(data.id+"_content"); 495 496 if (this._titleEl) { 497 this.setAttribute('aria-labelledby', this._titleEl.id); 498 this._titleEl.setAttribute('role', 'heading'); 499 this._titleEl.setAttribute('aria-level', '2'); 500 } 501 502 // NOTE: This is for backwards compatibility. There are just 503 // too many sub-classes of dialog that expect to return 504 // the dialog contents via the _getContentHtml method. 505 this.setContent(this._getContentHtml()); 506 }; 507 508 /** 509 * @private 510 */ 511 DwtBaseDialog.prototype._setModalEffect = 512 function() { 513 // place veil under this dialog 514 var dialogZ = this._shell._veilOverlay.dialogZ; 515 var currentDialogZ = null; 516 var thisZ, veilZ; 517 if (dialogZ.length) 518 currentDialogZ = dialogZ[dialogZ.length - 1]; 519 if (currentDialogZ) { 520 thisZ = currentDialogZ + 2; 521 veilZ = currentDialogZ + 1; 522 } else { 523 thisZ = this._zIndex; 524 veilZ = Dwt.Z_VEIL; 525 } 526 this._shell._veilOverlay.veilZ.push(veilZ); 527 this._shell._veilOverlay.dialogZ.push(thisZ); 528 Dwt.setZIndex(this._shell._veilOverlay, veilZ); 529 return thisZ; 530 }; 531 532 /** 533 * @private 534 */ 535 DwtBaseDialog.prototype._undoModality = 536 function (myZIndex) { 537 var veilZ = this._shell._veilOverlay.veilZ; 538 veilZ.pop(); 539 var newVeilZ = veilZ[veilZ.length - 1]; 540 Dwt.setZIndex(this._shell._veilOverlay, newVeilZ); 541 this._shell._veilOverlay.dialogZ.pop(); 542 this._shell._veilOverlay.activeDialogs.pop(); 543 if (this._shell._veilOverlay.activeDialogs.length > 0 ) { 544 this._shell._veilOverlay.activeDialogs[0].focus(); 545 } 546 }; 547 548 /** 549 * Subclasses should implement this method to return an array of input fields that 550 * they want to be cleaned up between instances of the dialog being popped up and 551 * down 552 * 553 * @return An array of the input fields to be reset 554 */ 555 DwtBaseDialog.prototype._getInputFields = 556 function() { 557 // overload me 558 } 559 560 DwtBaseDialog.prototype._resizeHdlr = 561 function(ev) { 562 if (this._loc.x === Dwt.LOC_NOWHERE && this._loc.y === Dwt.LOC_NOWHERE) { 563 this._position(); 564 } 565 }; 566 567 /** 568 * @private 569 */ 570 DwtBaseDialog.prototype._dragStart = 571 function (x, y){ 572 // fix for bug 3177 573 if (AjxEnv.isNav && !this._ignoreSetDragBoundries) { 574 this._currSize = this.getSize(); 575 var control = DwtControl.fromElementId(window._dwtShellId); 576 if (control) { 577 var p = Dwt.getSize(control.getHtmlElement()); 578 DwtDraggable.setDragBoundaries(DwtDraggable.dragEl, 0, p.x - this._currSize.x, 0, p.y - this._currSize.y); 579 } 580 } 581 }; 582 583 /** 584 * @private 585 */ 586 DwtBaseDialog.prototype._dragEnd = 587 function(x, y) { 588 // save dropped position so popup(null) will not re-center dialog box 589 this._loc.x = x; 590 this._loc.y = y; 591 } 592 593 /** 594 * @private 595 */ 596 DwtBaseDialog.prototype._duringDrag = 597 function(x, y) { 598 // overload me 599 }; 600 601 /** 602 * @private 603 */ 604 DwtBaseDialog.prototype._doesContainElement = 605 function (element) { 606 return Dwt.contains(this.getHtmlElement(), element); 607 };