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