1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2004, 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) 2004, 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 an application controller. 28 * 29 */ 30 31 /** 32 * Creates a controller. 33 * @class 34 * This class represents an application controller. 35 * 36 * @param {DwtShell} container the application container 37 * @param {ZmApp} app the application 38 * @param {constant} type type of controller (typically a view type) 39 * @param {string} sessionId the session id 40 */ 41 ZmController = function(container, app, type, sessionId) { 42 43 if (arguments.length == 0) { return; } 44 45 this.setCurrentViewType(this.getDefaultViewType()); 46 this.setCurrentViewId(this.getDefaultViewType()); 47 if (sessionId) { 48 this.setSessionId(sessionId, type); 49 } 50 51 this._container = container; 52 this._app = app; 53 54 this._shell = appCtxt.getShell(); 55 this._appViews = {}; 56 57 this._authenticating = false; 58 this.isHidden = (sessionId == ZmApp.HIDDEN_SESSION); 59 this._elementsToHide = null; 60 }; 61 62 ZmController.prototype.isZmController = true; 63 ZmController.prototype.toString = function() { return "ZmController"; }; 64 65 66 ZmController.SESSION_ID_SEP = "-"; 67 68 // Abstract methods 69 70 ZmController.prototype._setView = function() {}; 71 72 /** 73 * Returns the default view type 74 */ 75 ZmController.getDefaultViewType = function() {}; // needed by ZmApp::getSessionController 76 ZmController.prototype.getDefaultViewType = function() {}; 77 78 // _defaultView is DEPRECATED in 8.0 79 ZmController.prototype._defaultView = ZmController.prototype.getDefaultViewType; 80 81 82 83 // Public methods 84 85 /** 86 * Gets the session ID. 87 * 88 * @return {string} the session ID 89 */ 90 ZmController.prototype.getSessionId = 91 function() { 92 return this._sessionId; 93 }; 94 95 /** 96 * Sets the session id, view id, and tab id (using the type and session id). 97 * Controller for a view that shows up in a tab within the app chooser bar. 98 * Examples include compose, send confirmation, and msg view. 99 * 100 * @param {string} sessionId the session id 101 * @param {string} type the type 102 * @param {ZmSearchResultsController} searchResultsController owning controller 103 */ 104 ZmController.prototype.setSessionId = 105 function(sessionId, type) { 106 107 this._sessionId = sessionId; 108 if (type) { 109 this.setCurrentViewType(type); 110 this.setCurrentViewId(sessionId ? [type, sessionId].join(ZmController.SESSION_ID_SEP) : type); 111 this.tabId = sessionId ? ["tab", this.getCurrentViewId()].join("_") : ""; 112 } 113 114 // this.sessionId and this.viewId are DEPRECATED in 8.0; 115 // use getSessionId() and getCurrentViewId() instead 116 this.sessionId = this._sessionId; 117 this.viewId = this.getCurrentViewId(); 118 }; 119 120 /** 121 * Gets the current view type. 122 * 123 * @return {constant} the view type 124 */ 125 ZmController.prototype.getCurrentViewType = 126 function(viewType) { 127 return this._currentViewType; 128 }; 129 // _getViewType is DEPRECATED in 8.0 130 ZmController.prototype._getViewType = ZmController.prototype.getCurrentViewType; 131 132 /** 133 * Sets the current view type. 134 * 135 * @param {constant} viewType the view type 136 */ 137 ZmController.prototype.setCurrentViewType = 138 function(viewType) { 139 this._currentViewType = viewType; 140 }; 141 142 /** 143 * Gets the current view ID. 144 * 145 * @return {DwtComposite} the view Id 146 */ 147 ZmController.prototype.getCurrentViewId = 148 function() { 149 return this._currentViewIdOverride || this._currentViewId; 150 }; 151 152 /** 153 * Sets the current view ID. 154 * 155 * @param {string} viewId the view ID 156 */ 157 ZmController.prototype.setCurrentViewId = 158 function(viewId) { 159 this._currentViewId = viewId; 160 161 // this._currentView is DEPRECATED in 8.0; use getCurrentViewId() instead 162 this._currentView = this._currentViewId; 163 }; 164 165 /** 166 * Gets the application. 167 * 168 * @return {ZmApp} the application 169 */ 170 ZmController.prototype.getApp = function() { 171 return this._app; 172 }; 173 174 /** 175 * return the view elements. Currently a toolbar, app content, and "new" button. 176 * 177 * @param view (optional if provided toolbar) 178 * @param appContentView 179 * @param toolbar (used only if view param is null) 180 * 181 */ 182 ZmController.prototype.getViewElements = 183 function(view, appContentView, toolbar) { 184 var elements = {}; 185 toolbar = toolbar || this._toolbar[view]; 186 elements[ZmAppViewMgr.C_TOOLBAR_TOP] = toolbar; 187 elements[ZmAppViewMgr.C_APP_CONTENT] = appContentView; 188 189 return elements; 190 }; 191 192 /** 193 * Pops-up the error dialog. 194 * 195 * @param {String} msg the error msg 196 * @param {ZmCsfeException} ex the exception 197 * @param {Boolean} noExecReset (not used) 198 * @param {Boolean} hideReportButton if <code>true</code>, hide the "Send error report" button 199 * @param {Boolean} expanded if <code>true</code>, contents are expanded by default 200 */ 201 ZmController.prototype.popupErrorDialog = 202 function(msg, ex, noExecReset, hideReportButton, expanded, noEncoding) { 203 // popup alert 204 var errorDialog = appCtxt.getErrorDialog(); 205 var detailStr = ""; 206 if (typeof ex == "string") { 207 // in case an Error makes it here 208 detailStr = ex; 209 } else if (ex instanceof Object) { 210 ex.msg = ex.msg || msg; 211 var fields = ["method", "msg", "code", "detail", "trace", "request", 212 "fileName", "lineNumber", "message", "name", "stack" ]; 213 var html = [], i = 0; 214 html[i++] = "<table>"; 215 for (var j = 0; j < fields.length; j++) { 216 var fld = fields[j]; 217 var value = AjxStringUtil.htmlEncode(ex[fld]); 218 if (value) { 219 if (fld == "request") { 220 value = ["<pre>", value, "</pre>"].join(""); 221 var msgDiv = document.getElementById(errorDialog._msgCellId); 222 if (msgDiv) { 223 msgDiv.className = "DwtMsgDialog-wide"; 224 } 225 } 226 html[i++] = ["<tr><td valign='top'>", fields[j], ":</td><td valign='top'>", value, "</td></tr>"].join(""); 227 } 228 } 229 html[i++] = "</table>"; 230 detailStr = html.join(""); 231 } 232 errorDialog.registerCallback(DwtDialog.OK_BUTTON, this._errorDialogCallback, this); 233 if (!noEncoding) { 234 msg = AjxStringUtil.htmlEncode(msg); 235 } 236 errorDialog.setMessage(msg, detailStr, DwtMessageDialog.CRITICAL_STYLE, ZmMsg.zimbraTitle); 237 errorDialog.popup(null, hideReportButton); 238 if (expanded) 239 errorDialog.showDetail(); 240 }; 241 242 /** 243 * Pops-up an error dialog describing an upload error. 244 * 245 * @param {constant} type the type of the uploaded item, e.g. <code>ZmItem.MSG</code>. 246 * @param {Number} respCode the HTTP reponse status code 247 * @param {String} extraMsg optional message to append to the status 248 */ 249 ZmController.prototype.popupUploadErrorDialog = 250 function(type, respCode, extraMsg) { 251 var warngDlg = appCtxt.getMsgDialog(); 252 var style = DwtMessageDialog.CRITICAL_STYLE; 253 var msg = this.createErrorMessage(type, respCode, extraMsg); 254 if (msg.length > 0) { 255 warngDlg.setMessage(msg, style); 256 warngDlg.popup(); 257 } 258 }; 259 260 ZmController.prototype.createErrorMessage = function(type, respCode, extraMsg) { 261 var msg = ""; 262 263 switch (respCode) { 264 case AjxPost.SC_OK: 265 break; 266 267 case AjxPost.SC_REQUEST_ENTITY_TOO_LARGE: 268 var basemsg = 269 type && ZmMsg['attachmentSizeError_' + type] || 270 ZmMsg.attachmentSizeError; 271 var sizelimit = 272 AjxUtil.formatSize(appCtxt.get(ZmSetting.MESSAGE_SIZE_LIMIT)); 273 msg = AjxMessageFormat.format(basemsg, sizelimit); 274 break; 275 276 default: 277 var basemsg = 278 type && ZmMsg['errorAttachment_' + type] || 279 ZmMsg.errorAttachment; 280 msg = AjxMessageFormat.format(basemsg, respCode || AjxPost.SC_NO_CONTENT); 281 break; 282 } 283 284 if ((msg.length > 0) && extraMsg) { 285 msg += '<br /><br />'; 286 msg += extraMsg; 287 } 288 return msg; 289 }; 290 291 ZmController.handleScriptError = 292 function(ex, debugWindowOnly) { 293 294 var text = []; 295 var eol = "<br/>"; 296 if (ex) { 297 var msg = ZmMsg.scriptError + ": " + ex.message; 298 var m = ex.fileName && ex.fileName.match(/(\w+\.js)/); 299 if (m && m.length) { 300 msg += " - " + m[1] + ":" + ex.lineNumber; 301 } 302 if (ex.fileName) { text.push("File: " + ex.fileName); } 303 if (ex.lineNumber) { text.push("Line: " + ex.lineNumber); } 304 if (ex.name) { text.push("Error: " + ex.name); } 305 if (ex.stack) { text.push("Stack: " + ex.stack.replace("\n", eol, "g")); } 306 } 307 var content = text.join(eol); 308 var errorMsg = [msg, content].join(eol + eol); 309 if (debugWindowOnly) { 310 // Display the error in the debug window 311 DBG.println(AjxDebug.DBG1, errorMsg); 312 } else { 313 // Record the error in a log buffer and display a script error popup 314 AjxDebug.println(AjxDebug.EXCEPTION, errorMsg); 315 appCtxt.getAppController().popupErrorDialog(msg, content, null, false, true); 316 } 317 }; 318 319 /** 320 * Gets the key map name. 321 * 322 * @return {String} the key map name 323 */ 324 ZmController.prototype.getKeyMapName = 325 function() { 326 return ZmKeyMap.MAP_GLOBAL; 327 }; 328 329 /** 330 * Handles the key action. 331 * 332 * @param {constant} actionCode the action code 333 * @return {Boolean} <code>true</code> if the key action is handled 334 * 335 * @see ZmApp.ACTION_CODES_R 336 * @see ZmKeyMap 337 */ 338 ZmController.prototype.handleKeyAction = 339 function(actionCode, ev) { 340 DBG.println(AjxDebug.DBG3, "ZmController.handleKeyAction"); 341 342 // tab navigation shortcut 343 var tabView = this.getTabView ? this.getTabView() : null; 344 if (tabView && tabView.handleKeyAction(actionCode)) { 345 return true; 346 } 347 348 // shortcuts tied directly to operations 349 var isExternalAccount = appCtxt.isExternalAccount(); 350 var app = ZmApp.ACTION_CODES_R[actionCode]; 351 if (app) { 352 var op = ZmApp.ACTION_CODES[actionCode]; 353 if (op) { 354 if (isExternalAccount) { return true; } 355 appCtxt.getApp(app).handleOp(op); 356 return true; 357 } 358 } 359 360 switch (actionCode) { 361 362 case ZmKeyMap.NEW: { 363 if (isExternalAccount) { break; } 364 // find default "New" action code for current app 365 app = appCtxt.getCurrentAppName(); 366 var newActionCode = ZmApp.NEW_ACTION_CODE[app]; 367 if (newActionCode) { 368 var op = ZmApp.ACTION_CODES[newActionCode]; 369 if (op) { 370 appCtxt.getApp(app).handleOp(op); 371 return true; 372 } 373 } 374 break; 375 } 376 377 case ZmKeyMap.NEW_FOLDER: 378 case ZmKeyMap.NEW_TAG: 379 if (isExternalAccount || appCtxt.isWebClientOffline()) { break; } 380 var op = ZmApp.ACTION_CODES[actionCode]; 381 if (op) { 382 this._newListener(null, op); 383 } 384 break; 385 386 case ZmKeyMap.NEW_SEARCH: { 387 appCtxt.getSearchController().openNewSearchTab(); 388 break; 389 } 390 391 case ZmKeyMap.SAVED_SEARCH: 392 if (isExternalAccount) { break; } 393 var searches = appCtxt.getFolderTree().getByType(ZmOrganizer.SEARCH); 394 if (searches && searches.length > 0) { 395 var dlg = appCtxt.getChooseFolderDialog(); 396 // include app name in ID so we have one overview per app to show only its saved searches 397 var params = {treeIds: [ZmOrganizer.SEARCH], 398 overviewId: dlg.getOverviewId(ZmOrganizer.SEARCH, this._app._name), 399 appName: this._app._name, 400 title: ZmMsg.selectSearch}; 401 ZmController.showDialog(dlg, new AjxCallback(null, ZmController._searchSelectionCallback, [dlg]), params); 402 } 403 break; 404 405 case ZmKeyMap.VISIT: 406 var dlg = appCtxt.getChooseFolderDialog(); 407 var orgType = ZmApp.ORGANIZER[this._app._name] || ZmOrganizer.FOLDER; 408 var params = {treeIds: [orgType], 409 overviewId: dlg.getOverviewId(ZmOrganizer.APP[orgType]), 410 appName: this._app._name, 411 noRootSelect: true, 412 title: AjxMessageFormat.format(ZmMsg.goToFolder, ZmMsg[ZmOrganizer.MSG_KEY[orgType]])}; 413 ZmController.showDialog(dlg, new AjxCallback(null, ZmController._visitOrgCallback, [dlg, orgType]), params); 414 break; 415 416 case ZmKeyMap.VISIT_TAG: 417 if (appCtxt.getTagTree().size() > 0) { 418 var dlg = appCtxt.getPickTagDialog(); 419 ZmController.showDialog(dlg, new AjxCallback(null, ZmController._visitOrgCallback, [dlg, ZmOrganizer.TAG])); 420 } 421 break; 422 423 default: 424 return false; 425 } 426 return true; 427 }; 428 429 /** 430 * @private 431 */ 432 ZmController._searchSelectionCallback = 433 function(dialog, searchFolder) { 434 if (searchFolder) { 435 appCtxt.getSearchController().redoSearch(searchFolder.search); 436 } 437 dialog.popdown(); 438 }; 439 440 /** 441 * @private 442 */ 443 ZmController._visitOrgCallback = 444 function(dialog, orgType, org) { 445 if (org) { 446 var tc = appCtxt.getOverviewController().getTreeController(orgType); 447 if (tc && tc._itemClicked) { 448 tc._itemClicked(org); 449 } 450 } 451 dialog.popdown(); 452 }; 453 454 /** 455 * Checks if shortcuts for the given map are supported for this view. For example, given the map 456 * "tabView", a controller that creates a tab view would return <code>true</code>. 457 * 458 * @param {String} map the name of a map (see {@link DwtKeyMap}) 459 * @return {Boolean} <code>true</code> if shortcuts are supported 460 */ 461 ZmController.prototype.mapSupported = 462 function(map) { 463 return false; 464 }; 465 466 /** 467 * @private 468 */ 469 ZmController.prototype._newListener = 470 function(ev, op) { 471 switch (op) { 472 // new organizers 473 case ZmOperation.NEW_FOLDER: { 474 // note that this shortcut only happens if mail app is around - it means "new mail folder" 475 ZmController.showDialog(appCtxt.getNewFolderDialog(), this.getNewFolderCallback()); 476 break; 477 } 478 case ZmOperation.NEW_TAG: { 479 if (!this._newTagCb) { 480 this._newTagCb = new AjxCallback(this, this._newTagCallback); 481 } 482 ZmController.showDialog(appCtxt.getNewTagDialog(), this._newTagCb); 483 break; 484 } 485 } 486 }; 487 488 /** 489 * @private 490 */ 491 ZmController.prototype._newFolderCallback = 492 function(parent, name, color, url) { 493 // REVISIT: Do we really want to close the dialog before we 494 // know if the create succeeds or fails? 495 var dialog = appCtxt.getNewFolderDialog(); 496 dialog.popdown(); 497 498 var oc = appCtxt.getOverviewController(); 499 oc.getTreeController(ZmOrganizer.FOLDER)._doCreate(parent, name, color, url); 500 }; 501 502 /** 503 * @private 504 */ 505 ZmController.prototype._newTagCallback = 506 function(params) { 507 appCtxt.getNewTagDialog().popdown(); 508 var oc = appCtxt.getOverviewController(); 509 oc.getTreeController(ZmOrganizer.TAG)._doCreate(params); 510 }; 511 512 /** 513 * @private 514 */ 515 ZmController.prototype._createTabGroup = 516 function(name) { 517 name = name ? name : this.toString(); 518 this._tabGroup = new DwtTabGroup(name); 519 return this._tabGroup; 520 }; 521 522 /** 523 * @private 524 */ 525 ZmController.prototype._setTabGroup = 526 function(tabGroup) { 527 this._tabGroup = tabGroup; 528 }; 529 530 /** 531 * Gets the tab group. 532 * 533 * @return {Object} the tab group 534 */ 535 ZmController.prototype.getTabGroup = 536 function() { 537 return this._tabGroup; 538 }; 539 540 /** 541 * Gets the new folder callback. 542 * 543 * @return {AjxCallback} the callback 544 */ 545 ZmController.prototype.getNewFolderCallback = 546 function() { 547 if (!this._newFolderCb) { 548 this._newFolderCb = new AjxCallback(this, this._newFolderCallback); 549 } 550 return this._newFolderCb; 551 }; 552 553 /** 554 * Remember the currently focused item before this view is hidden. Typically called by a preHideCallback. 555 * 556 * @private 557 */ 558 ZmController.prototype._saveFocus = 559 function() { 560 var currentFocusMember = appCtxt.getRootTabGroup().getFocusMember(); 561 var myTg = this.getTabGroup(); 562 this._savedFocusMember = (currentFocusMember && myTg && myTg.contains(currentFocusMember)) ? currentFocusMember : null; 563 return this._savedFocusMember; 564 }; 565 566 /** 567 * Make our tab group the current app view tab group, and restore focus to 568 * whatever had it last time we were visible. Typically called by a postShowCallback. 569 * 570 * @private 571 */ 572 ZmController.prototype._restoreFocus = 573 function(focusItem, noFocus) { 574 575 var rootTg = appCtxt.getRootTabGroup(); 576 577 var curApp = appCtxt.getCurrentApp(); 578 var ovId = curApp && curApp.getOverviewId(); 579 var overview = ovId && appCtxt.getOverviewController().getOverview(ovId); 580 if (rootTg && overview && (overview != ZmController._currentOverview)) { 581 var currTg = ZmController._currentOverview && 582 ZmController._currentOverview.getTabGroupMember(); 583 rootTg.replaceMember(currTg, overview.getTabGroupMember(), 584 false, false, null, true); 585 ZmController._currentOverview = overview; 586 } 587 588 var myTg = this.getTabGroup(); 589 focusItem = focusItem || this._savedFocusMember || this._getDefaultFocusItem() || rootTg.getFocusMember(); 590 noFocus = noFocus || ZmController.noFocus; 591 ZmController.noFocus = false; 592 if (rootTg && myTg && (myTg != ZmController._currentAppViewTabGroup)) { 593 rootTg.replaceMember(ZmController._currentAppViewTabGroup, myTg, false, false, focusItem, noFocus); 594 ZmController._currentAppViewTabGroup = myTg; 595 } else if (focusItem && !noFocus) { 596 appCtxt.getKeyboardMgr().grabFocus(focusItem); 597 } 598 }; 599 600 /** 601 * @private 602 */ 603 ZmController.prototype._getDefaultFocusItem = 604 function() { 605 var myTg = this.getTabGroup(); 606 return myTg ? myTg.getFirstMember(true) : null; 607 }; 608 609 // Callbacks to run on changes in view state 610 ZmController.prototype._preUnloadCallback = function() { return true; }; 611 ZmController.prototype._postHideCallback = function() { return true; }; 612 ZmController.prototype._postRemoveCallback = function() { return true; }; 613 ZmController.prototype._preShowCallback = function() { return true; }; 614 615 // preserve focus state 616 ZmController.prototype._preHideCallback = 617 function() { 618 DBG.println(AjxDebug.DBG2, "ZmController.prototype._preHideCallback"); 619 this._saveFocus(); 620 return true; 621 }; 622 623 // restore focus state 624 ZmController.prototype._postShowCallback = 625 function() { 626 DBG.println(AjxDebug.DBG2, "ZmController.prototype._postShowCallback"); 627 this._restoreFocus(); 628 return true; 629 }; 630 631 /** 632 * Common exception handling entry point for sync and async commands. 633 * 634 * @private 635 */ 636 ZmController.prototype._handleError = 637 function(ex, continuation) { 638 this._handleException(ex, continuation); 639 }; 640 641 /** 642 * Handles exceptions. There is special handling for auth-related exceptions. 643 * Other exceptions generally result in the display of an error dialog. An 644 * auth-expired exception results in the display of a login dialog. After the 645 * user logs in, we use the continuation to re-run the request that failed. 646 * 647 * @param {AjxException} ex the exception 648 * @param {Hash} continuation the original request params 649 * 650 * @private 651 */ 652 ZmController.prototype._handleException = function(ex, continuation) { 653 654 if (ex.code == AjxSoapException.INVALID_PDU) { 655 ex.code = ZmCsfeException.SVC_FAILURE; 656 ex.detail = ["contact your administrator (", ex.msg, ")"].join(""); 657 ex.msg = "Service failure"; 658 } 659 660 if (ex.code == ZmCsfeException.SVC_AUTH_EXPIRED || ex.code == ZmCsfeException.SVC_AUTH_REQUIRED || ex.code == ZmCsfeException.NO_AUTH_TOKEN) { 661 ZmCsfeCommand.noAuth = true; 662 DBG.println(AjxDebug.DBG1, "ZmController.prototype._handleException ex.code : " + ex.code + ". Invoking logout."); 663 ZmZimbraMail.logOff(null, true); 664 return; 665 } 666 667 // If we get this error, user is probably looking at a stale list. Let's 668 // refetch user's search results. This is more likely to happen in zdesktop. 669 // See bug 33760. 670 if (ex.code == ZmCsfeException.MAIL_NO_SUCH_MSG) { 671 var vid = appCtxt.getCurrentViewId(); 672 // only process if we're in one of these views otherwise, do the default 673 if (vid == ZmId.VIEW_CONVLIST || vid == ZmId.VIEW_TRAD) { 674 var mailApp = appCtxt.getApp(ZmApp.MAIL); 675 var callback = appCtxt.isOffline ? new AjxCallback(this, this._handleMailSearch, mailApp) : null; 676 mailApp.mailSearch(null, callback); 677 return; 678 } 679 } 680 681 // silently ignore polling exceptions 682 if (ex.method !== "NoOpRequest") { 683 var args; 684 if (ex.code === ZmCsfeException.MAIL_NO_SUCH_ITEM) { 685 args = ex.data.itemId; 686 } 687 else if (ex.code === ZmCsfeException.MAIL_SEND_FAILURE) { 688 args = ex.code; // bug fix #5603 - error msg for mail.SEND_FAILURE takes an argument 689 } 690 else if (ex.code === ZmCsfeException.MAIL_INVALID_NAME) { 691 args = ex.data.name; 692 } 693 else if (ex.code === ZmCsfeException.SVC_UNKNOWN_DOCUMENT) { 694 args = ex.msg.split(': ')[1]; 695 } 696 697 if (ex.lineNumber && !ex.detail) { 698 // JS error that was caught before our JS-specific handler got it 699 ZmController.handleScriptError(ex); 700 } 701 else { 702 var msg; 703 704 if (continuation && continuation.restUri && continuation.restUri.indexOf('zimbraim') !== -1) { 705 msg = ZmMsg.chatXMPPError; 706 } 707 else { 708 msg = ex.getErrorMsg ? ex.getErrorMsg(args) : ex.msg || ex.message; 709 } 710 711 this.popupErrorDialog(msg, ex, true, this._hideSendReportBtn(ex)); 712 } 713 } 714 }; 715 716 ZmController.prototype._handleMailSearch = 717 function(app) { 718 if (appCtxt.get(ZmSetting.OFFLINE_SHOW_ALL_MAILBOXES)) { 719 app.getOverviewContainer().highlightAllMboxes(); 720 } 721 }; 722 723 /** 724 * @private 725 */ 726 ZmController.prototype._hideSendReportBtn = 727 function(ex) { 728 return (ex.code == ZmCsfeException.MAIL_TOO_MANY_TERMS || 729 ex.code == ZmCsfeException.MAIL_MAINTENANCE_MODE || 730 ex.code == ZmCsfeException.MAIL_MESSAGE_TOO_BIG || 731 ex.code == ZmCsfeException.NETWORK_ERROR || 732 ex.code == ZmCsfeException.EMPTY_RESPONSE || 733 ex.code == ZmCsfeException.BAD_JSON_RESPONSE || 734 ex.code == ZmCsfeException.TOO_MANY_TAGS || 735 ex.code == ZmCsfeException.OFFLINE_ONLINE_ONLY_OP); 736 }; 737 738 // 739 // Msg dialog Callbacks 740 // 741 742 /** 743 * @private 744 */ 745 ZmController.prototype._errorDialogCallback = 746 function() { 747 appCtxt.getErrorDialog().popdown(); 748 }; 749 750 /** 751 * Shows a dialog. Since the dialog is a shared resource, a dialog reset is performed. 752 * 753 * @param {DwtDialog} dialog the dialog 754 * @param {AjxCallback} callback the callback 755 * @param {Hash} params a hash of parameters 756 * @param {ZmAccount} account the account 757 * 758 * @see DwtDialog#reset 759 * @see DwtDialog#popup 760 */ 761 ZmController.showDialog = 762 function(dialog, callback, params, account) { 763 dialog.reset(account); 764 dialog.registerCallback(DwtDialog.OK_BUTTON, callback); 765 dialog.popup(params, account); 766 }; 767 768 /** 769 * Pop down the dialog and clear any pending actions (initiated from an action menu). 770 * 771 * @private 772 */ 773 ZmController.prototype._clearDialog = 774 function(dialog) { 775 dialog.popdown(); 776 this._pendingActionData = null; 777 }; 778 779 /** 780 * @private 781 */ 782 ZmController.prototype._menuPopdownActionListener = function() {}; 783 784 /** 785 * Checks if the view is transient. 786 * 787 * @param {Object} oldView the old view 788 * @param {Object} newView the new view 789 * @return {Boolean} <code>true</code> if the controller is transient. 790 */ 791 ZmController.prototype.isTransient = 792 function(oldView, newView) { 793 return false; 794 }; 795 796