1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 /**
 24  * Creates a generic quick add dialog (which basically mean it has different 
 25  * than regular dialogs). See "DwtSemiModalDialog" in Ajax widget templates
 26  * for cosmetics.
 27  * @constructor
 28  * @class
 29  * This class represents a modal dialog which has at least a title and the 
 30  * standard buttons (OK/Cancel).
 31  * widgets (i.e. buttons, etc) as necessary.
 32  * <p>
 33  * Dialogs always hang off the main shell since their stacking order is managed 
 34  * through z-index.
 35  *
 36  * @author Parag Shah
 37  * 
 38  * @param {ZmShell}		parent				parent widget (the shell)
 39  * 
 40  * @extends		ZmQuickAddDialog
 41  * 
 42  */
 43 ZmApptQuickAddDialog = function(parent) {
 44 	// create extra "more details" button to be added at the footer of DwtDialog
 45     var moreDetailsButton = new DwtDialog_ButtonDescriptor(ZmApptQuickAddDialog.MORE_DETAILS_BUTTON,
 46                                                            ZmMsg.moreDetails, DwtDialog.ALIGN_LEFT);
 47     ZmQuickAddDialog.call(this, parent, null, null, [moreDetailsButton]);
 48 	DBG.timePt("ZmQuickAddDialog constructor", true);
 49 
 50 	AjxDispatcher.run("GetResources");
 51     AjxDispatcher.require(["MailCore", "CalendarCore"]);
 52 
 53     var app = appCtxt.getApp(ZmApp.CALENDAR);
 54     this._fbCache = new ZmFreeBusyCache(app);
 55 
 56 	var html = AjxTemplate.expand("calendar.Appointment#ZmApptQuickAddDialog", {id: this._htmlElId});
 57 	this.setContent(html);
 58 
 59 	this.setTitle(ZmMsg.quickAddAppt);
 60 	DBG.timePt("create content");
 61 	this._locations = [];
 62 	this._calendarOrgs = {};
 63 
 64 	this._createDwtObjects();
 65 	this._cacheFields();
 66 	this._addEventHandlers();
 67 	this._button[ZmApptQuickAddDialog.MORE_DETAILS_BUTTON].setSize("100");
 68     this._dateInfo = {};
 69 
 70 	DBG.timePt("create dwt controls, fields; register handlers");
 71 };
 72 
 73 ZmApptQuickAddDialog.prototype = new ZmQuickAddDialog;
 74 ZmApptQuickAddDialog.prototype.constructor = ZmApptQuickAddDialog;
 75 
 76 
 77 // Consts
 78 
 79 ZmApptQuickAddDialog.MORE_DETAILS_BUTTON = ++DwtDialog.LAST_BUTTON;
 80 
 81 // Public
 82 
 83 ZmApptQuickAddDialog.prototype.toString = 
 84 function() {
 85 	return "ZmApptQuickAddDialog";
 86 };
 87 
 88 
 89 ZmApptQuickAddDialog.prototype.getFreeBusyCache =
 90 function() {
 91     return this._fbCache;
 92 }
 93 
 94 ZmApptQuickAddDialog.prototype.initialize = 
 95 function(appt) {
 96 	this._appt = appt;
 97 
 98 	// reset fields...
 99 	this._subjectField.setValue(appt.getName() ? appt.getName() : "");
100 	this._locationField.setValue(appt.getLocation() ? appt.getLocation() : "");
101 	this._startDateField.value = AjxDateUtil.simpleComputeDateStr(appt.startDate);
102 	this._endDateField.value = AjxDateUtil.simpleComputeDateStr(appt.endDate);
103 	var isAllDay = appt.isAllDayEvent();
104 	this._showTimeFields(!isAllDay);
105     this._showAsSelect.setSelectedValue("B");
106 	if (!isAllDay) {
107 		this._startTimeSelect.set(appt.startDate);
108 		this._endTimeSelect.set(appt.endDate);
109         //need to capture initial time set while composing/editing appt
110         ZmApptViewHelper.getDateInfo(this, this._dateInfo);        
111 	} else {
112         this._dateInfo = {};
113         this._showAsSelect.setSelectedValue("F");
114     }
115 
116     this._privacySelect.enable();
117 	this._privacySelect.setSelectedValue("PUB");
118     this._calendarOrgs = {};
119 	ZmApptViewHelper.populateFolderSelect(this._folderSelect, this._folderRow, this._calendarOrgs, appt);
120 	this._repeatSelect.setSelectedValue("NON");
121 	this._repeatDescField.innerHTML = "";
122 	this._origFormValue = this._formValue();
123 	this._locations = [];
124 };
125 
126 /**
127  * Gets the appointment.
128  * 
129  * @return	{ZmAppt}	the appointment
130  */
131 ZmApptQuickAddDialog.prototype.getAppt = 
132 function() {
133 	// create a copy of the appointment so we dont muck w/ the original
134 	var appt = ZmAppt.quickClone(this._appt);
135 	appt.setViewMode(ZmCalItem.MODE_NEW);
136 
137 	// save field values of this view w/in given appt
138 	appt.setName(this._subjectField.getValue());
139 	appt.freeBusy = this._showAsSelect.getValue();
140 	appt.privacy = this._privacySelect.getValue();
141 	var calId = this._folderSelect.getValue();
142 	appt.setFolderId(calId);
143 	appt.setOrganizer(this._calendarOrgs[calId]);
144 
145 	// set the start date by aggregating start date/time fields
146 	var startDate = AjxDateUtil.simpleParseDateStr(this._startDateField.value);
147 	var endDate = AjxDateUtil.simpleParseDateStr(this._endDateField.value);
148 	if (this._appt.isAllDayEvent()) {
149 		appt.setAllDayEvent(true);
150         if(AjxDateUtil.isDayShifted(startDate)) {
151             AjxDateUtil.rollToNextDay(startDate);
152             AjxDateUtil.rollToNextDay(endDate);
153         }
154 	} else {
155 		appt.setAllDayEvent(false);
156 		startDate = this._startTimeSelect.getValue(startDate);
157 		endDate = this._endTimeSelect.getValue(endDate);
158 	}
159 	appt.setStartDate(startDate);
160 	appt.setEndDate(endDate);
161 	appt.setRecurType(this._repeatSelect.getValue());
162 	appt.location = this._locationField.getValue();
163 	appt.setAttendees(this._locations, ZmCalBaseItem.LOCATION);
164 
165 	//set alarm for reminders
166     if (this._hasReminderSupport) {
167         appt.setReminderMinutes(this._reminderSelect.getValue());
168         if (this._reminderEmailCheckbox && this._reminderEmailCheckbox.isSelected()) {
169             appt.addReminderAction(ZmCalItem.ALARM_EMAIL);
170         }
171         if (this._reminderDeviceEmailCheckbox && this._reminderDeviceEmailCheckbox.isSelected()) {
172             appt.addReminderAction(ZmCalItem.ALARM_DEVICE_EMAIL);
173         }
174     }
175 
176 	return appt;
177 };
178 
179 ZmApptQuickAddDialog.prototype.isValid = 
180 function() {
181 	var subj = AjxStringUtil.trim(this._subjectField.getValue());
182 	var errorMsg = null;
183 
184 	if (subj && subj.length) {
185 		if (!DwtTimeInput.validStartEnd( this._startDateField, this._endDateField, this._startTimeSelect, this._endTimeSelect)) {
186 			errorMsg = ZmMsg.errorInvalidDates;
187 		}
188 	} else {
189 		errorMsg = ZmMsg.errorMissingSubject;
190 	}
191     if (errorMsg) {
192         var dlg = appCtxt.getMsgDialog();
193 		dlg.setMessage(errorMsg, DwtMessageDialog.WARNING_STYLE);
194 		dlg.popup();
195     }
196 
197 	return errorMsg == null;
198 };
199 
200 ZmApptQuickAddDialog.prototype.isDirty = 
201 function() {
202 	return this._formValue() != this._origFormValue;
203 };
204 
205 ZmApptQuickAddDialog.prototype._setFocusToSubjectFeild =
206 function(){
207     this._tabGroup.setFocusMember(this._subjectField);
208 };
209 
210 ZmApptQuickAddDialog.prototype.popup =
211 function(loc) {
212 	ZmQuickAddDialog.prototype.popup.call(this, loc);
213     this._fbCache.clearCache();
214 	if (!this._tabGroupComplete) {
215 		// tab group filled in here rather than in the constructor b/c we need
216 		// all the content fields to have been created
217 		this._tabGroup.addMember([
218 			this._subjectField, this._locationField, this._showAsSelect,
219 			this._privacySelect, this._folderSelect,
220 			this._startDateField, this._startDateButton, this._startTimeSelect.getTabGroupMember(),
221 			this._endDateField, this._endDateButton, this._endTimeSelect.getTabGroupMember(),
222 			this._repeatSelect, this._reminderSelect
223 		]);
224 		this._tabGroupComplete = true;
225 	}
226     //bug:68208 Focus must be in the Subject of QuickAdd Appointment dialog after double-click in calendar
227     this._focusAction = new AjxTimedAction(this, this._setFocusToSubjectFeild);
228     AjxTimedAction.scheduleAction(this._focusAction, 300);
229 
230     if (this._hasReminderSupport) {
231         var defaultWarningTime = appCtxt.get(ZmSetting.CAL_REMINDER_WARNING_TIME);
232         this._reminderSelect.setSelectedValue(defaultWarningTime);
233         this._setEmailReminderControls();
234     }
235 
236 	var defaultPrivacyOption = appCtxt.get(ZmSetting.CAL_APPT_VISIBILITY);
237 	this._privacySelect.setSelectedValue((defaultPrivacyOption == ZmSetting.CAL_VISIBILITY_PRIV) ?  "PRI" : "PUB");
238 
239     Dwt.setVisible(this._suggestions, false);
240     Dwt.setVisible(this._suggestLocation, false);
241 
242 	DBG.timePt("ZmQuickAddDialog#popup", true);
243 };
244 
245 ZmApptQuickAddDialog.prototype._autoCompCallback =
246 function(text, el, match) {
247 	if (match.item) {
248 		this._locationField.setValue(match.item.getFullName());
249 	}
250 };
251 
252 // Private / protected methods
253 
254 ZmApptQuickAddDialog.prototype._createDwtObjects =
255 function() {
256 
257 	// create DwtInputField's
258 	this._subjectField = new DwtInputField({parent:this, type:DwtInputField.STRING,
259 											initialValue:null, size:null, maxLen:null,
260 											errorIconStyle:DwtInputField.ERROR_ICON_NONE,
261 											validationStyle:DwtInputField.CONTINUAL_VALIDATION,
262 											hint: ZmMsg.subject,
263 											parentElement:(this._htmlElId + "_subject")});
264 	this._subjectField.getInputElement().setAttribute('aria-labelledby', this._htmlElId + "_subject_label");
265 	this._subjectField.setRequired(true);
266 	Dwt.setSize(this._subjectField.getInputElement(), "100%", "2rem");
267 
268 
269     this._locationField = new DwtInputField({parent:this, type:DwtInputField.STRING,
270 											initialValue:null, size:null, maxLen:null,
271 											errorIconStyle:DwtInputField.ERROR_ICON_NONE,
272 											validationStyle:DwtInputField.ONEXIT_VALIDATION,
273 											label: ZmMsg.location, hint: ZmMsg.location,
274 											parentElement:(this._htmlElId + "_location")});
275 	this._locationField.getInputElement().setAttribute('aria-labelledby', this._htmlElId + "_location_label");
276 	Dwt.setSize(this._locationField.getInputElement(), "100%", "2rem");
277 
278     // create DwtSelects
279 	this._showAsSelect = new DwtSelect({parent:this, parentElement:(this._htmlElId + "_showAs")});
280 	this._showAsSelect.setAttribute('aria-labelledby', this._htmlElId + '_showAs_label');
281 	for (var i = 0; i < ZmApptViewHelper.SHOWAS_OPTIONS.length; i++) {
282 		var option = ZmApptViewHelper.SHOWAS_OPTIONS[i];
283 		this._showAsSelect.addOption(option.label, option.selected, option.value, "ShowAs" + option.value);
284 	}
285 
286 	this._privacySelect = new DwtSelect({parent:this, parentElement:(this._htmlElId + "_privacy")});
287 	this._privacySelect.setAttribute('aria-labelledby', this._htmlElId + "_privacy_label");
288 	for (var j = 0; j < ZmApptEditView.PRIVACY_OPTIONS.length; j++) {
289 		var option = ZmApptEditView.PRIVACY_OPTIONS[j];
290 		this._privacySelect.addOption(option.label, option.selected, option.value);
291 	}
292 	this._privacySelect.addChangeListener(new AjxListener(this, this._privacyListener));
293 
294 	this._folderSelect = new DwtSelect({parent:this, parentElement:(this._htmlElId + "_calendar"), label: ZmMsg.calendar});
295 	this._folderSelect.setAttribute('aria-labelledby', this._htmlElId + "_calendar_label");
296 	this._folderSelect.addChangeListener(new AjxListener(this, this._privacyListener));
297 
298 	var dateButtonListener = new AjxListener(this, this._dateButtonListener);
299 	var dateCalSelectionListener = new AjxListener(this, this._dateCalSelectionListener);
300 
301 	var startMiniCalId = this._htmlElId + "_startMiniCal";
302 	this._startDateButton = ZmCalendarApp.createMiniCalButton(this, startMiniCalId, dateButtonListener, dateCalSelectionListener);
303 	var endMiniCalId = this._htmlElId + "_endMiniCal";
304 	this._endDateButton = ZmCalendarApp.createMiniCalButton(this, endMiniCalId, dateButtonListener, dateCalSelectionListener);
305 
306 	// create selects for Time section
307 	var timeSelectListener = new AjxListener(this, this._timeChangeListener);
308 	
309 	this._startTimeSelect = new DwtTimeInput(this, DwtTimeInput.START);
310 	this._startTimeSelect.addChangeListener(timeSelectListener);
311 	this._startTimeSelect.reparentHtmlElement(this._htmlElId + "_startTime");
312 
313 	this._endTimeSelect = new DwtTimeInput(this, DwtTimeInput.END);
314 	this._endTimeSelect.addChangeListener(timeSelectListener);
315 	this._endTimeSelect.reparentHtmlElement(this._htmlElId + "_endTime");
316 
317 	this._repeatSelect = new DwtSelect({parent:this, parentElement:(this._htmlElId + "_repeat"), label: ZmMsg.repeat});
318 	this._repeatSelect.addChangeListener(new AjxListener(this, this._repeatChangeListener));
319 	for (var i = 0; i < ZmApptViewHelper.REPEAT_OPTIONS.length-1; i++) {
320 		var option = ZmApptViewHelper.REPEAT_OPTIONS[i];
321 		this._repeatSelect.addOption(option.label, option.selected, option.value);
322 	}
323 
324 	//reminder DwtSelect
325     var	displayOptions = [
326 		ZmMsg.apptRemindNever,
327         ZmMsg.apptRemindAtEventTime,
328 		ZmMsg.apptRemindNMinutesBefore,
329 		ZmMsg.apptRemindNMinutesBefore,
330 		ZmMsg.apptRemindNMinutesBefore,
331 		ZmMsg.apptRemindNMinutesBefore,
332 		ZmMsg.apptRemindNMinutesBefore,
333 		ZmMsg.apptRemindNMinutesBefore,
334 		ZmMsg.apptRemindNMinutesBefore,
335 		ZmMsg.apptRemindNHoursBefore,
336 		ZmMsg.apptRemindNHoursBefore,
337 		ZmMsg.apptRemindNHoursBefore,
338 		ZmMsg.apptRemindNHoursBefore,
339 		ZmMsg.apptRemindNHoursBefore,
340 		ZmMsg.apptRemindNDaysBefore,
341 		ZmMsg.apptRemindNDaysBefore,
342 		ZmMsg.apptRemindNDaysBefore,
343 		ZmMsg.apptRemindNDaysBefore,
344 		ZmMsg.apptRemindNWeeksBefore,
345 		ZmMsg.apptRemindNWeeksBefore
346 	];
347 
348     var	options = [-1, 0, 1, 5, 10, 15, 30, 45, 60, 120, 180, 240, 300, 1080, 1440, 2880, 4320, 5760, 10080, 20160];
349     var	labels =  [-1, 0, 1, 5, 10, 15, 30, 45, 60, 2, 3, 4, 5, 18, 1, 2, 3, 4, 1, 2];
350 	var defaultWarningTime = appCtxt.get(ZmSetting.CAL_REMINDER_WARNING_TIME);
351 
352     this._hasReminderSupport = Dwt.byId(this._htmlElId + "_reminderSelect") != null;
353 
354     if (this._hasReminderSupport) {
355         this._reminderSelect = new DwtSelect({parent:this, label: ZmMsg.reminder});
356         this._reminderSelect.addChangeListener(new AjxListener(this, this._setEmailReminderControls));
357         for (var j = 0; j < options.length; j++) {
358             var optLabel = ZmCalendarApp.__formatLabel(displayOptions[j], labels[j]);
359             this._reminderSelect.addOption(optLabel, (defaultWarningTime == options[j]), options[j]);
360         }
361         this._reminderSelect.reparentHtmlElement(this._htmlElId + "_reminderSelect");
362 
363         this._reminderEmailCheckbox = new DwtCheckbox({parent: this});
364         this._reminderEmailCheckbox.replaceElement(document.getElementById(this._htmlElId + "_reminderEmailCheckbox"));
365         this._reminderEmailCheckbox.setText(ZmMsg.email);
366         this._reminderDeviceEmailCheckbox = new DwtCheckbox({parent: this});
367         this._reminderDeviceEmailCheckbox.replaceElement(document.getElementById(this._htmlElId + "_reminderDeviceEmailCheckbox"));
368         this._reminderDeviceEmailCheckbox.setText(ZmMsg.deviceEmail);
369         this._setEmailReminderControls();
370 
371         var settings = appCtxt.getSettings();
372         var listener = new AjxListener(this, this._settingChangeListener);
373         settings.getSetting(ZmSetting.CAL_EMAIL_REMINDERS_ADDRESS).addChangeListener(listener);
374         settings.getSetting(ZmSetting.CAL_DEVICE_EMAIL_REMINDERS_ADDRESS).addChangeListener(listener);
375     }
376 
377 	// init auto-complete widget if contacts app enabled
378 	if (appCtxt.get(ZmSetting.CONTACTS_ENABLED)) {
379 		this._initAutocomplete();
380 	}
381 
382 	this._suggestLocationId = this._htmlElId + "_suggest_location";
383 	this._suggestLocation   = document.getElementById(this._suggestLocationId);
384 
385 	this._suggestions = document.getElementById(this._htmlElId + "_suggestions");
386 	Dwt.setVisible(this._suggestions, false);
387 
388 	var closeCallback = this._onSuggestionClose.bind(this);
389 	var dialogContentEl = document.getElementById(this._htmlElId + "_content");
390 	this._containerSize = Dwt.getSize(dialogContentEl);
391 	if (appCtxt.get(ZmSetting.GAL_ENABLED)) {
392 		this._locationAssistant = new ZmLocationAssistantView(this, appCtxt.getCurrentController(), this, closeCallback);
393 		this._locationAssistant.reparentHtmlElement(this._suggestions);
394 	}
395 	AjxTimedAction.scheduleAction(new AjxTimedAction(this, this.loadPreference), 300);
396 };
397 
398 ZmApptQuickAddDialog.prototype.loadPreference =
399 function() {
400     var prefDlg = appCtxt.getSuggestionPreferenceDialog();
401     prefDlg.setCallback(new AjxCallback(this, this._prefChangeListener));
402     prefDlg.getSearchPreference(appCtxt.getActiveAccount());
403 };
404 
405 ZmApptQuickAddDialog.prototype._prefChangeListener =
406 function() {
407     // Preference Dialog is only displayed when the suggestions panel is visible - so update suggestions
408     if (this._locationAssistant) {
409 		this._locationAssistant.clearResources();
410 		this._locationAssistant.suggestAction();
411 	}
412 };
413 
414 ZmApptQuickAddDialog.prototype._handleConfigureClick = function() {
415     // transfer settings to new appt compose tab
416     var button = this._button[ZmApptQuickAddDialog.MORE_DETAILS_BUTTON];
417     if (button) {
418         button._emulateSingleClick(); // HACK: should be public method on DwtButton
419     }
420 
421     // or just get rid of this modal dialog
422     else {
423         this.popdown();
424     }
425 
426     // go to reminders prefs page
427     // NOTE: We can't query the section name based on the pref id
428     // NOTE: because that info won't be available until the first time
429     // NOTE: prefs app is launched.
430     skin.gotoPrefs("NOTIFICATIONS");
431 };
432 
433 ZmApptQuickAddDialog.prototype._initAutocomplete =
434 function() {
435 	var acCallback = new AjxCallback(this, this._autocompleteCallback);
436 	this._acList = null;
437 
438 	if (appCtxt.get(ZmSetting.GAL_ENABLED) || appCtxt.get(ZmSetting.GAL_ENABLED)) {
439 		// autocomplete for locations
440 		var app = appCtxt.getApp(ZmApp.CALENDAR);
441 		var params = {
442 			dataClass:		appCtxt.getAutocompleter(),
443 			matchValue:		ZmAutocomplete.AC_VALUE_NAME,
444 			compCallback:	acCallback,
445             keyUpCallback:	this._handleLocationChange.bind(this),
446 			options:		{type:ZmAutocomplete.AC_TYPE_LOCATION},
447 			contextId:		[this.toString(), ZmCalBaseItem.LOCATION].join("-")
448 		};
449 		this._acLocationsList = new ZmAutocompleteListView(params);
450 		this._acLocationsList.handle(this._locationField.getInputElement());
451 		this._acList = this._acLocationsList;
452 	}
453 };
454 
455 ZmApptQuickAddDialog.prototype._autocompleteCallback =
456 function(text, el, match) {
457 	if (!match) {
458 		DBG.println(AjxDebug.DBG1, "ZmApptQuickAddDialog: match empty in autocomplete callback; text: " + text);
459 		return;
460 	}
461 	var attendee = match.item;
462 	if (attendee) {
463 		var type = el._attType;
464 		this._isKnownLocation = true;
465 		attendee = (attendee instanceof AjxVector) ? attendee.getArray() :
466 				   (attendee instanceof Array) ? attendee : [attendee];
467 		for (var i = 0; i < attendee.length; i++) {
468 			this._locations.push(attendee[i]);
469 		}
470 	}
471 };
472 
473 //monitor location field change and reset location resources array
474 ZmApptQuickAddDialog.prototype._handleLocationChange =
475 function(event, aclv, result) {
476 	var val = this._locationField.getValue();
477     if (val.length <= 1) {
478         // This is only called onKeyUp, so a length 1 string means typing just started
479         this._locations = [];
480 		this._isKnownLocation = false;
481 	}
482 };
483 
484 ZmApptQuickAddDialog.prototype._privacyListener =
485 function() {
486 	if (!this._privacySelect) { return; }
487 
488 	var value = this._privacySelect.getValue();
489 	var calId = this._folderSelect.getValue();
490 	var cal = calId && appCtxt.getById(calId);
491 
492     if (appCtxt.isOffline) {
493         var currAcct = cal.getAccount();
494         appCtxt.accountList.setActiveAccount(currAcct);
495     }
496 
497 	if (cal) {
498         var isRemote = cal.isRemote();        
499 		if (value == "PRI" && isRemote && !cal.hasPrivateAccess()) {
500 			this._privacySelect.setSelectedValue("PUB");
501 			this._privacySelect.disable();
502 		} else {
503 			this._privacySelect.enable();
504 		}
505 	}
506 };
507 
508 ZmApptQuickAddDialog.prototype._cacheFields =
509 function() {
510 	this._folderRow			= document.getElementById(this._htmlElId + "_folderRow");
511 	this._startDateField 	= document.getElementById(this._htmlElId + "_startDate");
512 	this._endDateField 		= document.getElementById(this._htmlElId + "_endDate");
513 	this._repeatDescField 	= document.getElementById(this._htmlElId + "_repeatDesc");
514 };
515 
516 ZmApptQuickAddDialog.prototype._addEventHandlers = 
517 function() {
518 	var qadId = AjxCore.assignId(this);
519 
520 	Dwt.setHandler(this._startDateField, DwtEvent.ONCHANGE, ZmApptQuickAddDialog._onChange);
521 	Dwt.setHandler(this._endDateField, DwtEvent.ONCHANGE, ZmApptQuickAddDialog._onChange);
522 	Dwt.setHandler(this._suggestLocation, DwtEvent.ONCLICK, this._showLocationSuggestions.bind(this));
523 
524 	var dateSelectListener = this._dateChangeListener.bind(this);
525 	Dwt.setHandler(this._startDateField, DwtEvent.ONCHANGE, dateSelectListener);
526 	Dwt.setHandler(this._endDateField,   DwtEvent.ONCHANGE, dateSelectListener);
527 
528 	this._startDateField._qadId = this._endDateField._qadId =  qadId;
529 };
530 
531 ZmApptQuickAddDialog.prototype._showTimeFields = 
532 function(show) {
533 	Dwt.setVisibility(this._startTimeSelect.getHtmlElement(), show);
534 	Dwt.setVisibility(this._endTimeSelect.getHtmlElement(), show);
535 	if (this._supportTimeZones)
536 		Dwt.setVisibility(this._endTZoneSelect.getHtmlElement(), show);
537 	// also show/hide the "@" text
538 	Dwt.setVisibility(document.getElementById(this._htmlElId + "_startTimeAt"), show);
539 	Dwt.setVisibility(document.getElementById(this._htmlElId + "_endTimeAt"), show);
540 };
541 
542 
543 
544 ZmApptQuickAddDialog.prototype._onSuggestionClose =
545 function() {
546     // Make the trigger link visible
547     Dwt.setVisible(this._suggestLocation, true);
548 }
549 
550 ZmApptQuickAddDialog.prototype._showLocationSuggestions =
551 function() {
552     // Hide the trigger link and display the location suggestion panel
553     Dwt.setVisible(this._suggestLocation, false);
554     Dwt.setVisible(this._suggestions, true);
555     this._locationAssistant.show(this._containerSize);
556     this._locationAssistant.suggestAction();
557 };
558 
559 ZmApptQuickAddDialog.prototype._formValue =
560 function() {
561 	var vals = [];
562 
563 	vals.push(this._subjectField.getValue());
564 	vals.push(this._locationField.getValue());
565 	vals.push(this._startDateField.value);
566 	vals.push(this._endDateField.value);
567     if (this._hasReminderSupport) {
568         vals.push(this._reminderSelect.getValue());
569         vals.push(this._reminderEmailCheckbox.isSelected());
570         vals.push(this._reminderDeviceEmailCheckbox.isSelected());
571     }
572 	if (!this._appt.isAllDayEvent()) {
573 		vals.push(
574 			AjxDateUtil.getServerDateTime(this._startTimeSelect.getValue()),
575 			AjxDateUtil.getServerDateTime(this._endTimeSelect.getValue())
576 		);
577 	}
578 	vals.push(this._repeatSelect.getValue());
579 
580 	var str = vals.join("|");
581 	str = str.replace(/\|+/, "|");
582 	return str;
583 };
584 
585 // Listeners
586 
587 ZmApptQuickAddDialog.prototype._dateButtonListener = 
588 function(ev) {
589 	var calDate = ev.item == this._startDateButton
590 		? AjxDateUtil.simpleParseDateStr(this._startDateField.value)
591 		: AjxDateUtil.simpleParseDateStr(this._endDateField.value);
592 
593 	// if date was input by user and its foobar, reset to today's date
594 	if (isNaN(calDate)) {
595 		calDate = new Date();
596 		var field = ev.item == this._startDateButton
597 			? this._startDateField : this._endDateField;
598 		field.value = AjxDateUtil.simpleComputeDateStr(calDate);
599 	}
600 
601 	// always reset the date to current field's date
602 	var menu = ev.item.getMenu();
603 	var cal = menu.getItem(0);
604 	cal.setDate(calDate, true);
605 	ev.item.popup();
606 };
607 
608 ZmApptQuickAddDialog.prototype._dateCalSelectionListener = 
609 function(ev) {
610 	var parentButton = ev.item.parent.parent;
611 
612 	// do some error correction... maybe we can optimize this?
613 	var sd = AjxDateUtil.simpleParseDateStr(this._startDateField.value);
614 	var ed = AjxDateUtil.simpleParseDateStr(this._endDateField.value);
615 	var newDate = AjxDateUtil.simpleComputeDateStr(ev.detail);
616 
617 	// change the start/end date if they mismatch
618 	if (parentButton == this._startDateButton) {
619 		if (ed.valueOf() < ev.detail.valueOf())
620 			this._endDateField.value = newDate;
621 		this._startDateField.value = newDate;
622 	} else {
623 		if (sd.valueOf() > ev.detail.valueOf())
624 			this._startDateField.value = newDate;
625 		this._endDateField.value = newDate;
626 	}
627     this._dateChangeListener();
628 };
629 
630 ZmApptQuickAddDialog.prototype._repeatChangeListener = 
631 function(ev) {
632 	this._repeatDescField.innerHTML = ev._args.newValue != "NON" ? AjxStringUtil.htmlEncode(ZmMsg.recurEndNone) : "";
633     if (this._repeatSelect._selectedValue === "WEE") {
634         this._appt._recurrence.repeatCustom = 1;
635     }
636 };
637 
638 ZmApptQuickAddDialog.prototype._timeChangeListener =
639 function(ev, id) {
640 	DwtTimeInput.adjustStartEnd(ev, this._startTimeSelect, this._endTimeSelect, this._startDateField, this._endDateField, this._dateInfo, id);
641     if (!this._appt.isAllDayEvent()) {
642         ZmApptViewHelper.getDateInfo(this, this._dateInfo);
643     }
644 	this._locationAssistant && this._locationAssistant.updateTime();
645 };
646 
647 ZmApptQuickAddDialog.prototype._dateChangeListener =
648 function(ev, id) {
649     if (!this._appt.isAllDayEvent()) {
650         ZmApptViewHelper.getDateInfo(this, this._dateInfo);
651     }
652 	this._locationAssistant && this._locationAssistant.updateTime();
653 };
654 
655 ZmApptQuickAddDialog.prototype.getDurationInfo =
656 function() {
657     var startDate = AjxDateUtil.simpleParseDateStr(this._dateInfo.startDate);
658     var endDate   = AjxDateUtil.simpleParseDateStr(this._dateInfo.endDate);
659     startDate = this._startTimeSelect.getValue(startDate);
660     endDate   = this._endTimeSelect.getValue(endDate);
661 
662     var durationInfo = {};
663     durationInfo.startTime = startDate.getTime();
664     durationInfo.endTime   = endDate.getTime();
665     durationInfo.duration  = durationInfo.endTime - durationInfo.startTime;
666     return durationInfo;
667 };
668 
669 ZmApptQuickAddDialog.prototype._setEmailReminderControls =
670 function() {
671     var enabled = this._reminderSelect.getValue() != 0;
672 
673     var email = appCtxt.get(ZmSetting.CAL_EMAIL_REMINDERS_ADDRESS);
674     var emailEnabled = Boolean(email);
675     this._reminderEmailCheckbox.setEnabled(enabled && emailEnabled);
676     this._reminderEmailCheckbox.setToolTipContent(emailEnabled ? email : null);
677 
678     var deviceEmail = appCtxt.get(ZmSetting.CAL_DEVICE_EMAIL_REMINDERS_ADDRESS);
679     var deviceEmailEnabled = Boolean(deviceEmail);
680     this._reminderDeviceEmailCheckbox.setEnabled(enabled && deviceEmailEnabled);
681     this._reminderDeviceEmailCheckbox.setToolTipContent(deviceEmailEnabled ? deviceEmail : null);
682 
683     var configureEnabled = !emailEnabled && !deviceEmailEnabled;
684     this._reminderEmailCheckbox.setVisible(!configureEnabled);
685     this._reminderDeviceEmailCheckbox.setVisible((!configureEnabled && appCtxt.get(ZmSetting.CAL_DEVICE_EMAIL_REMINDERS_ENABLED)));
686 };
687 
688 ZmApptQuickAddDialog.prototype._settingChangeListener =
689 function(ev) {
690 	if (ev.type != ZmEvent.S_SETTING) { return; }
691 	var id = ev.source.id;
692 	if (id == ZmSetting.CAL_EMAIL_REMINDERS_ADDRESS || id == ZmSetting.CAL_DEVICE_EMAIL_REMINDERS_ADDRESS) {
693 		this._setEmailReminderControls();
694 	}
695 };
696 
697 // Static methods
698 ZmApptQuickAddDialog._onChange = 
699 function(ev) {
700 	var el = DwtUiEvent.getTarget(ev);
701 	var qad = AjxCore.objectWithId(el._qadId);
702 	ZmApptViewHelper.handleDateChange(qad._startDateField, qad._endDateField, el == qad._startDateField);
703 };
704 
705 ZmApptQuickAddDialog.prototype.updateLocation =
706 function(location, locationStr) {
707     this._locationField.setValue(locationStr);
708     if (this._useAcAddrBubbles) {
709         this._locationField.clear();
710         this._locationField.addBubble({address:locationStr, match:match, skipNotify:true});
711     }
712     this._locations.push(location);
713 };
714 
715 // Stub for the location picker and ZmScheduleAssistant
716 ZmApptQuickAddDialog.prototype.getCalendarAccount =
717 function() {
718     var cal = appCtxt.getById(this._folderSelect.getValue());
719     return cal && cal.getAccount();
720 };
721 
722 ZmApptQuickAddDialog.prototype.getFreeBusyExcludeInfo =
723 function(emailAddr){
724     return null;
725 };
726