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 the new window class. 27 */ 28 29 /** 30 * Creates a controller to run <code>ZmNewWindow</code>. Do not call directly, instead use 31 * the <code>run()</code> factory method. 32 * @class 33 * This class is the controller for a window created outside the main client 34 * window. It is a very stripped down and specialized version of {@link ZmZimbraMail}. 35 * The child window is single-use; it does not support switching among multiple 36 * views. 37 * 38 * @author Parag Shah 39 * 40 * @extends ZmController 41 * 42 * @see #run 43 */ 44 ZmNewWindow = function() { 45 46 ZmController.call(this, null); 47 48 appCtxt.setAppController(this); 49 50 //update body class to reflect user selected font 51 document.body.className = "user_font_" + appCtxt.get(ZmSetting.FONT_NAME); 52 //update root html elment class to reflect user selected font size 53 Dwt.addClass(document.documentElement, "user_font_size_" + appCtxt.get(ZmSetting.FONT_SIZE)); 54 55 56 this._settings = appCtxt.getSettings(); 57 this._settings.setReportScriptErrorsSettings(AjxException, ZmController.handleScriptError); //must set this for child window since AjxException is fresh for this window. Also must pass AjxException and the handler since we want it to update the one from this child window, and not the parent window 58 59 this._shell = appCtxt.getShell(); 60 61 // Register keymap and global key action handler w/ shell's keyboard manager 62 this._kbMgr = appCtxt.getKeyboardMgr(); 63 if (appCtxt.get(ZmSetting.USE_KEYBOARD_SHORTCUTS)) { 64 this._kbMgr.enable(true); 65 this._kbMgr.registerKeyMap(new ZmKeyMap()); 66 this._kbMgr.pushDefaultHandler(this); 67 } 68 69 this._apps = {}; 70 }; 71 72 ZmNewWindow.prototype = new ZmController; 73 ZmNewWindow.prototype.constructor = ZmNewWindow; 74 75 ZmNewWindow.prototype.isZmNewWindow = true; 76 ZmNewWindow.prototype.toString = function() { return "ZmNewWindow"; }; 77 78 // Public methods 79 80 /** 81 * Sets up new window and then starts it by calling its constructor. It is assumed that the 82 * CSFE is on the same host. 83 * 84 */ 85 ZmNewWindow.run = 86 function() { 87 88 // We're using a custom pkg that includes the mail classes we'll need, so pretend that 89 // we've already loaded mail packages so the real ones don't get loaded as well. 90 AjxDispatcher.setLoaded("MailCore", true); 91 AjxDispatcher.setLoaded("Mail", true); 92 93 var winOpener = window.opener || window; 94 95 if (!window.parentController) { 96 window.parentController = winOpener._zimbraMail; 97 } 98 99 // Create the global app context 100 window.appCtxt = new ZmAppCtxt(); 101 window.appCtxt.isChildWindow = true; 102 103 // XXX: DO NOT MOVE THIS LINE 104 // redefine ZmSetting from parent window since it loses this info. 105 window.parentAppCtxt = winOpener.appCtxt; 106 appCtxt.setSettings(parentAppCtxt.getSettings()); 107 appCtxt.isOffline = parentAppCtxt.isOffline; 108 appCtxt.multiAccounts = parentAppCtxt.multiAccounts; 109 appCtxt.sendAsEmails = parentAppCtxt.sendAsEmails; 110 appCtxt.sendOboEmails = parentAppCtxt.sendOboEmails; 111 window.ZmSetting = winOpener.ZmSetting; 112 113 ZmOperation.initialize(); 114 ZmApp.initialize(); 115 116 var shell = new DwtShell({className:"MainShell"}); 117 window.onbeforeunload = ZmNewWindow._confirmExitMethod; 118 appCtxt.setShell(shell); 119 120 // create new window and Go! 121 var newWindow = new ZmNewWindow(); 122 newWindow.startup(); 123 124 if (winOpener.onkeydown) { 125 window.onkeydown = winOpener.onkeydown; 126 } 127 }; 128 129 /** 130 * Allows this child window to inform parent it's going away 131 */ 132 ZmNewWindow.unload = function(ev) { 133 134 if (!window || !window.opener || !window.parentController) { 135 return; 136 } 137 138 var command = window.newWindowCommand; //bug 54409 - was using wrong attribute for command in unload 139 if (command == "compose" || command == "composeDetach" 140 || (command == "msgViewDetach" && appCtxt.composeCtlrSessionId)) { //msgViewDetach might turn into a compose session if user hits "reply"/etc 141 // compose controller adds listeners to parent window's list so we 142 // need to remove them before closing this window! 143 var cc = AjxDispatcher.run("GetComposeController", appCtxt.composeCtlrSessionId); 144 if (cc) { 145 cc.dispose(); 146 } 147 } 148 149 if (command == "msgViewDetach") { 150 // msg controller (as a ZmListController) adds listener to tag list 151 var mc = AjxDispatcher.run("GetMsgController", appCtxt.msgCtlrSessionId); 152 if (mc) { 153 mc.dispose(); 154 } 155 } 156 157 if (window.parentController) { 158 window.parentController.removeChildWindow(window); 159 } 160 }; 161 162 /** 163 * Presents a view based on a command passed through the window object. Possible commands are: 164 * 165 * <ul> 166 * <li><b>compose</b> compose window launched in child window</li> 167 * <li><b>composeDetach</b> compose window detached from client</li> 168 * <li><b>msgViewDetach</b> msg view detached from client</li> 169 * </ul> 170 * 171 */ 172 ZmNewWindow.prototype.startup = 173 function() { 174 // get params from parent window b/c of Safari bug #7162 175 // and in case of a refresh, our old window parameters are still stored there 176 if (window.parentController) { 177 var childWinObj = window.parentController.getChildWindow(window); 178 if (childWinObj) { 179 window.newWindowCommand = childWinObj.command; 180 window.newWindowParams = childWinObj.params; 181 } 182 } 183 184 if (!this._appViewMgr) { 185 this._appViewMgr = new ZmAppViewMgr(this._shell, this, true, false); 186 this._statusView = new ZmStatusView(this._shell, "ZmStatus", Dwt.ABSOLUTE_STYLE, ZmId.STATUS_VIEW); 187 } 188 189 var cmd = window.newWindowCommand; 190 var params = window.newWindowParams; 191 if (cmd == "shortcuts") { 192 var apps = {}; 193 apps[ZmApp.PREFERENCES] = true; 194 this._createEnabledApps(apps); 195 this._createView(); 196 return; 197 } 198 199 DBG.println(AjxDebug.DBG1, " ************ Hello from new window!"); 200 201 var rootTg = appCtxt.getRootTabGroup(); 202 203 var apps = {}; 204 apps[ZmApp.SEARCH] = true; 205 apps[ZmApp.MAIL] = true; 206 apps[ZmApp.CONTACTS] = true; 207 // only load calendar app if we're dealing with an invite 208 var msg = (cmd == "msgViewDetach") ? params.msg : null; 209 if (msg && 210 (msg.isInvite() || this._checkShareType(msg.share, "appointment"))) { 211 apps[ZmApp.CALENDAR] = true; 212 } else if (msg && this._checkShareType(msg.share, "task")) { 213 apps[ZmApp.TASKS] = true; 214 } 215 apps[ZmApp.PREFERENCES] = true; 216 apps[ZmApp.BRIEFCASE] = true; //Need this for both Compose & Msg View detach window. 217 this._createEnabledApps(apps); 218 219 // inherit parent's identity collection 220 var parentPrefsApp = parentAppCtxt.getApp(ZmApp.MAIL); 221 if (parentPrefsApp) { 222 appCtxt.getApp(ZmApp.MAIL)._identityCollection = parentPrefsApp.getIdentityCollection(); 223 } 224 225 // Find target first. 226 var target; 227 if (cmd == "compose" || cmd == "composeDetach") { 228 target = "compose-window"; 229 } else if (cmd == "msgViewDetach") { 230 target = "view-window"; 231 } 232 233 ZmZimbraMail.prototype._registerOrganizers.call(this); 234 ZmZimbraMail.registerViewsToTypeMap(); 235 236 237 // setup zimlets, Load it first becoz.. zimlets has to get processed first. 238 if (target) { 239 var allzimlets = parentAppCtxt.get(ZmSetting.ZIMLETS); 240 allzimlets = allzimlets || []; 241 var zimletArray = this._settings._getCheckedZimlets(allzimlets); 242 if (this._hasZimletsForTarget(zimletArray, target)) { 243 var zimletMgr = appCtxt.getZimletMgr(); 244 var userProps = this._getUserProps(); 245 var createViewCallback = new AjxCallback(this, this._createView); 246 appCtxt.setZimletsPresent(true); 247 zimletMgr.loadZimlets(zimletArray, userProps, target, createViewCallback, true); 248 return; 249 } 250 } 251 252 this._createView(); 253 }; 254 255 /** 256 * @private 257 */ 258 ZmNewWindow.prototype._checkShareType = 259 function(share, type) { 260 return share && share.link && (type === share.link.view); 261 }; 262 ZmNewWindow.prototype._createView = 263 function() { 264 265 var cmd = window.newWindowCommand; 266 var params = window.newWindowParams; 267 268 var rootTg = appCtxt.getRootTabGroup(); 269 var startupFocusItem; 270 271 //I null composeCtlrSessionId so it's not kept from irrelevant sessions from parent window. 272 // (since I set it in every compose session, in ZmMailApp.prototype.compose). 273 // This is important in case of cmd == "msgViewDetach" 274 appCtxt.composeCtlrSessionId = null; 275 // depending on the command, do the right thing 276 if (cmd == "compose" || cmd == "composeDetach") { 277 var cc = AjxDispatcher.run("GetComposeController"); // get a new compose ctlr 278 appCtxt.composeCtlrSessionId = cc.getSessionId(); 279 if (params.action == ZmOperation.REPLY_ALL) { 280 params.msg = this._deepCopyMsg(params.msg); 281 } 282 if (cmd == "compose") { 283 cc._setView(params); 284 } else { 285 AjxDispatcher.require(["MailCore", "ContactsCore", "CalendarCore"]); 286 var op = params.action || ZmOperation.NEW_MESSAGE; 287 if (params.msg && params.msg._mode) { 288 switch (params.msg._mode) { 289 case ZmAppt.MODE_DELETE: 290 case ZmAppt.MODE_DELETE_INSTANCE: 291 case ZmAppt.MODE_DELETE_SERIES: { 292 op = ZmOperation.REPLY_CANCEL; 293 break; 294 } 295 } 296 } 297 params.action = op; 298 cc._setView(params); 299 cc._composeView.setDetach(params); 300 301 // bug fix #5887 - get the parent window's compose controller based on its session ID 302 var parentCC = window.parentController.getApp(ZmApp.MAIL).getComposeController(params.sessionId); 303 if (parentCC && parentCC._composeView) { 304 // once everything is set in child window, pop parent window's compose view 305 parentCC._composeView.reset(true); 306 parentCC._app.popView(true); 307 } 308 } 309 cc._setComposeTabGroup(); 310 rootTg.addMember(cc.getTabGroup()); 311 startupFocusItem = cc._getDefaultFocusItem(); 312 313 target = "compose-window"; 314 } else if (cmd == "msgViewDetach") { 315 //bug 52366 - not sure why only REPLY_ALL causes the problem (and not REPLY for example), but in this case the window is opened first for view. But 316 //the user might of course click "reply to all" later in the window so I deep copy here in any case. 317 var msg = this._deepCopyMsg(params.msg); 318 msg.isRfc822 = params.isRfc822; //simpler 319 params.msg.addChangeListener(msg.detachedChangeListener.bind(msg)); 320 321 var msgController = AjxDispatcher.run("GetMsgController"); 322 appCtxt.msgCtlrSessionId = msgController.getSessionId(); 323 msgController.show(msg, params.parentController); 324 rootTg.addMember(msgController.getTabGroup()); 325 startupFocusItem = msgController.getCurrentView(); 326 327 target = "view-window"; 328 } else if (cmd == 'documentEdit') { 329 AjxDispatcher.require(["Docs"]); 330 ZmDocsEditApp.setFile(params.id, params.name, params.folderId); 331 ZmDocsEditApp.restUrl = params.restUrl; 332 new ZmDocsEditApp(); 333 if (params.name) { 334 Dwt.setTitle(params.name); 335 } 336 } else if (cmd == "shortcuts") { 337 var panel = appCtxt.getShortcutsPanel(); 338 panel.popup(params.cols); 339 } 340 341 if (this._appViewMgr.loadingView) { 342 this._appViewMgr.loadingView.setVisible(false); 343 } 344 345 this._kbMgr.setTabGroup(rootTg); 346 this._kbMgr.grabFocus(startupFocusItem); 347 }; 348 349 /** 350 * HACK: This should go away once we have a cleaner server solution that 351 * allows us to get just those zimlets for the specified target. 352 * 353 * @private 354 */ 355 ZmNewWindow.prototype._hasZimletsForTarget = 356 function(zimletArray, target) { 357 var targetRe = new RegExp("\\b"+target+"\\b"); 358 for (var i=0; i < zimletArray.length; i++) { 359 var zimletObj = zimletArray[i]; 360 var zimlet0 = zimletObj.zimlet[0]; 361 if (targetRe.test(zimlet0.target || "main")) { 362 return true; 363 } 364 } 365 return false; 366 }; 367 368 /** 369 * @private 370 */ 371 ZmNewWindow.prototype._getUserProps = 372 function() { 373 var userPropsArray = parentAppCtxt.get(ZmSetting.USER_PROPS); 374 375 // default to original user props 376 userPropsArray = userPropsArray ? [].concat(userPropsArray) : []; 377 378 // current user props take precedence, if available 379 var zimletHash = parentAppCtxt.getZimletMgr().getZimletsHash(); 380 var zimletArray = parentAppCtxt.get(ZmSetting.ZIMLETS); 381 for (var i = 0; i < zimletArray.length; i++) { 382 var zname = zimletArray[i].zimlet[0].name; 383 var zimlet = zimletHash[zname]; 384 if (!zimlet || !zimlet.userProperties) continue; 385 for (var j = 0; j < zimlet.userProperties.length; j++) { 386 var userProp = zimlet.userProperties[j]; 387 var userPropObj = { zimlet: zname, name: userProp.name, _content: userProp.value }; 388 userPropsArray.push(userPropObj); 389 } 390 } 391 392 // return user properties 393 return userPropsArray; 394 }; 395 396 /** 397 * Cancels the request. 398 * 399 * @param {String} reqId the request id 400 * @param {AjxCallback} errorCallback the callback 401 * @param {Boolean} noBusyOverlay if <code>true</code>, do not show a busy overlay 402 */ 403 ZmNewWindow.prototype.cancelRequest = 404 function(reqId, errorCallback, noBusyOverlay) { 405 return window.parentController ? window.parentController.cancelRequest(reqId, errorCallback, noBusyOverlay) : null; 406 }; 407 408 /** 409 * Sends the server requests to the main controller. 410 * 411 * @param {Hash} params a hash of parameters 412 */ 413 ZmNewWindow.prototype.sendRequest = 414 function(params) { 415 // reset onbeforeunload on send 416 window.onbeforeunload = null; 417 // bypass error callback to get control over exceptions in the childwindow. 418 params.errorCallback = new AjxCallback(this, this._handleException, [( params.errorCallback || null )]); 419 params.fromChildWindow = true; 420 return window.parentController ? window.parentController.sendRequest(params) : null; 421 }; 422 423 /** 424 * @private 425 */ 426 ZmNewWindow.prototype._handleException = 427 function(errCallback, ex) { 428 var handled = false; 429 if (errCallback) { 430 handled = errCallback.run(ex); 431 } 432 if (!handled) { 433 ZmController.prototype._handleException.apply(this, [ex]); 434 } 435 return true; 436 }; 437 438 /** 439 * Popup the error dialog. 440 * 441 * @param {String} msg the message 442 * @param {AjxException} ex the exception 443 * @param {Boolean} noExecReset 444 * @param {Boolean} hideReportButton 445 */ 446 ZmNewWindow.prototype.popupErrorDialog = 447 function(msg, ex, noExecReset, hideReportButton) { 448 // Since ex is from parent window, all the types seems like objects, so need 449 // to filter the functions 450 var detailStr; 451 if (ex instanceof Object || typeof ex == "object") { 452 var details = []; 453 ex.msg = ex.msg || msg; 454 for (var prop in ex) { 455 if (typeof ex[prop] == "function" || 456 (typeof ex[prop] == "object" && ex[prop].apply && ex[prop].call)) 457 { 458 continue; 459 } 460 details.push([prop, ": ", ex[prop], "<br/>\n"].join("")); 461 } 462 detailStr = details.join(""); 463 } 464 ZmController.prototype.popupErrorDialog.call(this, msg, ( detailStr || ex ), noExecReset, hideReportButton); 465 }; 466 467 /** 468 * Set status messages via the main controller, so they show up in the client's status area. 469 * 470 * @param {Hash} params a hash of parameters 471 */ 472 ZmNewWindow.prototype.setStatusMsg = 473 function(params) { 474 // bug: 26478. Changed status msg to be displayed within the child window. 475 params = Dwt.getParams(arguments, ZmStatusView.MSG_PARAMS); 476 this._statusView.setStatusMsg(params); 477 }; 478 479 /** 480 * Gets a handle to the given app. 481 * 482 * @param {String} appName the app name 483 * @return {ZmApp} the application 484 */ 485 ZmNewWindow.prototype.getApp = 486 function(appName) { 487 if (!this._apps[appName]) { 488 this._createApp(appName); 489 } 490 return this._apps[appName]; 491 }; 492 493 /** 494 * Gets a handle to the app view manager. 495 * 496 * @return {ZmAppViewMgr} the view manager 497 */ 498 ZmNewWindow.prototype.getAppViewMgr = 499 function() { 500 return this._appViewMgr; 501 }; 502 503 // App view mgr calls this, we don't need it to do anything. 504 ZmNewWindow.prototype.setActiveApp = function() {}; 505 506 /** 507 * Gets the key map manager. 508 * 509 * @return {DwtKeyMapMgr} the key map manager 510 */ 511 ZmNewWindow.prototype.getKeyMapMgr = 512 function() { 513 return this._kbMgr.__keyMapMgr; 514 }; 515 516 /** 517 * Gets the key map name. 518 * 519 * @return {String} the key map name 520 */ 521 ZmNewWindow.prototype.getKeyMapName = 522 function() { 523 var ctlr = appCtxt.getCurrentController(); 524 if (ctlr && ctlr.getKeyMapName) { 525 return ctlr.getKeyMapName(); 526 } 527 return ZmKeyMap.MAP_GLOBAL; 528 }; 529 530 /** 531 * Handles the key action. 532 * 533 * @param {Object} actionCode the action code 534 * @param {Object} ev the event 535 * @return {Boolean} <code>true</code> if the action is handled 536 */ 537 ZmNewWindow.prototype.handleKeyAction = function(actionCode, ev) { 538 539 // Ignore global shortcuts since they don't make sense in a child window 540 if (ZmApp.GOTO_ACTION_CODE_R[actionCode]) { 541 return false; 542 } 543 switch (actionCode) { 544 case ZmKeyMap.QUICK_REMINDER: 545 case ZmKeyMap.FOCUS_SEARCH_BOX: 546 case ZmKeyMap.FOCUS_CONTENT_PANE: 547 case ZmKeyMap.FOCUS_TOOLBAR: 548 case ZmKeyMap.SHORTCUTS: 549 return false; 550 } 551 552 // Hand shortcut to current controller 553 var ctlr = appCtxt.getCurrentController(); 554 if (ctlr && ctlr.handleKeyAction) { 555 return ctlr.handleKeyAction(actionCode, ev); 556 } 557 558 return false; 559 }; 560 561 562 // Private methods 563 564 /** 565 * Instantiates enabled apps. An optional argument may be given limiting the set 566 * of apps that may be created. 567 * 568 * @param {Hash} apps the set of apps to create 569 * 570 * @private 571 */ 572 ZmNewWindow.prototype._createEnabledApps = 573 function(apps) { 574 for (var app in ZmApp.CLASS) { 575 if (!apps || apps[app]) { 576 ZmApp.APPS.push(app); 577 } 578 } 579 ZmApp.APPS.sort(function(a, b) { 580 return ZmZimbraMail.hashSortCompare(ZmApp.LOAD_SORT, a, b); 581 }); 582 583 // instantiate enabled apps - this will invoke app registration 584 for (var i = 0; i < ZmApp.APPS.length; i++) { 585 var app = ZmApp.APPS[i]; 586 if (app != ZmApp.IM) { // Don't create im app. Seems like the safest way to avoid ever logging in. 587 var setting = ZmApp.SETTING[app]; 588 if (!setting || appCtxt.get(setting)) { 589 this._createApp(app); 590 } 591 } 592 } 593 }; 594 595 /** 596 * Creates an app object, which doesn't necessarily do anything just yet. 597 * 598 * @private 599 */ 600 ZmNewWindow.prototype._createApp = 601 function(appName) { 602 if (this._apps[appName]) return; 603 var appClass = eval(ZmApp.CLASS[appName]); 604 this._apps[appName] = appClass && new appClass(this._shell, window.parentController); 605 }; 606 607 /** 608 * @private 609 * TODO: get rid of this function 610 */ 611 ZmNewWindow.prototype._deepCopyMsg = 612 function(msg) { 613 // initialize new ZmSearch if applicable 614 var newSearch = null; 615 var oldSearch = msg.list.search; 616 617 if (oldSearch) { 618 newSearch = new ZmSearch(); 619 620 for (var i in oldSearch) { 621 if ((typeof oldSearch[i] == "object") || (typeof oldSearch[i] == "function")) { continue; } 622 newSearch[i] = oldSearch[i]; 623 } 624 625 // manually add objects since they are no longer recognizable 626 newSearch.types = new AjxVector(); 627 var types = oldSearch.types.getArray(); 628 for (var i = 0; i < types.length; i++) { 629 newSearch.types.add(types[i]); 630 } 631 } 632 633 // initialize new ZmMailList 634 var newMailList = new ZmMailList(msg.list.type, newSearch); 635 for (var i in msg.list) { 636 if ((typeof msg.list[i] == "object") || (typeof msg.list[i] == "function")) { continue; } 637 newMailList[i] = msg.list[i]; 638 } 639 640 // finally, initialize new ZmMailMsg 641 var newMsg = new ZmMailMsg(msg.id, newMailList); 642 643 for (var i in msg) { 644 if ((typeof msg[i] == "object") || (typeof msg[i] == "function")) { continue; } 645 newMsg[i] = msg[i]; 646 } 647 648 // manually add any objects since they are no longer recognizable 649 for (var i in msg._addrs) { 650 var addrs = msg._addrs[i].getArray(); 651 for (var j = 0; j < addrs.length; j++) { 652 newMsg._addrs[i].add(addrs[j]); 653 } 654 } 655 656 if (msg.attachments && msg.attachments.length > 0) { 657 for (var i = 0; i < msg.attachments.length; i++) { 658 newMsg.attachments.push(msg.attachments[i]); 659 } 660 } 661 662 for (var i = 0; i < msg._bodyParts.length; i++) { 663 newMsg._bodyParts.push(msg._bodyParts[i]); 664 } 665 666 for (var ct in msg._contentType) { 667 newMsg._contentType[ct] = true; 668 } 669 670 if (msg._topPart) { 671 newMsg._topPart = new ZmMimePart(); 672 for (var i in msg._topPart) { 673 if ((typeof msg._topPart[i] == "object") || (typeof msg._topPart[i] == "function")) 674 continue; 675 newMsg._topPart[i] = msg._topPart[i]; 676 } 677 var children = msg._topPart.children.getArray(); 678 for (var i = 0; i < children.length; i++) { 679 newMsg._topPart.children.add(children[i]); 680 } 681 } 682 683 if (msg.invite) { 684 newMsg.invite = msg.invite; 685 } 686 687 if (msg.share) { 688 newMsg.share = msg.share; 689 } 690 691 newMsg.subscribeReq = msg.subscribeReq; 692 693 // TODO: When/if you get rid of this function, also remove the cloneOf uses in: 694 // ZmBaseController.prototype._doTag 695 // ZmBaseController.prototype._setTagMenu 696 // ZmMailMsgView.prototype._setTags 697 // ZmMailMsgView.prototype._handleResponseSet 698 // ZmMailListController.prototype._handleResponseFilterListener 699 // ZmMailListController.prototype._handleResponseNewApptListener 700 // ZmMailListController.prototype._handleResponseNewTaskListener 701 newMsg.cloneOf = msg; 702 703 return newMsg; 704 }; 705 706 707 // Static Methods 708 709 /** 710 * @private 711 */ 712 ZmNewWindow._confirmExitMethod = function(ev) { 713 714 if (!appCtxt.get(ZmSetting.WARN_ON_EXIT) || !window.parentController) { 715 return; 716 } 717 718 var cmd = window.newWindowCommand; 719 720 if (cmd === "compose" || cmd === "composeDetach") { 721 var cc = AjxDispatcher.run("GetComposeController", appCtxt.composeCtlrSessionId), 722 cv = cc && cc._composeView, 723 viewId = cc.getCurrentViewId(), 724 avm = appCtxt.getAppViewMgr(); 725 726 // only show native confirmation dialog if compose view is dirty 727 if (cv && avm.isVisible(viewId) && cv.isDirty()) { 728 return ZmMsg.newWinComposeExit; 729 } 730 } else if (cmd == 'documentEdit') { 731 var ctrl = ZmDocsEditApp._controller; 732 var msg = ctrl.checkForChanges(); 733 return msg || ctrl.exit(); 734 } 735 }; 736