1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 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) 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 calendar item edit view. 26 * @constructor 27 * @class 28 * This is the main screen for creating/editing an appointment. It provides 29 * inputs for the various appointment details. 30 * 31 * @author Parag Shah 32 * 33 * @param {DwtControl} parent some container 34 * @param {Hash} attendees attendees/locations/equipment 35 * @param {Object} dateInfo a hash of date info 36 * @param {ZmController} controller the compose controller for this view 37 * 38 * @extends ZmCalItemEditView 39 * 40 * @private 41 */ 42 ZmApptEditView = function(parent, attendees, controller, dateInfo) { 43 44 var idParams = { 45 skinComponent: ZmId.SKIN_APP_MAIN, 46 app: ZmId.APP_CALENDAR, 47 componentType: ZmId.WIDGET_VIEW, 48 componentName: ZmId.VIEW_APPOINTMENT 49 }; 50 51 var domId = ZmId.create(idParams, "An appointment editing view"); 52 53 ZmCalItemEditView.call(this, parent, attendees, controller, dateInfo, null, "ZmApptEditView", domId); 54 55 // cache so we dont keep calling appCtxt 56 this.GROUP_CALENDAR_ENABLED = appCtxt.get(ZmSetting.GROUP_CALENDAR_ENABLED); 57 58 this._attTypes = []; 59 if (this.GROUP_CALENDAR_ENABLED) { 60 this._attTypes.push(ZmCalBaseItem.PERSON); 61 } 62 this._attTypes.push(ZmCalBaseItem.LOCATION); 63 if (appCtxt.get(ZmSetting.GAL_ENABLED) && this.GROUP_CALENDAR_ENABLED) { 64 this._attTypes.push(ZmCalBaseItem.EQUIPMENT); 65 } 66 this._locationTextMap = {}; 67 this._attendeePicker = {}; 68 this._pickerButton = {}; 69 70 //used to preserve original attendees while forwarding appt 71 this._fwdApptOrigAttendees = []; 72 this._attendeesHashMap = {}; 73 74 // Store Appt form values. 75 this._apptFormValue = {}; 76 this._showAsValueChanged = false; 77 78 this._locationExceptions = null; 79 this._alteredLocations = null; 80 this._enableResolveDialog = true; 81 82 this._locationConflict = false; 83 this._locationStatusMode = ZmApptEditView.LOCATION_STATUS_NONE; 84 85 var app = appCtxt.getApp(ZmApp.CALENDAR); 86 // Each ApptEditView must now have its own copy of the FreeBusyCache. The cache will 87 // now hold FreeBusy info that is unique to an appointment, in that the Server provides 88 // Free busy info that excludes the current appointment. So the cache information cannot 89 // be shared across appointments. 90 //this._fbCache = app.getFreeBusyCache(); 91 AjxDispatcher.require(["MailCore", "CalendarCore"]); 92 this._fbCache = new ZmFreeBusyCache(app); 93 94 this._customRecurDialogCallback = this._recurChangeForLocationConflict.bind(this); 95 }; 96 97 ZmApptEditView.prototype = new ZmCalItemEditView; 98 ZmApptEditView.prototype.constructor = ZmApptEditView; 99 100 // Consts 101 102 103 ZmApptEditView.PRIVACY_OPTION_PUBLIC = "PUB"; 104 ZmApptEditView.PRIVACY_OPTION_PRIVATE = "PRI"; 105 106 ZmApptEditView.PRIVACY_OPTIONS = [ 107 { label: ZmMsg._public, value: "PUB", selected: true }, 108 { label: ZmMsg._private, value: "PRI" } 109 // { label: ZmMsg.confidential, value: "CON" } // see bug #21205 110 ]; 111 112 ZmApptEditView.BAD = "_bad_addrs_"; 113 114 ZmApptEditView.REMINDER_MAX_VALUE = {}; 115 ZmApptEditView.REMINDER_MAX_VALUE[ZmCalItem.REMINDER_UNIT_DAYS] = 14; 116 ZmApptEditView.REMINDER_MAX_VALUE[ZmCalItem.REMINDER_UNIT_MINUTES] = 20160; 117 ZmApptEditView.REMINDER_MAX_VALUE[ZmCalItem.REMINDER_UNIT_HOURS] = 336; 118 ZmApptEditView.REMINDER_MAX_VALUE[ZmCalItem.REMINDER_UNIT_WEEKS] = 2; 119 120 ZmApptEditView.TIMEZONE_TYPE = "TZ_TYPE"; 121 122 ZmApptEditView.START_TIMEZONE = 1; 123 ZmApptEditView.END_TIMEZONE = 2; 124 125 ZmApptEditView.LOCATION_STATUS_UNDEFINED = -1; 126 ZmApptEditView.LOCATION_STATUS_NONE = 0; 127 ZmApptEditView.LOCATION_STATUS_VALIDATING = 1; 128 ZmApptEditView.LOCATION_STATUS_CONFLICT = 2; 129 ZmApptEditView.LOCATION_STATUS_RESOLVED = 3; 130 131 132 // Public Methods 133 134 ZmApptEditView.prototype.toString = 135 function() { 136 return "ZmApptEditView"; 137 }; 138 139 ZmApptEditView.prototype.isLocationConflictEnabled = 140 function() { 141 return ((this._mode != ZmCalItem.MODE_EDIT_SINGLE_INSTANCE) && 142 !this._isForward && !this._isProposeTime && 143 this.getRepeatType() != "NON"); 144 } 145 146 ZmApptEditView.prototype.getFreeBusyCache = 147 function() { 148 return this._fbCache; 149 } 150 151 152 ZmLocationAppt = function() { }; 153 ZmLocationRecurrence = function() { }; 154 155 ZmApptEditView.prototype.show = 156 function() { 157 ZmCalItemEditView.prototype.show.call(this); 158 159 if (this.parent.setLocationConflictCallback) { 160 var appt = this.parent.getAppt(); 161 this.initializeLocationConflictCheck(appt); 162 } 163 164 Dwt.setVisible(this._attendeeStatus, false); 165 Dwt.setVisible(this._suggestTime, !this._isForward); 166 Dwt.setVisible(this._suggestLocation, !this._isForward && !this._isProposeTime && appCtxt.get(ZmSetting.GAL_ENABLED)); 167 this._scheduleAssistant.close(); 168 169 if(!this.GROUP_CALENDAR_ENABLED) { 170 this.setSchedulerVisibility(false); 171 } 172 173 if (!appCtxt.get(ZmSetting.GAL_ENABLED)) { 174 Dwt.setSize(this._attInputField[ZmCalBaseItem.LOCATION]._input, "100%"); 175 } 176 177 //bug:48189 Hide schedule tab for non-ZCS acct 178 if (appCtxt.isOffline) { 179 var currAcct = appCtxt.getActiveAccount(); 180 this.setSchedulerVisibility(currAcct.isZimbraAccount && !currAcct.isMain); 181 } 182 183 this._editViewInitialized = true; 184 if(this._expandInlineScheduler) { 185 this._pickAttendeesInfo(ZmCalBaseItem.PERSON); 186 this._pickAttendeesInfo(ZmCalBaseItem.LOCATION); 187 } 188 189 this.resize(); 190 }; 191 192 ZmApptEditView.prototype.initializeLocationConflictCheck = 193 function(appt) { 194 // Create a 'Location-only' clone of the appt, for use with the 195 // resource conflict calls 196 ZmLocationAppt.prototype = appt; 197 ZmLocationRecurrence.prototype = appt.getRecurrence(); 198 this._locationConflictAppt = new ZmLocationAppt(); 199 this._locationConflictAppt._recurrence = new ZmLocationRecurrence(); 200 this._locationConflictAppt._attendees[ZmCalBaseItem.LOCATION] = 201 appt._attendees[ZmCalBaseItem.LOCATION]; 202 this._locationConflictAppt._attendees[ZmCalBaseItem.PERSON] = []; 203 this._locationConflictAppt._attendees[ZmCalBaseItem.EQUIPMENT]= []; 204 205 this._processLocationCallback = this.processLocationConflicts.bind(this); 206 this._noLocationCallback = 207 this.setLocationStatus.bind(this, ZmApptEditView.LOCATION_STATUS_NONE); 208 this.parent.setLocationConflictCallback(this.updatedLocationsConflictChecker.bind(this)); 209 210 this._getRecurrenceSearchResponseCallback = 211 this._getExceptionSearchResponse.bind(this, this._locationConflictAppt); 212 this._getRecurrenceSearchErrorCallback = 213 this._getExceptionSearchError.bind(this, this._locationConflictAppt); 214 215 if (!this._pendingLocationRequest && 216 this._scheduleAssistant && this._scheduleAssistant.isInitialized()) { 217 // Trigger an initial location check - the appt may have been saved 218 // with a location that has conflicts. Only do it if no pending 219 // request and the assistant is initialized (location preferences 220 // are loaded). If !initialized, the locationConflictChecker will 221 // be run when preferences are loaded. 222 this.locationConflictChecker(); 223 } 224 } 225 226 ZmApptEditView.prototype.cancelLocationRequest = 227 function() { 228 if (this._pendingLocationRequest) { 229 appCtxt.getRequestMgr().cancelRequest(this._pendingLocationRequest, null, true); 230 this._pendingLocationRequest = null; 231 } 232 } 233 234 ZmApptEditView.prototype.locationConflictChecker = 235 function() { 236 // Cancel any pending requests 237 this.cancelLocationRequest(); 238 if (this.isLocationConflictEnabled() && 239 this._locationConflictAppt.hasAttendeeForType(ZmCalBaseItem.LOCATION)) { 240 // Send a request to the server to get location conflicts 241 242 // DISABLED until Bug 56464 completed - server side CreateAppointment/ModifyAppointment 243 // SOAP API changes. When done, add code in ZmCalItemComposeController to add the 244 // altered locations as a list of exceptions to the SOAP call. 245 //if (this._apptExceptionList) { 246 // this._runLocationConflictChecker(); 247 //} else { 248 // // Get the existing exceptions, then runLocationConflictChecker 249 // this._doExceptionSearchRequest(); 250 //} 251 252 // Once bug 56464 completed, remove the following and enable the disabled code above 253 this._runLocationConflictChecker(); 254 255 } else { 256 if (this._noLocationCallback) { 257 // Restore the 'Suggest Location' line to its default 258 this._noLocationCallback.run(); 259 } 260 } 261 } 262 263 ZmApptEditView.prototype.updatedLocationsConflictChecker = 264 function(locations){ 265 // Update locations in the appt clone, then run the conflict checker 266 this._locationConflictAppt.setAttendees(locations.getArray(), ZmCalBaseItem.LOCATION); 267 this.locationConflictChecker(); 268 } 269 270 ZmApptEditView.prototype.getNumLocationConflictRecurrence = 271 function() { 272 var numRecurrence = ZmTimeSuggestionPrefDialog.DEFAULT_NUM_RECURRENCE; 273 if (this._scheduleAssistant) { 274 numRecurrence = this._scheduleAssistant.getLocationConflictNumRecurrence(); 275 } 276 return numRecurrence; 277 } 278 279 ZmApptEditView.prototype._runLocationConflictChecker = 280 function() { 281 var numRecurrence = this.getNumLocationConflictRecurrence(); 282 var locationCallback = this._controller.getCheckResourceConflicts( 283 this._locationConflictAppt, numRecurrence, this._processLocationCallback, false); 284 this.setLocationStatus(ZmApptEditView.LOCATION_STATUS_VALIDATING); 285 this._pendingLocationRequest = locationCallback.run(); 286 } 287 288 289 ZmApptEditView.prototype._doExceptionSearchRequest = 290 function() { 291 var numRecurrence = this.getNumLocationConflictRecurrence(); 292 var startDate = new Date(this._calItem.startDate); 293 var endTime = ZmApptComposeController.getCheckResourceConflictEndTime( 294 this._locationConflictAppt, startDate, numRecurrence); 295 296 var jsonObj = {SearchRequest:{_jsns:"urn:zimbraMail"}}; 297 var request = jsonObj.SearchRequest; 298 299 request.sortBy = "dateasc"; 300 request.limit = numRecurrence.toString(); 301 // AjxEnv.DEFAULT_LOCALE is set to the browser's locale setting in the case 302 // when the user's (or their COS) locale is not set. 303 request.locale = { _content: AjxEnv.DEFAULT_LOCALE }; 304 request.calExpandInstStart = startDate.getTime(); 305 request.calExpandInstEnd = endTime; 306 request.types = ZmSearch.TYPE[ZmItem.APPT]; 307 request.query = {_content:'item:"' + this._calItem.id.toString() + '"'}; 308 var accountName = appCtxt.multiAccounts ? appCtxt.accountList.mainAccount.name : null; 309 310 var params = { 311 jsonObj: jsonObj, 312 asyncMode: true, 313 callback: this._getExceptionSearchResponse.bind(this), 314 errorCallback: this._getExceptionSearchError.bind(this), 315 noBusyOverlay: true, 316 accountName: accountName 317 }; 318 appCtxt.getAppController().sendRequest(params); 319 } 320 321 ZmApptEditView.prototype._getExceptionSearchResponse = 322 function(result) { 323 if (!result) { return; } 324 325 var resp; 326 var appt; 327 try { 328 resp = result.getResponse(); 329 } catch (ex) { 330 return; 331 } 332 333 // See ZmApptCache.prototype.processSearchResponse 334 var rawAppts = resp.SearchResponse.appt; 335 this._apptExceptionList = new ZmApptList(); 336 this._apptExceptionList.loadFromSummaryJs(rawAppts); 337 this._apptExceptionLookup = {}; 338 339 this._locationExceptions = {} 340 for (var i = 0; i < this._apptExceptionList.size(); i++) { 341 appt = this._apptExceptionList.get(i); 342 this._apptExceptionLookup[appt.startDate.getTime()] = appt; 343 if (appt.isException) { 344 // Found an exception, store its location info, using its start date as the key 345 var location = appt._attendees[ZmCalBaseItem.LOCATION]; 346 if (!location || (location.length == 0)) { 347 location = this.getAttendeesFromString(ZmCalBaseItem.LOCATION, appt.location, false); 348 location = location.getArray(); 349 } 350 this._locationExceptions[appt.startDate.getTime()] = location; 351 } 352 } 353 this._enableResolveDialog = true; 354 355 // Now find the conflicts 356 this._runLocationConflictChecker(); 357 }; 358 359 ZmApptEditView.prototype._getExceptionSearchError = 360 function(ex) { 361 // Disallow use of the resolve dialog if can't read the exceptions 362 this._enableResolveDialog = false; 363 } 364 365 // Callback executed when the CheckResourceConflictRequest completes. 366 // Store the conflict instances (if any) and update the status field 367 ZmApptEditView.prototype.processLocationConflicts = 368 function(inst) { 369 this._inst = inst; 370 var len = inst ? inst.length : 0, 371 locationStatus = ZmApptEditView.LOCATION_STATUS_NONE; 372 for (var i = 0; i < len; i++) { 373 if (this._inst[i].usr) { 374 // Conflict exists for this instance 375 if (this._locationExceptions && this._locationExceptions[this._inst[i].s]) { 376 // Assume that an existing exception (either persisted to the DB, or set via 377 // the current use of the resolve dialog) means that the instance conflict is resolved 378 locationStatus = ZmApptEditView.LOCATION_STATUS_RESOLVED; 379 } else { 380 // No exception for the instance, using default location which has a conflict 381 locationStatus = ZmApptEditView.LOCATION_STATUS_CONFLICT; 382 break; 383 } 384 } 385 } 386 387 this.setLocationStatus(locationStatus); 388 } 389 390 ZmApptEditView.prototype.setLocationStatus = 391 function(locationStatus, currentLocationConflict) { 392 var className = ""; 393 var statusMessage = ""; 394 var linkMessage = ""; 395 var msgVisible = false; 396 var linkVisible = false; 397 var statusText = ""; 398 399 if (locationStatus != ZmApptEditView.LOCATION_STATUS_UNDEFINED) { 400 this._locationStatusMode = locationStatus; 401 } 402 if (currentLocationConflict !== undefined) { 403 this._locationConflict = currentLocationConflict; 404 } 405 406 // Manage the location suggestion line beneath the location field. 407 switch (this._locationStatusMode) { 408 case ZmApptEditView.LOCATION_STATUS_NONE: 409 // No recurrence conflicts or nothing to check - display based on current conflict flag 410 if (this._locationConflict) { 411 statusMessage = AjxImg.getImageHtml("Warning_12", "display:inline-block;padding-right:4px;") + 412 ZmMsg.locationCurrentConflicts; 413 className = "ZmLocationStatusConflict"; 414 msgVisible = true; 415 } else { 416 msgVisible = false; 417 } 418 break; 419 case ZmApptEditView.LOCATION_STATUS_VALIDATING: 420 // The conflict resource check is in progress, show a busy spinner 421 className = "ZmLocationStatusValidating"; 422 // Don't incorporate currentConflict flag - just show validating; It will update upon completion 423 statusMessage = AjxImg.getImageHtml("Wait_16", "display:inline-block;padding-right:4px;") + 424 ZmMsg.validateLocation; 425 msgVisible = true; 426 linkVisible = false; 427 break; 428 case ZmApptEditView.LOCATION_STATUS_CONFLICT: 429 // Unresolved recurrence conflicts - show the 'Resolve Conflicts' link 430 className = "ZmLocationStatusConflict"; 431 statusText = this._locationConflict ? ZmMsg.locationCurrentAndRecurrenceConflicts : 432 ZmMsg.locationRecurrenceConflicts; 433 statusMessage = AjxImg.getImageHtml("Warning_12", "display:inline-block;padding-right:4px;") + 434 statusText; 435 linkMessage = ZmMsg.resolveConflicts; 436 msgVisible = true; 437 linkVisible = true; 438 break; 439 case ZmApptEditView.LOCATION_STATUS_RESOLVED: 440 // Resolved conflicts - show the 'View Resolutions' link 441 className = "ZmLocationStatusResolved"; 442 statusMessage = this._locationConflict ? ZmMsg.locationRecurrenceResolvedButCurrentConflict : 443 ZmMsg.locationRecurrenceConflictsResolved; 444 linkMessage = ZmMsg.viewResolutions; 445 msgVisible = true; 446 linkVisible = true; 447 break; 448 default: break; 449 } 450 451 Dwt.setVisible(this._locationStatus, msgVisible); 452 if (!this._enableResolveDialog) { 453 // Unable to read the exeptions, prevent the use of the resolve dialog 454 linkVisible = false; 455 } 456 457 // NOTE: Once CreateAppt/ModifyAppt SOAP API changes are completed (Bug 56464), enable 458 // the display of the resolve links and the use of the resolve dialog 459 // *** NOT DONE *** 460 linkVisible = false; 461 462 Dwt.setVisible(this._locationStatusAction, linkVisible); 463 Dwt.setInnerHtml(this._locationStatus, statusMessage); 464 Dwt.setInnerHtml(this._locationStatusAction, linkMessage); 465 this._locationStatus.className = className; 466 } 467 468 ZmApptEditView.prototype.blur = 469 function(useException) { 470 if (this._activeInputField) { 471 this._handleAttendeeField(this._activeInputField, useException); 472 // bug: 15251 - to avoid race condition, active field will anyway be 473 // cleared by onblur handler for input field this._activeInputField = null; 474 } 475 }; 476 477 ZmApptEditView.prototype.cleanup = 478 function() { 479 ZmCalItemEditView.prototype.cleanup.call(this); 480 481 if (this.GROUP_CALENDAR_ENABLED) { 482 this._attendeesInputField.clear(); 483 this._optAttendeesInputField.clear(); 484 this._forwardToField.clear(); 485 } 486 this._attInputField[ZmCalBaseItem.LOCATION].clear(); 487 this._locationTextMap = {}; 488 489 if (this._resourcesContainer) { 490 this.showResourceField(false); 491 this._resourceInputField.clear(); 492 } 493 494 this._allDayCheckbox.checked = false; 495 this._showTimeFields(true); 496 this._isKnownLocation = false; 497 498 // reset autocomplete lists 499 if (this._acContactsList) { 500 this._acContactsList.reset(); 501 this._acContactsList.show(false); 502 } 503 if (this._acLocationsList) { 504 this._acLocationsList.reset(); 505 this._acLocationsList.show(false); 506 } 507 508 if (this.GROUP_CALENDAR_ENABLED) { 509 for (var attType in this._attInputField) { 510 this._attInputField[attType].clear(); 511 } 512 } 513 514 this._attendeesHashMap = {}; 515 this._showAsValueChanged = false; 516 517 Dwt.setVisible(this._attendeeStatus, false); 518 this.setLocationStatus(ZmApptEditView.LOCATION_STATUS_NONE, false); 519 520 //Default Persona 521 this.setIdentity(); 522 if(this._scheduleAssistant) this._scheduleAssistant.cleanup(); 523 524 this._apptExceptionList = null; 525 this._locationExceptions = null; 526 this._alteredLocations = null; 527 528 }; 529 530 // Acceptable hack needed to prevent cursor from bleeding thru higher z-index'd views 531 ZmApptEditView.prototype.enableInputs = 532 function(bEnableInputs) { 533 ZmCalItemEditView.prototype.enableInputs.call(this, bEnableInputs); 534 if (this.GROUP_CALENDAR_ENABLED) { 535 var bEnableAttendees = bEnableInputs; 536 if (appCtxt.isOffline && bEnableAttendees && 537 this._calItem && this._calItem.getFolder().getAccount().isMain) 538 { 539 bEnableAttendees = false; 540 } 541 this._attendeesInputField.setEnabled(bEnableAttendees); 542 this._optAttendeesInputField.setEnabled(bEnableAttendees); 543 this._locationInputField.setEnabled(bEnableAttendees); //this was a small bug - the text field of that was not disabled! 544 this.enablePickers(bEnableAttendees); 545 }else { 546 //bug 57083 - disabling group calendar should disable attendee pickers 547 this.enablePickers(false); 548 } 549 this._attInputField[ZmCalBaseItem.LOCATION].setEnabled(bEnableInputs); 550 }; 551 552 ZmApptEditView.prototype.isOrganizer = 553 function() { 554 return Boolean(this._isOrganizer); 555 }; 556 557 ZmApptEditView.prototype.enablePickers = 558 function(bEnablePicker) { 559 for (var t = 0; t < this._attTypes.length; t++) { 560 var type = this._attTypes[t]; 561 if(this._pickerButton[type]) this._pickerButton[type].setEnabled(bEnablePicker); 562 } 563 564 if(this._pickerButton[ZmCalBaseItem.OPTIONAL_PERSON]) this._pickerButton[ZmCalBaseItem.OPTIONAL_PERSON].setEnabled(bEnablePicker); 565 566 }; 567 568 ZmApptEditView.prototype.isValid = 569 function() { 570 var errorMsg = []; 571 572 // check for required subject 573 var subj = AjxStringUtil.trim(this._subjectField.getValue()); 574 575 //bug: 49990 subject can be empty while proposing new time 576 if ((subj && subj.length) || this._isProposeTime) { 577 var allDay = this._allDayCheckbox.checked; 578 if (!DwtTimeInput.validStartEnd(this._startDateField, this._endDateField, (allDay ? null : this._startTimeSelect), (allDay ? null : this._endTimeSelect))) { 579 errorMsg.push(ZmMsg.errorInvalidDates); 580 } 581 582 } else { 583 errorMsg.push(ZmMsg.errorMissingSubject); 584 } 585 if (this._reminderSelectInput) { 586 var reminderString = this._reminderSelectInput.getValue(); 587 var reminderInfo = ZmCalendarApp.parseReminderString(reminderString); 588 if (reminderInfo.reminderValue > ZmApptEditView.REMINDER_MAX_VALUE[reminderInfo.reminderUnits]) { 589 errorMsg.push(ZmMsg.errorInvalidReminderValue); 590 } 591 } 592 if (errorMsg.length > 0) { 593 throw errorMsg.join("<br>"); 594 } 595 596 return true; 597 }; 598 599 // called by schedule tab view when user changes start date field 600 ZmApptEditView.prototype.updateDateField = 601 function(newStartDate, newEndDate) { 602 var oldTimeInfo = this._getDateTimeText(); 603 604 this._startDateField.value = newStartDate; 605 this._endDateField.value = newEndDate; 606 607 this._dateTimeChangeForLocationConflict(oldTimeInfo); 608 }; 609 610 ZmApptEditView.prototype.updateAllDayField = 611 function(isAllDay) { 612 var oldAllDay = this._allDayCheckbox.checked; 613 this._allDayCheckbox.checked = isAllDay; 614 this._showTimeFields(!isAllDay); 615 if (oldAllDay != isAllDay) { 616 var durationInfo = this.getDurationInfo(); 617 this._locationConflictAppt.startDate = new Date(durationInfo.startTime); 618 this._locationConflictAppt.endDate = new Date(durationInfo.endTime); 619 this._locationConflictAppt.allDayEvent = isAllDay ? "1" : "0"; 620 this.locationConflictChecker(); 621 } 622 }; 623 624 ZmApptEditView.prototype.toggleAllDayField = 625 function() { 626 this.updateAllDayField(!this._allDayCheckbox.checked); 627 }; 628 629 ZmApptEditView.prototype.updateShowAsField = 630 function(isAllDay) { 631 if(!this._showAsValueChanged) { 632 if(isAllDay) { 633 this._showAsSelect.setSelectedValue("F"); 634 } 635 else { 636 this._showAsSelect.setSelectedValue("B"); 637 } 638 } 639 }; 640 641 ZmApptEditView.prototype.setShowAsFlag = 642 function(flag) { 643 this._showAsValueChanged = flag; 644 }; 645 646 ZmApptEditView.prototype.updateTimeField = 647 function(dateInfo) { 648 this._startTimeSelect.setValue(dateInfo.startTimeStr); 649 this._endTimeSelect.setValue(dateInfo.endTimeStr); 650 }; 651 652 653 ZmApptEditView.prototype.setDate = 654 function(startDate, endDate, ignoreTimeUpdate) { 655 var oldTimeInfo = this._getDateTimeText(); 656 this._startDateField.value = AjxDateUtil.simpleComputeDateStr(startDate); 657 this._endDateField.value = AjxDateUtil.simpleComputeDateStr(endDate); 658 if(!ignoreTimeUpdate) { 659 this._startTimeSelect.set(startDate); 660 this._endTimeSelect.set(endDate); 661 } 662 663 if(this._schedulerOpened) { 664 this._scheduleView.handleTimeChange(); 665 } 666 appCtxt.notifyZimlets("onEditAppt_updateTime", [this, {startDate:startDate, endDate:endDate}]);//notify Zimlets 667 668 this._dateTimeChangeForLocationConflict(oldTimeInfo); 669 }; 670 671 // ?? Not used - and not setting this._dateInfo. If used, 672 // need to check change in timezone in caller and then update location conflict 673 ZmApptEditView.prototype.updateTimezone = 674 function(dateInfo) { 675 this._tzoneSelectStart.setSelectedValue(dateInfo.timezone); 676 this._tzoneSelectEnd.setSelectedValue(dateInfo.timezone); 677 this.handleTimezoneOverflow(); 678 }; 679 680 ZmApptEditView.prototype.updateLocation = 681 function(location, locationStr) { 682 this._updateAttendeeFieldValues(ZmCalBaseItem.LOCATION, [location]); 683 locationStr = locationStr || location.getAttendeeText(ZmCalBaseItem.LOCATION); 684 this.setApptLocation(locationStr); 685 }; 686 687 // Private / protected methods 688 689 ZmApptEditView.prototype._initTzSelect = 690 function() { 691 var options = AjxTimezone.getAbbreviatedZoneChoices(); 692 if (options.length != this._tzCount) { 693 this._tzCount = options.length; 694 this._tzoneSelectStart.clearOptions(); 695 this._tzoneSelectEnd.clearOptions(); 696 for (var i = 0; i < options.length; i++) { 697 this._tzoneSelectStart.addOption(options[i]); 698 this._tzoneSelectEnd.addOption(options[i]); 699 } 700 } 701 }; 702 703 ZmApptEditView.prototype._addTabGroupMembers = 704 function(tabGroup) { 705 tabGroup.addMember(this._subjectField); 706 if(this.GROUP_CALENDAR_ENABLED) { 707 tabGroup.addMember([this._pickerButton[ZmCalBaseItem.PERSON], 708 this._attInputField[ZmCalBaseItem.PERSON], 709 this._showOptional, 710 this._pickerButton[ZmCalBaseItem.OPTIONAL_PERSON], 711 this._attInputField[ZmCalBaseItem.OPTIONAL_PERSON]]); 712 } 713 tabGroup.addMember([this._suggestTime, 714 this._pickerButton[ZmCalBaseItem.LOCATION], 715 this._attInputField[ZmCalBaseItem.LOCATION]]); 716 if(this.GROUP_CALENDAR_ENABLED && appCtxt.get(ZmSetting.GAL_ENABLED)) { 717 tabGroup.addMember([this._pickerButton[ZmCalBaseItem.EQUIPMENT], 718 this._attInputField[ZmCalBaseItem.EQUIPMENT], 719 this._showResources, 720 this._suggestLocation]); 721 } 722 tabGroup.addMember([this._startDateField, 723 this._startDateButton, 724 this._startTimeSelect.getTabGroupMember(), 725 this._endDateField, 726 this._endDateButton, 727 this._endTimeSelect.getTabGroupMember(), 728 this._allDayCheckbox, 729 730 this._repeatSelect, 731 this._reminderSelectInput, 732 this._reminderButton, 733 this._reminderConfigure, 734 735 this._showAsSelect, 736 this._folderSelect, 737 this._privateCheckbox, 738 739 this._schButton, 740 this._scheduleView, 741 this.getHtmlEditor(), 742 743 this._suggestTime, 744 this._suggestLocation]); 745 }; 746 747 ZmApptEditView.prototype._finishReset = 748 function() { 749 ZmCalItemEditView.prototype._finishReset.call(this); 750 751 this._apptFormValue = {}; 752 this._apptFormValue[ZmApptEditView.CHANGES_SIGNIFICANT] = this._getFormValue(ZmApptEditView.CHANGES_SIGNIFICANT); 753 this._apptFormValue[ZmApptEditView.CHANGES_INSIGNIFICANT] = this._getFormValue(ZmApptEditView.CHANGES_INSIGNIFICANT); 754 this._apptFormValue[ZmApptEditView.CHANGES_LOCAL] = this._getFormValue(ZmApptEditView.CHANGES_LOCAL); 755 this._apptFormValue[ZmApptEditView.CHANGES_TIME_RECURRENCE] = this._getFormValue(ZmApptEditView.CHANGES_TIME_RECURRENCE); 756 757 var newMode = (this._mode == ZmCalItem.MODE_NEW); 758 759 // save the original form data in its initialized state 760 this._origFormValueMinusAttendees = newMode ? "" : this._formValue(true); 761 if (this._hasReminderSupport) { 762 this._origFormValueMinusReminder = newMode ? "" : this._formValue(false, true); 763 this._origReminderValue = this._reminderSelectInput.getValue(); 764 } 765 this._keyInfoValue = newMode ? "" : this._keyValue(); 766 }; 767 768 /** 769 * Checks if location/time/recurrence only are changed. 770 * 771 * @return {Boolean} <code>true</code> if location/time/recurrence only are changed 772 */ 773 ZmApptEditView.prototype.isKeyInfoChanged = 774 function() { 775 var formValue = this._keyInfoValue; 776 return (this._keyValue() != formValue); 777 }; 778 779 ZmApptEditView.prototype._getClone = 780 function() { 781 if (!this._calItem) { 782 return null; 783 } 784 return ZmAppt.quickClone(this._calItem); 785 }; 786 787 ZmApptEditView.prototype.getDurationInfo = 788 function() { 789 var startDate = AjxDateUtil.simpleParseDateStr(this._startDateField.value); 790 var endDate = AjxDateUtil.simpleParseDateStr(this._endDateField.value); 791 if (!this._allDayCheckbox.checked) { 792 startDate = this._startTimeSelect.getValue(startDate); 793 endDate = this._endTimeSelect.getValue(endDate); 794 } 795 var durationInfo = {}; 796 durationInfo.startTime = startDate.getTime(); 797 durationInfo.endTime = endDate.getTime(); 798 durationInfo.duration = durationInfo.endTime - durationInfo.startTime; 799 return durationInfo; 800 }; 801 802 ZmApptEditView.prototype.getDuration = 803 function() { 804 var startDate = AjxDateUtil.simpleParseDateStr(this._startDateField.value); 805 var endDate = AjxDateUtil.simpleParseDateStr(this._endDateField.value); 806 var duration = AjxDateUtil.MSEC_PER_DAY; 807 if (!this._allDayCheckbox.checked) { 808 startDate = this._startTimeSelect.getValue(startDate); 809 endDate = this._endTimeSelect.getValue(endDate); 810 duration = endDate.getTime() - startDate.getTime(); 811 } 812 return duration; 813 }; 814 815 ZmApptEditView.prototype._populateForSave = 816 function(calItem) { 817 818 if (!calItem) { 819 return null; 820 } 821 822 ZmCalItemEditView.prototype._populateForSave.call(this, calItem); 823 824 //Handle Persona's 825 var identity = this.getIdentity(); 826 if(identity){ 827 calItem.identity = identity; 828 calItem.sentBy = (identity && identity.getField(ZmIdentity.SEND_FROM_ADDRESS)); 829 } 830 831 calItem.freeBusy = this._showAsSelect.getValue(); 832 calItem.privacy = this._privateCheckbox.checked ? ZmApptEditView.PRIVACY_OPTION_PRIVATE : ZmApptEditView.PRIVACY_OPTION_PUBLIC; 833 834 // set the start date by aggregating start date/time fields 835 var startDate = AjxDateUtil.simpleParseDateStr(this._startDateField.value); 836 var endDate = AjxDateUtil.simpleParseDateStr(this._endDateField.value); 837 if (this._allDayCheckbox.checked) { 838 calItem.setAllDayEvent(true); 839 if(AjxDateUtil.isDayShifted(startDate)) { 840 AjxDateUtil.rollToNextDay(startDate); 841 AjxDateUtil.rollToNextDay(endDate); 842 } 843 } else { 844 calItem.setAllDayEvent(false); 845 startDate = this._startTimeSelect.getValue(startDate); 846 endDate = this._endTimeSelect.getValue(endDate); 847 } 848 calItem.setStartDate(startDate, true); 849 calItem.setEndDate(endDate, true); 850 if (Dwt.getVisibility(this._tzoneSelectStartElement)) { 851 calItem.timezone = this._tzoneSelectStart.getValue(); 852 } 853 if (Dwt.getVisibility(this._tzoneSelectEndElement)) { 854 calItem.setEndTimezone(this._tzoneSelectEnd.getValue()); 855 } 856 else { 857 calItem.setEndTimezone(calItem.timezone); //it's not necessarily set correctly before. Might be still set to the original end time zone. I think here is the safeset place to make sure. 858 } 859 860 // set attendees 861 for (var t = 0; t < this._attTypes.length; t++) { 862 var type = this._attTypes[t]; 863 calItem.setAttendees(this._attendees[type].getArray(), type); 864 } 865 866 var calLoc = AjxStringUtil.trim(this._attInputField[ZmCalBaseItem.LOCATION].getValue()); 867 //bug 44858, trimming ';' so that ;; does not appears in outlook, 868 calItem.location = AjxStringUtil.trim(calLoc, false, ';'); 869 870 // set any recurrence rules LAST 871 this._getRecurrence(calItem); 872 873 calItem.isForward = this._isForward; 874 calItem.isProposeTime = this._isProposeTime; 875 876 if(this._isForward) { 877 var addrs = this._collectForwardAddrs(); 878 var a = {}; 879 if (addrs[AjxEmailAddress.TO] && addrs[AjxEmailAddress.TO].good) { 880 a[AjxEmailAddress.TO] = addrs[AjxEmailAddress.TO].good.getArray(); 881 } 882 calItem.setForwardAddress(a[AjxEmailAddress.TO]); 883 } 884 885 // Only used for the save 886 calItem.alteredLocations = this._alteredLocations; 887 888 return calItem; 889 }; 890 891 892 ZmApptEditView.prototype.getRsvp = 893 function() { 894 return this.GROUP_CALENDAR_ENABLED ? this._controller.getRequestResponses() : false; 895 }; 896 897 ZmApptEditView.prototype.updateToolbarOps = 898 function(){ 899 this._controller.updateToolbarOps((this.isAttendeesEmpty() || !this.isOrganizer()) ? ZmCalItemComposeController.APPT_MODE : ZmCalItemComposeController.MEETING_MODE, this._calItem); 900 }; 901 902 ZmApptEditView.prototype.isAttendeesEmpty = 903 function() { 904 905 if(!this.GROUP_CALENDAR_ENABLED) return true; 906 907 var locations = this._attendees[ZmCalBaseItem.LOCATION]; 908 //non-resource location labels also contributes to empty attendee 909 var isLocationResource =(locations && locations.size() > 0); 910 var isAttendeesNotEmpty = AjxStringUtil.trim(this._attendeesInputField.getValue()) || AjxStringUtil.trim(this._optAttendeesInputField.getValue()) || (this._resourceInputField ? AjxStringUtil.trim(this._resourceInputField.getValue()) : "") || isLocationResource; 911 return !isAttendeesNotEmpty; 912 913 }; 914 915 ZmApptEditView.prototype._populateForEdit = 916 function(calItem, mode) { 917 918 ZmCalItemEditView.prototype._populateForEdit.call(this, calItem, mode); 919 920 var enableTimeSelection = !this._isForward; 921 var enableApptDetails = !this._isForward && !this._isProposeTime; 922 923 this._showAsSelect.setSelectedValue(calItem.freeBusy); 924 this._showAsSelect.setEnabled(enableApptDetails); 925 926 // reset the date/time values based on current time 927 var sd = new Date(calItem.startDate.getTime()); 928 var ed = new Date(calItem.endDate.getTime()); 929 930 var isNew = (mode == ZmCalItem.MODE_NEW || mode == ZmCalItem.MODE_NEW_FROM_QUICKADD); 931 var isAllDayAppt = calItem.isAllDayEvent(); 932 if (isAllDayAppt) { 933 this._allDayCheckbox.checked = true; 934 this._showTimeFields(false); 935 this.updateShowAsField(true); 936 this._showAsSelect.setSelectedValue(calItem.freeBusy); 937 this._showAsSelect.setEnabled(enableApptDetails); 938 939 // set time anyway to current time and default duration (in case user changes mind) 940 var now = AjxDateUtil.roundTimeMins(new Date(), 30); 941 this._startTimeSelect.set(now); 942 943 now.setTime(now.getTime() + ZmCalViewController.DEFAULT_APPOINTMENT_DURATION); 944 this._endTimeSelect.set(now); 945 946 // bug 9969: HACK - remove the all day durtion for display 947 if (!isNew && !calItem.draftUpdated && ed.getHours() == 0 && ed.getMinutes() == 0 && ed.getSeconds() == 0 && sd.getTime() != ed.getTime()) { 948 ed.setHours(-12); 949 } 950 } else { 951 this._showTimeFields(true); 952 this._startTimeSelect.set(calItem.startDate); 953 this._endTimeSelect.set(calItem.endDate); 954 } 955 this._startDateField.value = AjxDateUtil.simpleComputeDateStr(sd); 956 this._endDateField.value = AjxDateUtil.simpleComputeDateStr(ed); 957 958 this._initTzSelect(); 959 this._resetTimezoneSelect(calItem, isAllDayAppt); 960 961 //need to capture initial time set while composing/editing appt 962 ZmApptViewHelper.getDateInfo(this, this._dateInfo); 963 964 this._startTimeSelect.setEnabled(enableTimeSelection); 965 this._endTimeSelect.setEnabled(enableTimeSelection); 966 this._startDateButton.setEnabled(enableTimeSelection); 967 this._endDateButton.setEnabled(enableTimeSelection); 968 969 this._fwdApptOrigAttendees = []; 970 971 //editing an appt should exclude the original appt time for FB calculation 972 this._fbExcludeInfo = {}; 973 974 var showScheduleView = false; 975 // attendees 976 var attendees = calItem.getAttendees(ZmCalBaseItem.PERSON); 977 if (attendees && attendees.length) { 978 if (this.GROUP_CALENDAR_ENABLED) { 979 var people = calItem.getAttendees(ZmCalBaseItem.PERSON); 980 var reqAttendees = ZmApptViewHelper.filterAttendeesByRole(people, ZmCalItem.ROLE_REQUIRED); 981 this._setAddresses(this._attendeesInputField, reqAttendees, ZmCalBaseItem.PERSON); 982 var optAttendees = ZmApptViewHelper.filterAttendeesByRole(people, ZmCalItem.ROLE_OPTIONAL); 983 this._setAddresses(this._optAttendeesInputField, optAttendees, ZmCalBaseItem.PERSON); 984 if (optAttendees.length) { 985 this._toggleOptionalAttendees(true); 986 } 987 } 988 if(this._isForward) { 989 this._attInputField[ZmCalBaseItem.FORWARD] = this._forwardToField; 990 } 991 this._attendees[ZmCalBaseItem.PERSON] = AjxVector.fromArray(attendees); 992 for(var a=0;a<attendees.length;a++){ 993 this._attendeesHashMap[attendees[a].getEmail()+"-"+ZmCalBaseItem.PERSON]=attendees[a]; 994 if(!isNew) this.addFreeBusyExcludeInfo(attendees[a].getEmail(), calItem.startDate.getTime(), calItem.endDate.getTime()); 995 } 996 this._attInputField[ZmCalBaseItem.PERSON] = this._attendeesInputField; 997 this._fwdApptOrigAttendees = []; 998 showScheduleView = true; 999 } else { 1000 if (this.GROUP_CALENDAR_ENABLED) { 1001 this._attendeesInputField.clear(); 1002 this._optAttendeesInputField.clear(); 1003 } 1004 this._attendees[ZmCalBaseItem.PERSON] = new AjxVector(); 1005 } 1006 1007 // set the location attendee(s) 1008 // Always get the information from the location string. There may be non-attendee information included such 1009 // as conference call phone numbers, etc. 1010 var nonAttendeeLocationInfo = []; 1011 var locations = this.getAttendeesFromString(ZmCalBaseItem.LOCATION, calItem.getLocation(), false, nonAttendeeLocationInfo); 1012 if (locations) { 1013 locations = locations.getArray(); 1014 } 1015 if (locations && locations.length) { 1016 this.updateAttendeesCache(ZmCalBaseItem.LOCATION, locations); 1017 this._attendees[ZmCalBaseItem.LOCATION] = AjxVector.fromArray(locations); 1018 var locStr = ZmApptViewHelper.getAttendeesString(locations, ZmCalBaseItem.LOCATION); 1019 this._setAddresses(this._attInputField[ZmCalBaseItem.LOCATION], locStr); 1020 // Set the non-attendee info without bubbles 1021 var nonAttendeeStr = nonAttendeeLocationInfo.join(AjxEmailAddress.DELIMS[0]); 1022 this._attInputField[ZmCalBaseItem.LOCATION].setValue(nonAttendeeStr, true, true, false); 1023 showScheduleView = true; 1024 }else{ 1025 // set the location - Only non-attendee information was provided, if that 1026 this._attInputField[ZmCalBaseItem.LOCATION].setValue(calItem.getLocation()); 1027 } 1028 1029 // set the equipment attendee(s) 1030 var equipment = calItem.getAttendees(ZmCalBaseItem.EQUIPMENT); 1031 if (equipment && equipment.length) { 1032 this._toggleResourcesField(true); 1033 this._attendees[ZmCalBaseItem.EQUIPMENT] = AjxVector.fromArray(equipment); 1034 this.updateAttendeesCache(ZmCalBaseItem.EQUIPMENT, equipment); 1035 var equipStr = ZmApptViewHelper.getAttendeesString(equipment, ZmCalBaseItem.EQUIPMENT); 1036 this._setAddresses(this._attInputField[ZmCalBaseItem.EQUIPMENT], equipStr); 1037 showScheduleView = true; 1038 } 1039 1040 // privacy 1041 var isRemote = calItem.isShared(); 1042 var cal = isRemote ? appCtxt.getById(calItem.folderId) : null; 1043 var isPrivacyEnabled = ((!isRemote || (cal && cal.hasPrivateAccess())) && enableApptDetails); 1044 var defaultPrivacyOption = (appCtxt.get(ZmSetting.CAL_APPT_VISIBILITY) == ZmSetting.CAL_VISIBILITY_PRIV); 1045 1046 this._privateCheckbox.checked = (calItem.privacy == ZmApptEditView.PRIVACY_OPTION_PRIVATE); 1047 this._privateCheckbox.disabled = !isPrivacyEnabled; 1048 1049 if (this.GROUP_CALENDAR_ENABLED) { 1050 this._controller.setRequestResponses((attendees && attendees.length) ? calItem.shouldRsvp() : true); 1051 1052 this._isOrganizer = calItem.isOrganizer(); 1053 //this._attInputField[ZmCalBaseItem.PERSON].setEnabled(calItem.isOrganizer() || this._isForward); 1054 1055 //todo: disable notification for attendee 1056 1057 if(this._organizerData) { 1058 this._organizerData.innerHTML = calItem.getOrganizer() || ""; 1059 } 1060 this._calItemOrganizer = calItem.getOrganizer() || ""; 1061 1062 if(!isNew) this.addFreeBusyExcludeInfo(this.getOrganizerEmail(), calItem.startDate.getTime(), calItem.endDate.getTime()); 1063 1064 //enable forward field/picker if its not propose time view 1065 this._setAddresses(this._forwardToField, this._isProposeTime ? calItem.getOrganizer() : ""); 1066 this._forwardToField.setEnabled(!this._isProposeTime); 1067 this._forwardPicker.setEnabled(!this._isProposeTime); 1068 1069 for (var t = 0; t < this._attTypes.length; t++) { 1070 var type = this._attTypes[t]; 1071 if(this._pickerButton[type]) this._pickerButton[type].setEnabled(enableApptDetails); 1072 } 1073 1074 if(this._pickerButton[ZmCalBaseItem.OPTIONAL_PERSON]) this._pickerButton[ZmCalBaseItem.OPTIONAL_PERSON].setEnabled(enableApptDetails); 1075 } 1076 1077 1078 this._folderSelect.setEnabled(enableApptDetails); 1079 if (this._reminderSelect) { 1080 this._reminderSelect.setEnabled(enableTimeSelection); 1081 } 1082 1083 this._allDayCheckbox.disabled = !enableTimeSelection; 1084 1085 if(calItem.isAcceptingProposal) this._isDirty = true; 1086 1087 //Persona's [ Should select Persona as combination of both DisplayName, FromAddress ] 1088 if(calItem.identity){ 1089 this.setIdentity(calItem.identity); 1090 }else{ 1091 var sentBy = calItem.sentBy; 1092 sentBy = sentBy || (calItem.organizer != calItem.getFolder().getOwner() ? calItem.organizer : null); 1093 if(sentBy){ 1094 var ic = appCtxt.getIdentityCollection(); 1095 if (ic) { 1096 this.setIdentity(ic.getIdentityBySendAddress(sentBy)); 1097 } 1098 } 1099 } 1100 1101 this.setApptMessage(this._getMeetingStatusMsg(calItem)); 1102 1103 this.updateToolbarOps(); 1104 if(this._isProposeTime) { 1105 this._controller.setRequestResponses(false); 1106 } 1107 else if (this._isForward) { 1108 this._controller.setRequestResponses(calItem.rsvp); 1109 this._controller.setRequestResponsesEnabled(false); 1110 } 1111 else { 1112 this._controller.setRequestResponses(calItem && calItem.hasAttendees() ? calItem.shouldRsvp() : true); 1113 } 1114 1115 showScheduleView = showScheduleView && !this._isForward; 1116 1117 if(this._controller.isSave() && showScheduleView){ 1118 this._toggleInlineScheduler(true); 1119 }else{ 1120 this._schedulerOpened = null; 1121 this._closeScheduler(); 1122 } 1123 1124 this._expandInlineScheduler = (showScheduleView && !isNew); 1125 1126 }; 1127 1128 ZmApptEditView.prototype.getFreeBusyExcludeInfo = 1129 function(emailAddr){ 1130 return this._fbExcludeInfo ? this._fbExcludeInfo[emailAddr] : null; 1131 }; 1132 1133 ZmApptEditView.prototype.excludeLocationFBSlots = 1134 function(locations, startTime, endTime){ 1135 for(var i=0; i < locations.length; i++){ 1136 var location = locations[i]; 1137 if(!location) continue; 1138 this.addFreeBusyExcludeInfo(location.getEmail(), startTime, endTime); 1139 } 1140 }; 1141 1142 ZmApptEditView.prototype.addFreeBusyExcludeInfo = 1143 function(emailAddr, startTime, endTime){ 1144 if(!this._fbExcludeInfo) this._fbExcludeInfo = {}; 1145 // DISABLE client side exclude info usage. Now using the GetFreeBusyInfo 1146 // call with ExcludeId, where the server performs the exclusion of the 1147 // current appt. 1148 // 1149 //this._fbExcludeInfo[emailAddr] = { 1150 // s: startTime, 1151 // e: endTime 1152 //}; 1153 }; 1154 1155 ZmApptEditView.prototype._getMeetingStatusMsg = 1156 function(calItem){ 1157 var statusMsg = null; 1158 if(!this.isAttendeesEmpty() && calItem.isDraft){ 1159 if(calItem.inviteNeverSent){ 1160 statusMsg = ZmMsg.inviteNotSent; 1161 }else{ 1162 statusMsg = ZmMsg.updatedInviteNotSent; 1163 } 1164 } 1165 return statusMsg; 1166 }; 1167 1168 ZmApptEditView.prototype.setApptMessage = 1169 function(msg, icon){ 1170 if(msg){ 1171 Dwt.setVisible(this._inviteMsgContainer, true); 1172 this._inviteMsg.innerHTML = msg; 1173 }else{ 1174 Dwt.setVisible(this._inviteMsgContainer, false); 1175 } 1176 }; 1177 1178 ZmApptEditView.prototype.getCalItemOrganizer = 1179 function() { 1180 var folderId = this._folderSelect.getValue(); 1181 var organizer = new ZmContact(null); 1182 organizer.initFromEmail(this._calItemOrganizer, true); 1183 return organizer; 1184 }; 1185 1186 ZmApptEditView.prototype._createHTML = 1187 function() { 1188 // cache these Id's since we use them more than once 1189 this._allDayCheckboxId = this._htmlElId + "_allDayCheckbox"; 1190 this._repeatDescId = this._htmlElId + "_repeatDesc"; 1191 this._startTimeAtLblId = this._htmlElId + "_startTimeAtLbl"; 1192 this._endTimeAtLblId = this._htmlElId + "_endTimeAtLbl"; 1193 this._isAppt = true; 1194 1195 var subs = { 1196 id: this._htmlElId, 1197 height: (this.parent.getSize().y - 30), 1198 currDate: (AjxDateUtil.simpleComputeDateStr(new Date())), 1199 isGalEnabled: appCtxt.get(ZmSetting.GAL_ENABLED), 1200 isAppt: true, 1201 isGroupCalEnabled: this.GROUP_CALENDAR_ENABLED 1202 }; 1203 1204 this.getHtmlElement().innerHTML = AjxTemplate.expand("calendar.Appointment#ComposeView", subs); 1205 }; 1206 1207 ZmApptEditView.prototype._createWidgets = 1208 function(width) { 1209 ZmCalItemEditView.prototype._createWidgets.call(this, width); 1210 1211 this._attInputField = {}; 1212 1213 if (this.GROUP_CALENDAR_ENABLED) { 1214 this._attendeesInputField = this._createInputField("_person", ZmCalBaseItem.PERSON, { 1215 label: ZmMsg.attendees, 1216 bubbleAddedCallback: new AjxCallback(this, this._handleAddedAttendees, [ZmCalBaseItem.PERSON]), 1217 bubbleRemovedCallback: new AjxCallback(this, this._handleRemovedAttendees, [ZmCalBaseItem.PERSON]) 1218 }); 1219 this._optAttendeesInputField = this._createInputField("_optional", ZmCalBaseItem.OPTIONAL_PERSON, { 1220 label: ZmMsg.optionalAttendees, 1221 bubbleAddedCallback: new AjxCallback(this, this._handleAddedAttendees, [ZmCalBaseItem.OPTIONAL_PERSON]), 1222 bubbleRemovedCallback: new AjxCallback(this, this._handleRemovedAttendees, [ZmCalBaseItem.OPTIONAL_PERSON]) 1223 }); 1224 //add Resources Field 1225 if (appCtxt.get(ZmSetting.GAL_ENABLED)) { 1226 this._resourceInputField = this._createInputField("_resourcesData", ZmCalBaseItem.EQUIPMENT, { 1227 strictMode:false, 1228 label: ZmMsg.equipmentAttendee, 1229 bubbleAddedCallback: this._handleAddedAttendees.bind(this, ZmCalBaseItem.EQUIPMENT), 1230 bubbleRemovedCallback: this._handleRemovedAttendees.bind(this, ZmCalBaseItem.EQUIPMENT) 1231 }); 1232 } 1233 } 1234 1235 // add location input field 1236 this._locationInputField = this._createInputField("_location", ZmCalBaseItem.LOCATION, { 1237 strictMode: false, 1238 noAddrBubbles: !appCtxt.get(ZmSetting.GAL_ENABLED), 1239 bubbleAddedCallback: this._handleAddedAttendees.bind(this, ZmCalBaseItem.LOCATION), 1240 bubbleRemovedCallback: this._handleRemovedAttendees.bind(this, ZmCalBaseItem.LOCATION), 1241 label: ZmMsg.location 1242 }); 1243 1244 this._mainId = this._htmlElId + "_main"; 1245 this._main = document.getElementById(this._mainId); 1246 1247 this._mainTableId = this._htmlElId + "_table"; 1248 this._mainTable = document.getElementById(this._mainTableId); 1249 1250 var edvId = AjxCore.assignId(this); 1251 this._schButtonId = this._htmlElId + "_scheduleButton"; 1252 this._showOptionalId = this._htmlElId + "_show_optional"; 1253 this._showResourcesId = this._htmlElId + "_show_resources"; 1254 1255 this._showOptional = document.getElementById(this._showOptionalId); 1256 this._showResources = document.getElementById(this._showResourcesId); 1257 1258 this._schButton = document.getElementById(this._schButtonId); 1259 this._schButton._editViewId = edvId; 1260 this._schImage = document.getElementById(this._htmlElId + "_scheduleImage"); 1261 this._schImage._editViewId = edvId; 1262 this._makeFocusable(this._schButton); 1263 this._makeFocusable(this._schImage); 1264 Dwt.setHandler(this._schButton, DwtEvent.ONCLICK, ZmCalItemEditView._onClick); 1265 Dwt.setHandler(this._schImage, DwtEvent.ONCLICK, ZmCalItemEditView._onClick); 1266 1267 this._resourcesContainer = document.getElementById(this._htmlElId + "_resourcesContainer"); 1268 1269 this._resourcesData = document.getElementById(this._htmlElId + "_resourcesData"); 1270 this._schedulerContainer = document.getElementById(this._htmlElId + "_scheduler"); 1271 this._suggestions = document.getElementById(this._htmlElId + "_suggestions"); 1272 Dwt.setVisible(this._suggestions, false); 1273 1274 this._attendeeStatusId = this._htmlElId + "_attendee_status"; 1275 this._attendeeStatus = document.getElementById(this._attendeeStatusId); 1276 Dwt.setVisible(this._attendeeStatus, false); 1277 1278 this._suggestTimeId = this._htmlElId + "_suggest_time"; 1279 this._suggestTime = document.getElementById(this._suggestTimeId); 1280 Dwt.setVisible(this._suggestTime, !this._isForward); 1281 this._suggestLocationId = this._htmlElId + "_suggest_location"; 1282 this._suggestLocation = document.getElementById(this._suggestLocationId); 1283 Dwt.setVisible(this._suggestLocation, !this._isForward && !this._isProposeTime); 1284 1285 this._locationStatusId = this._htmlElId + "_location_status"; 1286 this._locationStatus = document.getElementById(this._locationStatusId); 1287 Dwt.setVisible(this._locationStatus, false); 1288 this._locationStatusMode = ZmApptEditView.LOCATION_STATUS_NONE; 1289 1290 this._locationStatusActionId = this._htmlElId + "_location_status_action"; 1291 this._locationStatusAction = document.getElementById(this._locationStatusActionId); 1292 Dwt.setVisible(this._locationStatusAction, false); 1293 1294 this._schedulerOptions = document.getElementById(this._htmlElId + "_scheduler_option"); 1295 1296 // show-as DwtSelect 1297 this._showAsSelect = new DwtSelect({parent:this, parentElement: (this._htmlElId + "_showAsSelect")}); 1298 this._showAsSelect.setAttribute('aria-label', ZmMsg.showAs); 1299 for (var i = 0; i < ZmApptViewHelper.SHOWAS_OPTIONS.length; i++) { 1300 var option = ZmApptViewHelper.SHOWAS_OPTIONS[i]; 1301 this._showAsSelect.addOption(option.label, option.selected, option.value, "ShowAs" + option.value); 1302 } 1303 1304 this._showAsSelect.addChangeListener(new AjxListener(this, this.setShowAsFlag, [true])); 1305 this._folderSelect.addChangeListener(new AjxListener(this, this._folderListener)); 1306 this._showAsSelect.setAttribute('aria-label', ZmMsg.showAs); 1307 1308 this._privateCheckbox = document.getElementById(this._htmlElId + "_privateCheckbox"); 1309 1310 // time DwtTimeSelect 1311 var timeSelectListener = new AjxListener(this, this._timeChangeListener); 1312 this._startTimeSelect = new DwtTimeInput(this, DwtTimeInput.START); 1313 this._startTimeSelect.reparentHtmlElement(this._htmlElId + "_startTimeSelect"); 1314 this._startTimeSelect.addChangeListener(timeSelectListener); 1315 1316 this._endTimeSelect = new DwtTimeInput(this, DwtTimeInput.END); 1317 this._endTimeSelect.reparentHtmlElement(this._htmlElId + "_endTimeSelect"); 1318 this._endTimeSelect.addChangeListener(timeSelectListener); 1319 1320 if (this.GROUP_CALENDAR_ENABLED) { 1321 // create without saving in this._attInputField (will overwrite attendee input) 1322 this._forwardToField = this._createInputField("_to_control",ZmCalBaseItem.FORWARD); 1323 } 1324 1325 // timezone DwtSelect 1326 var timezoneListener = new AjxListener(this, this._timezoneListener); 1327 this._tzoneSelectStartElement = document.getElementById(this._htmlElId + "_tzoneSelectStart"); 1328 this._tzoneSelectStart = new DwtSelect({parent:this, parentElement:this._tzoneSelectStartElement, layout:DwtMenu.LAYOUT_SCROLL, maxRows:7}); 1329 this._tzoneSelectStart.addChangeListener(timezoneListener); 1330 this._tzoneSelectStart.setData(ZmApptEditView.TIMEZONE_TYPE, ZmApptEditView.START_TIMEZONE); 1331 this._tzoneSelectStart.dynamicButtonWidth(); 1332 1333 this._tzoneSelectEndElement = document.getElementById(this._htmlElId + "_tzoneSelectEnd"); 1334 this._tzoneSelectEnd = new DwtSelect({parent:this, parentElement:this._tzoneSelectEndElement, layout:DwtMenu.LAYOUT_SCROLL, maxRows:7}); 1335 this._tzoneSelectEnd.addChangeListener(timezoneListener); 1336 this._tzoneSelectEnd.setData(ZmApptEditView.TIMEZONE_TYPE, ZmApptEditView.END_TIMEZONE); 1337 this._tzoneSelectEnd.dynamicButtonWidth(); 1338 1339 // NOTE: tzone select is initialized later 1340 1341 // init auto-complete widget if contacts app enabled 1342 if (appCtxt.get(ZmSetting.CONTACTS_ENABLED)) { 1343 this._initAutocomplete(); 1344 } 1345 1346 this._organizerOptions = document.getElementById(this._htmlElId + "_organizer_options"); 1347 this._organizerData = document.getElementById(this._htmlElId + "_organizer"); 1348 this._optionalAttendeesContainer = document.getElementById(this._htmlElId + "_optionalContainer"); 1349 1350 this._maxPickerWidth = 0; 1351 var isPickerEnabled = (appCtxt.get(ZmSetting.CONTACTS_ENABLED) || 1352 appCtxt.get(ZmSetting.GAL_ENABLED) || 1353 appCtxt.multiAccounts); 1354 if (isPickerEnabled) { 1355 this._createContactPicker(this._htmlElId + "_picker", new AjxListener(this, this._addressButtonListener), ZmCalBaseItem.PERSON, true); 1356 this._createContactPicker(this._htmlElId + "_req_att_picker", new AjxListener(this, this._attendeesButtonListener, ZmCalBaseItem.PERSON), ZmCalBaseItem.PERSON); 1357 this._createContactPicker(this._htmlElId + "_opt_att_picker", new AjxListener(this, this._attendeesButtonListener, ZmCalBaseItem.OPTIONAL_PERSON), ZmCalBaseItem.OPTIONAL_PERSON); 1358 if (appCtxt.get(ZmSetting.GAL_ENABLED)) { 1359 //do not create picker if GAL is disabled. 1360 this._createContactPicker(this._htmlElId + "_loc_picker", new AjxListener(this, this._locationButtonListener, ZmCalBaseItem.LOCATION), ZmCalBaseItem.LOCATION); 1361 } 1362 this._createContactPicker(this._htmlElId + "_res_btn", new AjxListener(this, this._locationButtonListener, ZmCalBaseItem.EQUIPMENT), ZmCalBaseItem.EQUIPMENT); 1363 } 1364 1365 //Personas 1366 //TODO: Remove size check once we add identityCollection change listener. 1367 if (appCtxt.get(ZmSetting.IDENTITIES_ENABLED) && !appCtxt.multiAccounts){ 1368 var identityOptions = this._getIdentityOptions(); 1369 this.identitySelect = new DwtSelect({parent:this, options:identityOptions, parentElement: (this._htmlElId + "_identity")}); 1370 this.identitySelect.setToolTipContent(ZmMsg.chooseIdentity); 1371 } 1372 1373 this._setIdentityVisible(); 1374 this.updateToolbarOps(); 1375 1376 if (this._resourcesContainer) { 1377 Dwt.setVisible(this._resourcesContainer, false); 1378 } 1379 1380 if(this.GROUP_CALENDAR_ENABLED) { 1381 Dwt.setVisible(this._optionalAttendeesContainer, false); 1382 Dwt.setVisible(this._optAttendeesInputField.getInputElement(), false); 1383 if(this._resourceInputField) { Dwt.setVisible(this._resourceInputField.getInputElement(), false); } 1384 } 1385 1386 this._inviteMsgContainer = document.getElementById(this._htmlElId + "_invitemsg_container"); 1387 this._inviteMsg = document.getElementById(this._htmlElId + "_invitemsg"); 1388 1389 this.resize(); 1390 }; 1391 1392 ZmApptEditView.prototype._createInputField = 1393 function(idTag, attType, params) { 1394 1395 params = params || {}; 1396 1397 var height = AjxEnv.isSafari && !AjxEnv.isSafariNightly ? "52px;" : "21px"; 1398 var overflow = AjxEnv.isSafari && !AjxEnv.isSafariNightly ? false : true; 1399 1400 var inputId = this.parent._htmlElId + idTag + "_input"; 1401 var cellId = this._htmlElId + idTag; 1402 var input; 1403 if (!params.noAddrBubbles) { 1404 var aifParams = { 1405 label: params.label, 1406 autocompleteListView: this._acAddrSelectList, 1407 inputId: inputId, 1408 bubbleAddedCallback: params.bubbleAddedCallback, 1409 bubbleRemovedCallback: params.bubbleRemovedCallback, 1410 type: attType, 1411 strictMode: params.strictMode 1412 } 1413 var input = this._attInputField[attType] = new ZmAddressInputField(aifParams); 1414 input.reparentHtmlElement(cellId); 1415 } else { 1416 var params = { 1417 parent: this, 1418 parentElement: cellId, 1419 label: params.label, 1420 inputId: inputId 1421 }; 1422 if (idTag == '_person' || 1423 idTag == '_optional' || 1424 idTag == '_to_control') { 1425 params.forceMultiRow = true; 1426 } 1427 input = this._attInputField[attType] = new DwtInputField(params); 1428 } 1429 1430 var inputEl = input.getInputElement(); 1431 Dwt.setSize(inputEl, "100%", height); 1432 inputEl._attType = attType; 1433 1434 return input; 1435 }; 1436 1437 ZmApptEditView.prototype._createContactPicker = 1438 function(pickerId, listener, addrType, isForwardPicker) { 1439 var pickerEl = document.getElementById(pickerId); 1440 if (pickerEl) { 1441 var buttonId = Dwt.getNextId(); 1442 var button = new DwtButton({parent:this, id:buttonId, className: "ZButton ZPicker"}); 1443 if(isForwardPicker) { 1444 this._forwardPicker = button; 1445 }else { 1446 this._pickerButton[addrType] = button; 1447 } 1448 button.setText(pickerEl.innerHTML); 1449 button.replaceElement(pickerEl); 1450 1451 button.addSelectionListener(listener); 1452 button.addrType = addrType; 1453 1454 var btnWidth = button.getSize().x; 1455 if(btnWidth > this._maxPickerWidth) this._maxPickerWidth = btnWidth; 1456 } 1457 }; 1458 1459 1460 ZmApptEditView.prototype._onSuggestionClose = 1461 function() { 1462 // Make the trigger links visible and resize now that the suggestion panel is hidden 1463 Dwt.setVisible(this._suggestTime, !this._isForward); 1464 Dwt.setVisible(this._suggestLocation, !this._isForward && !this._isProposeTime && appCtxt.get(ZmSetting.GAL_ENABLED)); 1465 this.resize(); 1466 } 1467 1468 ZmApptEditView.prototype._showTimeSuggestions = 1469 function() { 1470 // Display the time suggestion panel. 1471 Dwt.setVisible(this._suggestions, true); 1472 Dwt.setVisible(this._suggestTime, false); 1473 Dwt.setVisible(this._suggestLocation, !this._isProposeTime && appCtxt.get(ZmSetting.GAL_ENABLED)); 1474 this._scheduleAssistant.show(true); 1475 this._scheduleAssistant.suggestAction(true, false); 1476 1477 this.resize(); 1478 }; 1479 1480 ZmApptEditView.prototype._showLocationSuggestions = 1481 function() { 1482 // Display the location suggestion panel 1483 Dwt.setVisible(this._suggestions, true); 1484 Dwt.setVisible(this._suggestLocation, false); 1485 Dwt.setVisible(this._suggestTime, true); 1486 this._scheduleAssistant.show(false); 1487 this._scheduleAssistant.suggestAction(true, false); 1488 1489 this.resize(); 1490 }; 1491 1492 ZmApptEditView.prototype._showLocationStatusAction = 1493 function() { 1494 if (!this._resolveLocationDialog) { 1495 this._resolveLocationDialog = new ZmResolveLocationConflictDialog( 1496 this._controller, this, 1497 this._locationConflictOKCallback.bind(this), 1498 this._scheduleAssistant); 1499 } else { 1500 this._resolveLocationDialog.cleanup(); 1501 } 1502 1503 this._resolveLocationDialog.popup(this._calItem, this._inst, this._locationExceptions); 1504 }; 1505 1506 // Invoked from 'OK' button of location conflict resolve dialog 1507 ZmApptEditView.prototype._locationConflictOKCallback = 1508 function(locationExceptions, alteredLocations) { 1509 this._locationExceptions = locationExceptions; 1510 this._alteredLocations = alteredLocations; 1511 this.locationConflictChecker(); 1512 }; 1513 1514 ZmApptEditView.prototype._toggleOptionalAttendees = 1515 function(forceShow) { 1516 this._optionalAttendeesShown = ! this._optionalAttendeesShown || forceShow; 1517 this._showOptional.innerHTML = this._optionalAttendeesShown ? ZmMsg.hideOptional : ZmMsg.showOptional; 1518 Dwt.setVisible(this._optionalAttendeesContainer, Boolean(this._optionalAttendeesShown)) 1519 1520 var inputEl = this._attInputField[ZmCalBaseItem.OPTIONAL_PERSON].getInputElement(); 1521 Dwt.setVisible(inputEl, Boolean(this._optionalAttendeesShown)); 1522 this.resize(); 1523 }; 1524 1525 ZmApptEditView.prototype._toggleResourcesField = 1526 function(forceShow) { 1527 this._resourcesShown = ! this._resourcesShown || forceShow; 1528 this.showResourceField(this._resourcesShown); 1529 1530 var inputEl = this._attInputField[ZmCalBaseItem.EQUIPMENT].getInputElement(); 1531 Dwt.setVisible(inputEl, Boolean(this._resourcesShown)); 1532 this.resize(); 1533 }; 1534 1535 ZmApptEditView.prototype.showResourceField = 1536 function(show){ 1537 this._showResources.innerHTML = show ? ZmMsg.hideEquipment : ZmMsg.showEquipment; 1538 Dwt.setVisible(this._resourcesContainer, Boolean(show)) 1539 this.resize(); 1540 }; 1541 1542 1543 ZmApptEditView.prototype.showOptional = 1544 function() { 1545 this._toggleOptionalAttendees(true); 1546 }; 1547 1548 ZmApptEditView.prototype._closeScheduler = 1549 function() { 1550 this._schButton.innerHTML = ZmMsg.show; 1551 this._schImage.className = "ImgSelectPullDownArrow"; 1552 if(this._scheduleView) { 1553 this._scheduleView.setVisible(false); 1554 this.resize(); 1555 } 1556 }; 1557 1558 ZmApptEditView.prototype._toggleInlineScheduler = 1559 function(forceShow) { 1560 1561 if(this._schedulerOpened && !forceShow) { 1562 this._schedulerOpened = false; 1563 this._closeScheduler(); 1564 return; 1565 } 1566 1567 this._schedulerOpened = true; 1568 this._schButton.innerHTML = ZmMsg.hide; 1569 this._schImage.className = "ImgSelectPullUpArrow"; 1570 1571 var scheduleView = this.getScheduleView(); 1572 1573 //todo: scheduler auto complete 1574 Dwt.setVisible(this._schedulerContainer, true); 1575 scheduleView.setVisible(true); 1576 scheduleView.resetPagelessMode(true); 1577 scheduleView.showMe(); 1578 1579 this.resize(); 1580 }; 1581 1582 ZmApptEditView.prototype.getScheduleView = 1583 function() { 1584 if(!this._scheduleView) { 1585 var appt = this.parent.getAppt(); 1586 this._scheduleView = new ZmFreeBusySchedulerView(this, this._attendees, this._controller, 1587 this._dateInfo, appt, this.showConflicts.bind(this)); 1588 this._scheduleView.reparentHtmlElement(this._schedulerContainer); 1589 this._scheduleView.setScrollStyle(Dwt.SCROLL_Y); 1590 1591 var closeCallback = this._onSuggestionClose.bind(this); 1592 this._scheduleAssistant = new ZmScheduleAssistantView(this, this._controller, this, closeCallback); 1593 this._scheduleAssistant.reparentHtmlElement(this._suggestions); 1594 AjxTimedAction.scheduleAction(new AjxTimedAction(this, this.loadPreference), 300); 1595 } 1596 return this._scheduleView; 1597 }; 1598 1599 ZmApptEditView.prototype._resetAttendeeCount = 1600 function() { 1601 for (var i = 0; i < ZmFreeBusySchedulerView.FREEBUSY_NUM_CELLS; i++) { 1602 this._allAttendees[i] = 0; 1603 delete this._allAttendeesStatus[i]; 1604 } 1605 }; 1606 1607 1608 //TODO: 1609 // 1. Organizer/From is always Persona - Done 1610 // 2. Remote Cals - sentBy is Persona - Done 1611 // 3. Appt. Summary body needs Persona details - Needs Action 1612 // 4. No Persona's Case - Done 1613 1614 ZmApptEditView.prototype.setIdentity = 1615 function(identity){ 1616 if (this.identitySelect) { 1617 identity = identity || appCtxt.getIdentityCollection().defaultIdentity; 1618 this.identitySelect.setSelectedValue(identity.id); 1619 } 1620 }; 1621 1622 ZmApptEditView.prototype.getIdentity = 1623 function() { 1624 1625 if (this.identitySelect) { 1626 var collection = appCtxt.getIdentityCollection(); 1627 var val = this.identitySelect.getValue(); 1628 var identity = collection.getById(val); 1629 return identity ? identity : collection.defaultIdentity; 1630 } 1631 }; 1632 1633 ZmApptEditView.prototype._setIdentityVisible = 1634 function() { 1635 var div = document.getElementById(this._htmlElId + "_identityContainer"); 1636 if (!div) return; 1637 1638 var visible = this.identitySelect && appCtxt.get(ZmSetting.IDENTITIES_ENABLED) ? this.identitySelect.getOptionCount() > 1 : false; 1639 Dwt.setVisible(div, visible); 1640 }; 1641 1642 ZmApptEditView.prototype._getIdentityOptions = 1643 function() { 1644 var options = []; 1645 var identityCollection = appCtxt.getIdentityCollection(); 1646 var identities = identityCollection.getIdentities(); 1647 var defaultIdentity = identityCollection.defaultIdentity; 1648 for (var i = 0, count = identities.length; i < count; i++) { 1649 var identity = identities[i]; 1650 options.push(new DwtSelectOptionData(identity.id, this._getIdentityText(identity), (identity.id == defaultIdentity.id))); 1651 } 1652 return options; 1653 }; 1654 1655 ZmApptEditView.prototype._getIdentityText = 1656 function(identity, account) { 1657 var name = identity.name; 1658 if (identity.isDefault && name == ZmIdentity.DEFAULT_NAME) { 1659 name = account ? account.getDisplayName() : ZmMsg.accountDefault; 1660 } 1661 1662 // default replacement parameters 1663 var defaultIdentity = appCtxt.getIdentityCollection().defaultIdentity; 1664 var params = [ 1665 name, 1666 (identity.sendFromDisplay || ''), 1667 identity.sendFromAddress, 1668 ZmMsg.accountDefault, 1669 appCtxt.get(ZmSetting.DISPLAY_NAME), 1670 defaultIdentity.sendFromAddress 1671 ]; 1672 1673 // get appropriate pattern 1674 var pattern; 1675 if (identity.isDefault) { 1676 pattern = ZmMsg.identityTextPrimary; 1677 } 1678 else if (identity.isFromDataSource) { 1679 var ds = appCtxt.getDataSourceCollection().getById(identity.id); 1680 params[1] = ds.userName || ''; 1681 params[2] = ds.getEmail(); 1682 var provider = ZmDataSource.getProviderForAccount(ds); 1683 pattern = (provider && ZmMsg["identityText-"+provider.id]) || ZmMsg.identityTextExternal; 1684 } 1685 else { 1686 pattern = ZmMsg.identityTextPersona; 1687 } 1688 1689 // format text 1690 return AjxMessageFormat.format(pattern, params); 1691 }; 1692 1693 ZmApptEditView.prototype._addressButtonListener = 1694 function(ev) { 1695 var obj = ev ? DwtControl.getTargetControl(ev) : null; 1696 this._forwardToField.setEnabled(false); 1697 if (!this._contactPicker) { 1698 AjxDispatcher.require("ContactsCore"); 1699 var buttonInfo = [ 1700 { id: AjxEmailAddress.TO, label: ZmMsg.toLabel } 1701 ]; 1702 this._contactPicker = new ZmContactPicker(buttonInfo); 1703 this._contactPicker.registerCallback(DwtDialog.OK_BUTTON, this._contactPickerOkCallback, this); 1704 this._contactPicker.registerCallback(DwtDialog.CANCEL_BUTTON, this._contactPickerCancelCallback, this); 1705 } 1706 1707 var addrList = {}; 1708 var type = AjxEmailAddress.TO; 1709 addrList[type] = this._forwardToField.getAddresses(true); 1710 1711 var str = (this._forwardToField.getValue() && !(addrList[type] && addrList[type].length)) ? this._forwardToField.getValue() : ""; 1712 this._contactPicker.popup(type, addrList, str); 1713 }; 1714 1715 ZmApptEditView.prototype._attendeesButtonListener = 1716 function(addrType, ev) { 1717 var obj = ev ? DwtControl.getTargetControl(ev) : null; 1718 var inputObj = this._attInputField[addrType]; 1719 inputObj.setEnabled(false); 1720 var contactPicker = this._attendeePicker[addrType]; 1721 if (!contactPicker) { 1722 AjxDispatcher.require("ContactsCore"); 1723 var buttonInfo = [ 1724 { id: AjxEmailAddress.TO, label: ZmMsg.toLabel } 1725 ]; 1726 contactPicker = this._attendeePicker[addrType] = new ZmContactPicker(buttonInfo); 1727 contactPicker.registerCallback(DwtDialog.OK_BUTTON, this._attendeePickerOkCallback, this, [addrType]); 1728 contactPicker.registerCallback(DwtDialog.CANCEL_BUTTON, this._attendeePickerCancelCallback, this, [addrType]); 1729 } 1730 1731 var addrList = {}; 1732 var type = AjxEmailAddress.TO; 1733 addrList[type] = this._attInputField[addrType].getAddresses(true); 1734 1735 var str = (inputObj.getValue() && !(addrList[type] && addrList[type].length)) ? inputObj.getValue() : ""; 1736 contactPicker.popup(type, addrList, str); 1737 }; 1738 1739 ZmApptEditView.prototype._locationButtonListener = 1740 function(addrType, ev) { 1741 var obj = ev ? DwtControl.getTargetControl(ev) : null; 1742 var inputObj = this._attInputField[addrType]; 1743 if(inputObj) inputObj.setEnabled(false); 1744 var locationPicker = this.getAttendeePicker(addrType); 1745 locationPicker.popup(); 1746 }; 1747 1748 ZmApptEditView.prototype.getAttendeePicker = 1749 function(addrType) { 1750 var attendeePicker = this._attendeePicker[addrType]; 1751 if (!attendeePicker) { 1752 attendeePicker = this._attendeePicker[addrType] = new ZmAttendeePicker(this, this._attendees, this._controller, addrType, this._dateInfo); 1753 attendeePicker.registerCallback(DwtDialog.OK_BUTTON, this._locationPickerOkCallback, this, [addrType]); 1754 attendeePicker.registerCallback(DwtDialog.CANCEL_BUTTON, this._attendeePickerCancelCallback, this, [addrType]); 1755 attendeePicker.initialize(this._calItem, this._mode, this._isDirty, this._apptComposeMode); 1756 } 1757 return attendeePicker; 1758 }; 1759 1760 // Transfers addresses from the contact picker to the appt compose view. 1761 ZmApptEditView.prototype._attendeePickerOkCallback = 1762 function(addrType, addrs) { 1763 1764 this._attInputField[addrType].setEnabled(true); 1765 var vec = (addrs instanceof AjxVector) ? addrs : addrs[AjxEmailAddress.TO]; 1766 this._setAddresses(this._attInputField[addrType], vec); 1767 1768 this._activeInputField = addrType; 1769 this._handleAttendeeField(addrType); 1770 this._attendeePicker[addrType].popdown(); 1771 }; 1772 1773 /** 1774 * One-stop shop for setting address field content. The input may be either a DwtInputField or a 1775 * ZmAddressInputField. The address(es) passed in may be a string, an array, or an AjxVector. The 1776 * latter two types may have a member type of string, AjxEmailAddress, or ZmContact/ZmResource. 1777 * 1778 * @param addrInput 1779 * @param addrs 1780 * @param type 1781 * @param shortForm 1782 * 1783 * @private 1784 */ 1785 ZmApptEditView.prototype._setAddresses = 1786 function(addrInput, addrs, type, shortForm) { 1787 1788 // non-person attendees are shown in short form by default 1789 shortForm = (shortForm || (type && type != ZmCalBaseItem.PERSON)); 1790 1791 // if we get a string with multiple email addresses, split it 1792 if (typeof addrs == "string" && (addrs.indexOf(ZmAppt.ATTENDEES_SEPARATOR) != -1)) { 1793 var result = AjxEmailAddress.parseEmailString(addrs, type); 1794 addrs = result.good; 1795 } 1796 1797 if (addrs.isAjxVector) { 1798 //todo - why aren't we using ZmRecipients way more here? We probably could use a refactoring to unite this code with the 1799 //mail compose recipients case - same thing as attendees, more or less. 1800 addrs = ZmRecipients.expandAddrs(addrs); //expand groups to their individual emails (not DLs). 1801 } 1802 1803 // make sure we have an array to deal with 1804 addrs = (addrs instanceof AjxVector) ? addrs.getArray() : (typeof addrs == "string") ? [addrs] : addrs; 1805 1806 addrInput.clear(); 1807 if (addrs && addrs.length) { 1808 var len = addrs.length; 1809 for (var i = 0; i < len; i++) { 1810 var addr = addrs[i]; 1811 if (addr) { 1812 var addrStr, email, match; 1813 if (typeof addr == "string") { 1814 addrStr = addr; 1815 } 1816 else if (addr.isAjxEmailAddress) { 1817 addrStr = addr.toString(shortForm); 1818 match = {isDL: addr.isGroup && addr.canExpand, email: addrStr}; 1819 } 1820 else if (addr instanceof ZmContact) { 1821 email = addr.getEmail(true); 1822 //bug: 57858 - give preference to lookup email address if its present 1823 //bug:60427 to show display name format the lookupemail 1824 addrStr = addr.getLookupEmail() ? (new AjxEmailAddress(addr.getLookupEmail(),null,addr.getFullNameForDisplay())).toString() : ZmApptViewHelper.getAttendeesText(addr, type); 1825 match = {isDL: addr.isGroup() && addr.canExpand, email: addrStr}; 1826 } 1827 addrInput.addBubble({address:addrStr, match:match, skipNotify:true}); 1828 } 1829 } 1830 } 1831 }; 1832 1833 // Transfers addresses from the location/resource picker to the appt compose view. 1834 ZmApptEditView.prototype._locationPickerOkCallback = 1835 function(addrType, attendees) { 1836 1837 this.parent.updateAttendees(attendees, addrType); 1838 1839 if(this._attInputField[addrType]) { 1840 this._attInputField[addrType].setEnabled(true); 1841 this._activeInputField = addrType; 1842 } 1843 1844 if(addrType == ZmCalBaseItem.LOCATION || addrType == ZmCalBaseItem.EQUIPMENT) { 1845 this.updateAttendeesCache(addrType, this._attendees[addrType].getArray()); 1846 var attendeeStr = ZmApptViewHelper.getAttendeesString(this._attendees[addrType].getArray(), addrType); 1847 this.setAttendeesField(addrType, attendeeStr); 1848 } 1849 1850 this._attendeePicker[addrType].popdown(); 1851 }; 1852 1853 // Updates the local cache with attendee objects 1854 ZmApptEditView.prototype.updateAttendeesCache = 1855 function(addrType, attendees){ 1856 1857 if (!(attendees && attendees.length)) return ""; 1858 1859 var a = []; 1860 for (var i = 0; i < attendees.length; i++) { 1861 var attendee = attendees[i]; 1862 var addr = attendee.getLookupEmail() || attendee.getEmail(); 1863 var key = addr + "-" + addrType; 1864 this._attendeesHashMap[key] = attendee; 1865 } 1866 }; 1867 1868 ZmApptEditView.prototype.setAttendeesField = 1869 function(addrType, attendees){ 1870 this._setAddresses(this._attInputField[addrType], attendees); 1871 this._handleAttendeeField(addrType); 1872 }; 1873 1874 1875 ZmApptEditView.prototype._attendeePickerCancelCallback = 1876 function(addrType) { 1877 if(this._attInputField[addrType]) { 1878 this._handleAttendeeField(addrType); 1879 this._attInputField[addrType].setEnabled(true); 1880 } 1881 }; 1882 1883 // Transfers addresses from the contact picker to the appt compose view. 1884 ZmApptEditView.prototype._contactPickerOkCallback = 1885 function(addrs) { 1886 this._forwardToField.setEnabled(true); 1887 var vec = (addrs instanceof AjxVector) ? addrs : addrs[AjxEmailAddress.TO]; 1888 this._setAddresses(this._forwardToField, vec); 1889 this._activeInputField = ZmCalBaseItem.PERSON; 1890 this._handleAttendeeField(ZmCalBaseItem.PERSON); 1891 //this._contactPicker.removePopdownListener(this._controller._dialogPopdownListener); 1892 this._contactPicker.popdown(); 1893 }; 1894 1895 ZmApptEditView.prototype._contactPickerCancelCallback = 1896 function() { 1897 this._handleAttendeeField(ZmCalBaseItem.PERSON); 1898 this._forwardToField.setEnabled(true); 1899 }; 1900 1901 ZmApptEditView.prototype.getForwardAddress = 1902 function() { 1903 return this._collectForwardAddrs(); 1904 }; 1905 1906 // Grab the good addresses out of the forward to field 1907 ZmApptEditView.prototype._collectForwardAddrs = 1908 function() { 1909 return this._collectAddrs(this._forwardToField.getValue()); 1910 }; 1911 1912 // Grab the good addresses out of the forward to field 1913 ZmApptEditView.prototype._collectAddrs = 1914 function(addrStr) { 1915 var addrs = {}; 1916 addrs[ZmApptEditView.BAD] = new AjxVector(); 1917 var val = AjxStringUtil.trim(addrStr); 1918 if (val.length == 0) return addrs; 1919 var result = AjxEmailAddress.parseEmailString(val, AjxEmailAddress.TO, false); 1920 if (result.all.size() == 0) return addrs; 1921 addrs.gotAddress = true; 1922 addrs[AjxEmailAddress.TO] = result; 1923 if (result.bad.size()) { 1924 addrs[ZmApptEditView.BAD].addList(result.bad); 1925 addrs.badType = AjxEmailAddress.TO; 1926 } 1927 return addrs; 1928 }; 1929 1930 1931 ZmApptEditView.prototype.initialize = 1932 function(calItem, mode, isDirty, apptComposeMode) { 1933 this._fbCache.clearCache(); 1934 this._editViewInitialized = false; 1935 this._isForward = (apptComposeMode == ZmApptComposeView.FORWARD); 1936 this._isProposeTime = (apptComposeMode == ZmApptComposeView.PROPOSE_TIME); 1937 this._apptComposeMode = apptComposeMode; 1938 1939 ZmCalItemEditView.prototype.initialize.call(this, calItem, mode, isDirty, apptComposeMode); 1940 1941 var scheduleView = this.getScheduleView(); 1942 scheduleView.initialize(calItem, mode, isDirty, apptComposeMode); 1943 }; 1944 1945 ZmApptEditView.prototype.isSuggestionsNeeded = 1946 function() { 1947 if (appCtxt.isOffline) { 1948 var ac = window["appCtxt"].getAppController(); 1949 return !this._isForward && this.GROUP_CALENDAR_ENABLED && ac._isPrismOnline && ac._isUserOnline; 1950 } else { 1951 return !this._isForward && this.GROUP_CALENDAR_ENABLED; 1952 } 1953 }; 1954 1955 ZmApptEditView.prototype.getCalendarAccount = 1956 function() { 1957 var cal = appCtxt.getById(this._folderSelect.getValue()); 1958 return cal && cal.getAccount(); 1959 }; 1960 1961 ZmApptEditView.prototype._folderListener = 1962 function() { 1963 var calId = this._folderSelect.getValue(); 1964 var cal = appCtxt.getById(calId); 1965 1966 // bug: 48189 - Hide schedule tab for non-ZCS acct 1967 if (appCtxt.isOffline) { 1968 var currAcct = cal.getAccount(); 1969 appCtxt.accountList.setActiveAccount(currAcct); 1970 this.setSchedulerVisibility(currAcct.isZimbraAccount && !currAcct.isMain); 1971 } 1972 1973 var isEnabled = !appCtxt.isRemoteId(cal.id) || cal.hasPrivateAccess(); 1974 1975 this._privateCheckbox.disabled = !isEnabled; 1976 1977 if(this._schedulerOpened) { 1978 var organizer = this._isProposeTime ? this.getCalItemOrganizer() : this.getOrganizer(); 1979 this._scheduleView.update(this._dateInfo, organizer, this._attendees); 1980 this._scheduleView.updateFreeBusy(); 1981 } 1982 if (appCtxt.isOffline) { 1983 this._calItem.setFolderId(calId); 1984 this.enableInputs(true); //enableInputs enables or disables the attendees/location/etc inputs based on the selected folder (calendar) - if it's local it will be disabled, and if remote - enabled. 1985 } 1986 }; 1987 1988 ZmApptEditView.prototype.setSchedulerVisibility = 1989 function(visible) { 1990 Dwt.setVisible(this._schedulerOptions, visible); 1991 Dwt.setVisible(this._schedulerContainer, visible); 1992 this.resize(); 1993 }; 1994 1995 ZmApptEditView.prototype._resetFolderSelect = 1996 function(calItem, mode) { 1997 ZmCalItemEditView.prototype._resetFolderSelect.call(this, calItem, mode); 1998 this._resetAutocompleteListView(appCtxt.getById(calItem.folderId)); 1999 }; 2000 2001 ZmApptEditView.prototype._resetAttendeesField = 2002 function(enabled) { 2003 var attField = this._attInputField[ZmCalBaseItem.PERSON]; 2004 if (attField) { 2005 attField.setEnabled(enabled); 2006 } 2007 2008 attField = this._attInputField[ZmCalBaseItem.OPTIONAL_PERSON]; 2009 if (attField) { 2010 attField.setEnabled(enabled); 2011 } 2012 }; 2013 2014 ZmApptEditView.prototype._folderPickerCallback = 2015 function(dlg, folder) { 2016 ZmCalItemEditView.prototype._folderPickerCallback.call(this, dlg, folder); 2017 this._resetAutocompleteListView(folder); 2018 if (appCtxt.isOffline) { 2019 this._resetAttendeesField(!folder.getAccount().isMain); 2020 } 2021 }; 2022 2023 ZmApptEditView.prototype._resetAutocompleteListView = 2024 function(folder) { 2025 if (appCtxt.multiAccounts && this._acContactsList) { 2026 this._acContactsList.setActiveAccount(folder.getAccount()); 2027 } 2028 }; 2029 2030 ZmApptEditView.prototype._initAutocomplete = 2031 function() { 2032 2033 var acCallback = this._autocompleteCallback.bind(this); 2034 var keyPressCallback = this._onAttendeesChange.bind(this); 2035 this._acList = {}; 2036 2037 var params = { 2038 dataClass: appCtxt.getAutocompleter(), 2039 matchValue: ZmAutocomplete.AC_VALUE_FULL, 2040 compCallback: acCallback, 2041 keyPressCallback: keyPressCallback 2042 }; 2043 2044 // autocomplete for attendees (required and optional) and forward recipients 2045 if (appCtxt.get(ZmSetting.CONTACTS_ENABLED) && this.GROUP_CALENDAR_ENABLED) { 2046 params.contextId = [this._controller.getCurrentViewId(), ZmCalBaseItem.PERSON].join("-"); 2047 var aclv = this._acContactsList = new ZmAutocompleteListView(params); 2048 this._setAutocompleteHandler(aclv, ZmCalBaseItem.PERSON); 2049 this._setAutocompleteHandler(aclv, ZmCalBaseItem.OPTIONAL_PERSON); 2050 if (this._forwardToField) { 2051 this._setAutocompleteHandler(aclv, ZmCalBaseItem.FORWARD, this._forwardToField); 2052 } 2053 } 2054 2055 if (appCtxt.get(ZmSetting.GAL_ENABLED)) { 2056 // autocomplete for locations 2057 params.keyUpCallback = this._handleLocationChange.bind(this); 2058 //params.matchValue = ZmAutocomplete.AC_VALUE_NAME; 2059 params.options = { type: ZmAutocomplete.AC_TYPE_LOCATION }; 2060 if (AjxEnv.isIE) { 2061 params.keyDownCallback = this._resetKnownLocation.bind(this); 2062 } 2063 params.contextId = [this._controller.getCurrentViewId(), ZmCalBaseItem.LOCATION].join("-"); 2064 var aclv = this._acLocationsList = new ZmAutocompleteListView(params); 2065 this._setAutocompleteHandler(aclv, ZmCalBaseItem.LOCATION); 2066 } 2067 2068 if (appCtxt.get(ZmSetting.GAL_ENABLED) && this.GROUP_CALENDAR_ENABLED) { 2069 // autocomplete for locations 2070 var app = appCtxt.getApp(ZmApp.CALENDAR); 2071 params.keyUpCallback = this._handleResourceChange.bind(this); 2072 //params.matchValue = ZmAutocomplete.AC_VALUE_NAME; 2073 params.options = { type:ZmAutocomplete.AC_TYPE_EQUIPMENT }; 2074 params.contextId = [this._controller.getCurrentViewId(), ZmCalBaseItem.EQUIPMENT].join("-"); 2075 var aclv = this._acResourcesList = new ZmAutocompleteListView(params); 2076 this._setAutocompleteHandler(aclv, ZmCalBaseItem.EQUIPMENT); 2077 } 2078 }; 2079 2080 ZmApptEditView.prototype._handleResourceChange = 2081 function(event, aclv, result) { 2082 var val = this._attInputField[ZmCalBaseItem.EQUIPMENT].getValue(); 2083 if (val == "") { 2084 this.parent.updateAttendees([], ZmCalBaseItem.EQUIPMENT); 2085 this._isKnownResource = false; 2086 } 2087 }; 2088 2089 2090 ZmApptEditView.prototype._setAutocompleteHandler = 2091 function(aclv, attType, input) { 2092 2093 input = input || this._attInputField[attType]; 2094 input.setAutocompleteListView(aclv); 2095 aclv.handle(input.getInputElement(), input._htmlElId); 2096 2097 this._acList[attType] = aclv; 2098 }; 2099 2100 ZmApptEditView.prototype._handleLocationChange = 2101 function(event, aclv, result) { 2102 var val = this._attInputField[ZmCalBaseItem.LOCATION].getValue(); 2103 if (val == "") { 2104 this.parent.updateAttendees([], ZmCalBaseItem.LOCATION); 2105 this._isKnownLocation = false; 2106 } 2107 }; 2108 2109 ZmApptEditView.prototype._autocompleteCallback = 2110 function(text, el, match) { 2111 if (!match) { 2112 DBG.println(AjxDebug.DBG1, "ZmApptEditView: match empty in autocomplete callback; text: " + text); 2113 return; 2114 } 2115 var attendee = match.item; 2116 var type = el && el._attType; 2117 if (attendee) { 2118 if (type == ZmCalBaseItem.FORWARD) { 2119 DBG.println("forward auto complete match : " + match) 2120 return; 2121 } 2122 if (type == ZmCalBaseItem.LOCATION || type == ZmCalBaseItem.EQUIPMENT) { 2123 var name = ZmApptViewHelper.getAttendeesText(attendee); 2124 if(name) { 2125 this._locationTextMap[name] = attendee; 2126 } 2127 var locations = text.split(/[\n,;]/); 2128 var newAttendees = []; 2129 for(var i = 0; i < locations.length; i++) { 2130 var l = AjxStringUtil.trim(locations[i]); 2131 if(this._locationTextMap[l]) { 2132 newAttendees.push(this._locationTextMap[l]); 2133 } 2134 } 2135 attendee = newAttendees; 2136 } 2137 2138 //controller tracks both optional & required attendees in common var 2139 if (type == ZmCalBaseItem.OPTIONAL_PERSON) { 2140 this.setAttendeesRole(attendee, ZmCalItem.ROLE_OPTIONAL); 2141 type = ZmCalBaseItem.PERSON; 2142 } 2143 2144 this.parent.updateAttendees(attendee, type, (type == ZmCalBaseItem.LOCATION || type == ZmCalBaseItem.EQUIPMENT )?ZmApptComposeView.MODE_REPLACE : ZmApptComposeView.MODE_ADD); 2145 2146 if (type == ZmCalBaseItem.LOCATION) { 2147 this._isKnownLocation = true; 2148 }else if(type == ZmCalBaseItem.EQUIPMENT){ 2149 this._isKnownResource = true; 2150 } 2151 2152 this._updateScheduler(type, attendee); 2153 2154 }else if(match.email){ 2155 if((type == ZmCalBaseItem.PERSON || type == ZmCalBaseItem.OPTIONAL_PERSON) && this._scheduleAssistant) { 2156 var attendees = this.getAttendeesFromString(ZmCalBaseItem.PERSON, this._attInputField[type].getValue()); 2157 this.setAttendeesRole(attendees, (type == ZmCalBaseItem.OPTIONAL_PERSON) ? ZmCalItem.ROLE_OPTIONAL : ZmCalItem.ROLE_REQUIRED); 2158 if (type == ZmCalBaseItem.OPTIONAL_PERSON) { 2159 type = ZmCalBaseItem.PERSON; 2160 } 2161 this.parent.updateAttendees(attendees, type, (type == ZmCalBaseItem.LOCATION )?ZmApptComposeView.MODE_REPLACE : ZmApptComposeView.MODE_ADD); 2162 this._updateScheduler(type, attendees); 2163 } 2164 } 2165 2166 this.updateToolbarOps(); 2167 }; 2168 2169 ZmApptEditView.prototype._handleAddedAttendees = 2170 function(addrType) { 2171 this._activeInputField = addrType; 2172 this.handleAttendeeChange(); 2173 }; 2174 2175 ZmApptEditView.prototype._handleRemovedAttendees = 2176 function(addrType) { 2177 this._activeInputField = addrType; 2178 this.handleAttendeeChange(); 2179 }; 2180 2181 ZmApptEditView.prototype._addEventHandlers = 2182 function() { 2183 var edvId = AjxCore.assignId(this); 2184 2185 // add event listeners where necessary 2186 Dwt.setHandler(this._allDayCheckbox, DwtEvent.ONCLICK, ZmCalItemEditView._onClick); 2187 Dwt.setHandler(this._repeatDescField, DwtEvent.ONCLICK, ZmCalItemEditView._onClick); 2188 if (this._showOptional) { 2189 this._makeFocusable(this._showOptional); 2190 Dwt.setHandler(this._showOptional, DwtEvent.ONCLICK, ZmCalItemEditView._onClick); 2191 } 2192 if (this._showResources) { 2193 this._makeFocusable(this._showResources); 2194 Dwt.setHandler(this._showResources, DwtEvent.ONCLICK, ZmCalItemEditView._onClick); 2195 } 2196 Dwt.setHandler(this._repeatDescField, DwtEvent.ONMOUSEOVER, ZmCalItemEditView._onMouseOver); 2197 Dwt.setHandler(this._repeatDescField, DwtEvent.ONMOUSEOUT, ZmCalItemEditView._onMouseOut); 2198 Dwt.setHandler(this._startDateField, DwtEvent.ONCHANGE, ZmCalItemEditView._onChange); 2199 Dwt.setHandler(this._endDateField, DwtEvent.ONCHANGE, ZmCalItemEditView._onChange); 2200 Dwt.setHandler(this._startDateField, DwtEvent.ONFOCUS, ZmCalItemEditView._onFocus); 2201 Dwt.setHandler(this._endDateField, DwtEvent.ONFOCUS, ZmCalItemEditView._onFocus); 2202 if (this.GROUP_CALENDAR_ENABLED) { 2203 this._makeFocusable(this._suggestTime); 2204 Dwt.setHandler(this._suggestTime, DwtEvent.ONCLICK, ZmCalItemEditView._onClick); 2205 } 2206 this._makeFocusable(this._suggestLocation); 2207 Dwt.setHandler(this._suggestLocation, DwtEvent.ONCLICK, ZmCalItemEditView._onClick); 2208 this._makeFocusable(this._locationStatusAction); 2209 Dwt.setHandler(this._locationStatusAction, DwtEvent.ONCLICK, ZmCalItemEditView._onClick); 2210 2211 this._allDayCheckbox._editViewId = this._repeatDescField._editViewId = edvId; 2212 this._startDateField._editViewId = this._endDateField._editViewId = edvId; 2213 if(this._showOptional) this._showOptional._editViewId = edvId; 2214 if(this._showResources) this._showResources._editViewId = edvId; 2215 if (this.GROUP_CALENDAR_ENABLED) { 2216 this._suggestTime._editViewId = edvId; 2217 } 2218 this._suggestLocation._editViewId = edvId; 2219 this._locationStatusAction._editViewId = edvId; 2220 2221 var inputFields = [this._attendeesInputField, this._optAttendeesInputField, 2222 this._locationInputField, this._forwardToField, this._resourceInputField]; 2223 for (var i = 0; i < inputFields.length; i++) { 2224 if(!inputFields[i]) continue; 2225 var inputField = inputFields[i]; 2226 var inputEl = inputField.getInputElement(); 2227 inputEl._editViewId = edvId; 2228 inputField.addListener(DwtEvent.ONFOCUS, this._handleOnFocus.bind(this, inputEl)); 2229 inputField.addListener(DwtEvent.ONBLUR, this._handleOnBlur.bind(this, inputEl)); 2230 inputEl.onkeyup = AjxCallback.simpleClosure(this._onAttendeesChange, this); 2231 } 2232 2233 if (this._subjectField) { 2234 this._subjectField.addListener(DwtEvent.ONBLUR, this._handleSubjectOnBlur.bind(this)); 2235 } 2236 }; 2237 2238 // cache all input fields so we dont waste time traversing DOM each time 2239 ZmApptEditView.prototype._cacheFields = 2240 function() { 2241 ZmCalItemEditView.prototype._cacheFields.call(this); 2242 this._allDayCheckbox = document.getElementById(this._allDayCheckboxId); 2243 }; 2244 2245 ZmApptEditView.prototype._resetTimezoneSelect = 2246 function(calItem, isAllDayAppt) { 2247 this._tzoneSelectStart.setSelectedValue(calItem.timezone); 2248 this._tzoneSelectEnd.setSelectedValue(calItem.endTimezone || calItem.timezone); 2249 this.handleTimezoneOverflow(); 2250 }; 2251 2252 ZmApptEditView.prototype._setTimezoneVisible = 2253 function(dateInfo) { 2254 var showTimezones = appCtxt.get(ZmSetting.CAL_SHOW_TIMEZONE) || dateInfo.timezone != AjxTimezone.getServerId(AjxTimezone.DEFAULT); 2255 var showStartTimezone = showTimezones && !dateInfo.isAllDay; 2256 var showEndTimezone = showStartTimezone && this._repeatSelect && this._repeatSelect.getValue()=="NON"; 2257 2258 if (this._tzoneSelectStartElement) { 2259 Dwt.setVisible(this._tzoneSelectStartElement, showStartTimezone); 2260 Dwt.setVisibility(this._tzoneSelectStartElement, showStartTimezone); 2261 } 2262 2263 if (this._tzoneSelectEndElement) { 2264 Dwt.setVisible(this._tzoneSelectEndElement, showEndTimezone); 2265 Dwt.setVisibility(this._tzoneSelectEndElement, showEndTimezone); 2266 } 2267 }; 2268 2269 ZmApptEditView.prototype._showTimeFields = 2270 function(show) { 2271 Dwt.setVisibility(this._startTimeSelect.getHtmlElement(), show); 2272 Dwt.setVisibility(this._endTimeSelect.getHtmlElement(), show); 2273 this._setTimezoneVisible(this._dateInfo); 2274 }; 2275 2276 ZmApptEditView.CHANGES_LOCAL = 1; 2277 ZmApptEditView.CHANGES_SIGNIFICANT = 2; 2278 ZmApptEditView.CHANGES_INSIGNIFICANT = 3; 2279 ZmApptEditView.CHANGES_TIME_RECURRENCE = 4; 2280 2281 2282 ZmApptEditView.prototype._getFormValue = 2283 function(type, attribs){ 2284 2285 var vals = []; 2286 attribs = attribs || {}; 2287 2288 switch(type){ 2289 2290 case ZmApptEditView.CHANGES_LOCAL: 2291 vals.push(this._folderSelect.getValue()); // Folder 2292 vals.push(this._showAsSelect.getValue()); // Busy Status 2293 if(!attribs.excludeReminder){ // Reminder 2294 vals.push(this._reminderSelectInput.getValue()); 2295 vals.push(this._reminderEmailCheckbox.isSelected()); 2296 vals.push(this._reminderDeviceEmailCheckbox.isSelected()); 2297 } 2298 break; 2299 2300 case ZmApptEditView.CHANGES_SIGNIFICANT: 2301 2302 vals = this._getTimeAndRecurrenceChanges(); 2303 2304 if (!attribs.excludeAttendees) { //Attendees 2305 vals.push(ZmApptViewHelper.getAttendeesString(this._attendees[ZmCalBaseItem.PERSON].getArray(), ZmCalBaseItem.PERSON, false, true)); 2306 } 2307 if(!attribs.excludeLocation) { 2308 vals.push(ZmApptViewHelper.getAttendeesString(this._attendees[ZmCalBaseItem.LOCATION].getArray(), ZmCalBaseItem.LOCATION, false, true)); 2309 //location can even be a normal label text 2310 vals.push(this._locationInputField.getValue()); 2311 } 2312 if(!attribs.excludeEquipment) { 2313 vals.push(ZmApptViewHelper.getAttendeesString(this._attendees[ZmCalBaseItem.EQUIPMENT].getArray(), ZmCalBaseItem.EQUIPMENT, false, true)); 2314 } 2315 2316 if(this._isForward && !attribs.excludeAttendees) { 2317 vals.push(this._forwardToField.getValue()); //ForwardTo 2318 } 2319 if(this.identitySelect){ 2320 vals.push(this.getIdentity().id); //Identity Select 2321 } 2322 break; 2323 2324 case ZmApptEditView.CHANGES_INSIGNIFICANT: 2325 vals.push(this._subjectField.getValue()); 2326 vals.push(this._notesHtmlEditor.getContent()); 2327 vals.push(this._privateCheckbox.checked ? ZmApptEditView.PRIVACY_OPTION_PRIVATE : ZmApptEditView.PRIVACY_OPTION_PUBLIC); 2328 //TODO: Attachments, Priority 2329 break; 2330 2331 case ZmApptEditView.CHANGES_TIME_RECURRENCE: 2332 vals = this._getTimeAndRecurrenceChanges(); 2333 break; 2334 } 2335 2336 vals = vals.join("|").replace(/\|+/, "|"); 2337 2338 return vals; 2339 }; 2340 2341 ZmApptEditView.prototype._getTimeAndRecurrenceChanges = function(){ 2342 var vals = []; 2343 var startDate = AjxDateUtil.simpleParseDateStr(this._startDateField.value); 2344 var endDate = AjxDateUtil.simpleParseDateStr(this._endDateField.value); 2345 startDate = this._startTimeSelect.getValue(startDate); 2346 endDate = this._endTimeSelect.getValue(endDate); 2347 vals.push( 2348 AjxDateUtil.getServerDateTime(startDate), // Start DateTime 2349 AjxDateUtil.getServerDateTime(endDate) // End DateTime 2350 ); 2351 if (Dwt.getDisplay(this._tzoneSelectStart.getHtmlElement()) != Dwt.DISPLAY_NONE) { 2352 vals.push(this._tzoneSelectStart.getValue()); // Start timezone 2353 vals.push(this._tzoneSelectEnd.getValue()); // End timezone 2354 } 2355 vals.push("" + this._allDayCheckbox.checked); // All Day Appt. 2356 //TODO: Detailed Recurrence, Repeat support 2357 vals.push(this._repeatSelect.getValue()); //Recurrence 2358 2359 return vals; 2360 } 2361 2362 // Returns a string representing the form content 2363 ZmApptEditView.prototype._formValue = 2364 function(excludeAttendees, excludeReminder) { 2365 2366 var attribs = { 2367 excludeAttendees: excludeAttendees, 2368 excludeReminder: excludeReminder 2369 }; 2370 2371 var sigFormValue = this._getFormValue(ZmApptEditView.CHANGES_SIGNIFICANT, attribs); 2372 var insigFormValue = this._getFormValue(ZmApptEditView.CHANGES_INSIGNIFICANT, attribs); 2373 var localFormValue = this._getFormValue(ZmApptEditView.CHANGES_LOCAL, attribs); 2374 2375 var formVals = []; 2376 formVals.push(sigFormValue, insigFormValue, localFormValue); 2377 formVals = formVals.join('|').replace(/\|+/, "|"); 2378 return formVals; 2379 }; 2380 2381 2382 ZmApptEditView.prototype.checkIsDirty = 2383 function(type, attribs){ 2384 return (this._apptFormValue[type] != this._getFormValue(type, attribs)) 2385 }; 2386 2387 ZmApptEditView.prototype._keyValue = 2388 function() { 2389 2390 return this._getFormValue(ZmApptEditView.CHANGES_SIGNIFICANT, 2391 {excludeAttendees: true, excludeEquipment: true}); 2392 }; 2393 2394 // Listeners 2395 2396 ZmApptEditView.prototype._getDateTimeText = 2397 function() { 2398 return this._dateInfo.startDate + "-" + this._dateInfo.startTimeStr + "_" + 2399 this._dateInfo.endDate + "_" + this._dateInfo.endTimeStr; 2400 2401 } 2402 2403 ZmApptEditView.prototype._timeChangeListener = 2404 function(ev, id) { 2405 DwtTimeInput.adjustStartEnd(ev, this._startTimeSelect, this._endTimeSelect, this._startDateField, this._endDateField, this._dateInfo, id); 2406 var oldTimeInfo = this._getDateTimeText(); 2407 2408 ZmApptViewHelper.getDateInfo(this, this._dateInfo); 2409 var newTimeInfo = this._getDateTimeText(); 2410 if (oldTimeInfo != newTimeInfo) { 2411 2412 this._dateInfo.isTimeModified = true; 2413 2414 if(this._schedulerOpened) { 2415 this._scheduleView._timeChangeListener(ev, id); 2416 } 2417 2418 if(this._scheduleAssistant) this._scheduleAssistant.updateTime(true, true); 2419 2420 var durationInfo = this.getDurationInfo(); 2421 this._locationConflictAppt.startDate = new Date(durationInfo.startTime); 2422 this._locationConflictAppt.endDate = new Date(durationInfo.endTime); 2423 this.locationConflictChecker(); 2424 } 2425 }; 2426 2427 ZmApptEditView.prototype._recurChangeForLocationConflict = 2428 function() { 2429 this._getRecurrence(this._locationConflictAppt); 2430 this.locationConflictChecker(); 2431 } 2432 2433 ZmApptEditView.prototype._dateTimeChangeForLocationConflict = 2434 function(oldTimeInfo) { 2435 var newTimeInfo = this._getDateTimeText(); 2436 if (oldTimeInfo != newTimeInfo) { 2437 var durationInfo = this.getDurationInfo(); 2438 this._locationConflictAppt.startDate = new Date(durationInfo.startTime); 2439 this._locationConflictAppt.endDate = new Date(durationInfo.endTime); 2440 this.locationConflictChecker(); 2441 } 2442 } 2443 2444 ZmApptEditView.prototype._dateCalSelectionListener = 2445 function(ev) { 2446 var oldTimeInfo = this._getDateTimeText(); 2447 2448 ZmCalItemEditView.prototype._dateCalSelectionListener.call(this, ev); 2449 if(this._schedulerOpened) { 2450 ZmApptViewHelper.getDateInfo(this, this._dateInfo); 2451 this._scheduleView._updateFreeBusy(); 2452 } 2453 2454 if(this._scheduleAssistant) this._scheduleAssistant.updateTime(true, true); 2455 2456 this._dateTimeChangeForLocationConflict(oldTimeInfo); 2457 }; 2458 2459 2460 ZmApptEditView.prototype.handleTimezoneOverflow = 2461 function() { 2462 var timezoneTxt = this._tzoneSelectStart.getText(); 2463 var limit = AjxEnv.isIE ? 25 : 30; 2464 if(timezoneTxt.length > limit) { 2465 var newTimezoneTxt = timezoneTxt.substring(0, limit) + '...'; 2466 this._tzoneSelectStart.setText(newTimezoneTxt); 2467 } 2468 var option = this._tzoneSelectStart.getSelectedOption(); 2469 this._tzoneSelectStart.setToolTipContent(option ? option.getDisplayValue() : timezoneTxt); 2470 timezoneTxt = this._tzoneSelectEnd.getText(); 2471 if(timezoneTxt.length > limit) { 2472 var newTimezoneTxt = timezoneTxt.substring(0, limit) + '...'; 2473 this._tzoneSelectEnd.setText(newTimezoneTxt); 2474 } 2475 option = this._tzoneSelectEnd.getSelectedOption(); 2476 this._tzoneSelectEnd.setToolTipContent(option ? option.getDisplayValue() : timezoneTxt); 2477 }; 2478 2479 ZmApptEditView.prototype._timezoneListener = 2480 function(ev) { 2481 var oldTZ = this._dateInfo.timezone; 2482 var dwtSelect = ev.item.parent.parent; 2483 var type = dwtSelect ? dwtSelect.getData(ZmApptEditView.TIMEZONE_TYPE) : ZmApptEditView.START_TIMEZONE; 2484 //bug: 55256 - Changing start timezone should auto-change end timezone 2485 if(type == ZmApptEditView.START_TIMEZONE) { 2486 var tzValue = dwtSelect.getValue(); 2487 this._tzoneSelectEnd.setSelectedValue(tzValue); 2488 } 2489 this.handleTimezoneOverflow(); 2490 ZmApptViewHelper.getDateInfo(this, this._dateInfo); 2491 if(this._schedulerOpened) { 2492 //this._controller.getApp().getFreeBusyCache().clearCache(); 2493 this._scheduleView._timeChangeListener(ev); 2494 } 2495 2496 if (oldTZ != this._dateInfo.timezone) { 2497 this._locationConflictAppt.timezone = this._dateInfo.timezone; 2498 this.locationConflictChecker(); 2499 } 2500 }; 2501 2502 2503 ZmApptEditView.prototype._repeatChangeListener = 2504 function(ev) { 2505 ZmCalItemEditView.prototype._repeatChangeListener.call(this, ev); 2506 this._setTimezoneVisible(this._dateInfo); 2507 var newSelectVal = ev._args.newValue; 2508 if (newSelectVal != "CUS") { 2509 // CUS (Custom) launches a dialog. Otherwise act upon the change here 2510 this._locationConflictAppt.setRecurType(newSelectVal); 2511 this.locationConflictChecker(); 2512 } 2513 if (newSelectVal === "WEE") { 2514 this._calItem._recurrence.repeatCustom =1; 2515 } 2516 }; 2517 2518 /** 2519 * Sets the values of the attendees input fields to reflect the current lists of 2520 * attendees. 2521 */ 2522 ZmApptEditView.prototype._setAttendees = 2523 function() { 2524 2525 for (var t = 0; t < this._attTypes.length; t++) { 2526 var type = this._attTypes[t]; 2527 var attendees = this._attendees[type].getArray(); 2528 var numAttendees = attendees.length; 2529 var addrInput = this._attInputField[type]; 2530 var curVal = AjxStringUtil.trim(this._attInputField[type].getValue()); 2531 if (type == ZmCalBaseItem.PERSON) { 2532 var reqAttendees = ZmApptViewHelper.filterAttendeesByRole(attendees, ZmCalItem.ROLE_REQUIRED); 2533 var optAttendees = ZmApptViewHelper.filterAttendeesByRole(attendees, ZmCalItem.ROLE_OPTIONAL); 2534 //bug: 62008 - always compute all the required/optional arrays before setting them to avoid race condition 2535 //_setAddress is a costly operation which will trigger focus listeners and change the state of attendees 2536 this._setAddresses(addrInput, reqAttendees, type); 2537 this._setAddresses(this._attInputField[ZmCalBaseItem.OPTIONAL_PERSON], optAttendees, type); 2538 } 2539 else if (type == ZmCalBaseItem.LOCATION) { 2540 if (!curVal || numAttendees || this._isKnownLocation) { 2541 var nonAttendeeLocationInfo = this.getNonAttendeeLocationFromString(curVal); 2542 this._setAddresses(addrInput, attendees, type); 2543 this._attInputField[ZmCalBaseItem.LOCATION].setValue(nonAttendeeLocationInfo, true, true, false); 2544 this._isKnownLocation = true; 2545 } 2546 } 2547 else if (type == ZmCalBaseItem.EQUIPMENT) { 2548 if (!curVal || numAttendees) { 2549 if (numAttendees) { 2550 this._toggleResourcesField(true); 2551 } 2552 this._setAddresses(addrInput, attendees, type); 2553 } 2554 } 2555 } 2556 }; 2557 2558 ZmApptEditView.prototype.removeAttendees = 2559 function(attendees, type) { 2560 attendees = (attendees instanceof AjxVector) ? attendees.getArray() : 2561 (attendees instanceof Array) ? attendees : [attendees]; 2562 2563 for (var i = 0; i < attendees.length; i++) { 2564 var attendee = attendees[i]; 2565 var idx = -1; 2566 if (attendee instanceof ZmContact) { 2567 idx = this._attendees[type].indexOfLike(attendee, attendee.getAttendeeKey); 2568 if (idx !== -1) { 2569 this._attendees[type].removeAt(idx); 2570 } 2571 } 2572 else { 2573 this._attendees[type].remove(attendee); 2574 } 2575 } 2576 }; 2577 2578 ZmApptEditView.prototype.setApptLocation = 2579 function(val) { 2580 this._setAddresses(this._attInputField[ZmCalBaseItem.LOCATION], val); 2581 }; 2582 2583 ZmApptEditView.prototype.getAttendees = 2584 function(type) { 2585 return this.getAttendeesFromString(type, this._attInputField[type].getValue()); 2586 }; 2587 2588 ZmApptEditView.prototype.getMode = 2589 function(type) { 2590 return this._mode; 2591 }; 2592 2593 ZmApptEditView.prototype.getRequiredAttendeeEmails = 2594 function() { 2595 var attendees = []; 2596 var inputField = this._attInputField[ZmCalBaseItem.PERSON]; 2597 if(!inputField) { return attendees; } // input field can be null if zimbraFeatureGroupCalendarEnabled is FALSE 2598 2599 var requiredEmails = inputField.getValue(); 2600 var items = AjxEmailAddress.split(requiredEmails); 2601 for (var i = 0; i < items.length; i++) { 2602 2603 var item = AjxStringUtil.trim(items[i]); 2604 if (!item) { continue; } 2605 2606 var contact = AjxEmailAddress.parse(item); 2607 if (!contact) { continue; } 2608 2609 var email = contact.getAddress(); 2610 if(email instanceof Array) email = email[0]; 2611 2612 attendees.push(email) 2613 } 2614 return attendees; 2615 }; 2616 2617 ZmApptEditView.prototype.getOrganizerEmail = 2618 function() { 2619 var organizer = this.getOrganizer(); 2620 var email = organizer.getEmail(); 2621 if (email instanceof Array) { 2622 email = email[0]; 2623 } 2624 return email; 2625 }; 2626 2627 ZmApptEditView.prototype._handleAttendeeField = 2628 function(type, useException) { 2629 if (!this._activeInputField || !this.GROUP_CALENDAR_ENABLED) { return; } 2630 if (type != ZmCalBaseItem.LOCATION) { 2631 this._controller.clearInvalidAttendees(); 2632 } 2633 2634 return this._pickAttendeesInfo(type, useException); 2635 }; 2636 2637 ZmApptEditView.prototype._pickAttendeesInfo = 2638 function(type, useException) { 2639 var attendees = new AjxVector(); 2640 2641 if(type == ZmCalBaseItem.OPTIONAL_PERSON || type == ZmCalBaseItem.PERSON || type == ZmCalBaseItem.FORWARD) { 2642 attendees = this.getAttendeesFromString(ZmCalBaseItem.PERSON, this._attInputField[ZmCalBaseItem.PERSON].getValue()); 2643 this.setAttendeesRole(attendees, ZmCalItem.ROLE_REQUIRED); 2644 2645 var optionalAttendees = this.getAttendeesFromString(ZmCalBaseItem.PERSON, this._attInputField[ZmCalBaseItem.OPTIONAL_PERSON].getValue(), true); 2646 this.setAttendeesRole(optionalAttendees, ZmCalItem.ROLE_OPTIONAL); 2647 2648 var forwardAttendees = this.getAttendeesFromString(ZmCalBaseItem.PERSON, this._attInputField[ZmCalBaseItem.FORWARD].getValue(), false); 2649 this.setAttendeesRole(forwardAttendees, ZmCalItem.ROLE_REQUIRED); 2650 2651 //merge optional & required attendees to update parent controller 2652 attendees.addList(optionalAttendees); 2653 attendees.addList(forwardAttendees); 2654 type = ZmCalBaseItem.PERSON; 2655 }else { 2656 var value = this._attInputField[type].getValue(); 2657 attendees = this.getAttendeesFromString(type, value); 2658 } 2659 return this._updateAttendeeFieldValues(type, attendees); 2660 }; 2661 2662 ZmApptEditView.prototype.setAttendeesRole = 2663 function(attendees, role) { 2664 2665 var personalAttendees = (attendees instanceof AjxVector) ? attendees.getArray() : 2666 (attendees instanceof Array) ? attendees : [attendees]; 2667 2668 for (var i = 0; i < personalAttendees.length; i++) { 2669 var attendee = personalAttendees[i]; 2670 if(attendee) attendee.setParticipantRole(role); 2671 } 2672 }; 2673 2674 ZmApptEditView.prototype.resetParticipantStatus = 2675 function() { 2676 if (this.isOrganizer() && this.isKeyInfoChanged()) { 2677 var personalAttendees = this._attendees[ZmCalBaseItem.PERSON].getArray(); 2678 for (var i = 0; i < personalAttendees.length; i++) { 2679 var attendee = personalAttendees[i]; 2680 if(attendee) attendee.setParticipantStatus(ZmCalBaseItem.PSTATUS_NEEDS_ACTION); 2681 } 2682 } 2683 }; 2684 2685 ZmApptEditView.prototype.getAttendeesFromString = 2686 function(type, value, markAsOptional, nonAttendeeLocationInfo) { 2687 var attendees = new AjxVector(); 2688 var items = AjxEmailAddress.split(value); 2689 2690 for (var i = 0; i < items.length; i++) { 2691 var item = AjxStringUtil.trim(items[i]); 2692 if (!item) { continue; } 2693 2694 var contact = AjxEmailAddress.parse(item); 2695 if (!contact) { 2696 if(type != ZmCalBaseItem.LOCATION) { 2697 this._controller.addInvalidAttendee(item); 2698 } else if (nonAttendeeLocationInfo) { 2699 nonAttendeeLocationInfo.push(item); 2700 } 2701 continue; 2702 } 2703 2704 var addr = contact.getAddress(); 2705 var key = addr + "-" + type; 2706 if(!this._attendeesHashMap[key]) { 2707 this._attendeesHashMap[key] = ZmApptViewHelper.getAttendeeFromItem(item, type); 2708 } 2709 var attendee = this._attendeesHashMap[key]; 2710 if (attendee) { 2711 if(markAsOptional) attendee.setParticipantRole(ZmCalItem.ROLE_OPTIONAL); 2712 attendees.add(attendee); 2713 } else if (type != ZmCalBaseItem.LOCATION) { 2714 this._controller.addInvalidAttendee(item); 2715 } 2716 } 2717 2718 return attendees; 2719 }; 2720 2721 2722 ZmApptEditView.prototype.getNonAttendeeLocationFromString = function(value) { 2723 var items = AjxEmailAddress.split(value); 2724 var nonAttendeeLocationInfo = []; 2725 2726 for (var i = 0; i < items.length; i++) { 2727 var item = AjxStringUtil.trim(items[i]); 2728 if (item) { 2729 if (!AjxEmailAddress.parse(item)) { 2730 // No contact found 2731 nonAttendeeLocationInfo.push(item); 2732 } 2733 } 2734 } 2735 return nonAttendeeLocationInfo.join(AjxEmailAddress.DELIMS[0]); 2736 }; 2737 2738 2739 ZmApptEditView.prototype._updateAttendeeFieldValues = 2740 function(type, attendees) { 2741 // *always* force replace of attendees list with what we've found 2742 this.parent.updateAttendees(attendees, type); 2743 this._updateScheduler(type, attendees); 2744 appCtxt.notifyZimlets("onEditAppt_updateAttendees", [this]);//notify Zimlets 2745 }; 2746 2747 ZmApptEditView.prototype._updateScheduler = 2748 function(type, attendees) { 2749 // *always* force replace of attendees list with what we've found 2750 2751 attendees = (attendees instanceof AjxVector) ? attendees.getArray() : 2752 (attendees instanceof Array) ? attendees : [attendees]; 2753 2754 if (appCtxt.isOffline && !appCtxt.isZDOnline()) { return; } 2755 //avoid duplicate freebusy request by updating the view in sequence 2756 if(type == ZmCalBaseItem.PERSON) { 2757 this._scheduleView.setUpdateCallback(new AjxCallback(this, this.updateScheduleAssistant, [attendees, type])) 2758 } 2759 2760 var organizer = this._isProposeTime ? this.getCalItemOrganizer() : this.getOrganizer(); 2761 if(this._schedulerOpened) { 2762 this._scheduleView.update(this._dateInfo, organizer, this._attendees); 2763 this.resize(); 2764 }else { 2765 this._toggleInlineScheduler(true); 2766 }; 2767 2768 this.updateToolbarOps(); 2769 this.resize(); 2770 2771 //After everything gets rendered, run the resize method again to make the height calculations for individual components using the correct height value 2772 var self = this; 2773 setTimeout(function(){ 2774 self.resize(); 2775 }, 0); 2776 }; 2777 2778 ZmApptEditView.prototype.updateScheduleAssistant = 2779 function(attendees, type) { 2780 if(this._scheduleAssistant && type == ZmCalBaseItem.PERSON) this._scheduleAssistant.updateAttendees(attendees); 2781 }; 2782 2783 ZmApptEditView.prototype._getAttendeeByName = 2784 function(type, name) { 2785 if(!this._attendees[type]) { 2786 return null; 2787 } 2788 var a = this._attendees[type].getArray(); 2789 for (var i = 0; i < a.length; i++) { 2790 if (a[i].getFullName() == name) { 2791 return a[i]; 2792 } 2793 } 2794 return null; 2795 }; 2796 2797 ZmApptEditView.prototype._getAttendeeByItem = 2798 function(item, type) { 2799 if(!this._attendees[type]) { 2800 return null; 2801 } 2802 var attendees = this._attendees[type].getArray(); 2803 for (var i = 0; i < attendees.length; i++) { 2804 var value = (type == ZmCalBaseItem.PERSON) ? attendees[i].getEmail() : attendees[i].getFullName(); 2805 if (item == value) { 2806 return attendees[i]; 2807 } 2808 } 2809 return null; 2810 }; 2811 2812 2813 // Callbacks 2814 2815 ZmApptEditView.prototype._emailValidator = 2816 function(value) { 2817 // first parse the value string based on separator 2818 var attendees = AjxStringUtil.trim(value); 2819 if (attendees.length > 0) { 2820 var addrs = AjxEmailAddress.parseEmailString(attendees); 2821 if (addrs.bad.size() > 0) { 2822 throw ZmMsg.errorInvalidEmail2; 2823 } 2824 } 2825 2826 return value; 2827 }; 2828 2829 ZmApptEditView.prototype._handleOnClick = 2830 function(el) { 2831 if (el.id == this._allDayCheckboxId) { 2832 var edv = AjxCore.objectWithId(el._editViewId); 2833 ZmApptViewHelper.getDateInfo(edv, edv._dateInfo); 2834 this._showTimeFields(!el.checked); 2835 this.updateShowAsField(el.checked); 2836 if (el.checked && this._reminderSelect) { 2837 this._reminderSelect.setSelectedValue(1080); 2838 } 2839 this._scheduleView.handleTimeChange(); 2840 if(this._scheduleAssistant) this._scheduleAssistant.updateTime(true, true); 2841 2842 var durationInfo = this.getDurationInfo(); 2843 this._locationConflictAppt.startDate = new Date(durationInfo.startTime); 2844 this._locationConflictAppt.endDate = new Date(durationInfo.startTime + 2845 AjxDateUtil.MSEC_PER_DAY); 2846 this._locationConflictAppt.allDayEvent = el.checked ? "1" : "0"; 2847 this.locationConflictChecker(); 2848 2849 } else if(el.id == this._schButtonId || el.id == this._htmlElId + "_scheduleImage") { 2850 this._toggleInlineScheduler(); 2851 } else if(el.id == this._showOptionalId) { 2852 this._toggleOptionalAttendees(); 2853 }else if(el.id == this._showResourcesId){ 2854 this._toggleResourcesField(); 2855 }else if(el.id == this._suggestTimeId){ 2856 this._showTimeSuggestions(); 2857 }else if(el.id == this._suggestLocationId){ 2858 this._showLocationSuggestions(); 2859 }else if(el.id == this._locationStatusActionId){ 2860 this._showLocationStatusAction(); 2861 }else{ 2862 ZmCalItemEditView.prototype._handleOnClick.call(this, el); 2863 } 2864 }; 2865 2866 ZmApptEditView.prototype._handleOnFocus = 2867 function(inputEl) { 2868 if(!this._editViewInitialized) return; 2869 this._activeInputField = inputEl._attType; 2870 this.setFocusMember(inputEl); 2871 }; 2872 2873 ZmApptEditView.prototype.setFocusMember = 2874 function(member) { 2875 var kbMgr = appCtxt.getKeyboardMgr(); 2876 var tabGroup = kbMgr.getCurrentTabGroup(); 2877 if (tabGroup) { 2878 tabGroup.setFocusMember(member); 2879 } 2880 }; 2881 2882 ZmApptEditView.prototype._handleOnBlur = 2883 function(inputEl) { 2884 if(!this._editViewInitialized) return; 2885 this._handleAttendeeField(inputEl._attType); 2886 this._activeInputField = null; 2887 }; 2888 2889 ZmApptEditView.prototype._handleSubjectOnBlur = 2890 function() { 2891 var subject = AjxStringUtil.trim(this._subjectField.getValue()); 2892 if(subject) { 2893 var buttonText = subject.substr(0, ZmAppViewMgr.TAB_BUTTON_MAX_TEXT); 2894 appCtxt.getAppViewMgr().setTabTitle(this._controller.getCurrentViewId(), buttonText); 2895 } 2896 }; 2897 2898 ZmApptEditView.prototype._resetKnownLocation = 2899 function() { 2900 this._isKnownLocation = false; 2901 }; 2902 2903 ZmApptEditView._switchTab = 2904 function(type) { 2905 var appCtxt = window.parentAppCtxt || window.appCtxt; 2906 var tabView = appCtxt.getApp(ZmApp.CALENDAR).getApptComposeController().getTabView(); 2907 var key = (type == ZmCalBaseItem.LOCATION) 2908 ? tabView._tabKeys[ZmApptComposeView.TAB_LOCATIONS] 2909 : tabView._tabKeys[ZmApptComposeView.TAB_EQUIPMENT]; 2910 tabView.switchToTab(key); 2911 }; 2912 2913 ZmApptEditView._showNotificationWarning = 2914 function(ev) { 2915 ev = ev || window.event; 2916 var el = DwtUiEvent.getTarget(ev); 2917 if (el && !el.checked) { 2918 var dialog = appCtxt.getMsgDialog(); 2919 dialog.setMessage(ZmMsg.sendNotificationMailWarning, DwtMessageDialog.WARNING_STYLE); 2920 dialog.popup(); 2921 } 2922 }; 2923 2924 ZmApptEditView.prototype._getComponents = 2925 function() { 2926 var components = 2927 ZmCalItemEditView.prototype._getComponents.call(this); 2928 2929 components.aside.push(this._suggestions); 2930 2931 return components; 2932 }; 2933 2934 ZmApptEditView.prototype.resize = 2935 function() { 2936 ZmCalItemEditView.prototype.resize.apply(this, arguments); 2937 2938 if (this._scheduleAssistant) { 2939 var bounds = this.boundsForChild(this._scheduleAssistant); 2940 this._scheduleAssistant.setSize(Dwt.CLEAR, bounds.height); 2941 } 2942 }; 2943 2944 ZmApptEditView.prototype._initAttachContainer = 2945 function() { 2946 2947 this._attachmentRow = document.getElementById(this._htmlElId + "_attachment_container"); 2948 this._attachmentRow.style.display=""; 2949 var cell = this._attachmentRow.insertCell(-1); 2950 cell.colSpan = 5; 2951 2952 this._uploadFormId = Dwt.getNextId(); 2953 this._attachDivId = Dwt.getNextId(); 2954 2955 var subs = { 2956 uploadFormId: this._uploadFormId, 2957 attachDivId: this._attachDivId, 2958 url: appCtxt.get(ZmSetting.CSFE_UPLOAD_URI)+"&fmt=extended" 2959 }; 2960 2961 cell.innerHTML = AjxTemplate.expand("calendar.Appointment#AttachContainer", subs); 2962 }; 2963 2964 // if user presses space or semicolon, add attendee 2965 ZmApptEditView.prototype._onAttendeesChange = 2966 function(ev) { 2967 2968 var el = DwtUiEvent.getTarget(ev); 2969 // forward recipient is not an attendee 2970 2971 var key = DwtKeyEvent.getCharCode(ev); 2972 var _nodeName = el.nodeName; 2973 if (appCtxt.get(ZmSetting.CONTACTS_ENABLED) ){ 2974 ZmAutocompleteListView.onKeyUp(ev); 2975 } 2976 if (key === DwtKeyEvent.KEY_SPACE || key === DwtKeyEvent.KEY_SEMICOLON || key === DwtKeyEvent.KEY_SEMICOLON_1) { 2977 this._activeInputField = el._attType; 2978 this.handleAttendeeChange(); 2979 }else { 2980 this.updateToolbarOps(); 2981 } 2982 2983 if (el._attType == ZmCalBaseItem.LOCATION) { 2984 this._resetKnownLocation(); 2985 } 2986 }; 2987 2988 ZmApptEditView.prototype.handleAttendeeChange = 2989 function(ev) { 2990 if (this._schedActionId) { 2991 AjxTimedAction.cancelAction(this._schedActionId); 2992 } 2993 var attType = this._activeInputField || ZmCalBaseItem.PERSON; 2994 this._schedActionId = AjxTimedAction.scheduleAction(new AjxTimedAction(this, this._handleAttendeeField, attType), 300); 2995 2996 // attendee changes may cause our input fields to change their height 2997 this.resize(); 2998 }; 2999 3000 ZmApptEditView.prototype.loadPreference = 3001 function() { 3002 var prefDlg = appCtxt.getSuggestionPreferenceDialog(); 3003 prefDlg.setCallback(new AjxCallback(this, this._prefChangeListener)); 3004 // Trigger an initial location check - the appt may have been saved 3005 // with a location that has conflicts. Need to do from here, so that 3006 // the user's numRecurrence preference is loaded 3007 var locationConflictCheckCallback = this.locationConflictChecker.bind(this); 3008 prefDlg.getSearchPreference(appCtxt.getActiveAccount(), 3009 locationConflictCheckCallback); 3010 }; 3011 3012 ZmApptEditView.prototype._prefChangeListener = 3013 function() { 3014 // Preference Dialog is only displayed when the suggestions panel is visible - so update suggestions 3015 this._scheduleAssistant.clearResources(); 3016 this._scheduleAssistant.suggestAction(true); 3017 3018 var newNumRecurrence = this.getNumLocationConflictRecurrence(); 3019 if (newNumRecurrence != this._scheduleAssistant.numRecurrence) { 3020 // Trigger Location Conflict test if enabled 3021 this.locationConflictChecker(); 3022 } 3023 }; 3024 3025 // Show/Hide the conflict warning beneath the attendee and location input fields, and 3026 // color any attendee or location that conflicts with the current appointment time. If 3027 // the appointment is recurring, the conflict status and coloration only apply for the 3028 // current instance of the series. 3029 ZmApptEditView.prototype.showConflicts = 3030 function() { 3031 var conflictColor = "#F08080"; 3032 var color, isFree, type, addressElId, addressEl; 3033 var attendeeConflict = false; 3034 var locationConflict = false; 3035 var conflictEmails = this._scheduleView.getConflicts(); 3036 var orgEmail = this.getOrganizerEmail(); 3037 for (var email in conflictEmails) { 3038 type = this.parent.getAttendeeType(email); 3039 if ((type == ZmCalBaseItem.PERSON) || (type == ZmCalBaseItem.LOCATION)) { 3040 isFree = orgEmail == email ? true : conflictEmails[email]; 3041 if (!isFree) { 3042 // Record attendee or location conflict 3043 if (type == ZmCalBaseItem.PERSON) { 3044 attendeeConflict = true 3045 } else { 3046 locationConflict = true; 3047 } 3048 } 3049 3050 // Color the address bubble or reset to default 3051 color = isFree ? "" : conflictColor; 3052 addressElId = this._attInputField[type].getAddressBubble(email); 3053 if (addressElId) { 3054 addressEl = document.getElementById(addressElId); 3055 if (addressEl) { 3056 addressEl.style.backgroundColor = color; 3057 } 3058 } 3059 } 3060 } 3061 Dwt.setVisible(this._attendeeStatus, attendeeConflict); 3062 this.setLocationStatus(ZmApptEditView.LOCATION_STATUS_UNDEFINED, locationConflict); 3063 } 3064 3065