1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2011, 2012, 2013, 2014, 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) 2011, 2012, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 25 /** 26 * Creates a new appointment controller to manage read-only appointment viewing. 27 * @constructor 28 * @class 29 * 30 * @author Vince Bellows 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 ZmApptController = function(container, app, type, sessionId) { 40 ZmCalItemComposeController.apply(this, arguments); 41 }; 42 43 ZmApptController.prototype = new ZmCalItemComposeController; 44 ZmApptController.prototype.constructor = ZmApptController; 45 46 ZmApptController.prototype.isZmApptController = true; 47 ZmApptController.prototype.toString = function() { return "ZmApptController"; }; 48 49 ZmApptController.DEFAULT_TAB_TEXT = ZmMsg.message; 50 51 ZmApptController.viewToTab = {}; 52 53 ZmApptController.getDefaultViewType = 54 function() { 55 return ZmId.VIEW_APPOINTMENT_READONLY; 56 }; 57 ZmApptController.prototype.getDefaultViewType = ZmApptController.getDefaultViewType; 58 59 ZmApptController.prototype._createComposeView = 60 function() { 61 // override 62 return new ZmApptView(this._container, DwtControl.ABSOLUTE_STYLE, this); 63 }; 64 65 ZmApptController.prototype._createToolBar = 66 function() { 67 68 var buttons = [ ZmOperation.SEND_INVITE, ZmOperation.SAVE, ZmOperation.CANCEL, ZmOperation.SEP, 69 ZmOperation.TAG_MENU 70 ]; 71 var secondaryButtons = [ZmOperation.EDIT, ZmOperation.DUPLICATE_APPT, ZmOperation.SEP, 72 ZmOperation.REPLY, ZmOperation.REPLY_ALL, ZmOperation.FORWARD_APPT, ZmOperation.PROPOSE_NEW_TIME, ZmOperation.DELETE, ZmOperation.SEP, 73 ZmOperation.SHOW_ORIG 74 ]; 75 if (appCtxt.get(ZmSetting.PRINT_ENABLED)) { 76 buttons.push(ZmOperation.PRINT); 77 } 78 79 this._toolbar = new ZmButtonToolBar({parent:this._container, buttons:buttons, context:this._currentViewId, controller:this, secondaryButtons:secondaryButtons}); 80 this._toolbar.addSelectionListener(ZmOperation.SAVE, this._saveListener.bind(this)); 81 this._toolbar.addSelectionListener(ZmOperation.CANCEL, this._cancelListener.bind(this)); 82 this._toolbar.addSelectionListener(ZmOperation.REPLY, this._replyListener.bind(this)); 83 this._toolbar.addSelectionListener(ZmOperation.REPLY_ALL, this._replyAllListener.bind(this)); 84 this._toolbar.addSelectionListener(ZmOperation.FORWARD_APPT, this._forwardListener.bind(this)); 85 this._toolbar.addSelectionListener(ZmOperation.EDIT, this._editListener.bind(this)); 86 this._toolbar.addSelectionListener(ZmOperation.PROPOSE_NEW_TIME, this._proposeTimeListener.bind(this)); 87 this._toolbar.addSelectionListener(ZmOperation.DELETE, this._deleteListener.bind(this)); 88 this._toolbar.addSelectionListener(ZmOperation.DUPLICATE_APPT, this._duplicateApptListener.bind(this)); 89 this._toolbar.addSelectionListener(ZmOperation.SHOW_ORIG, this._showOrigListener.bind(this)); 90 91 if (appCtxt.get(ZmSetting.PRINT_ENABLED)) { 92 this._toolbar.addSelectionListener(ZmOperation.PRINT, this._printListener.bind(this)); 93 } 94 95 var sendButton = this._toolbar.getButton(ZmOperation.SEND_INVITE); 96 sendButton.setVisible(false); 97 98 99 var tagButton = this._toolbar.getButton(ZmOperation.TAG_MENU); 100 if (tagButton) { 101 tagButton.noMenuBar = true; 102 this._setupTagMenu(this._toolbar); 103 } 104 // change default button style to toggle for spell check button 105 var spellCheckButton = this._toolbar.getButton(ZmOperation.SPELL_CHECK); 106 if (spellCheckButton) { 107 spellCheckButton.setAlign(DwtLabel.IMAGE_LEFT | DwtButton.TOGGLE_STYLE); 108 } 109 110 }; 111 112 ZmApptController.prototype._initToolbar = 113 function(mode) { 114 ZmCalItemComposeController.prototype._initToolbar.call(this, mode); 115 var saveButton = this._toolbar.getButton(ZmOperation.SAVE); 116 saveButton.setEnabled(false); 117 118 var editButton = this._toolbar.getButton(ZmOperation.EDIT), 119 forwardApptButton, 120 deleteButton; 121 122 if (editButton) { 123 if (mode === ZmCalItem.MODE_EDIT_SINGLE_INSTANCE) { 124 if (editButton.getText() !== ZmMsg.editInstance) { 125 editButton.setText(ZmMsg.editInstance); 126 forwardApptButton = this._toolbar.getButton(ZmOperation.FORWARD_APPT); 127 deleteButton = this._toolbar.getButton(ZmOperation.DELETE); 128 forwardApptButton && forwardApptButton.setText(ZmMsg.forwardInstance); 129 deleteButton && deleteButton.setText(ZmMsg.deleteApptInstance); 130 } 131 } 132 else if (mode === ZmCalItem.MODE_EDIT_SERIES) { 133 if (editButton.getText() !== ZmMsg.editSeries) { 134 editButton.setText(ZmMsg.editSeries); 135 forwardApptButton = this._toolbar.getButton(ZmOperation.FORWARD_APPT); 136 deleteButton = this._toolbar.getButton(ZmOperation.DELETE); 137 forwardApptButton && forwardApptButton.setText(ZmMsg.forwardSeries); 138 deleteButton && deleteButton.setText(ZmMsg.deleteApptSeries); 139 } 140 } 141 else { 142 if (editButton.getText() !== ZmMsg.edit) { 143 editButton.setText(ZmMsg.edit); 144 forwardApptButton = this._toolbar.getButton(ZmOperation.FORWARD_APPT); 145 deleteButton = this._toolbar.getButton(ZmOperation.DELETE); 146 forwardApptButton && forwardApptButton.setText(ZmMsg.forward); 147 deleteButton && deleteButton.setText(ZmMsg.del); 148 } 149 } 150 } 151 152 // bug 68451: disabling edit options for trashed appointments 153 var calItem = this.getCalItem(); 154 var calendar = calItem && calItem.getFolder(); 155 var isTrash = calendar && calendar.nId==ZmOrganizer.ID_TRASH; 156 var isReadOnly = calendar && calendar.isReadOnly(); 157 158 if(isTrash){ 159 this._disableEditForTrashedItems(); 160 } 161 162 if (appCtxt.isWebClientOffline()) { 163 this._disableEditForOffline(); 164 } 165 if (isReadOnly) { 166 this._disableActionsForReadOnlyAppt(); 167 } 168 if (appCtxt.isExternalAccount()) { 169 this._disableActionsForExternalAccount(); 170 } 171 }; 172 173 ZmApptController.prototype._disableEditForOffline = 174 function() { 175 var actionMenu = this._toolbar.getActionsMenu(); 176 if(actionMenu){ 177 actionMenu.enable([ 178 ZmOperation.EDIT, 179 ZmOperation.TAG, 180 ZmOperation.TAG_MENU, 181 ZmOperation.REPLY, 182 ZmOperation.REPLY_ALL, 183 ZmOperation.PROPOSE_NEW_TIME, 184 ZmOperation.DUPLICATE_APPT, 185 ZmOperation.FORWARD_APPT, 186 ZmOperation.DELETE, 187 ZmOperation.SHOW_ORIG 188 ], false); 189 } 190 var tagButton = this._toolbar.getButton(ZmOperation.TAG_MENU); 191 if (tagButton) { 192 tagButton.setEnabled(false); 193 } 194 var printButton = this._toolbar.getButton(ZmOperation.PRINT); 195 if (printButton) { 196 printButton.setEnabled(false); 197 } 198 } 199 200 ZmApptController.prototype._disableEditForTrashedItems = 201 function() { 202 var actionMenu = this._toolbar.getActionsMenu(); 203 if(actionMenu){ 204 actionMenu.enable([ 205 ZmOperation.EDIT, 206 ZmOperation.REPLY, 207 ZmOperation.REPLY_ALL, 208 ZmOperation.PROPOSE_NEW_TIME, 209 ZmOperation.FORWARD_APPT 210 ], false); 211 } 212 }; 213 214 ZmApptController.prototype._disableActionsForReadOnlyAppt = 215 function() { 216 var actionMenu = this._toolbar.getActionsMenu(); 217 if(actionMenu){ 218 actionMenu.enable([ 219 ZmOperation.EDIT, 220 ZmOperation.TAG, 221 ZmOperation.TAG_MENU, 222 ZmOperation.FORWARD_APPT, 223 ZmOperation.PROPOSE_NEW_TIME, 224 ZmOperation.DELETE 225 ], false); 226 } 227 var tagButton = this._toolbar.getButton(ZmOperation.TAG_MENU); 228 if (tagButton) { 229 tagButton.setEnabled(false); 230 } 231 }; 232 233 ZmApptController.prototype._disableActionsForExternalAccount = 234 function() { 235 var actionMenu = this._toolbar.getActionsMenu(); 236 if(actionMenu){ 237 actionMenu.enable([ 238 ZmOperation.EDIT, 239 ZmOperation.TAG, 240 ZmOperation.TAG_MENU, 241 ZmOperation.REPLY, 242 ZmOperation.REPLY_ALL, 243 ZmOperation.PROPOSE_NEW_TIME, 244 ZmOperation.DUPLICATE_APPT, 245 ZmOperation.FORWARD_APPT, 246 ZmOperation.DELETE 247 ], false); 248 } 249 var tagButton = this._toolbar.getButton(ZmOperation.TAG_MENU); 250 if (tagButton) { 251 tagButton.setEnabled(false); 252 } 253 }; 254 255 ZmApptController.prototype._deleteListener = 256 function(ev) { 257 var op = this.getMode(); 258 var calItem = this.getCalItem(); 259 if(!calItem) { 260 return; 261 } 262 263 if (calItem.isRecurring()) { 264 var mode = (op == ZmCalItem.MODE_EDIT_SINGLE_INSTANCE) 265 ? ZmCalItem.MODE_DELETE_INSTANCE 266 : ZmCalItem.MODE_DELETE_SERIES; 267 this._app.getCalController()._promptDeleteAppt(calItem, mode); 268 } 269 else { 270 this._app.getCalController()._deleteAppointment(calItem); 271 } 272 }; 273 274 ZmApptController.prototype._editListener = 275 function(ev) { 276 var op = (ev && ev.item instanceof DwtMenuItem) 277 ? ev.item.getData(ZmOperation.KEY_ID) : null; 278 var calItem = this.getCalItem(); 279 if(!calItem) { 280 return; 281 } 282 this._composeView.edit(ev); 283 }; 284 285 ZmApptController.prototype._replyListener = 286 function(ev) { 287 var op = (ev && ev.item instanceof DwtMenuItem) 288 ? ev.item.getData(ZmOperation.KEY_ID) : null; 289 var calItem = this.getCalItem(); 290 if(!calItem) { 291 return; 292 } 293 this._app.getCalController()._replyAppointment(calItem, false); 294 }; 295 296 ZmApptController.prototype._replyAllListener = 297 function(ev) { 298 var op = (ev && ev.item instanceof DwtMenuItem) 299 ? ev.item.getData(ZmOperation.KEY_ID) : null; 300 var calItem = this.getCalItem(); 301 if(!calItem) { 302 return; 303 } 304 this._app.getCalController()._replyAppointment(calItem, true); 305 }; 306 307 ZmApptController.prototype._saveListener = 308 function(ev) { 309 if(!this.isDirty() || !this.getOpValue()) { 310 return; 311 } 312 var op = (ev && ev.item instanceof DwtMenuItem) 313 ? ev.item.getData(ZmOperation.KEY_ID) : null; 314 315 316 var calItem = this.getCalItem(); 317 if(!calItem) { 318 return; 319 } 320 if (calItem.isRecurring() && !op) { 321 var mode = this.getMode(); 322 op = (mode == ZmCalItem.MODE_EDIT_SINGLE_INSTANCE) ? ZmOperation.VIEW_APPT_INSTANCE : ZmOperation.VIEW_APPT_SERIES; 323 } 324 325 this._action = ZmCalItemComposeController.SAVE_CLOSE; 326 var saveCallback = new AjxCallback(this, this._handleSaveResponse); 327 var calViewCtrl = this._app.getCalController(); 328 // This will trigger a call to ZmMailMsg.sendInviteReply, which updates the offline appointment ptst field 329 // and the invite mail msg. 330 var respCallback = new AjxCallback(calViewCtrl, calViewCtrl._handleResponseHandleApptRespondAction, [calItem, this.getOpValue(), op, saveCallback]); 331 calItem.getDetails(null, respCallback, this._errorCallback); 332 333 //this._app.getCalController()._replyAppointment(calItem, true); 334 }; 335 336 ZmApptController.prototype._duplicateApptListener = 337 function(ev) { 338 var op = this.getMode(); 339 var appt = this.getCalItem(); 340 var isException = (appt.isRecurring() && op == ZmCalItem.MODE_EDIT_SINGLE_INSTANCE); 341 var calViewCtrl = this._app.getCalController(); 342 calViewCtrl.duplicateAppt(appt, {isException: isException}); 343 }; 344 345 ZmApptController.prototype._showOrigListener = 346 function(ev) { 347 var appt = this.getCalItem(); 348 var calViewCtrl = this._app.getCalController(); 349 if (appt) 350 calViewCtrl._showApptSource(appt); 351 }; 352 353 ZmApptController.prototype._handleSaveResponse = 354 function(result, value) { 355 if (appCtxt.isWebClientOffline()) { 356 // Set the value of the appt stored in-memory in the list. Normally, this would be updated 357 // by a notification, but not offline. 358 //var appt = this.getCalItem(); 359 //appt.ptst = value; 360 361 // Update the version currently in use. It may get updated again below, but it doesn't matter 362 this.getCurrentView().setOrigPtst(value); 363 } 364 if (this.isCloseAction()) { 365 this._closeView(); 366 } else { 367 this.getCurrentView().setOrigPtst(value); 368 } 369 }; 370 371 ZmApptController.prototype.isCloseAction = 372 function() { 373 return this._action == ZmCalItemComposeController.SAVE_CLOSE || this._action == ZmCalItemComposeController.SAVE; 374 }; 375 376 ZmApptController.prototype._forwardListener = 377 function(ev) { 378 var op = this.getMode(); 379 var calItem = this.getCalItem(); 380 if(!calItem) { 381 return; 382 } 383 384 var mode = ZmCalItem.MODE_FORWARD; 385 if (calItem.isRecurring()) { 386 mode = (op == ZmCalItem.MODE_EDIT_SINGLE_INSTANCE) 387 ? ZmCalItem.MODE_FORWARD_SINGLE_INSTANCE 388 : ZmCalItem.MODE_FORWARD_SERIES; 389 } 390 391 this._app.getCalController()._forwardAppointment(calItem, mode); 392 }; 393 394 ZmApptController.prototype._printListener = 395 function() { 396 var calItem = this.getCalItem(); 397 if(!calItem) { 398 return; 399 } 400 var url = ["/h/printappointments?id=", calItem.invId, "&tz=", AjxTimezone.getServerId(AjxTimezone.DEFAULT)]; //bug:53493 401 if (appCtxt.isOffline) { 402 url.push("&zd=true", "&acct=", this._composeView.getApptEditView().getCalendarAccount().name); 403 } 404 window.open(appContextPath + url.join(""), "_blank"); 405 }; 406 407 ZmApptController.prototype._tagButtonListener = 408 function(ev) { 409 var toolbar = this.getCurrentToolbar(); 410 if (ev.item.parent == toolbar) { 411 this._setTagMenu(toolbar); 412 } 413 }; 414 415 ZmApptController.prototype._setupTagMenu = 416 function(parent) { 417 if (!parent) return; 418 var tagMenu = parent.getTagMenu(); 419 if (tagMenu) { 420 tagMenu.addSelectionListener(new AjxListener(this, this._tagListener)); 421 } 422 if (parent instanceof ZmButtonToolBar) { 423 var tagButton = parent.getOp(ZmOperation.TAG_MENU); 424 if (tagButton) { 425 tagButton.addDropDownSelectionListener(new AjxListener(this, this._tagButtonListener)); 426 } 427 } 428 }; 429 430 ZmApptController.prototype._proposeTimeListener = 431 function(ev) { 432 var calItem = this.getCalItem(); 433 if(!calItem) { 434 return; 435 } 436 //Pass mode edit to open the appt in edit mode. The mode 'propose new time' will be added later. 437 var mode = ZmCalItem.MODE_EDIT; 438 if (calItem.isRecurring()) { 439 mode = this.getMode(); 440 } 441 var appt = calItem; 442 var clone = ZmAppt.quickClone(appt); 443 clone.setProposeTimeMode(true); 444 clone.getDetails(mode, new AjxCallback(this, this._proposeTimeContinue, [clone, mode])); 445 }; 446 447 ZmApptController.prototype._proposeTimeContinue = 448 function(appt, mode) { 449 appt.setViewMode(mode); 450 AjxDispatcher.run("GetApptComposeController").proposeNewTime(appt); 451 }; 452 453 ZmApptController.prototype._doTag = 454 function(items, tag, doTag) { 455 456 var list = this._getTaggableItems(items); 457 458 if (doTag) { 459 if (list.length > 0 && list.length == items.length) { 460 // there are items to tag, and all are taggable 461 ZmBaseController.prototype._doTag.call(this, list, tag, doTag); 462 } else { 463 var msg; 464 var dlg = appCtxt.getMsgDialog(); 465 if (list.length > 0 && list.length < items.length) { 466 // there are taggable and nontaggable items 467 var listener = new AjxListener(this, this._handleDoTag, [dlg, list, tag, doTag]); 468 dlg.setButtonListener(DwtDialog.OK_BUTTON, listener); 469 msg = ZmMsg.tagReadonly; 470 } else if (list.length == 0) { 471 // no taggable items 472 msg = ZmMsg.nothingToTag; 473 } 474 dlg.setMessage(msg); 475 dlg.popup(); 476 } 477 } else if (list.length > 0) { 478 ZmBaseController.prototype._doTag.call(this, list, tag, doTag); 479 } 480 }; 481 482 ZmApptController.prototype._doRemoveAllTags = 483 function(items) { 484 var list = this._getTaggableItems(items); 485 ZmBaseController.prototype._doRemoveAllTags.call(this, list); 486 }; 487 488 ZmApptController.prototype._handleDoTag = 489 function(dlg, list, tag, doTag) { 490 dlg.popdown(); 491 ZmBaseController.prototype._doTag.call(this, list, tag, doTag); 492 }; 493 494 ZmApptController.prototype._getTaggableItems = 495 function(items) { 496 var calItem = this.getCalItem(); 497 items = []; 498 items.push(calItem); 499 return items; 500 }; 501 502 ZmApptController.prototype.getItems = 503 function() { 504 return this._getTaggableItems([]); 505 }; 506 507 ZmApptController.prototype.getCalItem = 508 function() { 509 var ci = this._composeView ? this._composeView._calItem : null; 510 return ci; 511 }; 512 513 ZmApptController.prototype.getOpValue = 514 function() { 515 var s = this._composeView ? this._composeView.getOpValue() : null; 516 return s; 517 }; 518 519 ZmApptController.prototype.isDirty = 520 function() { 521 var dirty = this._composeView ? this._composeView.isDirty() : false; 522 return dirty; 523 }; 524 525 ZmApptController.prototype.getMode = 526 function() { 527 var m = this._composeView ? this._composeView._mode : null; 528 return m; 529 }; 530 531 ZmApptController.prototype.getCurrentView = 532 function() { 533 return this._composeView; 534 }; 535 536 ZmApptController.prototype.getCurrentToolbar = 537 function() { 538 return this._toolbar; 539 }; 540 541 ZmApptController.prototype._postShowCallback = 542 function() { 543 ZmCalItemComposeController.prototype._postShowCallback.call(this); 544 this._app.setOverviewPanelContent(); 545 }; 546 547 ZmApptController.prototype.saveCalItem = 548 function(attId) { 549 var done = true; 550 if (this.isDirty()) { 551 var calItem = this.getCalItem(); 552 if(calItem) { 553 var saveCallback = new AjxCallback(this, this._handleSaveResponse); 554 var calViewCtrl = this._app.getCalController(); 555 var respCallback = 556 new AjxCallback(calViewCtrl, calViewCtrl._handleResponseHandleApptRespondAction, 557 [calItem, this.getOpValue(), null, saveCallback]); 558 calItem.getDetails(null, respCallback, this._errorCallback); 559 done = false; 560 } 561 } 562 if(done && this.isCloseAction()) { 563 this._closeView(); 564 } 565 566 }; 567 568 ZmApptController.prototype._closeView = 569 function() { 570 this._app.popView(true, this.getCurrentViewId()); 571 this._composeView.cleanup(); 572 }; 573 574 ZmApptController.prototype.getKeyMapName = function() { 575 return "viewAppointment"; 576 }; 577