1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 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) 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 an invite mail message singleton class. 27 * 28 */ 29 30 31 /** 32 * Default constructor for invite mail message class. 33 * @class 34 * When a user receives an invite, this class is instatiated to help 35 * ZmMailMsgView deal with invite-specific rendering/logic. 36 * 37 * @author Parag Shah 38 */ 39 ZmInviteMsgView = function(params) { 40 if (arguments.length == 0) { return; } 41 42 this.parent = params.parent; // back reference to ZmMailMsgView 43 this.mode = params.mode; 44 }; 45 46 // Consts 47 ZmInviteMsgView.REPLY_INVITE_EVENT = "inviteReply"; 48 49 50 ZmInviteMsgView.prototype.toString = 51 function() { 52 return "ZmInviteMsgView"; 53 }; 54 55 ZmInviteMsgView.prototype.reset = 56 function(cleanupHTML) { 57 if (this._inviteToolbar) { 58 if (cleanupHTML) { 59 this._inviteToolbar.dispose(); 60 this._inviteToolbar = null; 61 } else { 62 this._inviteToolbar.setDisplay(Dwt.DISPLAY_NONE); 63 } 64 } 65 66 if (this._counterToolbar) { 67 if (cleanupHTML) { 68 this._counterToolbar.dispose(); 69 this._counterToolbar = null; 70 } else { 71 this._counterToolbar.setDisplay(Dwt.DISPLAY_NONE); 72 } 73 } 74 75 if (this._dayView) { 76 if (cleanupHTML) { 77 this._dayView.dispose(); 78 this._dayView = null; 79 } else { 80 this._dayView.setDisplay(Dwt.DISPLAY_NONE); 81 } 82 Dwt.delClass(this.parent.getHtmlElement(), "RightBorderSeparator"); 83 } 84 85 this._msg = null; 86 this._invite = null; 87 }; 88 89 ZmInviteMsgView.prototype.isActive = 90 function() { 91 return ((this._invite && !this._invite.isEmpty()) || 92 (this._inviteToolbar && this._inviteToolbar.getVisible()) || 93 (this._counterToolbar && this._counterToolbar.getVisible())); 94 }; 95 96 ZmInviteMsgView.prototype.set = 97 function(msg) { 98 99 this._msg = msg; 100 var invite = this._invite = msg.invite; 101 102 this.parent._lazyCreateObjectManager(); 103 104 // Can operate the toolbar if user is the invite recipient, or invite is in a 105 // non-trash shared folder with admin/workflow access permissions 106 var folder = appCtxt.getById(msg.folderId); 107 var enabled = !appCtxt.isExternalAccount(); 108 if (enabled && folder && folder.isRemote()) { 109 var workflow = folder.isPermAllowed(ZmOrganizer.PERM_WORKFLOW); 110 var admin = folder.isPermAllowed(ZmOrganizer.PERM_ADMIN); 111 var enabled = (admin || workflow) && 112 (ZmOrganizer.normalizeId(msg.folderId) != ZmFolder.ID_TRASH); 113 } 114 if (invite && invite.hasAcceptableComponents() && msg.folderId != ZmFolder.ID_SENT) { 115 if (msg.isInviteCanceled()) { 116 //appointment was canceled (not necessarily on this instance, but by now it is canceled. Do not show the toolbar. 117 return; 118 } 119 if (invite.hasCounterMethod()) { 120 if (!this._counterToolbar) { 121 this._counterToolbar = this._getCounterToolbar(); 122 } 123 this._counterToolbar.reparentHtmlElement(this.parent.getHtmlElement(), 0); 124 this._counterToolbar.setVisible(enabled); 125 } 126 else if (!invite.isOrganizer() && invite.hasInviteReplyMethod()) { 127 var ac = window.parentAppCtxt || window.appCtxt; 128 if (AjxEnv.isIE && this._inviteToolbar) { 129 //according to fix to bug 52412 reparenting doestn't work on IE. so I don't reparent for IE but also if the toolbar element exists, 130 //I remove it from parent since in the case of double-click message view, it appears multiple times without removing it 131 this._inviteToolbar.dispose(); 132 this._inviteToolbar = null; 133 } 134 135 var inviteToolbar = this.getInviteToolbar(); 136 inviteToolbar.setVisible(enabled); 137 138 // show on-behalf-of info? 139 this._respondOnBehalfLabel.setContent(msg.cif ? AjxMessageFormat.format(ZmMsg.onBehalfOfText, [msg.cif]) : ""); 140 this._respondOnBehalfLabel.setVisible(!!msg.cif); 141 142 // logic for showing calendar/folder chooser 143 var cc = AjxDispatcher.run("GetCalController"); 144 //note that for a msg from a mountpoint, msgAcct returns the main account, so it's not really msgAcct. 145 var msgAcct = msg.getAccount(); 146 var calendars = ac.get(ZmSetting.CALENDAR_ENABLED, null, msgAcct) && (!msg.cif) 147 ? cc.getCalendars({includeLinks:true, account:msgAcct, onlyWritable:true}) : []; 148 149 var msgFolder = ac.getById(msg.getFolderId()); 150 var msgAcctId = msgFolder && msgFolder.isMountpoint ? ZmOrganizer.parseId(msgFolder.id).acctId : msgAcct.id; 151 152 if (appCtxt.multiAccounts) { 153 var accounts = ac.accountList.visibleAccounts; 154 for (var i = 0; i < accounts.length; i++) { 155 var acct = accounts[i]; 156 if (acct == msgAcct || !ac.get(ZmSetting.CALENDAR_ENABLED, null, acct)) { continue; } 157 if (appCtxt.isOffline && acct.isMain) { continue; } 158 159 calendars = calendars.concat(cc.getCalendars({includeLinks:true, account:acct, onlyWritable:true})); 160 } 161 162 // always add the local account *last* 163 if (appCtxt.isOffline) { 164 calendars.push(appCtxt.getById(ZmOrganizer.ID_CALENDAR)); 165 } 166 } 167 168 var visible = (calendars.length > 1 || appCtxt.multiAccounts); 169 if (visible) { 170 this._inviteMoveSelect.clearOptions(); 171 for (var i = 0; i < calendars.length; i++) { 172 var calendar = calendars[i]; 173 var calAcct = null; 174 var calAcctId; 175 if (calendar.isMountpoint) { 176 //we can't get account object for mountpoint, just get the ID. 177 calAcctId = ZmOrganizer.parseId(calendar.id).acctId; 178 } 179 else { 180 calAcct = calendar.getAccount(); 181 calAcctId = calAcct.id; 182 } 183 var icon = (appCtxt.multiAccounts && calAcct) ? calAcct.getIcon() : (calendar.getIcon() + ",color=" + calendar.color); 184 var name = (appCtxt.multiAccounts && calAcct) 185 ? ([calendar.name, " (", calAcct.getDisplayName(), ")"].join("")) 186 : calendar.name; 187 var isSelected = (calAcctId && msgAcctId) 188 ? (calAcctId == msgAcctId && calendar.nId == ZmOrganizer.ID_CALENDAR) 189 : calendar.nId == ZmOrganizer.ID_CALENDAR; 190 //bug: 57538 - this invite is intended for owner of shared calendar which should be selected 191 if(msg.cif && calendar.owner == msg.cif && calendar.rid == ZmOrganizer.ID_CALENDAR) isSelected = true; 192 var option = new DwtSelectOptionData(calendar.id, name, isSelected, null, icon); 193 this._inviteMoveSelect.addOption(option); 194 } 195 196 // for accounts that don't support calendar, always set the 197 // selected calendar to the Local calendar 198 if (!ac.get(ZmSetting.CALENDAR_ENABLED, null, msgAcct)) { 199 this._inviteMoveSelect.setSelectedValue(ZmOrganizer.ID_CALENDAR); 200 } 201 } 202 this._inviteMoveSelect.setVisible(visible); 203 } 204 } 205 }; 206 207 /** 208 * This method does two things: 209 * 1) Checks if invite was responded to with accept/decline/tentative, and if so, 210 * a GetAppointmentRequest is made to get the status for the other attendees. 211 * 212 * 2) Requests the free/busy status for the start date and renders the day view 213 * with the results returned. 214 */ 215 ZmInviteMsgView.prototype.showMoreInfo = 216 function(callback, dayViewCallback) { 217 var apptId = this._invite && this._invite.hasAttendeeResponse() && this._invite.getAppointmentId(); 218 219 // Fix for bug: 83785. apptId: 0 is default id for an appointment without any parent. 220 // Getting apptId: 0 when external user takes action on appointment and organizer gets reply mail. 221 if (apptId !== '0' && apptId) { 222 var jsonObj = {GetAppointmentRequest:{_jsns:"urn:zimbraMail"}}; 223 var request = jsonObj.GetAppointmentRequest; 224 var msgId = this._invite.msgId; 225 var inx = msgId.indexOf(":"); 226 if (inx !== -1) { 227 apptId = [msgId.substr(0, inx), apptId].join(":"); 228 } 229 request.id = apptId; 230 231 appCtxt.getAppController().sendRequest({ 232 jsonObj: jsonObj, 233 asyncMode: true, 234 callback: (new AjxCallback(this, this._handleShowMoreInfo, [callback, dayViewCallback])) 235 }); 236 } 237 else { 238 this._showFreeBusy(dayViewCallback); 239 if (callback) { 240 callback.run(); 241 } 242 } 243 }; 244 245 ZmInviteMsgView.prototype._handleShowMoreInfo = 246 function(callback, dayViewCallback, result) { 247 var appt = result && result.getResponse().GetAppointmentResponse.appt[0]; 248 if (appt) { 249 var om = this.parent._objectManager; 250 var html = []; 251 var idx = 0; 252 var attendees = appt.inv[0].comp[0].at || []; 253 AjxDispatcher.require(["MailCore", "CalendarCore"]); 254 255 var options = {}; 256 options.shortAddress = appCtxt.get(ZmSetting.SHORT_ADDRESS); 257 258 for (var i = 0; i < attendees.length; i++) { 259 var at = attendees[i]; 260 var subs = { 261 icon: ZmCalItem.getParticipationStatusIcon(at.ptst), 262 attendee: this.parent._getBubbleHtml(new AjxEmailAddress(at.a), options) 263 }; 264 html[idx++] = AjxTemplate.expand("mail.Message#InviteHeaderPtst", subs); 265 } 266 267 var ptstEl = document.getElementById(this._ptstId); 268 if(ptstEl) 269 ptstEl.innerHTML = html.join(""); 270 } 271 272 if (callback) { 273 callback.run(); 274 } 275 276 this._showFreeBusy(dayViewCallback); 277 }; 278 279 ZmInviteMsgView.prototype._showFreeBusy = 280 function(dayViewCallback) { 281 var ac = window.parentAppCtxt || window.appCtxt; 282 283 if (!appCtxt.isChildWindow && 284 (ac.get(ZmSetting.CALENDAR_ENABLED) || ac.multiAccounts) && 285 (this._invite && this._invite.type != "task")) 286 { 287 var inviteDate = this._getInviteDate(); 288 if (inviteDate == null) { 289 return; 290 } 291 292 AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar"]); 293 var cc = AjxDispatcher.run("GetCalController"); 294 295 if (!this._dayView) { 296 // create a new ZmCalDayView under msgview's parent otherwise, we 297 // cannot position the day view correctly. 298 var dayViewParent = (this.mode && (this.mode == ZmId.VIEW_CONV2)) ? 299 this.parent : this.parent.parent; 300 this._dayView = new ZmCalDayView(dayViewParent, DwtControl.ABSOLUTE_STYLE, cc, null, 301 this.parent._viewId, null, true, true, this.isRight()); 302 this._dayView.addSelectionListener(new AjxListener(this, this._apptSelectionListener)); 303 this._dayView.setZIndex(Dwt.Z_VIEW); // needed by ZmMsgController's msgview 304 } 305 306 this._dayView.setDisplay(Dwt.DISPLAY_BLOCK); 307 this._dayView.setDate(inviteDate, 0, false); 308 this.resize(); 309 310 var acctFolderIds = [].concat(cc.getCheckedCalendarFolderIds()); // create a *copy* 311 if(this._msg.cif) { 312 acctFolderIds = acctFolderIds.concat(cc.getUncheckedCalendarIdsByOwner(this._msg.cif)); 313 } 314 var rt = this._dayView.getTimeRange(); 315 var params = { 316 start: rt.start, 317 end: rt.end, 318 fanoutAllDay: this._dayView._fanoutAllDay(), 319 callback: (new AjxCallback(this, this._dayResultsCallback, [dayViewCallback, inviteDate.getHours()])), 320 accountFolderIds: [acctFolderIds] // pass in array of array 321 }; 322 cc.apptCache.batchRequest(params); 323 } 324 }; 325 326 ZmInviteMsgView.prototype._getInviteDate = 327 function() { 328 if (!this._invite) { return null; } 329 var inviteDate = this._invite.getServerStartDate(null, true); 330 // Not sure when null inviteDate happens (probably a bug) but this is defensive 331 // check for bug 51754 332 if (inviteDate != null) { 333 var inviteTz = this._invite.getServerStartTimeTz(); 334 inviteDate = AjxTimezone.convertTimezone(inviteDate, 335 AjxTimezone.getClientId(inviteTz), AjxTimezone.DEFAULT); 336 } 337 return inviteDate; 338 } 339 340 ZmInviteMsgView.prototype.isRight = 341 function() { 342 return this.parent._controller.isReadingPaneOnRight(); 343 }; 344 345 ZmInviteMsgView.prototype.convResize = 346 function() { 347 var parentSize = this.parent.getSize(); 348 if (this._dayView) { 349 this._dayView.setSize(parentSize.x - 5, 218); 350 var el = this._dayView.getHtmlElement(); 351 el.style.left = el.style.top = "auto"; 352 this._dayView.layout(); 353 } 354 } 355 356 /** 357 * Resizes the view depending on whether f/b is being shown or not. 358 * 359 * @param reset Boolean If true, day view is not shown and msgview's bounds need to be "reset" 360 */ 361 ZmInviteMsgView.prototype.resize = 362 function(reset) { 363 if (appCtxt.isChildWindow) { return; } 364 if (this.parent.isZmMailMsgCapsuleView) { return; } 365 366 var isRight = this.isRight(); 367 var grandParentSize = this.parent.parent.getSize(); 368 369 if (reset) { 370 if (isRight) { 371 this.parent.setSize(Dwt.DEFAULT, grandParentSize.y); 372 } 373 else { 374 this.parent.setSize(grandParentSize.x, Dwt.DEFAULT); 375 } 376 } else if (this._dayView) { 377 // bug: 50412 - fix day view for stand-alone message view which is a parent 378 // of DwtShell and needs to be resized manually. 379 var padding = 0; 380 if (this.parent.getController() instanceof ZmMsgController) { 381 // get the bounds for the app content area so we can position the day view 382 var appContentBounds = appCtxt.getAppViewMgr()._getContainerBounds(ZmAppViewMgr.C_APP_CONTENT); 383 if (!isRight) 384 grandParentSize = {x: appContentBounds.width, y: appContentBounds.height}; 385 386 // set padding so we can add it to the day view's x-location since it is a child of the shell 387 padding = appContentBounds.x; 388 } 389 390 var mvBounds = this.parent.getBounds(); 391 392 /* on IE sometimes the value of top and left is "auto", in which case we get a NaN value here due to parseInt in getLocation. */ 393 /* not sure if 0 is the right value we should use in this case, but it seems to work */ 394 if (isNaN(mvBounds.x)) { 395 mvBounds.x = 0; 396 } 397 if (isNaN(mvBounds.y)) { 398 mvBounds.y = 0; 399 } 400 401 if (isRight) { 402 var parentHeight = grandParentSize.y; 403 var dvHeight = Math.floor(parentHeight / 3); 404 var mvHeight = parentHeight - dvHeight; 405 406 this._dayView.setBounds(mvBounds.x, mvHeight, mvBounds.width, dvHeight); 407 if (this.parent && this.parent instanceof ZmMailMsgView){ 408 var el = this.parent.getHtmlElement(); 409 if (this.mode && this.mode != ZmId.VIEW_MSG) { 410 if (el){ 411 el.style.height = mvHeight + "px"; 412 Dwt.setScrollStyle(el, Dwt.SCROLL); 413 } 414 } 415 else { 416 var bodyDiv = this.parent.getMsgBodyElement(); 417 if (bodyDiv) Dwt.setScrollStyle(bodyDiv, Dwt.CLIP); 418 if (el) { 419 Dwt.setScrollStyle(el, Dwt.SCROLL); 420 var yOffset = this.parent.getBounds().y || 0; 421 el.style.height = (mvHeight - yOffset) + "px"; 422 } 423 } 424 } 425 426 // don't call DwtControl's setSize() since it triggers control 427 // listener and leads to infinite loop 428 429 Dwt.delClass(this.parent.getHtmlElement(), "RightBorderSeparator"); 430 } else { 431 var parentWidth = grandParentSize.x; 432 var dvWidth = Math.floor(parentWidth / 3); 433 var separatorWidth = 5; 434 var mvWidth = parentWidth - dvWidth - separatorWidth; 435 436 this._dayView.setBounds(mvWidth + padding + separatorWidth, mvBounds.y, dvWidth, mvBounds.height); 437 // don't call DwtControl's setSize() since it triggers control 438 // listener and leads to infinite loop 439 Dwt.setSize(this.parent.getHtmlElement(), mvWidth, Dwt.DEFAULT); 440 Dwt.addClass(this.parent.getHtmlElement(), "RightBorderSeparator"); 441 } 442 } 443 }; 444 445 /** 446 * enables all invite toolbar buttons, except one that matches the current ptst 447 * @param ptst participant status 448 */ 449 ZmInviteMsgView.prototype.enableToolbarButtons = 450 function(ptst) { 451 var disableButtonIds = {}; 452 switch (ptst) { 453 case ZmCalBaseItem.PSTATUS_ACCEPT: 454 disableButtonIds[ZmOperation.REPLY_ACCEPT] = true; 455 break; 456 case ZmCalBaseItem.PSTATUS_DECLINED: 457 disableButtonIds[ZmOperation.REPLY_DECLINE] = true; 458 break; 459 case ZmCalBaseItem.PSTATUS_TENTATIVE: 460 disableButtonIds[ZmOperation.REPLY_TENTATIVE] = true; 461 break; 462 } 463 if (appCtxt.isWebClientOffline()) { 464 disableButtonIds[ ZmOperation.PROPOSE_NEW_TIME] = true; 465 } 466 var inviteToolbar = this.getInviteToolbar(); 467 468 var buttonIds = [ZmOperation.REPLY_ACCEPT, ZmOperation.REPLY_DECLINE, ZmOperation.REPLY_TENTATIVE, ZmOperation.PROPOSE_NEW_TIME]; 469 for (var i = 0; i < buttonIds.length; i++) { 470 var buttonId = buttonIds[i]; 471 inviteToolbar.getButton(buttonId).setEnabled(appCtxt.isExternalAccount() ? false : !disableButtonIds[buttonId]); 472 } 473 }; 474 475 /** 476 * hide the participant status message (no longer relevant) 477 */ 478 ZmInviteMsgView.prototype.updatePtstMsg = 479 function(ptst) { 480 var ptstMsgBannerDiv = document.getElementById(this._ptstMsgBannerId); 481 if (!ptstMsgBannerDiv) { 482 return; 483 } 484 ptstMsgBannerDiv.className = ZmInviteMsgView.PTST_MSG[ptst].className; 485 ptstMsgBannerDiv.style.display = "block"; // since it might be display none if there's no message to begin with (this is the first time ptst is set by buttons) 486 487 var ptstMsgElement = document.getElementById(this._ptstMsgId); 488 ptstMsgElement.innerHTML = ZmInviteMsgView.PTST_MSG[ptst].msg; 489 490 var ptstIconImg = document.getElementById(this._ptstMsgIconId); 491 var icon = ZmCalItem.getParticipationStatusIcon(ptst); 492 ptstIconImg.innerHTML = AjxImg.getImageHtml(icon) 493 494 495 }; 496 497 498 ZmInviteMsgView.PTST_MSG = []; 499 ZmInviteMsgView.PTST_MSG[ZmCalBaseItem.PSTATUS_ACCEPT] = {msg: AjxMessageFormat.format(ZmMsg.inviteAccepted), className: "InviteStatusAccept"}; 500 ZmInviteMsgView.PTST_MSG[ZmCalBaseItem.PSTATUS_DECLINED] = {msg: AjxMessageFormat.format(ZmMsg.inviteDeclined), className: "InviteStatusDecline"}; 501 ZmInviteMsgView.PTST_MSG[ZmCalBaseItem.PSTATUS_TENTATIVE] = {msg: AjxMessageFormat.format(ZmMsg.inviteAcceptedTentatively), className: "InviteStatusTentative"}; 502 503 ZmInviteMsgView.prototype.addSubs = 504 function(subs, sentBy, sentByAddr, obo) { 505 506 AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar"]); 507 subs.invite = this._invite; 508 509 if (!this._msg.isInviteCanceled() && !subs.invite.isOrganizer() && subs.invite.hasInviteReplyMethod()) { 510 var yourPtst = this._msg.getPtst(); 511 this.enableToolbarButtons(yourPtst); 512 if (yourPtst) { 513 subs.ptstMsg = ZmInviteMsgView.PTST_MSG[yourPtst].msg; 514 subs.ptstClassName = ZmInviteMsgView.PTST_MSG[yourPtst].className; 515 subs.ptstIcon = ZmCalItem.getParticipationStatusIcon(yourPtst); 516 } 517 } 518 //ids for updating later 519 subs.ptstMsgBannerId = this._ptstMsgBannerId = (this.parent._htmlElId + "_ptstMsgBanner"); 520 subs.ptstMsgId = this._ptstMsgId = (this.parent._htmlElId + "_ptstMsg"); 521 subs.ptstMsgIconId = this._ptstMsgIconId = (this.parent._htmlElId + "_ptstMsgIcon"); 522 523 var isOrganizer = this._invite && this._invite.isOrganizer(); 524 var isInviteCancelled = this._invite.components && this._invite.components[0].method === ZmId.OP_CANCEL; 525 // counter proposal 526 if (this._invite.hasCounterMethod() && 527 this._msg.folderId != ZmFolder.ID_SENT) 528 { 529 var from = this._msg.getAddress(AjxEmailAddress.FROM) && this._msg.getAddress(AjxEmailAddress.FROM).getAddress(); 530 subs.counterInvMsg = (!sentByAddr || sentByAddr == from) ? 531 AjxMessageFormat.format(ZmMsg.counterInviteMsg, [from]):AjxMessageFormat.format(ZmMsg.counterInviteMsgOnBehalfOf, [sentByAddr, from]); 532 } 533 // Fix for bug: 88052 and 77237. Display cancellation banner to organizer or attendee 534 else if (isInviteCancelled) { 535 var organizer = this._invite.getOrganizerName() || this._invite.getOrganizerEmail(); 536 subs.ptstMsg = AjxMessageFormat.format(ZmMsg.inviteMsgCancelled, organizer.split()); 537 subs.ptstIcon = ZmCalItem.getParticipationStatusIcon(ZmCalBaseItem.PSTATUS_DECLINED); 538 subs.ptstClassName = "InviteStatusDecline"; 539 } 540 // if this an action'ed invite, show the status banner 541 else if (isOrganizer && this._invite.hasAttendeeResponse()) { 542 var attendee = this._invite.getAttendees()[0]; 543 var ptst = attendee && attendee.ptst; 544 if (ptst) { 545 var names = []; 546 var dispName = attendee.d || attendee.a; 547 var sentBy = attendee.sentBy; 548 var ptstStr = null; 549 if (sentBy) names.push(attendee.sentBy); 550 names.push(dispName); 551 subs.ptstIcon = ZmCalItem.getParticipationStatusIcon(ptst); 552 switch (ptst) { 553 case ZmCalBaseItem.PSTATUS_ACCEPT: 554 ptstStr = (!sentBy) ? ZmMsg.inviteMsgAccepted : ZmMsg.inviteMsgOnBehalfOfAccepted; 555 subs.ptstClassName = "InviteStatusAccept"; 556 break; 557 case ZmCalBaseItem.PSTATUS_DECLINED: 558 ptstStr = (!sentBy) ? ZmMsg.inviteMsgDeclined : ZmMsg.inviteMsgOnBehalfOfDeclined; 559 subs.ptstClassName = "InviteStatusDecline"; 560 break; 561 case ZmCalBaseItem.PSTATUS_TENTATIVE: 562 ptstStr = (!sentBy) ? ZmMsg.inviteMsgTentative:ZmMsg.inviteMsgOnBehalfOfTentative; 563 subs.ptstClassName = "InviteStatusTentative"; 564 break; 565 } 566 if (ptstStr){ 567 subs.ptstMsg = AjxMessageFormat.format(ptstStr, names); 568 } 569 } 570 } 571 572 if (isOrganizer && this._invite && this._invite.hasAttendeeResponse() && this._invite.getAppointmentId()){ 573 // set an Id for adding more detailed info later 574 subs.ptstId = this._ptstId = (this.parent._htmlElId + "_ptst"); 575 } 576 577 var options = {}; 578 options.shortAddress = appCtxt.get(ZmSetting.SHORT_ADDRESS); 579 580 var om = this.parent._objectManager; 581 // organizer 582 var org = new AjxEmailAddress(this._invite.getOrganizerEmail(), null, this._invite.getOrganizerName()); 583 subs.invOrganizer = this.parent._getBubbleHtml(org, options); 584 585 if (obo) { 586 subs.obo = this.parent._getBubbleHtml(obo, options); 587 } 588 589 // sent-by 590 var sentBy = this._invite.getSentBy(); 591 if (sentBy) { 592 subs.invSentBy = this.parent._getBubbleHtml(sentBy, options); 593 } 594 595 if(this._msg.cif) { 596 subs.intendedForMsg = AjxMessageFormat.format(ZmMsg.intendedForInfo, [this._msg.cif]); 597 subs.intendedForClassName = "InviteIntendedFor"; 598 } 599 600 // inviteees 601 var invitees = []; 602 var optInvitees = []; 603 604 var list = this._invite.getAttendees(); 605 for (var i = 0; i < list.length; i++) { 606 var at = list[i]; 607 var attendee = new AjxEmailAddress(at.a, null, at.d); 608 if (at.role == ZmCalItem.ROLE_OPTIONAL) { 609 optInvitees.push(attendee); 610 } 611 else { 612 invitees.push(attendee); 613 } 614 } 615 var addressInfo = this.parent.getAddressesFieldInfo(invitees, options, "inv"); 616 subs.invitees = addressInfo.html; 617 addressInfo = this.parent.getAddressesFieldInfo(optInvitees, options, "opt"); 618 subs.optInvitees = addressInfo.html; 619 620 // convert to local timezone if necessary 621 var inviteTz = this._invite.getServerStartTimeTz(); 622 var defaultTz = AjxTimezone.getServerId(AjxTimezone.DEFAULT); 623 624 if (inviteTz) { 625 var sd = AjxTimezone.convertTimezone(this._invite.getServerStartDate(null, true), AjxTimezone.getClientId(inviteTz), AjxTimezone.DEFAULT); 626 var ed = AjxTimezone.convertTimezone(this._invite.getServerEndDate(null, true), AjxTimezone.getClientId(inviteTz), AjxTimezone.DEFAULT); 627 628 subs.timezone = AjxTimezone.getMediumName(defaultTz); 629 } 630 631 // duration text 632 var durText = this._invite.getDurationText(null, null, null, true, sd, ed); 633 subs.invDate = durText; 634 635 // recurrence 636 if (this._invite.isRecurring()) { 637 var recur = new ZmRecurrence(); 638 recur.setRecurrenceRules(this._invite.getRecurrenceRules(), this._invite.getServerStartDate()); 639 subs.recur = recur.getBlurb(); 640 } 641 642 // set changes to the invite 643 var changes = this._invite.getChanges(); 644 if (changes && changes[ZmInvite.CHANGES_LOCATION]) { 645 subs.locChangeClass = "InvChanged"; 646 } 647 if (changes && changes[ZmInvite.CHANGES_SUBJECT]) { 648 subs.subjChangeClass = "InvChanged"; 649 } 650 if (changes && changes[ZmInvite.CHANGES_TIME]) { 651 subs.timeChangeClass = "InvChanged"; 652 } 653 }; 654 655 ZmInviteMsgView.truncateBodyContent = 656 function(content, isHtml) { 657 if (!content) return content; 658 var sepIdx = content.indexOf(ZmItem.NOTES_SEPARATOR); 659 if (sepIdx == -1) { 660 return content; 661 } 662 if (isHtml) { 663 //if it is a html content then just remove the content and preserve the html tags 664 //surrounding the content. 665 content = content.replace("<div>"+ ZmItem.NOTES_SEPARATOR +"</div>", ZmItem.NOTES_SEPARATOR); // Striping div if ZmItem.NOTES_SEPARATOR is part of div. 666 content = content.replace(ZmItem.NOTES_SEPARATOR, "<div id='separatorId'>" + ZmItem.NOTES_SEPARATOR + "</div>"); 667 var divEle = document.createElement("div"); 668 divEle.innerHTML = content; 669 var node = Dwt.byId("separatorId",divEle) ; 670 if (node){ 671 var parent = node.parentNode 672 // Removing all previousSiblings of node that contains ZmItem.NOTES_SEPARATOR 673 while(node.previousSibling){ 674 parent.removeChild(node.previousSibling); 675 } 676 parent.removeChild(node); 677 } 678 return divEle.innerHTML; 679 } 680 return content.substring(sepIdx+ZmItem.NOTES_SEPARATOR.length); 681 }; 682 683 ZmInviteMsgView.prototype._getCounterToolbar = 684 function() { 685 var params = { 686 parent: this.parent, 687 buttons: [ZmOperation.ACCEPT_PROPOSAL, ZmOperation.DECLINE_PROPOSAL], 688 posStyle: DwtControl.STATIC_STYLE, 689 className: "ZmCounterToolBar", 690 buttonClassName: "DwtToolbarButton", 691 context: this.mode, 692 toolbarType: ZmId.TB_COUNTER 693 }; 694 var tb = new ZmButtonToolBar(params); 695 696 var listener = new AjxListener(this, this._inviteToolBarListener); 697 for (var i = 0; i < tb.opList.length; i++) { 698 tb.addSelectionListener(tb.opList[i], listener); 699 } 700 701 return tb; 702 }; 703 704 /** 705 * returns the toolbar. Creates a new one only if it's not already set to the internal field 706 */ 707 ZmInviteMsgView.prototype.getInviteToolbar = 708 function() { 709 if (!this._inviteToolbar) { 710 this._inviteToolbar = this._createInviteToolbar(); 711 //hide it till needed. Just in case after the fix I submit with this, some future change will call it before needs to be displayed. 712 this._inviteToolbar.setDisplay(Dwt.DISPLAY_NONE); 713 714 } 715 return this._inviteToolbar; 716 }; 717 718 719 ZmInviteMsgView.prototype._createInviteToolbar = 720 function() { 721 var replyButtonIds = [ 722 ZmOperation.INVITE_REPLY_ACCEPT, 723 ZmOperation.INVITE_REPLY_TENTATIVE, 724 ZmOperation.INVITE_REPLY_DECLINE 725 ]; 726 var notifyOperationButtonIds = [ 727 ZmOperation.REPLY_ACCEPT_NOTIFY, 728 ZmOperation.REPLY_TENTATIVE_NOTIFY, 729 ZmOperation.REPLY_DECLINE_NOTIFY 730 ]; 731 var ignoreOperationButtonIds = [ 732 ZmOperation.REPLY_ACCEPT_IGNORE, 733 ZmOperation.REPLY_TENTATIVE_IGNORE, 734 ZmOperation.REPLY_DECLINE_IGNORE 735 ]; 736 var inviteOps = [ 737 ZmOperation.REPLY_ACCEPT, 738 ZmOperation.REPLY_TENTATIVE, 739 ZmOperation.REPLY_DECLINE, 740 ZmOperation.PROPOSE_NEW_TIME 741 ]; 742 743 var params = { 744 parent: this.parent, 745 buttons: inviteOps, 746 posStyle: DwtControl.STATIC_STYLE, 747 className: "ZmInviteToolBar", 748 buttonClassName: "DwtToolbarButton", 749 context: this.parent.getHTMLElId(), 750 toolbarType: ZmId.TB_INVITE 751 }; 752 var tb = new ZmButtonToolBar(params); 753 754 var listener = new AjxListener(this, this._inviteToolBarListener); 755 for (var i = 0; i < tb.opList.length; i++) { 756 var id = tb.opList[i]; 757 758 tb.addSelectionListener(id, listener); 759 760 if (id == ZmOperation.PROPOSE_NEW_TIME) { continue; } 761 762 var button = tb.getButton(id); 763 var standardItems = [notifyOperationButtonIds[i], replyButtonIds[i], ignoreOperationButtonIds[i]]; 764 var menu = new ZmActionMenu({parent:button, menuItems:standardItems}); 765 standardItems = menu.opList; 766 for (var j = 0; j < standardItems.length; j++) { 767 var menuItem = menu.getItem(j); 768 menuItem.addSelectionListener(listener); 769 } 770 button.setMenu(menu); 771 } 772 773 this._respondOnBehalfLabel = tb.addFiller(); 774 tb.addFiller(); 775 776 // folder picker 777 this._inviteMoveSelect = new DwtSelect({parent:tb}); 778 this._inviteMoveSelect.setVisible(false); //by default hide it. bug 74254 779 780 return tb; 781 }; 782 783 ZmInviteMsgView.prototype._inviteToolBarListener = 784 function(ev) { 785 ev._inviteReplyType = ev.item.getData(ZmOperation.KEY_ID); 786 ev._inviteReplyFolderId = ((this._inviteMoveSelect && this._inviteMoveSelect.getValue()) || ZmOrganizer.ID_CALENDAR); 787 ev._inviteComponentId = null; 788 ev._msg = this._msg; 789 this.parent.notifyListeners(ZmInviteMsgView.REPLY_INVITE_EVENT, ev); 790 }; 791 792 ZmInviteMsgView.prototype._dayResultsCallback = 793 function(dayViewCallback, invitedHour, list, skipMiniCalUpdate, query) { 794 if (this._dayView) { 795 this._dayView.set(list, true); 796 this._dayView._scrollToTime(invitedHour); 797 } 798 if (dayViewCallback) { 799 dayViewCallback.run(); 800 } 801 }; 802 803 ZmInviteMsgView.prototype.getDayView = 804 function() { 805 return this._dayView; 806 }; 807 808 ZmInviteMsgView.prototype._apptSelectionListener = 809 function(ev) { 810 if (ev.detail == DwtListView.ITEM_DBL_CLICKED) { 811 var appt = ev.item; 812 if (appt.isPrivate() && appt.getFolder().isRemote() && !appt.getFolder().hasPrivateAccess()) { 813 var msgDialog = appCtxt.getMsgDialog(); 814 msgDialog.setMessage(ZmMsg.apptIsPrivate, DwtMessageDialog.INFO_STYLE); 815 msgDialog.popup(); 816 } else { 817 // open a appointment view 818 var cc = AjxDispatcher.run("GetCalController"); 819 cc._showAppointmentDetails(appt); 820 } 821 } 822 }; 823 824 ZmInviteMsgView.prototype.scrollToInvite = 825 function() { 826 var inviteDate = this._getInviteDate(); 827 if ((inviteDate != null) && this._dayView) { 828 this._dayView._scrollToTime(inviteDate.getHours()); 829 } 830 } 831 832 ZmInviteMsgView.prototype.repositionCounterToolbar = 833 function(hdrTableId) { 834 if (this._invite && this._invite.hasCounterMethod() && hdrTableId && this._counterToolbar) { 835 this._counterToolbar.reparentHtmlElement(hdrTableId + '_counterToolbar', 0); 836 } 837 } 838