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 * Does nothing. 26 * @constructor 27 * @class 28 * This static class provides utility functions for dealing with appointments 29 * and their related forms and views. 30 * 31 * @author Parag Shah 32 * @author Conrad Damon 33 * 34 * - Helper methods shared by several views associated w/ creating new appointments. 35 * XXX: move to new files when fully baked! 36 * 37 * @private 38 */ 39 ZmApptViewHelper = function() { 40 }; 41 42 ZmApptViewHelper.REPEAT_OPTIONS = [ 43 { label: ZmMsg.none, value: "NON", selected: true }, 44 { label: ZmMsg.everyDay, value: "DAI", selected: false }, 45 { label: ZmMsg.everyWeek, value: "WEE", selected: false }, 46 { label: ZmMsg.everyMonth, value: "MON", selected: false }, 47 { label: ZmMsg.everyYear, value: "YEA", selected: false }, 48 { label: ZmMsg.custom, value: "CUS", selected: false }]; 49 50 51 ZmApptViewHelper.SHOWAS_OPTIONS = [ 52 { label: ZmMsg.free, value: "F", selected: false }, 53 { label: ZmMsg.organizerTentative, value: "T", selected: false }, 54 { label: ZmMsg.busy, value: "B", selected: true }, 55 { label: ZmMsg.outOfOffice, value: "O", selected: false } 56 ]; 57 58 /** 59 * returns the label of the option specified by it's value. This is used in calendar.Appointment#Tooltip template 60 * 61 * @param value 62 * returns the label 63 */ 64 ZmApptViewHelper.getShowAsOptionLabel = 65 function(value) { 66 67 for (var i = 0; i < ZmApptViewHelper.SHOWAS_OPTIONS.length; i++) { 68 var option = ZmApptViewHelper.SHOWAS_OPTIONS[i]; 69 if (option.value == value) { 70 return option.label; 71 } 72 } 73 }; 74 75 76 /** 77 * Gets an object with the indices of the currently selected time fields. 78 * 79 * @param {ZmApptEditView} tabView the edit/tab view containing time widgets 80 * @param {Hash} dateInfo a hash of date info to fill in 81 */ 82 ZmApptViewHelper.getDateInfo = 83 function(tabView, dateInfo) { 84 dateInfo.startDate = tabView._startDateField.value; 85 dateInfo.endDate = tabView._endDateField.value; 86 var tzoneSelect = tabView._tzoneSelect || tabView._tzoneSelectStart; 87 dateInfo.timezone = tzoneSelect ? tzoneSelect.getValue() : ""; 88 if (tabView._allDayCheckbox && tabView._allDayCheckbox.checked) { 89 dateInfo.showTime = false; 90 91 //used by DwtTimeInput - advanced time picker 92 dateInfo.startTimeStr = dateInfo.endTimeStr = null; 93 94 //used by DwtTimeSelect 95 dateInfo.startHourIdx = dateInfo.startMinuteIdx = dateInfo.startAmPmIdx = 96 dateInfo.endHourIdx = dateInfo.endMinuteIdx = dateInfo.endAmPmIdx = null; 97 98 dateInfo.isAllDay = true; 99 } else { 100 dateInfo.showTime = true; 101 102 if(tabView._startTimeSelect instanceof DwtTimeSelect) { 103 dateInfo.startHourIdx = tabView._startTimeSelect.getSelectedHourIdx(); 104 dateInfo.startMinuteIdx = tabView._startTimeSelect.getSelectedMinuteIdx(); 105 dateInfo.startAmPmIdx = tabView._startTimeSelect.getSelectedAmPmIdx(); 106 dateInfo.endHourIdx = tabView._endTimeSelect.getSelectedHourIdx(); 107 dateInfo.endMinuteIdx = tabView._endTimeSelect.getSelectedMinuteIdx(); 108 dateInfo.endAmPmIdx = tabView._endTimeSelect.getSelectedAmPmIdx(); 109 }else { 110 dateInfo.startHourIdx = dateInfo.startMinuteIdx = dateInfo.startAmPmIdx = 111 dateInfo.endHourIdx = dateInfo.endMinuteIdx = dateInfo.endAmPmIdx = null; 112 } 113 114 if(tabView._startTimeSelect instanceof DwtTimeInput) { 115 dateInfo.startTimeStr = tabView._startTimeSelect.getTimeString(); 116 dateInfo.endTimeStr = tabView._endTimeSelect.getTimeString(); 117 }else { 118 dateInfo.startTimeStr = dateInfo.endTimeStr = null; 119 } 120 121 dateInfo.isAllDay = false; 122 } 123 }; 124 125 ZmApptViewHelper.handleDateChange = 126 function(startDateField, endDateField, isStartDate, skipCheck, oldStartDate) { 127 var needsUpdate = false; 128 var sd = AjxDateUtil.simpleParseDateStr(startDateField.value); 129 var ed = AjxDateUtil.simpleParseDateStr(endDateField.value); 130 131 // if start date changed, reset end date if necessary 132 if (isStartDate) { 133 // if date was input by user and it's foobar, reset to today's date 134 if (!skipCheck) { 135 if (sd == null || isNaN(sd)) { 136 sd = new Date(); 137 } 138 // always reset the field value in case user entered date in wrong format 139 startDateField.value = AjxDateUtil.simpleComputeDateStr(sd); 140 } 141 142 if (ed.valueOf() < sd.valueOf()) { 143 endDateField.value = startDateField.value; 144 }else if(oldStartDate != null) { 145 var delta = ed.getTime() - oldStartDate.getTime(); 146 var newEndDate = new Date(sd.getTime() + delta); 147 endDateField.value = AjxDateUtil.simpleComputeDateStr(newEndDate); 148 } 149 needsUpdate = true; 150 } else { 151 // if date was input by user and it's foobar, reset to today's date 152 if (!skipCheck) { 153 if (ed == null || isNaN(ed)) { 154 ed = new Date(); 155 } 156 // always reset the field value in case user entered date in wrong format 157 endDateField.value = AjxDateUtil.simpleComputeDateStr(ed); 158 } 159 160 // otherwise, reset start date if necessary 161 if (sd.valueOf() > ed.valueOf()) { 162 startDateField.value = endDateField.value; 163 needsUpdate = true; 164 } 165 } 166 167 return needsUpdate; 168 }; 169 170 ZmApptViewHelper.getApptToolTipText = 171 function(origAppt, controller) { 172 if(origAppt._toolTip) { 173 return origAppt._toolTip; 174 } 175 var appt = ZmAppt.quickClone(origAppt); 176 var organizer = appt.getOrganizer(); 177 var sentBy = appt.getSentBy(); 178 var userName = appCtxt.get(ZmSetting.USERNAME); 179 if (sentBy || (organizer && organizer != userName)) { 180 organizer = (appt.message && appt.message.invite && appt.message.invite.getOrganizerName()) || organizer; 181 if (sentBy) { 182 var contactsApp = appCtxt.getApp(ZmApp.CONTACTS); 183 var contact = contactsApp && contactsApp.getContactByEmail(sentBy); 184 sentBy = (contact && contact.getFullName()) || sentBy; 185 } 186 } else { 187 organizer = null; 188 sentBy = null; 189 } 190 191 var params = { 192 appt: appt, 193 cal: (appt.folderId != ZmOrganizer.ID_CALENDAR && controller) ? controller.getCalendar() : null, 194 organizer: organizer, 195 sentBy: sentBy, 196 when: appt.getDurationText(false, false), 197 location: appt.getLocation(), 198 width: "250", 199 hideAttendees: true 200 }; 201 202 var toolTip = origAppt._toolTip = AjxTemplate.expand("calendar.Appointment#Tooltip", params); 203 return toolTip; 204 }; 205 206 207 ZmApptViewHelper.getDayToolTipText = 208 function(date, list, controller, noheader, emptyMsg, isMinical, getSimpleToolTip) { 209 210 if (!emptyMsg) { 211 emptyMsg = ZmMsg.noAppts; 212 } 213 214 var html = new AjxBuffer(); 215 216 var formatter = DwtCalendar.getDateFullFormatter(); 217 var title = formatter.format(date); 218 219 html.append("<div>"); 220 221 html.append("<table cellpadding='0' cellspacing='0' border='0'>"); 222 if (!noheader) html.append("<tr><td><div class='calendar_tooltip_month_day_label'>", title, "</div></td></tr>"); 223 html.append("<tr><td>"); 224 html.append("<table cellpadding='1' cellspacing='0' border='0'>"); 225 226 var size = list ? list.size() : 0; 227 228 var useEmptyMsg = true; 229 var dateTime = date.getTime(); 230 for (var i = 0; i < size; i++) { 231 var ao = list.get(i); 232 var isAllDay = ao.isAllDayEvent(); 233 if (isAllDay || getSimpleToolTip) { 234 // Multi-day "appts/all day events" will be broken up into one sub-appt per day, so only show 235 // the one that matches the selected date 236 var apptDate = new Date(ao.startDate.getTime()); 237 apptDate.setHours(0,0,0,0); 238 if (apptDate.getTime() != dateTime) continue; 239 } 240 241 if (isAllDay && !getSimpleToolTip) { 242 useEmptyMsg = false; 243 if(!isMinical && ao.toString() == "ZmAppt") { 244 html.append("<tr><td><div class=appt>"); 245 html.append(ZmApptViewHelper.getApptToolTipText(ao, controller)); 246 html.append("</div></td></tr>"); 247 } 248 else { 249 //DBG.println("AO "+ao); 250 var widthField = AjxEnv.isIE ? "width:500px;" : "min-width:300px;"; 251 html.append("<tr><td><div style='" + widthField + "' class=appt>"); 252 html.append(ZmApptViewHelper._allDayItemHtml(ao, Dwt.getNextId(), controller, true, true)); 253 html.append("</div></td></tr>"); 254 } 255 } 256 else { 257 useEmptyMsg = false; 258 if (!isMinical && ao.toString() == "ZmAppt") { 259 html.append("<tr><td><div class=appt>"); 260 html.append(ZmApptViewHelper.getApptToolTipText(ao, controller)); 261 html.append("</div></td></tr>"); 262 } 263 else { 264 var color = ZmCalendarApp.COLORS[controller.getCalendarColor(ao.folderId)]; 265 var isNew = ao.status == ZmCalBaseItem.PSTATUS_NEEDS_ACTION; 266 html.append("<tr><td class='calendar_month_day_item'><div class='", color, isNew ? "DarkC" : "C", "'>"); 267 if (isNew) html.append("<b>"); 268 269 var dur; 270 if (isAllDay) { 271 dur = ao._orig.getDurationText(false, false, true) 272 } 273 else { 274 //html.append("• "); 275 //var dur = ao.getShortStartHour(); 276 dur = getSimpleToolTip ? ao._orig.getDurationText(false,false,true) : ao.getDurationText(false,false); 277 } 278 html.append(dur); 279 if (dur != "") { 280 html.append(" "); 281 if (isAllDay) { 282 html.append("- "); 283 } 284 } 285 html.append(AjxStringUtil.htmlEncode(ao.getName())); 286 287 if (isNew) html.append("</b>"); 288 html.append("</div>"); 289 html.append("</td></tr>"); 290 } 291 } 292 } 293 if (useEmptyMsg) { 294 html.append("<tr><td>"+emptyMsg+"</td></tr>"); 295 } 296 html.append("</table>"); 297 html.append("</td></tr></table>"); 298 html.append("</div>"); 299 300 return html.toString(); 301 }; 302 303 /** 304 * Returns a list of calendars based on certain conditions. Especially useful 305 * for multi-account 306 * 307 * @param folderSelect [DwtSelect] DwtSelect object to populate 308 * @param folderRow [HTMLElement] Table row element to show/hide 309 * @param calendarOrgs [Object] Hash map of calendar ID to calendar owner 310 * @param calItem [ZmCalItem] a ZmAppt or ZmTask object 311 */ 312 ZmApptViewHelper.populateFolderSelect = 313 function(folderSelect, folderRow, calendarOrgs, calItem) { 314 // get calendar folders (across all accounts) 315 var org = ZmOrganizer.ITEM_ORGANIZER[calItem.type]; 316 var data = []; 317 var folderTree; 318 var accounts = appCtxt.accountList.visibleAccounts; 319 for (var i = 0; i < accounts.length; i++) { 320 var acct = accounts[i]; 321 322 var appEnabled = ZmApp.SETTING[ZmItem.APP[calItem.type]]; 323 if ((appCtxt.isOffline && acct.isMain) || 324 !appCtxt.get(appEnabled, null, acct)) 325 { 326 continue; 327 } 328 329 folderTree = appCtxt.getFolderTree(acct); 330 data = data.concat(folderTree.getByType(org)); 331 } 332 333 // add the local account last for multi-account 334 if (appCtxt.isOffline) { 335 folderTree = appCtxt.getFolderTree(appCtxt.accountList.mainAccount); 336 data = data.concat(folderTree.getByType(org)); 337 } 338 339 folderSelect.clearOptions(); 340 341 for (var i = 0; i < data.length; i++) { 342 var cal = data[i]; 343 var acct = cal.getAccount(); 344 345 if (cal.noSuchFolder || cal.isFeed() || (cal.link && cal.isReadOnly()) || cal.isInTrash()) { continue; } 346 347 if (appCtxt.multiAccounts && 348 cal.nId == ZmOrganizer.ID_CALENDAR && 349 acct.isCalDavBased()) 350 { 351 continue; 352 } 353 354 var id = cal.link ? cal.getRemoteId() : cal.id; 355 calendarOrgs[id] = cal.owner; 356 357 // bug: 28363 - owner attribute is not available for shared sub folders 358 if (cal.isRemote() && !cal.owner && cal.parent && cal.parent.isRemote()) { 359 calendarOrgs[id] = cal.parent.getOwner(); 360 } 361 362 var selected = ((calItem.folderId == cal.id) || (calItem.folderId == id)); 363 var icon = appCtxt.multiAccounts ? acct.getIcon() : cal.getIconWithColor(); 364 var name = AjxStringUtil.htmlDecode(appCtxt.multiAccounts 365 ? ([cal.getName(), " (", acct.getDisplayName(), ")"].join("")) 366 : cal.getName()); 367 var option = new DwtSelectOption(id, selected, name, null, null, icon); 368 folderSelect.addOption(option, selected); 369 } 370 371 ZmApptViewHelper.folderSelectResize(folderSelect); 372 //todo: new ui hide folder select if there is only one folder 373 }; 374 375 /** 376 * Takes a string, AjxEmailAddress, or contact/resource and returns 377 * a ZmContact or a ZmResource. If the attendee cannot be found in 378 * contacts, locations, or equipment, a new contact or 379 * resource is created and initialized. 380 * 381 * @param item [object] string, AjxEmailAddress, ZmContact, or ZmResource 382 * @param type [constant]* attendee type 383 * @param strictText [boolean]* if true, new location will not be created from free text 384 * @param strictEmail [boolean]* if true, new attendee will not be created from email address 385 */ 386 ZmApptViewHelper.getAttendeeFromItem = 387 function(item, type, strictText, strictEmail, checkForAvailability) { 388 389 if (!item || !type) return null; 390 391 if (type == ZmCalBaseItem.LOCATION && !ZmApptViewHelper._locations) { 392 if (!appCtxt.get(ZmSetting.GAL_ENABLED)) { 393 //if GAL is disabled then user does not have permission to load locations. 394 return null; 395 } 396 var locations = ZmApptViewHelper._locations = appCtxt.getApp(ZmApp.CALENDAR).getLocations(); 397 if(!locations.isLoaded) { 398 locations.load(); 399 } 400 401 } 402 if (type == ZmCalBaseItem.EQUIPMENT && !ZmApptViewHelper._equipment) { 403 if (!appCtxt.get(ZmSetting.GAL_ENABLED)) { 404 //if GAL is disabled then user does not have permission to load equipment. 405 return null; 406 } 407 var equipment = ZmApptViewHelper._equipment = appCtxt.getApp(ZmApp.CALENDAR).getEquipment(); 408 if(!equipment.isLoaded) { 409 equipment.load(); 410 } 411 } 412 413 var attendee = null; 414 if (item.type == ZmItem.CONTACT || item.type == ZmItem.GROUP || item.type == ZmItem.RESOURCE) { 415 // it's already a contact or resource, return it as is 416 attendee = item; 417 } else if (item instanceof AjxEmailAddress) { 418 var addr = item.getAddress(); 419 // see if we have this contact/resource by checking email address 420 attendee = ZmApptViewHelper._getAttendeeFromAddr(addr, type); 421 422 // Bug 7837: preserve the email address as it was typed 423 // instead of using the contact's primary email. 424 if (attendee && (type === ZmCalBaseItem.PERSON || type === ZmCalBaseItem.GROUP)) { 425 attendee = AjxUtil.createProxy(attendee); 426 attendee._inviteAddress = addr; 427 attendee.getEmail = function() { 428 return this._inviteAddress || this.constructor.prototype.getEmail.apply(this); 429 }; 430 } 431 432 if (!checkForAvailability && !attendee && !strictEmail) { 433 // AjxEmailAddress has name and email, init a new contact/resource from those 434 if (type === ZmCalBaseItem.PERSON) { 435 attendee = new ZmContact(null, null, ZmItem.CONTACT); 436 } 437 else if (type === ZmCalBaseItem.GROUP) { 438 attendee = new ZmContact(null, null, ZmItem.GROUP); 439 } 440 else { 441 attendee = new ZmResource(type); 442 } 443 attendee.initFromEmail(item, true); 444 } 445 attendee.canExpand = item.canExpand; 446 var ac = window.parentAppCtxt || window.appCtxt; 447 ac.setIsExpandableDL(addr, attendee.canExpand); 448 } else if (typeof item == "string") { 449 item = AjxStringUtil.trim(item); // trim white space 450 item = item.replace(/;$/, ""); // trim separator 451 // see if it's an email we can use for lookup 452 var email = AjxEmailAddress.parse(item); 453 if (email) { 454 var addr = email.getAddress(); 455 // is it a contact/resource we already know about? 456 attendee = ZmApptViewHelper._getAttendeeFromAddr(addr, type); 457 if (!checkForAvailability && !attendee && !strictEmail) { 458 if (type === ZmCalBaseItem.PERSON || type === ZmCalBaseItem.FORWARD) { 459 attendee = new ZmContact(null, null, ZmItem.CONTACT); 460 } 461 else if (type === ZmCalBaseItem.GROUP) { 462 attendee = new ZmContact(null, null, ZmItem.GROUP); 463 } 464 else if (type === ZmCalBaseItem.LOCATION) { 465 attendee = new ZmResource(null, ZmApptViewHelper._locations, ZmCalBaseItem.LOCATION); 466 } 467 else if (type === ZmCalBaseItem.EQUIPMENT) { 468 attendee = new ZmResource(null, ZmApptViewHelper._equipment, ZmCalBaseItem.EQUIPMENT); 469 } 470 attendee.initFromEmail(email, true); 471 } else if (attendee && (type === ZmCalBaseItem.PERSON || type === ZmCalBaseItem.GROUP)) { 472 // remember actual address (in case it's email2 or email3) 473 attendee._inviteAddress = addr; 474 attendee.getEmail = function() { 475 return this._inviteAddress || this.constructor.prototype.getEmail.apply(this); 476 }; 477 } 478 } 479 } 480 return attendee; 481 }; 482 483 ZmApptViewHelper._getAttendeeFromAddr = 484 function(addr, type) { 485 486 var attendee = null; 487 if (type === ZmCalBaseItem.PERSON || type === ZmCalBaseItem.GROUP || type === ZmCalBaseItem.FORWARD) { 488 var contactsApp = appCtxt.getApp(ZmApp.CONTACTS); 489 attendee = contactsApp && contactsApp.getContactByEmail(addr); 490 } else if (type == ZmCalBaseItem.LOCATION) { 491 attendee = ZmApptViewHelper._locations.getResourceByEmail(addr); 492 } else if (type == ZmCalBaseItem.EQUIPMENT) { 493 attendee = ZmApptViewHelper._equipment.getResourceByEmail(addr); 494 } 495 return attendee; 496 }; 497 498 /** 499 * Returns a AjxEmailAddress for the organizer. 500 * 501 * @param organizer [string]* organizer's email address 502 * @param account [ZmAccount]* organizer's account 503 */ 504 ZmApptViewHelper.getOrganizerEmail = 505 function(organizer, account) { 506 var orgAddress = organizer ? organizer : appCtxt.get(ZmSetting.USERNAME, null, account); 507 var orgName = (orgAddress == appCtxt.get(ZmSetting.USERNAME, null, account)) 508 ? appCtxt.get(ZmSetting.DISPLAY_NAME, null, account) : null; 509 return new AjxEmailAddress(orgAddress, null, orgName); 510 }; 511 512 ZmApptViewHelper.getAddressEmail = 513 function(email, isIdentity) { 514 var orgAddress = email ? email : appCtxt.get(ZmSetting.USERNAME); 515 var orgName; 516 if(email == appCtxt.get(ZmSetting.USERNAME)){ 517 orgName = appCtxt.get(ZmSetting.DISPLAY_NAME); 518 }else{ 519 //Identity 520 var iCol = appCtxt.getIdentityCollection(), 521 identity = iCol ? iCol.getIdentityBySendAddress(orgAddress) : ""; 522 if(identity){ 523 orgName = identity.sendFromDisplay; 524 } 525 } 526 return new AjxEmailAddress(orgAddress, null, orgName); 527 }; 528 529 /** 530 * Creates a string from a list of attendees/locations/resources. If an item 531 * doesn't have a name, its address is used. 532 * 533 * @param list [array] list of attendees (ZmContact or ZmResource) 534 * @param type [constant] attendee type 535 * @param includeDisplayName [boolean]* if true, include location info in parens (ZmResource) 536 * @param includeRole [boolean]* if true, include attendee role 537 */ 538 ZmApptViewHelper.getAttendeesString = 539 function(list, type, includeDisplayName, includeRole) { 540 if (!(list && list.length)) return ""; 541 542 var a = []; 543 for (var i = 0; i < list.length; i++) { 544 var attendee = list[i]; 545 var text = ZmApptViewHelper.getAttendeesText(attendee, type); 546 if (includeDisplayName && list.length == 1) { 547 var displayName = attendee.getAttr(ZmResource.F_locationName); 548 if (displayName) { 549 text = [text, " (", displayName, ")"].join(""); 550 } 551 } 552 if(includeRole) { 553 text += " " + (attendee.getParticipantRole() || ZmCalItem.ROLE_REQUIRED); 554 } 555 a.push(text); 556 } 557 558 return a.join(ZmAppt.ATTENDEES_SEPARATOR); 559 }; 560 561 ZmApptViewHelper.getAttendeesText = 562 function(attendee, type, shortForm) { 563 564 //give preference to lookup email is the attendee object is located by looking up email address 565 var lookupEmailObj = attendee.getLookupEmail(true); 566 if(lookupEmailObj) { 567 return lookupEmailObj.toString(shortForm || (type && type !== ZmCalBaseItem.PERSON && type !== ZmCalBaseItem.GROUP)); 568 } 569 570 return attendee.getAttendeeText(type, shortForm); 571 }; 572 573 /** 574 * Creates a string of attendees by role. If an item 575 * doesn't have a name, its address is used. 576 * 577 * calls common code from mail msg view to get the collapse/expand "show more" funcitonality for large lists. 578 * 579 * @param list [array] list of attendees (ZmContact or ZmResource) 580 * @param type [constant] attendee type 581 * @param role [constant] attendee role 582 * @param count [number] number of attendees to be returned 583 */ 584 ZmApptViewHelper.getAttendeesByRoleCollapsed = 585 function(list, type, role, objectManager, htmlElId) { 586 if (!(list && list.length)) return ""; 587 var attendees = ZmApptViewHelper.getAttendeesArrayByRole(list, role); 588 589 var emails = []; 590 for (var i = 0; i < attendees.length; i++) { 591 var att = attendees[i]; 592 emails.push(new AjxEmailAddress(att.getEmail(), type, att.getFullName(), att.getFullName())); 593 } 594 595 var options = {}; 596 options.shortAddress = appCtxt.get(ZmSetting.SHORT_ADDRESS); 597 var addressInfo = ZmMailMsgView.getAddressesFieldHtmlHelper(emails, options, 598 role, objectManager, htmlElId); 599 return addressInfo.html; 600 }; 601 602 /** 603 * Creates a string of attendees by role. this allows to show only count elements, with "..." appended. 604 * 605 * @param list [array] list of attendees (ZmContact or ZmResource) 606 * @param type [constant] attendee type 607 * @param role [constant] attendee role 608 * @param count [number] number of attendees to be returned 609 */ 610 ZmApptViewHelper.getAttendeesByRole = 611 function(list, type, role, count) { 612 if (!(list && list.length)) return ""; 613 614 var res = []; 615 616 var attendees = ZmApptViewHelper.getAttendeesArrayByRole(list, role); 617 for (var i = 0; i < attendees.length; i++) { 618 if (count && i > count) { 619 res.push(" ..."); 620 break; 621 } 622 if (i > 0) { 623 res.push(ZmAppt.ATTENDEES_SEPARATOR); 624 } 625 res.push(attendees[i].getAttendeeText(type)); 626 } 627 return res.join(""); 628 }; 629 630 631 632 /** 633 * returns array of attendees by role. 634 * 635 * @param list [array] list of attendees (ZmContact or ZmResource) 636 * @param role [constant] attendee role 637 */ 638 ZmApptViewHelper.getAttendeesArrayByRole = 639 function(list, role, count) { 640 641 if (!(list && list.length)) { 642 return []; 643 } 644 645 var a = []; 646 for (var i = 0; i < list.length; i++) { 647 var attendee = list[i]; 648 var attendeeRole = attendee.getParticipantRole() || ZmCalItem.ROLE_REQUIRED; 649 if (attendeeRole === role){ 650 a.push(attendee); 651 } 652 } 653 return a; 654 }; 655 656 ZmApptViewHelper._allDayItemHtml = 657 function(appt, id, controller, first, last) { 658 var isNew = appt.ptst == ZmCalBaseItem.PSTATUS_NEEDS_ACTION; 659 var isAccepted = appt.ptst == ZmCalBaseItem.PSTATUS_ACCEPT; 660 var calendar = appt.getFolder(); 661 AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar"]); 662 663 var tagNames = appt.getVisibleTags(); 664 var tagIcon = last ? appt.getTagImageFromNames(tagNames) : null; 665 666 var fba = isNew ? ZmCalBaseItem.PSTATUS_NEEDS_ACTION : appt.fba; 667 var headerColors = ZmApptViewHelper.getApptColor(isNew, calendar, tagNames, "header"); 668 var headerStyle = ZmCalBaseView._toColorsCss(headerColors.appt); 669 var bodyColors = ZmApptViewHelper.getApptColor(isNew, calendar, tagNames, "body"); 670 var bodyStyle = ZmCalBaseView._toColorsCss(bodyColors.appt); 671 672 var borderLeft = first ? "" : "border-left:0;"; 673 var borderRight = last ? "" : "border-right:0;"; 674 675 var newState = isNew ? "_new" : ""; 676 var subs = { 677 id: id, 678 headerStyle: headerStyle, 679 bodyStyle: bodyStyle, 680 newState: newState, 681 name: first ? AjxStringUtil.htmlEncode(appt.getName()) : " ", 682 // tag: isNew ? "NEW" : "", // HACK: i18n 683 starttime: appt.getDurationText(true, true), 684 endtime: (!appt._fanoutLast && (appt._fanoutFirst || (appt._fanoutNum > 0))) ? "" : ZmCalBaseItem._getTTHour(appt.endDate), 685 location: AjxStringUtil.htmlEncode(appt.getLocation()), 686 status: appt.isOrganizer() ? "" : appt.getParticipantStatusStr(), 687 icon: first && appt.isPrivate() ? "ReadOnly" : null, 688 showAsColor: first ? ZmApptViewHelper._getShowAsColorFromId(fba) : "", 689 showAsClass: first ? "" : "appt_allday" + newState + "_name", 690 boxBorder: ZmApptViewHelper.getBoxBorderFromId(fba), 691 borderLeft: borderLeft, 692 borderRight: borderRight, 693 tagIcon: tagIcon 694 }; 695 ZmApptViewHelper.setupCalendarColor(last, headerColors, tagNames, subs, "headerStyle", null, 1, 1); 696 return AjxTemplate.expand("calendar.Calendar#calendar_appt_allday", subs); 697 }; 698 699 ZmApptViewHelper._getShowAsColorFromId = 700 function(id) { 701 var color = "#4AA6F1"; 702 switch(id) { 703 case ZmCalBaseItem.PSTATUS_NEEDS_ACTION: color = "#FF3300"; break; 704 case "F": color = "#FFFFFF"; break; 705 case "B": color = "#4AA6F1"; break; 706 case "T": color = "#BAE0E3"; break; 707 case "O": color = "#7B5BAC"; break; 708 } 709 var colorCss = Dwt.createLinearGradientCss("#FFFFFF", color, "v"); 710 if (!colorCss) { 711 colorCss = "background-color: " + color + ";"; 712 } 713 return colorCss; 714 }; 715 716 ZmApptViewHelper.getBoxBorderFromId = 717 function(id) { 718 switch(id) { 719 case "F": return "ZmSchedulerApptBorder-free"; 720 case ZmCalBaseItem.PSTATUS_NEEDS_ACTION: 721 case "B": return "ZmSchedulerApptBorder-busy"; 722 case "T": return "ZmSchedulerApptBorder-tentative"; 723 case "O": return "ZmSchedulerApptBorder-outOfOffice"; 724 } 725 return "ZmSchedulerApptBorder-busy"; 726 }; 727 728 /** 729 * Returns a list of attendees with the given role. 730 * 731 * @param {array} list list of attendees 732 * @param {constant} role defines the role of the attendee (required/optional) 733 * 734 * @return {array} a list of attendees 735 */ 736 ZmApptViewHelper.filterAttendeesByRole = 737 function(list, role) { 738 739 var result = []; 740 for (var i = 0; i < list.length; i++) { 741 var attendee = list[i]; 742 var attRole = attendee.getParticipantRole() || ZmCalItem.ROLE_REQUIRED; 743 if (attRole == role){ 744 result.push(attendee); 745 } 746 } 747 return result; 748 }; 749 750 ZmApptViewHelper.getApptColor = 751 function(deeper, calendar, tagNames, segment) { 752 var colors = ZmCalBaseView._getColors(calendar.rgb || ZmOrganizer.COLOR_VALUES[calendar.color]); 753 var calColor = deeper ? colors.deeper[segment] : colors.standard[segment]; 754 var apptColor = calColor; 755 if (tagNames && (tagNames.length == 1)) { 756 var tagList = appCtxt.getAccountTagList(calendar); 757 758 var tag = tagList.getByNameOrRemote(tagNames[0]); 759 if(tag){apptColor = { bgcolor: tag.getColor() };} 760 } 761 return {calendar:calColor, appt:apptColor}; 762 }; 763 764 ZmApptViewHelper.setupCalendarColor = 765 function(last, colors, tagNames, templateData, colorParam, clearParam, peelTopOffset, peelRightOffset, div) { 766 var colorCss = Dwt.createLinearGradientCss("#FFFFFF", colors.appt.bgcolor, "v"); 767 if (colorCss) { 768 templateData[colorParam] = colorCss; 769 if (clearParam) { 770 templateData[clearParam] = null; 771 } 772 } 773 if (last && tagNames && (tagNames.length == 1)) { 774 if (!colorCss) { 775 // Can't use the gradient color. IE masking doesn't work properly for tags on appts; 776 // Since the color is already set in the background, just print the overlay image 777 var match = templateData.tagIcon.match(AjxImg.RE_COLOR); 778 if (match) { 779 templateData.tagIcon = (match && match[1]) + "Overlay"; 780 } 781 } 782 // Tag color has been applied to the appt. Add the calendar peel image 783 templateData.peelIcon = "Peel,color=" + colors.calendar.bgcolor; 784 templateData.peelTop = peelTopOffset; 785 templateData.peelRight = peelRightOffset; 786 } 787 }; 788 789 /** 790 * Gets the attach list as HTML. 791 * 792 * @param {ZmCalItem} calItem calendar item 793 * @param {Object} attach a generic Object contain meta info about the attachment 794 * @param {Boolean} hasCheckbox <code>true</code> to insert a checkbox prior to the attachment 795 * @return {String} the HTML 796 * 797 * TODO: replace string onclick handlers with funcs 798 */ 799 ZmApptViewHelper.getAttachListHtml = 800 function(calItem, attach, hasCheckbox, getLinkIdCallback) { 801 var msgFetchUrl = appCtxt.get(ZmSetting.CSFE_MSG_FETCHER_URI); 802 803 // gather meta data for this attachment 804 var mimeInfo = ZmMimeTable.getInfo(attach.ct); 805 var icon = mimeInfo ? mimeInfo.image : "GenericDoc"; 806 var size = attach.s; 807 var sizeText; 808 if (size != null) { 809 if (size < 1024) sizeText = size + " B"; 810 else if (size < 1024^2) sizeText = Math.round((size/1024) * 10) / 10 + " KB"; 811 else sizeText = Math.round((size / (1024*1024)) * 10) / 10 + " MB"; 812 } 813 814 var html = []; 815 var i = 0; 816 817 // start building html for this attachment 818 html[i++] = "<table border=0 cellpadding=0 cellspacing=0><tr>"; 819 if (hasCheckbox) { 820 html[i++] = "<td width=1%><input type='checkbox' checked value='"; 821 html[i++] = attach.part; 822 html[i++] = "' name='"; 823 html[i++] = ZmCalItem.ATTACHMENT_CHECKBOX_NAME; 824 html[i++] = "'></td>"; 825 } 826 827 var hrefRoot = ["href='", msgFetchUrl, "&id=", calItem.invId, "&part=", attach.part].join(""); 828 html[i++] = "<td width=20><a target='_blank' class='AttLink' "; 829 if (getLinkIdCallback) { 830 var imageLinkId = getLinkIdCallback(attach.part, ZmCalItem.ATT_LINK_IMAGE); 831 html[i++] = "id='"; 832 html[i++] = imageLinkId; 833 html[i++] = "' "; 834 } 835 html[i++] = hrefRoot; 836 html[i++] = "'>"; 837 html[i++] = AjxImg.getImageHtml(icon); 838 839 html[i++] = "</a></td><td><a target='_blank' class='AttLink' "; 840 841 if (appCtxt.get(ZmSetting.MAIL_ENABLED) && attach.ct == ZmMimeTable.MSG_RFC822) { 842 html[i++] = " href='javascript:;' onclick='ZmCalItemView.rfc822Callback("; 843 html[i++] = '"'; 844 html[i++] = calItem.invId; 845 html[i++] = '"'; 846 html[i++] = ",\""; 847 html[i++] = attach.part; 848 html[i++] = "\"); return false;'"; 849 } else { 850 html[i++] = hrefRoot; 851 html[i++] = "'"; 852 } 853 if (getLinkIdCallback) { 854 var mainLinkId = getLinkIdCallback(attach.part, ZmCalItem.ATT_LINK_MAIN); 855 html[i++] = " id='"; 856 html[i++] = mainLinkId; 857 html[i++] = "'"; 858 } 859 html[i++] = ">"; 860 html[i++] = AjxStringUtil.htmlEncode(attach.filename); 861 html[i++] = "</a>"; 862 863 var addHtmlLink = (appCtxt.get(ZmSetting.VIEW_ATTACHMENT_AS_HTML) && 864 attach.body == null && ZmMimeTable.hasHtmlVersion(attach.ct)); 865 866 if (sizeText || addHtmlLink) { 867 html[i++] = " ("; 868 if (sizeText) { 869 html[i++] = sizeText; 870 html[i++] = ") "; 871 } 872 var downloadLinkId = ""; 873 if (getLinkIdCallback) { 874 downloadLinkId = getLinkIdCallback(attach.part, ZmCalItem.ATT_LINK_DOWNLOAD); 875 } 876 if (addHtmlLink) { 877 html[i++] = "<a style='text-decoration:underline' target='_blank' class='AttLink' "; 878 if (getLinkIdCallback) { 879 html[i++] = "id='"; 880 html[i++] = downloadLinkId; 881 html[i++] = "' "; 882 } 883 html[i++] = hrefRoot; 884 html[i++] = "&view=html'>"; 885 html[i++] = ZmMsg.preview; 886 html[i++] = "</a> "; 887 } 888 if (attach.ct != ZmMimeTable.MSG_RFC822) { 889 html[i++] = "<a style='text-decoration:underline' class='AttLink' onclick='ZmZimbraMail.unloadHackCallback();' "; 890 if (getLinkIdCallback) { 891 html[i++] = " id='"; 892 html[i++] = downloadLinkId; 893 html[i++] = "' "; 894 } 895 html[i++] = hrefRoot; 896 html[i++] = "&disp=a'>"; 897 html[i++] = ZmMsg.download; 898 html[i++] = "</a>"; 899 } 900 } 901 902 html[i++] = "</td></tr></table>"; 903 904 // Provide lookup id and label for offline mode 905 if (!attach.mid) { 906 attach.mid = calItem.invId; 907 attach.label = attach.filename; 908 } 909 910 return html.join(""); 911 }; 912 913 /** 914 * @param {DwtSelect} folderSelect 915 * 916 * TODO: set the width for folderSelect once the image icon gets loaded if any 917 */ 918 ZmApptViewHelper.folderSelectResize = 919 function(folderSelect) { 920 921 var divEl = folderSelect._containerEl, 922 childNodes, 923 img; 924 925 if (divEl) { 926 childNodes = divEl.childNodes[0]; 927 if (childNodes) { 928 img = childNodes.getElementsByTagName("img")[0]; 929 if (img) { 930 img.onload = function() { 931 divEl.style.width = childNodes.offsetWidth || "auto";// offsetWidth doesn't work in IE if the element or one of its parents has display:none 932 img.onload = ""; 933 } 934 } 935 } 936 } 937 }; 938