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 * Creates a new appointment controller to manage appointment creation/editing. 26 * @constructor 27 * @class 28 * This class manages appointment creation/editing. 29 * 30 * @author Parag Shah 31 * 32 * @param {DwtShell} container the containing shell 33 * @param {ZmApp} app the containing app 34 * @param {constant} type controller type 35 * @param {string} sessionId the session id 36 * 37 * @extends ZmCalItemComposeController 38 */ 39 ZmApptComposeController = function(container, app, type, sessionId) { 40 if (arguments.length == 0) { return; } 41 42 ZmCalItemComposeController.apply(this, arguments); 43 44 this._addedAttendees = []; 45 this._removedAttendees = []; 46 this._kbMgr = appCtxt.getKeyboardMgr(); 47 }; 48 49 ZmApptComposeController.prototype = new ZmCalItemComposeController; 50 ZmApptComposeController.prototype.constructor = ZmApptComposeController; 51 52 ZmApptComposeController.prototype.isZmApptComposeController = true; 53 ZmApptComposeController.prototype.toString = function() { return "ZmApptComposeController"; }; 54 55 ZmApptComposeController._VALUE = "value"; 56 57 ZmApptComposeController._DIALOG_OPTIONS = { 58 SEND: 'SEND', 59 CANCEL: 'CANCEL', 60 DISCARD: 'DISCARD' 61 }; 62 63 // Public methods 64 65 ZmApptComposeController.getDefaultViewType = 66 function() { 67 return ZmId.VIEW_APPOINTMENT; 68 }; 69 ZmApptComposeController.prototype.getDefaultViewType = ZmApptComposeController.getDefaultViewType; 70 71 ZmApptComposeController.prototype.show = 72 function(calItem, mode, isDirty) { 73 ZmCalItemComposeController.prototype.show.call(this, calItem, mode, isDirty); 74 75 this._addedAttendees.length = this._removedAttendees.length = 0; 76 this._setComposeTabGroup(); 77 }; 78 79 /** 80 * Forwards the calendar item. 81 * 82 * @param {ZmAppt} appt the appointment 83 * @return {Boolean} <code>true</code> indicates the forward is executed 84 */ 85 ZmApptComposeController.prototype.forwardCalItem = 86 function(appt, forwardCallback) { 87 // todo: to address input validation 88 var callback = new AjxCallback(this, this._handleForwardInvite, forwardCallback); 89 appt.forward(callback); 90 return true; 91 }; 92 93 /** 94 * Propose new time for an appointment 95 * 96 * @param {ZmAppt} appt the appointment 97 * @param {AjxCallback} proposeTimeCallback callback executed after proposing time 98 * @return {Boolean} <code>true</code> indicates that propose time is executed 99 */ 100 ZmApptComposeController.prototype.sendCounterAppointmentRequest = 101 function(appt, proposeTimeCallback) { 102 var callback = new AjxCallback(this, this._handleCounterAppointmentRequest, proposeTimeCallback); 103 var apptEditView = this._composeView ? this._composeView.getApptEditView() : null; 104 var viewMode = apptEditView ? apptEditView.getMode() : null; 105 appt.sendCounterAppointmentRequest(callback, null, viewMode); 106 return true; 107 }; 108 109 ZmApptComposeController.prototype._handleCounterAppointmentRequest = 110 function(proposeTimeCallback) { 111 appCtxt.setStatusMsg(ZmMsg.newTimeProposed); 112 if (proposeTimeCallback instanceof AjxCallback) { 113 proposeTimeCallback.run(); 114 } 115 }; 116 117 ZmApptComposeController.prototype._handleForwardInvite = 118 function(forwardCallback) { 119 appCtxt.setStatusMsg(ZmMsg.forwardInviteSent); 120 if (forwardCallback instanceof AjxCallback) { 121 forwardCallback.run(); 122 } 123 }; 124 125 ZmApptComposeController.prototype._badAddrsOkCallback = 126 function(dialog, appt) { 127 dialog.popdown(); 128 this.forwardCalItem(appt, new AjxCallback(this, this._apptForwardCallback)); 129 }; 130 131 ZmApptComposeController.prototype._apptForwardCallback = 132 function() { 133 this.closeView(); 134 }; 135 136 ZmApptComposeController.prototype._checkIsDirty = 137 function(type, attribs){ 138 return this._composeView.checkIsDirty(type, attribs) 139 }; 140 141 ZmApptComposeController.prototype._getChangesDialog = 142 function(){ 143 var id, 144 dlg, 145 isOrganizer = this._composeView.isOrganizer(); 146 if(isOrganizer) { 147 dlg = this._changesDialog; 148 if (!dlg) { 149 dlg = this._changesDialog = new DwtOptionDialog({ 150 parent: appCtxt.getShell(), 151 id: Dwt.getNextId("CHNG_DLG_ORG_"), 152 title: ZmMsg.apptSave, 153 message: ZmMsg.apptSignificantChanges, 154 options: [ 155 { 156 name: ZmApptComposeController._DIALOG_OPTIONS.SEND, 157 text: ZmMsg.apptSaveChanges 158 }, 159 { 160 name: ZmApptComposeController._DIALOG_OPTIONS.CANCEL, 161 text: ZmMsg.apptSaveCancel 162 }, 163 { 164 name: ZmApptComposeController._DIALOG_OPTIONS.DISCARD, 165 text: ZmMsg.apptSaveDiscard 166 } 167 ] 168 }); 169 dlg.registerCallback(DwtDialog.OK_BUTTON, 170 this._changesDialogListener.bind(this)); 171 } 172 } 173 else { 174 dlg = this._attendeeChangesDialog; 175 if (!dlg) { 176 dlg = this._attendeeChangesDialog = new DwtDialog({parent:appCtxt.getShell(), id:Dwt.getNextId("CHNG_DLG_ATTNDE_")}); 177 id = this._attendeeChangesDialogId = Dwt.getNextId(); 178 dlg.setContent(AjxTemplate.expand("calendar.Appointment#ChangesDialogAttendee", {id: id})); 179 dlg.setTitle(ZmMsg.apptSave); 180 dlg.setButtonListener(DwtDialog.OK_BUTTON, new AjxListener(this, this._attendeeChangesDialogListener, id)); 181 } 182 } 183 return dlg; 184 }; 185 186 ZmApptComposeController.prototype._changesDialogListener = 187 function(){ 188 189 this.clearInvalidAttendees(); 190 delete this._invalidAttendees; 191 192 switch (this._changesDialog.getSelection()) { 193 case ZmApptComposeController._DIALOG_OPTIONS.SEND: 194 this._sendListener(); 195 break; 196 197 case ZmApptComposeController._DIALOG_OPTIONS.CANCEL: 198 break; 199 200 case ZmApptComposeController._DIALOG_OPTIONS.DISCARD: 201 this.closeView(); 202 break; 203 } 204 205 this._changesDialog.popdown(); 206 }; 207 208 ZmApptComposeController.prototype._attendeeChangesDialogListener = 209 function(id){ 210 this.clearInvalidAttendees(); 211 delete this._invalidAttendees; 212 this.closeView(); 213 this._attendeeChangesDialog.popdown(); 214 }; 215 216 ZmApptComposeController.prototype.saveCalItem = 217 function(attId) { 218 this._composeView.cancelLocationRequest(); 219 var appt = this._composeView.getAppt(attId); 220 var numRecurrence = this._composeView.getNumLocationConflictRecurrence ? 221 this._composeView.getNumLocationConflictRecurrence() : 222 ZmTimeSuggestionPrefDialog.DEFAULT_NUM_RECURRENCE; 223 224 if (appt) { 225 226 if (!appt.isValidDuration()) { 227 this._composeView.showInvalidDurationMsg(); 228 this.enableToolbar(true); 229 return false; 230 } 231 if (!appt.isValidDurationRecurrence()) { 232 this._composeView.showInvalidDurationRecurrenceMsg(); 233 this.enableToolbar(true); 234 return false; 235 } 236 237 if (appCtxt.get(ZmSetting.GROUP_CALENDAR_ENABLED)) { 238 if (this._requestResponses) 239 appt.setRsvp(this._requestResponses.getChecked()); 240 appt.setMailNotificationOption(true); 241 } 242 243 if(appt.isProposeTime && !appt.isOrganizer()) { 244 return this.sendCounterAppointmentRequest(appt); 245 } 246 247 if (appt.isForward) { 248 var addrs = this._composeView.getForwardAddress(); 249 250 // validate empty forward address 251 if (!addrs.gotAddress) { 252 var msgDialog = appCtxt.getMsgDialog(); 253 msgDialog.setMessage(ZmMsg.noForwardAddresses, DwtMessageDialog.CRITICAL_STYLE); 254 msgDialog.popup(); 255 this.enableToolbar(true); 256 return false; 257 } 258 259 if (addrs[ZmApptEditView.BAD] && addrs[ZmApptEditView.BAD].size()) { 260 var cd = appCtxt.getOkCancelMsgDialog(); 261 cd.reset(); 262 var bad = AjxStringUtil.htmlEncode(addrs[ZmApptEditView.BAD].toString(AjxEmailAddress.SEPARATOR)); 263 var msg = AjxMessageFormat.format(ZmMsg.compBadAddresses, bad); 264 cd.setMessage(msg, DwtMessageDialog.WARNING_STYLE); 265 cd.registerCallback(DwtDialog.OK_BUTTON, this._badAddrsOkCallback, this, [cd,appt]); 266 cd.setVisible(true); // per fix for bug 3209 267 cd.popup(); 268 this.enableToolbar(true); 269 return false; 270 } 271 272 //attendee forwarding an appt 273 /* if(!appt.isOrganizer()) */ return this.forwardCalItem(appt); 274 } 275 276 if (!this._attendeeValidated && this._invalidAttendees && this._invalidAttendees.length > 0) { 277 var dlg = appCtxt.getYesNoMsgDialog(); 278 dlg.registerCallback(DwtDialog.YES_BUTTON, this._clearInvalidAttendeesCallback, this, [appt, attId, dlg]); 279 var msg = ""; 280 if(this._action == ZmCalItemComposeController.SAVE){ 281 msg = AjxMessageFormat.format(ZmMsg.compSaveBadAttendees, AjxStringUtil.htmlEncode(this._invalidAttendees.join(","))); 282 } 283 else{ 284 msg = AjxMessageFormat.format(ZmMsg.compBadAttendees, AjxStringUtil.htmlEncode(this._invalidAttendees.join(","))); 285 } 286 dlg.setMessage(msg, DwtMessageDialog.WARNING_STYLE); 287 dlg.popup(); 288 this.enableToolbar(true); 289 this._attendeeValidated = true; 290 return false; 291 } 292 293 //Validation Check for Significant / Insignificant / Local changes 294 if(this._action == ZmCalItemComposeController.SAVE && !appt.inviteNeverSent){ 295 //Check for Significant Changes 296 if(this._checkIsDirty(ZmApptEditView.CHANGES_SIGNIFICANT)){ 297 this._getChangesDialog().popup(); 298 this.enableToolbar(true); 299 return false; 300 } 301 } 302 303 var origAttendees = appt.origAttendees; // bug fix #4160 304 if (origAttendees && origAttendees.length > 0 && // make sure we're not u/l'ing a file 305 attId == null) // make sure we are editing an existing appt w/ attendees 306 { 307 if (!appt.inviteNeverSent && !this._composeView.getApptEditView().isDirty(true)) { // make sure other fields (besides attendees field) have not changed 308 var attendees = appt.getAttendees(ZmCalBaseItem.PERSON); 309 if (attendees.length > 0) { 310 // check whether organizer has added/removed any attendees 311 if (this._action == ZmCalItemComposeController.SEND && this._attendeesUpdated(appt, attId, attendees, origAttendees)) 312 return false; 313 } 314 } 315 316 // check whether moving appt from local to remote folder with attendees 317 var cc = AjxDispatcher.run("GetCalController"); 318 if (cc.isMovingBetwAccounts(appt, appt.__newFolderId)) { 319 var dlg = appCtxt.getMsgDialog(); 320 dlg.setMessage(ZmMsg.orgChange, DwtMessageDialog.WARNING_STYLE); 321 dlg.popup(); 322 this.enableToolbar(true); 323 return false; 324 } 325 } 326 327 var ret = this._initiateSaveWithChecks(appt, attId, numRecurrence); 328 return ret; 329 } 330 331 return false; 332 }; 333 334 ZmApptComposeController.prototype._initiateSaveWithChecks = 335 function(appt, attId, numRecurrence) { 336 var resources = appt.getAttendees(ZmCalBaseItem.EQUIPMENT); 337 var locations = appt.getAttendees(ZmCalBaseItem.LOCATION); 338 var attendees = appt.getAttendees(ZmCalBaseItem.PERSON); 339 340 var notifyList; 341 342 var needsPermissionCheck = (attendees && attendees.length > 0) || 343 (resources && resources.length > 0) || 344 (locations && locations.length > 0); 345 346 var needsConflictCheck = !appt.isForward && 347 ((resources && resources.length > 0) || 348 // If alteredLocations specified, it implies the user 349 // has already examined and modified the location conflicts 350 // that they want - so issue no further warnings. 351 352 // NOTE: appt.alteredLocations is disabled (and hence undefined) 353 // for now. It will be set once CreateAppt/ModifyAppt 354 // SOAP API changes are completed (Bug 56464) 355 (!appt.alteredLocations && locations && locations.length > 0)); 356 357 if (needsConflictCheck) { 358 this.checkConflicts(appt, numRecurrence, attId, notifyList); 359 return false; 360 } else if (needsPermissionCheck) { 361 this.checkAttendeePermissions(appt, attId, notifyList); 362 return false; 363 } else { 364 this._saveCalItemFoRealz(appt, attId, notifyList); 365 } 366 return true; 367 }; 368 369 ZmApptComposeController.prototype.updateToolbarOps = 370 function(mode, appt) { 371 372 var saveButton = this._toolbar.getButton(ZmOperation.SAVE); 373 var sendButton = this._toolbar.getButton(ZmOperation.SEND_INVITE); 374 375 if (mode == ZmCalItemComposeController.APPT_MODE) { 376 saveButton.setText(ZmMsg.saveClose); 377 saveButton.setVisible(true); 378 sendButton.setVisible(false); 379 } else { 380 sendButton.setVisible(true); 381 saveButton.setVisible(true); 382 saveButton.setText(ZmMsg.save); 383 384 //change cancel button's text/icon to close 385 var cancelButton = this._toolbar.getButton(ZmOperation.CANCEL); 386 cancelButton.setText(ZmMsg.close); 387 } 388 if (this._requestResponses) { 389 this._requestResponses.setEnabled(mode !== ZmCalItemComposeController.APPT_MODE); 390 } 391 392 if ((this._mode == ZmCalItem.MODE_PROPOSE_TIME) || ZmCalItem.FORWARD_MAPPING[this._mode]) { 393 sendButton.setVisible(true); 394 saveButton.setVisible(false); 395 // Enable the RequestResponse when Forwarding 396 if (this._requestResponses) { 397 this._requestResponses.setEnabled(this._mode !== ZmCalItem.MODE_PROPOSE_TIME); 398 } 399 } 400 401 }; 402 403 ZmApptComposeController.prototype._initToolbar = 404 function(mode) { 405 406 ZmCalItemComposeController.prototype._initToolbar.call(this, mode); 407 408 //use send button for forward appt view 409 //Switch Save Btn label n listeners 410 var saveButton = this._toolbar.getButton(ZmOperation.SAVE); 411 saveButton.removeSelectionListeners(); 412 if(ZmCalItem.FORWARD_MAPPING[mode]) { 413 saveButton.addSelectionListener(new AjxListener(this, this._sendBtnListener)); 414 }else { 415 saveButton.addSelectionListener(new AjxListener(this, this._saveBtnListener)); 416 } 417 418 var sendButton = this._toolbar.getButton(ZmOperation.SEND_INVITE); 419 sendButton.removeSelectionListeners(); 420 sendButton.addSelectionListener(new AjxListener(this, this._sendBtnListener)); 421 422 var saveButton = this._toolbar.getButton(ZmOperation.SAVE); 423 saveButton.setToolTipContent(ZmMsg.saveToCalendar); 424 425 var btn = this._toolbar.getButton(ZmOperation.ATTACHMENT); 426 if(btn) 427 btn.setEnabled(!(this._mode == ZmCalItem.MODE_PROPOSE_TIME || ZmCalItem.FORWARD_MAPPING[mode])); 428 }; 429 430 ZmApptComposeController.prototype._sendListener = 431 function(ev){ 432 433 var appt = this._composeView.getApptEditView()._calItem; 434 435 if(!appt.inviteNeverSent){ 436 this._sendAfterExceptionCheck(); 437 } 438 else{this._sendContinue();} 439 440 return true; 441 }; 442 443 ZmApptComposeController.prototype._sendAfterExceptionCheck = 444 function(){ 445 var appt = this._composeView.getApptEditView()._calItem; 446 var isExceptionAllowed = appCtxt.get(ZmSetting.CAL_EXCEPTION_ON_SERIES_TIME_CHANGE); 447 var isEditingSeries = (this._mode == ZmCalItem.MODE_EDIT_SERIES); 448 var showWarning = appt.isRecurring() && appt.hasEx && isEditingSeries && appt.getAttendees(ZmCalBaseItem.PERSON) && !isExceptionAllowed && this._checkIsDirty(ZmApptEditView.CHANGES_TIME_RECURRENCE); 449 if(showWarning){ 450 var dialog = appCtxt.getYesNoCancelMsgDialog(); 451 dialog.setMessage(ZmMsg.recurrenceUpdateWarning, DwtMessageDialog.WARNING_STYLE); 452 dialog.registerCallback(DwtDialog.YES_BUTTON, this._sendContinue, this,[dialog]); 453 dialog.registerCallback(DwtDialog.NO_BUTTON, this._dontSend,this,[dialog]); 454 dialog.getButton(DwtDialog.CANCEL_BUTTON).setText(ZmMsg.discard); 455 dialog.registerCallback(DwtDialog.CANCEL_BUTTON, this._dontSendAndClose,this,[dialog]); 456 dialog.popup(); 457 } 458 else{ 459 this._sendContinue(); 460 } 461 } 462 463 ZmApptComposeController.prototype._dontSend = 464 function(dialog){ 465 this._revertWarningDialog(dialog); 466 } 467 468 ZmApptComposeController.prototype._dontSendAndClose = 469 function(dialog){ 470 this._revertWarningDialog(dialog); 471 this.closeView(); 472 } 473 474 ZmApptComposeController.prototype._revertWarningDialog = 475 function(dialog){ 476 if(dialog){ 477 dialog.popdown(); 478 dialog.getButton(DwtDialog.CANCEL_BUTTON).setText(ZmMsg.cancel); 479 } 480 } 481 482 ZmApptComposeController.prototype._sendContinue = 483 function(dialog){ 484 this._revertWarningDialog(dialog); 485 this._action = ZmCalItemComposeController.SEND; 486 this.enableToolbar(false); 487 if (this._doSave() === false) { 488 return; 489 } 490 this.closeView(); 491 } 492 493 ZmApptComposeController.prototype.isSave = 494 function(){ 495 return (this._action == ZmCalItemComposeController.SAVE); 496 }; 497 498 ZmApptComposeController.prototype._saveBtnListener = 499 function(ev) { 500 delete this._attendeeValidated; 501 return this._saveListener(ev, true); 502 }; 503 504 ZmApptComposeController.prototype._sendBtnListener = 505 function(ev) { 506 delete this._attendeeValidated; 507 return this._sendListener(ev); 508 }; 509 510 ZmApptComposeController.prototype._saveListener = 511 function(ev, force) { 512 var isMeeting = !this._composeView.isAttendeesEmpty(); 513 514 this._action = isMeeting ? ZmCalItemComposeController.SAVE : ZmCalItemComposeController.SAVE_CLOSE; 515 516 //attendee should not have send/save option 517 if(!this._composeView.isOrganizer()) { 518 this._action = ZmCalItemComposeController.SAVE_CLOSE; 519 } 520 this.enableToolbar(false); 521 522 var dlg = appCtxt.getOkCancelMsgDialog(); 523 if(dlg.isPoppedUp()){ 524 dlg.popdown(); 525 } 526 527 if(!force && this._action == ZmCalItemComposeController.SAVE){ 528 var appt = this._composeView.getApptEditView()._calItem; 529 var inviteNeverSent = (appt && appt.inviteNeverSent); 530 var showDlg = true; 531 if(appt.isDraft){ 532 showDlg = false; 533 } 534 if(showDlg && !inviteNeverSent && (this._checkIsDirty(ZmApptEditView.CHANGES_SIGNIFICANT) 535 || this._checkIsDirty(ZmApptEditView.CHANGES_LOCAL))){ 536 showDlg = false; 537 } 538 if(showDlg){ 539 dlg.setMessage(ZmMsg.saveApptInfoMsg); 540 dlg.setButtonListener(DwtDialog.OK_BUTTON, new AjxListener(this, this._saveListener, [ev, true])); 541 dlg.popup(); 542 this.enableToolbar(true); 543 return; 544 } 545 } 546 547 if (this._doSave() === false) { 548 return; 549 } 550 }; 551 552 ZmApptComposeController.prototype._createToolBar = 553 function() { 554 555 ZmCalItemComposeController.prototype._createToolBar.call(this); 556 557 var optionsButton = this._toolbar.getButton(ZmOperation.COMPOSE_OPTIONS); 558 if (optionsButton){ 559 optionsButton.setVisible(true); //might be invisible if not ZmSetting.HTML_COMPOSE_ENABLED (see ZmCalItemComposeController._createToolBar) 560 561 var m = optionsButton.getMenu(); 562 if (m) { 563 var sepMi = new DwtMenuItem({parent:m, style:DwtMenuItem.SEPARATOR_STYLE}); 564 } 565 else { 566 m = new DwtMenu({parent:optionsButton}); 567 optionsButton.setMenu(m); 568 } 569 570 var mi = this._requestResponses = new DwtMenuItem({parent:m, style:DwtMenuItem.CHECK_STYLE}); 571 mi.setText(ZmMsg.requestResponses); 572 mi.setChecked(true, true); 573 574 sepMi = new DwtMenuItem({parent:m, style:DwtMenuItem.SEPARATOR_STYLE}); 575 mi = new DwtMenuItem({parent:m, style:DwtMenuItem.NO_STYLE}); 576 mi.setText(ZmMsg.suggestionPreferences); 577 mi.addSelectionListener(this._prefListener.bind(this)); 578 } 579 580 this._toolbar.addSelectionListener(ZmOperation.SPELL_CHECK, new AjxListener(this, this._spellCheckListener)); 581 }; 582 583 ZmApptComposeController.prototype._prefListener = 584 function(ev) { 585 this._prefDialog = appCtxt.getSuggestionPreferenceDialog(); 586 this._prefDialog.popup(this.getCalendarAccount()); 587 }; 588 589 ZmApptComposeController.prototype.setRequestResponsesEnabled = 590 function(enabled) { 591 if (this._requestResponses) 592 this._requestResponses.setEnabled(enabled); 593 }; 594 595 ZmApptComposeController.prototype.setRequestResponses = 596 function(requestResponses) { 597 if (this._requestResponses) 598 this._requestResponses.setChecked(requestResponses); 599 }; 600 601 ZmApptComposeController.prototype.getRequestResponses = 602 function() { 603 if (this._requestResponses) 604 return this._requestResponses.getEnabled() ? this._requestResponses.getChecked() : true; 605 }; 606 607 ZmApptComposeController.prototype.getNotifyList = 608 function(addrs) { 609 var notifyList = []; 610 for(var i = 0; i < addrs.length; i++) { 611 notifyList.push(addrs[i]._inviteAddress || addrs[i].address || addrs[i].getEmail()); 612 } 613 614 return notifyList; 615 }; 616 617 ZmApptComposeController.prototype.isAttendeesEmpty = 618 function(appt) { 619 var resources = appt.getAttendees(ZmCalBaseItem.EQUIPMENT); 620 var locations = appt.getAttendees(ZmCalBaseItem.LOCATION); 621 var attendees = appt.getAttendees(ZmCalBaseItem.PERSON); 622 623 var isAttendeesNotEmpty = (attendees && attendees.length > 0) || 624 (resources && resources.length > 0) || 625 (locations && locations.length > 0); 626 return !isAttendeesNotEmpty 627 }; 628 629 ZmApptComposeController.prototype.checkConflicts = 630 function(appt, numRecurrence, attId, notifyList) { 631 var resources = appt.getAttendees(ZmCalBaseItem.EQUIPMENT); 632 var locations = appt.getAttendees(ZmCalBaseItem.LOCATION); 633 var attendees = appt.getAttendees(ZmCalBaseItem.PERSON); 634 635 var needsPermissionCheck = (attendees && attendees.length > 0) || 636 (resources && resources.length > 0) || 637 (locations && locations.length > 0); 638 639 var callback = needsPermissionCheck 640 ? (new AjxCallback(this, this.checkAttendeePermissions, [appt, attId, notifyList])) 641 : (new AjxCallback(this, this.saveCalItemContinue, [appt, attId, notifyList])); 642 643 this._checkResourceConflicts(appt, numRecurrence, callback, false, true, false); 644 }; 645 646 ZmApptComposeController.prototype.checkAttendeePermissions = 647 function(appt, attId, notifyList) { 648 var newEmails = []; 649 650 var attendees = appt.getAttendees(ZmCalBaseItem.PERSON); 651 if (attendees && attendees.length > 0) { 652 for (var i = 0; i < attendees.length; i++) { 653 newEmails.push(attendees[i].getEmail()); 654 } 655 } 656 657 var locations = appt.getAttendees(ZmCalBaseItem.LOCATION); 658 if (locations && locations.length > 0) { 659 for (var i = 0; i < locations.length; i++) { 660 newEmails.push(locations[i].getEmail()); 661 } 662 } 663 664 var resources = appt.getAttendees(ZmCalBaseItem.EQUIPMENT); 665 if (resources && resources.length > 0) { 666 for (var i = 0; i < resources.length; i++) { 667 newEmails.push(resources[i].getEmail()); 668 } 669 } 670 671 if (newEmails.length) { 672 this.checkPermissionRequest(newEmails, appt, attId, notifyList); 673 return false; 674 } 675 676 // otherwise, just save the appointment 677 this._saveCalItemFoRealz(appt, attId, notifyList); 678 }; 679 680 // Expose the resource conflict check call to allow the ApptEditView to 681 // trigger a location conflict check 682 ZmApptComposeController.prototype.getCheckResourceConflicts = 683 function(appt, numRecurrence, callback, displayConflictDialog) { 684 return this.checkResourceConflicts.bind(this, appt, numRecurrence, callback, displayConflictDialog); 685 } 686 687 ZmApptComposeController.prototype.checkResourceConflicts = 688 function(appt, numRecurrence, callback, displayConflictDialog) { 689 return this._checkResourceConflicts(appt, numRecurrence, callback, 690 true, displayConflictDialog, true); 691 }; 692 693 ZmApptComposeController.prototype._checkResourceConflicts = 694 function(appt, numRecurrence, callback, showAll, displayConflictDialog, conflictCallbackOverride) { 695 var mode = appt.viewMode; 696 var reqId; 697 if (mode!=ZmCalItem.MODE_NEW_FROM_QUICKADD && mode!= ZmCalItem.MODE_NEW) { 698 if(appt.isRecurring() && mode != ZmCalItem.MODE_EDIT_SINGLE_INSTANCE) { 699 // for recurring appt - user GetRecurRequest to get full recurrence 700 // information and use the component in CheckRecurConflictRequest 701 var recurInfoCallback = this._checkResourceConflicts.bind(this, 702 appt, numRecurrence, callback, showAll, displayConflictDialog, conflictCallbackOverride); 703 reqId = this.getRecurInfo(appt, recurInfoCallback); 704 } 705 else { 706 reqId = this._checkResourceConflicts(appt, numRecurrence, callback, 707 showAll, displayConflictDialog, conflictCallbackOverride); 708 } 709 } 710 else { 711 reqId = this._checkResourceConflicts(appt, numRecurrence, callback, 712 showAll, displayConflictDialog, conflictCallbackOverride); 713 } 714 return reqId; 715 }; 716 717 /** 718 * JSON request is used to make easy re-use of "comp" elements from GetRecurResponse. 719 * 720 * @private 721 */ 722 ZmApptComposeController.prototype._checkResourceConflicts = 723 function(appt, numRecurrence, callback, showAll, displayConflictDialog, 724 conflictCallbackOverride, recurInfo) { 725 var mode = appt.viewMode, 726 jsonObj = { 727 CheckRecurConflictsRequest: { 728 _jsns:"urn:zimbraMail" 729 } 730 }, 731 request = jsonObj.CheckRecurConflictsRequest, 732 startDate = new Date(appt.startDate), 733 comps = request.comp = [], 734 comp = request.comp[0] = {}, 735 recurrence, 736 recur; 737 738 startDate.setHours(0,0,0,0); 739 request.s = startDate.getTime(); 740 request.e = ZmApptComposeController.getCheckResourceConflictEndTime( 741 appt, startDate, numRecurrence); 742 743 if (showAll) { 744 request.all = "1"; 745 } 746 747 if (mode!=ZmCalItem.MODE_NEW_FROM_QUICKADD && mode!= ZmCalItem.MODE_NEW) { 748 request.excludeUid = appt.uid; 749 } 750 751 752 appt._addDateTimeToRequest(request, comp); 753 754 //preserve the EXDATE (exclude recur) information 755 if(recurInfo) { 756 recurrence = appt.getRecurrence(); 757 recur = (recurInfo && recurInfo.comp) ? recurInfo.comp[0].recur : null; 758 recurrence.parseExcludeInfo(recur); 759 } 760 761 if(mode != ZmCalItem.MODE_EDIT_SINGLE_INSTANCE) { 762 appt._recurrence.setJson(comp); 763 } 764 765 this.setExceptFromRecurInfo(request, recurInfo); 766 767 appt.addAttendeesToChckConflictsRequest(request); 768 769 return appCtxt.getAppController().sendRequest({ 770 jsonObj: jsonObj, 771 asyncMode: true, 772 callback: (new AjxCallback(this, this._handleResourceConflict, [appt, callback, 773 displayConflictDialog, conflictCallbackOverride])), 774 errorCallback: (new AjxCallback(this, this._handleResourceConflictError, [appt, callback])), 775 noBusyOverlay: true 776 }); 777 }; 778 779 ZmApptComposeController.prototype.setExceptFromRecurInfo = 780 function(request, recurInfo) { 781 var exceptInfo = recurInfo && recurInfo.except, 782 i, 783 s, 784 e, 785 exceptId, 786 except, 787 sNode, 788 eNode, 789 exceptIdNode; 790 if (!exceptInfo) { return; } 791 792 for (i in exceptInfo) { 793 s = exceptInfo[i].s ? exceptInfo[i].s[0] : null; 794 e = exceptInfo[i].e ? exceptInfo[i].e[0] : null; 795 exceptId = exceptInfo[i].exceptId ? exceptInfo[i].exceptId[0] : null; 796 797 except = request.except = {}; 798 if (s) { 799 sNode = except.s = {}; 800 sNode.d = s.d; 801 if (s.tz) { 802 sNode.tz = s.tz; 803 } 804 } 805 806 if (e) { 807 eNode = except.e = {}; 808 eNode.d = e.d; 809 if (e.tz) { 810 eNode.tz = e.tz; 811 } 812 } 813 814 if (exceptId) { 815 exceptIdNode = except.exceptId = {}; 816 exceptIdNode.d = exceptId.d; 817 if (exceptId.tz) { 818 exceptIdNode.tz = exceptId.tz; 819 } 820 } 821 } 822 }; 823 824 // Use the (numRecurrences * the recurrence period * repeat.customCount) 825 // time interval to determine the endDate of the resourceConflict check 826 ZmApptComposeController.getCheckResourceConflictEndTime = 827 function(appt, originalStartDate, numRecurrence) { 828 var startDate = new Date(originalStartDate.getTime()); 829 var recurrence = appt.getRecurrence(); 830 var endDate; 831 var range = recurrence.repeatCustomCount * numRecurrence; 832 if (recurrence.repeatType == ZmRecurrence.NONE) { 833 endDate = appt.endDate; 834 } else if (recurrence.repeatType == ZmRecurrence.DAILY) { 835 endDate = AjxDateUtil.roll(startDate, AjxDateUtil.DAY, range); 836 } else if (recurrence.repeatType == ZmRecurrence.WEEKLY) { 837 endDate = AjxDateUtil.roll(startDate, AjxDateUtil.WEEK, range); 838 } else if (recurrence.repeatType == ZmRecurrence.MONTHLY) { 839 endDate = AjxDateUtil.roll(startDate, AjxDateUtil.MONTH, range); 840 } else if (recurrence.repeatType == ZmRecurrence.YEARLY) { 841 endDate = AjxDateUtil.roll(startDate, AjxDateUtil.YEAR, range); 842 } 843 var endTime = endDate.getTime(); 844 if (recurrence.repeatEndDate) { 845 var repeatEndTime = recurrence.repeatEndDate.getTime(); 846 if (endTime > repeatEndTime) { 847 endTime = repeatEndTime; 848 } 849 } 850 return endTime; 851 } 852 853 /** 854 * Gets the recurrence definition of an appointment. 855 * 856 * @param {ZmAppt} appt the appointment 857 * @param {AjxCallback} recurInfoCallback the callback module after getting recurrence info 858 */ 859 ZmApptComposeController.prototype.getRecurInfo = 860 function(appt, recurInfoCallback) { 861 var soapDoc = AjxSoapDoc.create("GetRecurRequest", "urn:zimbraMail"); 862 soapDoc.setMethodAttribute("id", appt.id); 863 864 return appCtxt.getAppController().sendRequest({ 865 soapDoc: soapDoc, 866 asyncMode: true, 867 callback: (new AjxCallback(this, this._handleRecurInfo, [appt, recurInfoCallback])), 868 errorCallback: (new AjxCallback(this, this._handleRecurInfoError, [appt, recurInfoCallback])), 869 noBusyOverlay: true 870 }); 871 }; 872 873 /** 874 * Handle Response for GetRecurRequest call 875 * 876 * @private 877 */ 878 ZmApptComposeController.prototype._handleRecurInfo = 879 function(appt, callback, result) { 880 var recurResponse = result.getResponse().GetRecurResponse; 881 if (callback) { 882 callback.run(recurResponse); 883 } 884 }; 885 886 ZmApptComposeController.prototype._handleRecurInfoError = 887 function(appt, callback, result) { 888 if (callback) { 889 callback.run(); 890 } 891 }; 892 893 ZmApptComposeController.prototype.checkPermissionRequest = 894 function(names, appt, attId, notifyList) { 895 // CheckPermissions to be retired after IronMaiden. Replaced with CheckRights 896 var jsonObj = {CheckRightsRequest:{_jsns:"urn:zimbraAccount"}}; 897 var request = jsonObj.CheckRightsRequest; 898 899 request.target = []; 900 for (var i = 0; i < names.length; i++) { 901 var targetInstance = { 902 type: "account", 903 by: "name", 904 key: names[i] 905 }; 906 targetInstance.right = [{_content: "invite"}]; 907 request.target.push(targetInstance); 908 } 909 910 var respCallback = new AjxCallback(this, this.handleCheckRightsResponse, [appt, attId, names, notifyList]); 911 var errorCallback = new AjxCallback(this, this.handleCheckRightsResponse, [appt, attId, names, notifyList]); 912 appCtxt.getAppController().sendRequest({jsonObj:jsonObj, asyncMode:true, callback:respCallback, errorCallback: errorCallback, noBusyOverlay:true}); 913 }; 914 915 ZmApptComposeController.prototype.handleCheckRightsResponse = 916 function(appt, attId, names, notifyList, response) { 917 var checkRightsResponse = response && response._data && response._data.CheckRightsResponse; 918 if (checkRightsResponse && checkRightsResponse.target) { 919 var deniedAttendees = []; 920 for (var i in checkRightsResponse.target) { 921 if (!checkRightsResponse.target[i].allow) { 922 deniedAttendees.push(names[i]); 923 } 924 } 925 if (deniedAttendees.length > 0) { 926 var msg = AjxMessageFormat.format(ZmMsg.invitePermissionDenied, [deniedAttendees.join(",")]); 927 var msgDialog = appCtxt.getMsgDialog(); 928 msgDialog.reset(); 929 msgDialog.setMessage(msg, DwtMessageDialog.INFO_STYLE); 930 msgDialog.popup(); 931 this.enableToolbar(true); 932 return; 933 } 934 } 935 this.saveCalItemContinue(appt, attId, notifyList); 936 }; 937 938 ZmApptComposeController.prototype._saveAfterPermissionCheck = 939 function(appt, attId, notifyList, msgDialog) { 940 msgDialog.popdown(); 941 this.saveCalItemContinue(appt, attId, notifyList); 942 }; 943 944 ZmApptComposeController.prototype.saveCalItemContinue = 945 function(appt, attId, notifyList) { 946 this._saveCalItemFoRealz(appt, attId, notifyList); 947 }; 948 949 ZmApptComposeController.prototype.handleCheckPermissionResponseError = 950 function(appt, attId, names, notifyList, response) { 951 var resp = response && response._data && response._data.BatchResponse; 952 this.saveCalItemContinue(appt, attId, notifyList); 953 }; 954 955 ZmApptComposeController.prototype._handleResourceConflict = 956 function(appt, callback, displayConflictDialog, conflictCallbackOverride, result) { 957 var conflictExist = false; 958 var inst = null; 959 if (result) { 960 var conflictResponse = result.getResponse().CheckRecurConflictsResponse; 961 inst = this._conflictingInstances = conflictResponse.inst; 962 if (inst && inst.length > 0) { 963 if (displayConflictDialog) { 964 this.showConflictDialog(appt, callback, inst); 965 } 966 conflictExist = true; 967 this.enableToolbar(true); 968 } 969 } 970 971 if ((conflictCallbackOverride || !conflictExist) && callback) { 972 callback.run(inst); 973 } 974 }; 975 976 ZmApptComposeController.prototype.showConflictDialog = 977 function(appt, callback, inst) { 978 DBG.println("conflict instances :" + inst.length); 979 980 var conflictDialog = this.getConflictDialog(); 981 conflictDialog.initialize(inst, appt, callback); 982 conflictDialog.popup(); 983 }; 984 985 ZmApptComposeController.prototype.getConflictDialog = 986 function() { 987 if (!this._resConflictDialog) { 988 this._resConflictDialog = new ZmResourceConflictDialog(this._shell); 989 } 990 return this._resConflictDialog; 991 }; 992 993 ZmApptComposeController.prototype._handleResourceConflictError = 994 function(appt, callback) { 995 // continue with normal saving process via callback 996 if (callback) { 997 callback.run(); 998 } 999 }; 1000 1001 ZmApptComposeController.prototype.getFreeBusyInfo = 1002 function(startTime, endTime, emailList, callback, errorCallback, noBusyOverlay) { 1003 var soapDoc = AjxSoapDoc.create("GetFreeBusyRequest", "urn:zimbraMail"); 1004 soapDoc.setMethodAttribute("s", startTime); 1005 soapDoc.setMethodAttribute("e", endTime); 1006 soapDoc.setMethodAttribute("uid", emailList); 1007 1008 var acct = (appCtxt.multiAccounts) 1009 ? this._composeView.getApptEditView().getCalendarAccount() : null; 1010 1011 return appCtxt.getAppController().sendRequest({ 1012 soapDoc: soapDoc, 1013 asyncMode: true, 1014 callback: callback, 1015 errorCallback: errorCallback, 1016 noBusyOverlay: noBusyOverlay, 1017 accountName: (acct ? acct.name : null) 1018 }); 1019 }; 1020 1021 ZmApptComposeController.prototype._createComposeView = 1022 function() { 1023 return (new ZmApptComposeView(this._container, null, this._app, this)); 1024 }; 1025 1026 ZmApptComposeController.prototype._setComposeTabGroup = 1027 function(setFocus) { 1028 DBG.println(AjxDebug.DBG2, "_setComposeTabGroup"); 1029 var tg = this._createTabGroup(); 1030 var rootTg = appCtxt.getRootTabGroup(); 1031 tg.newParent(rootTg); 1032 tg.addMember(this._toolbar); 1033 var editView = this._composeView.getApptEditView(); 1034 editView._addTabGroupMembers(tg); 1035 1036 var focusItem = editView._savedFocusMember || editView._getDefaultFocusItem() || tg.getFirstMember(true); 1037 var ta = new AjxTimedAction(this, this._setFocus, [focusItem, !setFocus]); 1038 AjxTimedAction.scheduleAction(ta, 10); 1039 }; 1040 1041 ZmApptComposeController.prototype._getDefaultFocusItem = 1042 function() { 1043 return this._composeView.getApptEditView()._getDefaultFocusItem(); 1044 }; 1045 1046 ZmApptComposeController.prototype.getKeyMapName = 1047 function() { 1048 return ZmKeyMap.MAP_EDIT_APPOINTMENT; 1049 }; 1050 1051 1052 // Private / Protected methods 1053 1054 ZmApptComposeController.prototype._attendeesUpdated = 1055 function(appt, attId, attendees, origAttendees) { 1056 // create hashes of emails for comparison 1057 var origEmails = {}; 1058 for (var i = 0; i < origAttendees.length; i++) { 1059 var email = origAttendees[i].getEmail(); 1060 origEmails[email] = true; 1061 } 1062 var fwdEmails = {}; 1063 var fwdAddrs = appt.getForwardAddress(); 1064 for(var i=0;i<fwdAddrs.length;i++) { 1065 var email = fwdAddrs[i].getAddress(); 1066 fwdEmails[email] = true; 1067 } 1068 var curEmails = {}; 1069 for (var i = 0; i < attendees.length; i++) { 1070 var email = attendees[i].getEmail(); 1071 curEmails[email] = true; 1072 } 1073 1074 // walk the current list of attendees and check if there any new ones 1075 for (var i = 0 ; i < attendees.length; i++) { 1076 var email = attendees[i].getEmail(); 1077 if (!origEmails[email] && !fwdEmails[email]) { 1078 this._addedAttendees.push(email); 1079 } 1080 } 1081 1082 for (var i = 0 ; i < origAttendees.length; i++) { 1083 var email = origAttendees[i].getEmail(); 1084 if (!curEmails[email]) { 1085 this._removedAttendees.push(email); 1086 } 1087 } 1088 1089 if (this._addedAttendees.length > 0 || this._removedAttendees.length > 0) { 1090 if (!this._notifyDialog) { 1091 this._notifyDialog = new ZmApptNotifyDialog(this._shell); 1092 this._notifyDialog.addSelectionListener(DwtDialog.OK_BUTTON, new AjxListener(this, this._notifyDlgOkListener)); 1093 this._notifyDialog.addSelectionListener(DwtDialog.CANCEL_BUTTON, new AjxListener(this, this._notifyDlgCancelListener)); 1094 } 1095 appt.setMailNotificationOption(true); 1096 this._notifyDialog.initialize(appt, attId, this._addedAttendees, this._removedAttendees); 1097 this._notifyDialog.popup(); 1098 this.enableToolbar(true); 1099 return true; 1100 } 1101 1102 return false; 1103 }; 1104 1105 1106 // Listeners 1107 1108 // Cancel button was pressed 1109 ZmApptComposeController.prototype._cancelListener = 1110 function(ev) { 1111 1112 var isDirty = false; 1113 1114 if(this._composeView.gotNewAttachments()) { 1115 isDirty = true; 1116 }else { 1117 var appt = this._composeView.getAppt(this._attId); 1118 if (appt && !appt.inviteNeverSent){ 1119 //Check for Significant Changes 1120 isDirty = this._checkIsDirty(ZmApptEditView.CHANGES_SIGNIFICANT) 1121 } 1122 } 1123 1124 if(isDirty){ 1125 this._getChangesDialog().popup(); 1126 this.enableToolbar(true); 1127 return; 1128 } 1129 1130 this._app.getCalController().setNeedsRefresh(true); 1131 1132 ZmCalItemComposeController.prototype._cancelListener.call(this, ev); 1133 }; 1134 1135 ZmApptComposeController.prototype._printListener = 1136 function() { 1137 var calItem = this._composeView._apptEditView._calItem; 1138 var url = ["/h/printappointments?id=", calItem.invId, "&tz=", AjxTimezone.getServerId(AjxTimezone.DEFAULT)]; //bug:53493 1139 if (appCtxt.isOffline) { 1140 url.push("&zd=true", "&acct=", this._composeView.getApptEditView().getCalendarAccount().name); 1141 } 1142 window.open(appContextPath + url.join(""), "_blank"); 1143 }; 1144 1145 1146 // Callbacks 1147 1148 ZmApptComposeController.prototype._notifyDlgOkListener = 1149 function(ev) { 1150 var notifyList = this._notifyDialog.notifyNew() ? this._addedAttendees : null; 1151 this._saveCalItemFoRealz(this._notifyDialog.getAppt(), this._notifyDialog.getAttId(), notifyList); 1152 }; 1153 1154 ZmApptComposeController.prototype._notifyDlgCancelListener = 1155 function(ev) { 1156 this._addedAttendees.length = this._removedAttendees.length = 0; 1157 }; 1158 1159 ZmApptComposeController.prototype._changeOrgCallback = 1160 function(appt, attId, dlg) { 1161 dlg.popdown(); 1162 this._saveCalItemFoRealz(appt, attId); 1163 }; 1164 1165 ZmApptComposeController.prototype._saveCalItemFoRealz = 1166 function(calItem, attId, notifyList, force){ 1167 force = force || ( this._action == ZmCalItemComposeController.SEND ); 1168 1169 //organizer forwarding an appt is same as organizer editing appt while adding new attendees 1170 if(calItem.isForward) { 1171 notifyList = this.getForwardNotifyList(calItem); 1172 } 1173 1174 this._composeView.getApptEditView().resetParticipantStatus(); 1175 1176 // NOTE: Once CreateAppt/ModifyAppt SOAP API changes are completed (Bug 56464), pass to 1177 // the base _saveCalItemFoRealz appt.alteredLocations, to create a set of location 1178 // exceptions along with creation/modification of the underlying appt 1179 // *** NOT DONE *** 1180 ZmCalItemComposeController.prototype._saveCalItemFoRealz.call(this, calItem, attId, notifyList, force); 1181 }; 1182 1183 /** 1184 * To get the array of forward email addresses 1185 * 1186 * @param {ZmAppt} appt the appointment 1187 * @return {Array} an array of email addresses 1188 */ 1189 ZmApptComposeController.prototype.getForwardNotifyList = 1190 function(calItem){ 1191 var fwdAddrs = calItem.getForwardAddress(); 1192 var notifyList = []; 1193 for(var i=0;i<fwdAddrs.length;i++) { 1194 var email = fwdAddrs[i].getAddress(); 1195 notifyList.push(email); 1196 } 1197 return notifyList; 1198 }; 1199 1200 ZmApptComposeController.prototype._doSaveCalItem = 1201 function(appt, attId, callback, errorCallback, notifyList){ 1202 delete this._attendeeValidated; 1203 if(this._action == ZmCalItemComposeController.SEND){ 1204 appt.send(attId, callback, errorCallback, notifyList); 1205 }else{ 1206 var isMeeting = appt.hasAttendees(); 1207 if(isMeeting){ 1208 this._draftFlag = appt.isDraft || appt.inviteNeverSent || this._checkIsDirty(ZmApptEditView.CHANGES_INSIGNIFICANT); 1209 }else{ 1210 this._draftFlag = false; 1211 } 1212 appt.save(attId, callback, errorCallback, notifyList, this._draftFlag); 1213 } 1214 }; 1215 1216 ZmApptComposeController.prototype._handleResponseSave = 1217 function(calItem, result) { 1218 if (calItem.__newFolderId) { 1219 var folder = appCtxt.getById(calItem.__newFolderId); 1220 calItem.__newFolderId = null; 1221 this._app.getListController()._doMove(calItem, folder, null, false); 1222 } 1223 1224 var isNewAppt; 1225 var viewMode = calItem.getViewMode(); 1226 if(viewMode == ZmCalItem.MODE_NEW || viewMode == ZmCalItem.MODE_NEW_FROM_QUICKADD || viewMode == ZmAppt.MODE_DRAG_OR_SASH) { 1227 isNewAppt = true; 1228 } 1229 1230 if(this.isCloseAction()) { 1231 calItem.handlePostSaveCallbacks(); 1232 this.closeView(); 1233 }else { 1234 this.enableToolbar(true); 1235 if(isNewAppt) { 1236 viewMode = calItem.isRecurring() ? ZmCalItem.MODE_EDIT_SERIES : ZmCalItem.MODE_EDIT; 1237 } 1238 calItem.setFromSavedResponse(result); 1239 if(this._action == ZmCalItemComposeController.SAVE){ 1240 calItem.isDraft = this._draftFlag; 1241 calItem.draftUpdated = true; 1242 } 1243 this._composeView.set(calItem, viewMode); 1244 } 1245 1246 var msg = isNewAppt ? ZmMsg.apptCreated : ZmMsg.apptSaved; 1247 if(calItem.hasAttendees()){ 1248 if(this._action == ZmCalItemComposeController.SAVE || this._action == ZmCalItemComposeController.SAVE_CLOSE){ 1249 msg = ZmMsg.apptSaved; 1250 }else{ 1251 if(viewMode != ZmCalItem.MODE_NEW){ 1252 msg = ZmMsg.apptSent; 1253 } 1254 } 1255 } 1256 appCtxt.setStatusMsg(msg); 1257 1258 appCtxt.notifyZimlets("onSaveApptSuccess", [this, calItem, result]);//notify Zimlets on success 1259 }; 1260 1261 ZmApptComposeController.prototype._resetNavToolBarButtons = 1262 function(view) { 1263 //do nothing 1264 }; 1265 1266 ZmApptComposeController.prototype._clearInvalidAttendeesCallback = 1267 function(appt, attId, dlg) { 1268 dlg.popdown(); 1269 this.clearInvalidAttendees(); 1270 delete this._invalidAttendees; 1271 if(this._action == ZmCalItemComposeController.SAVE){ 1272 this._saveListener(); 1273 }else{ 1274 this._sendListener(); 1275 } 1276 }; 1277 1278 ZmApptComposeController.prototype.clearInvalidAttendees = 1279 function() { 1280 this._invalidAttendees = []; 1281 }; 1282 1283 ZmApptComposeController.prototype.addInvalidAttendee = 1284 function(item) { 1285 if (AjxUtil.indexOf(this._invalidAttendees, item)==-1) { 1286 this._invalidAttendees.push(item); 1287 } 1288 }; 1289 1290 ZmApptComposeController.prototype.closeView = 1291 function() { 1292 this._closeView(); 1293 }; 1294 1295 ZmApptComposeController.prototype.forwardInvite = 1296 function(newAppt) { 1297 this.show(newAppt, ZmCalItem.MODE_FORWARD_INVITE); 1298 }; 1299 1300 ZmApptComposeController.prototype.proposeNewTime = 1301 function(newAppt) { 1302 this.show(newAppt, ZmCalItem.MODE_PROPOSE_TIME); 1303 }; 1304 1305 ZmApptComposeController.prototype.initComposeView = 1306 function(initHide) { 1307 1308 if (!this._composeView) { 1309 this._composeView = this._createComposeView(); 1310 var appEditView = this._composeView.getApptEditView(); 1311 this._savedFocusMember = appEditView._getDefaultFocusItem(); 1312 1313 var callbacks = {}; 1314 callbacks[ZmAppViewMgr.CB_PRE_HIDE] = new AjxCallback(this, this._preHideCallback); 1315 callbacks[ZmAppViewMgr.CB_PRE_UNLOAD] = new AjxCallback(this, this._preUnloadCallback); 1316 callbacks[ZmAppViewMgr.CB_POST_SHOW] = new AjxCallback(this, this._postShowCallback); 1317 callbacks[ZmAppViewMgr.CB_PRE_SHOW] = new AjxCallback(this, this._preShowCallback); 1318 callbacks[ZmAppViewMgr.CB_POST_HIDE] = new AjxCallback(this, this._postHideCallback); 1319 if (!this._toolbar) 1320 this._createToolBar(); 1321 1322 var elements = this.getViewElements(null, this._composeView, this._toolbar); 1323 1324 this._app.createView({ viewId: this._currentViewId, 1325 viewType: this._currentViewType, 1326 elements: elements, 1327 hide: this._elementsToHide, 1328 controller: this, 1329 callbacks: callbacks, 1330 tabParams: this._getTabParams()}); 1331 if (initHide) { 1332 this._composeView.preload(); 1333 } 1334 return true; 1335 } 1336 else{ 1337 this._savedFocusMember = this._composeView.getApptEditView()._getDefaultFocusItem(); 1338 } 1339 return false; 1340 }; 1341 1342 ZmApptComposeController.prototype.getCalendarAccount = 1343 function() { 1344 return (appCtxt.multiAccounts) 1345 ? this._composeView.getApptEditView().getCalendarAccount() : null; 1346 1347 }; 1348 1349 ZmApptComposeController.prototype.getAttendees = 1350 function(type) { 1351 return this._composeView.getAttendees(type); 1352 }; 1353 1354 ZmApptComposeController.prototype._postHideCallback = 1355 function() { 1356 1357 ZmCalItemComposeController.prototype._postHideCallback(); 1358 1359 if (appCtxt.getCurrentAppName() == ZmApp.CALENDAR || appCtxt.get(ZmSetting.CAL_ALWAYS_SHOW_MINI_CAL)) { 1360 appCtxt.getAppViewMgr().displayComponent(ZmAppViewMgr.C_TREE_FOOTER, true); 1361 } 1362 }; 1363 1364 ZmApptComposeController.prototype._postShowCallback = 1365 function(view, force) { 1366 var ta = new AjxTimedAction(this, this._setFocus); 1367 AjxTimedAction.scheduleAction(ta, 10); 1368 }; 1369 1370 ZmApptComposeController.prototype.getWorkingInfo = 1371 function(startTime, endTime, emailList, callback, errorCallback, noBusyOverlay) { 1372 var soapDoc = AjxSoapDoc.create("GetWorkingHoursRequest", "urn:zimbraMail"); 1373 soapDoc.setMethodAttribute("s", startTime); 1374 soapDoc.setMethodAttribute("e", endTime); 1375 soapDoc.setMethodAttribute("name", emailList); 1376 1377 var acct = (appCtxt.multiAccounts) 1378 ? this._composeView.getApptEditView().getCalendarAccount() : null; 1379 1380 return appCtxt.getAppController().sendRequest({ 1381 soapDoc: soapDoc, 1382 asyncMode: true, 1383 callback: callback, 1384 errorCallback: errorCallback, 1385 noBusyOverlay: noBusyOverlay, 1386 accountName: (acct ? acct.name : null) 1387 }); 1388 }; 1389 1390 ZmApptComposeController.prototype._resetToolbarOperations = 1391 function() { 1392 //do nothing - this gets called when this controller handles a list view 1393 }; 1394 1395 // --- Subclass the ApptComposeController for saving Quick Add dialog appointments, and doing a 1396 // save when the CalColView drag and drop is used 1397 ZmSimpleApptComposeController = function(container, app, type, sessionId) { 1398 ZmApptComposeController.apply(this, arguments); 1399 this._closeCallback = null; 1400 // Initialize a static/dummy compose view. It is never actually used 1401 // for display (only for the function calls made to it during the save), 1402 // so it can be setup here. 1403 this.initComposeView(); 1404 }; 1405 1406 ZmSimpleApptComposeController.prototype = new ZmApptComposeController; 1407 ZmSimpleApptComposeController.prototype.constructor = ZmSimpleApptComposeController; 1408 1409 ZmSimpleApptComposeController.prototype.toString = function() { return "ZmSimpleApptComposeController"; }; 1410 1411 ZmSimpleApptComposeController.getDefaultViewType = 1412 function() { 1413 return ZmId.VIEW_SIMPLE_ADD_APPOINTMENT; 1414 }; 1415 1416 ZmSimpleApptComposeController.prototype.doSimpleSave = 1417 function(appt, action, closeCallback, errorCallback, cancelCallback) { 1418 var ret = false; 1419 this._action = action; 1420 this._closeCallback = null; 1421 if(!appt.isValidDuration()){ 1422 this._composeView.showInvalidDurationMsg(); 1423 } else if (appt) { 1424 this._simpleCloseCallback = closeCallback; 1425 this._simpleErrorCallback = errorCallback; 1426 this._simpleCancelCallback = cancelCallback; 1427 ret = this._initiateSaveWithChecks(appt, null, ZmTimeSuggestionPrefDialog.DEFAULT_NUM_RECURRENCE); 1428 } 1429 return ret; 1430 }; 1431 1432 ZmSimpleApptComposeController.prototype._handleResponseSave = 1433 function(calItem, result) { 1434 if (this._simpleCloseCallback) { 1435 this._simpleCloseCallback.run(); 1436 } 1437 appCtxt.notifyZimlets("onSaveApptSuccess", [this, calItem, result]);//notify Zimlets on success 1438 }; 1439 1440 ZmSimpleApptComposeController.prototype._getErrorSaveStatus = 1441 function(calItem, ex) { 1442 var status = ZmCalItemComposeController.prototype._getErrorSaveStatus.call(this, calItem, ex); 1443 if (!status.continueSave && this._simpleErrorCallback) { 1444 this._simpleErrorCallback.run(this); 1445 } 1446 1447 return status; 1448 }; 1449 1450 ZmSimpleApptComposeController.prototype.initComposeView = 1451 function() { 1452 if (!this._composeView) { 1453 // Create an empty compose view and make it always return isDirty == true 1454 this._composeView = this._createComposeView(); 1455 this._composeView.isDirty = function() { return true; }; 1456 return true; 1457 } 1458 return false; 1459 }; 1460 1461 ZmSimpleApptComposeController.prototype.enableToolbar = 1462 function(enabled) { } 1463 1464 1465 ZmSimpleApptComposeController.prototype.showConflictDialog = 1466 function(appt, callback, inst) { 1467 DBG.println("conflict instances :" + inst.length); 1468 1469 var conflictDialog = this.getConflictDialog(); 1470 conflictDialog.initialize(inst, appt, callback, this._simpleCancelCallback); 1471 conflictDialog.popup(); 1472 }; 1473