1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2004, 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) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * @overview
 26  * This file contains the calendar application class.
 27  */
 28 
 29 /**
 30  * Creates and initializes the calendar application.
 31  * @class
 32  * The calendar application manages the creation and display of appointments.
 33  *
 34  * @param	{DwtControl}	container		the container
 35  * @param	{ZmController}	parentController	the parent window controller (set by the child window)
 36  *
 37  * @author Conrad Damon
 38  * 
 39  * @extends		ZmApp
 40  */
 41 ZmCalendarApp = function(container, parentController) {
 42 
 43 	ZmApp.call(this, ZmApp.CALENDAR, container, parentController);
 44 
 45 	this._addSettingsChangeListeners();
 46 
 47 	// resource cache
 48 	this._resByName = {};
 49 	this._resByEmail = {};
 50 };
 51 
 52 ZmCalendarApp.prototype = new ZmApp;
 53 ZmCalendarApp.prototype.constructor = ZmCalendarApp;
 54 
 55 ZmCalendarApp.prototype.isZmCalendarApp = true;
 56 ZmCalendarApp.prototype.toString = function() {	return "ZmCalendarApp"; };
 57 
 58 
 59 // Organizer and item-related constants
 60 ZmEvent.S_APPT				= ZmId.ITEM_APPOINTMENT;
 61 ZmEvent.S_RESOURCE			= ZmId.ITEM_RESOURCE;
 62 ZmItem.APPT					= ZmEvent.S_APPT;
 63 ZmItem.RESOURCE				= ZmEvent.S_RESOURCE;
 64 /**
 65  * Defines the "calendar" organizer.
 66  */
 67 ZmOrganizer.CALENDAR		= ZmId.ORG_CALENDAR;
 68 
 69 // App-related constants
 70 /**
 71  * Defines the "calendar" application.
 72  */
 73 ZmApp.CALENDAR							= ZmId.APP_CALENDAR;
 74 ZmApp.CLASS[ZmApp.CALENDAR]				= "ZmCalendarApp";
 75 ZmApp.SETTING[ZmApp.CALENDAR]			= ZmSetting.CALENDAR_ENABLED;
 76 ZmApp.UPSELL_SETTING[ZmApp.CALENDAR]	= ZmSetting.CALENDAR_UPSELL_ENABLED;
 77 ZmApp.LOAD_SORT[ZmApp.CALENDAR]			= 40;
 78 ZmApp.QS_ARG[ZmApp.CALENDAR]			= "calendar";
 79 
 80 // ms to wait before fetching reminders
 81 ZmCalendarApp.REMINDER_START_DELAY = 10000;
 82 ZmCalendarApp.MINICAL_DELAY = 5000;
 83 
 84 ZmCalendarApp.VIEW_FOR_SETTING = {};
 85 ZmCalendarApp.VIEW_FOR_SETTING[ZmSetting.CAL_DAY]		= ZmId.VIEW_CAL_DAY;
 86 ZmCalendarApp.VIEW_FOR_SETTING[ZmSetting.CAL_WEEK]		= ZmId.VIEW_CAL_WEEK;
 87 ZmCalendarApp.VIEW_FOR_SETTING[ZmSetting.CAL_WORK_WEEK]	= ZmId.VIEW_CAL_WORK_WEEK;
 88 ZmCalendarApp.VIEW_FOR_SETTING[ZmSetting.CAL_MONTH]		= ZmId.VIEW_CAL_MONTH;
 89 ZmCalendarApp.VIEW_FOR_SETTING[ZmSetting.CAL_LIST]		= ZmId.VIEW_CAL_LIST;
 90 
 91 ZmCalendarApp.COLORS = [];
 92 // these need to match CSS rules
 93 ZmCalendarApp.COLORS[ZmOrganizer.C_ORANGE]	= "Orange";
 94 ZmCalendarApp.COLORS[ZmOrganizer.C_BLUE]	= "Blue";
 95 ZmCalendarApp.COLORS[ZmOrganizer.C_CYAN]	= "Cyan";
 96 ZmCalendarApp.COLORS[ZmOrganizer.C_GREEN]	= "Green";
 97 ZmCalendarApp.COLORS[ZmOrganizer.C_PURPLE]	= "Purple";
 98 ZmCalendarApp.COLORS[ZmOrganizer.C_RED]		= "Red";
 99 ZmCalendarApp.COLORS[ZmOrganizer.C_YELLOW]	= "Yellow";
100 ZmCalendarApp.COLORS[ZmOrganizer.C_PINK]	= "Pink";
101 ZmCalendarApp.COLORS[ZmOrganizer.C_GRAY]	= "Gray";
102 
103 ZmCalendarApp.CUTYPE_INDIVIDUAL			= "IND";
104 ZmCalendarApp.CUTYPE_GROUP				= "GRO";
105 ZmCalendarApp.CUTYPE_RESOURCE			= "RES";
106 ZmCalendarApp.CUTYPE_ROOM				= "ROO";
107 ZmCalendarApp.CUTYPE_UNKNOWN			= "UNK";
108 
109 ZmCalendarApp.STATUS_CANC				= "CANC";		// vevent, vtodo
110 ZmCalendarApp.STATUS_COMP				= "COMP";		// vtodo
111 ZmCalendarApp.STATUS_CONF				= "CONF";		// vevent
112 ZmCalendarApp.STATUS_DEFR				= "DEFERRED";	// vtodo					[outlook]
113 ZmCalendarApp.STATUS_INPR				= "INPR";		// vtodo
114 ZmCalendarApp.STATUS_NEED				= "NEED";		// vtodo
115 ZmCalendarApp.STATUS_TENT				= "TENT";		// vevent
116 ZmCalendarApp.STATUS_WAIT				= "WAITING";	// vtodo					[outlook]
117 
118 ZmCalendarApp.METHOD_CANCEL				= "CANCEL";
119 ZmCalendarApp.METHOD_PUBLISH			= "PUBLISH";
120 ZmCalendarApp.METHOD_REPLY				= "REPLY";
121 ZmCalendarApp.METHOD_REQUEST			= "REQUEST";
122 ZmCalendarApp.METHOD_COUNTER			= "COUNTER";
123 
124 ZmCalendarApp.DEFAULT_WORKING_HOURS			= "1:N:0800:1700,2:Y:0800:1700,3:Y:0800:1700,4:Y:0800:1700,5:Y:0800:1700,6:Y:0800:1700,7:N:0800:1700";
125 ZmCalendarApp.DEFAULT_APPT_DURATION         = "60"; //60minutes
126 
127 ZmCalendarApp.reminderTimeWarningDisplayMsgs = [
128 	ZmMsg.apptRemindNever,
129     ZmMsg.apptRemindAtEventTime,
130 	ZmMsg.apptRemindNMinutesBefore,
131 	ZmMsg.apptRemindNMinutesBefore,
132 	ZmMsg.apptRemindNMinutesBefore,
133 	ZmMsg.apptRemindNMinutesBefore,
134 	ZmMsg.apptRemindNMinutesBefore,
135 	ZmMsg.apptRemindNMinutesBefore,
136 	ZmMsg.apptRemindNMinutesBefore,
137 	ZmMsg.apptRemindNHoursBefore,
138 	ZmMsg.apptRemindNHoursBefore,
139 	ZmMsg.apptRemindNHoursBefore,
140 	ZmMsg.apptRemindNHoursBefore,
141 	ZmMsg.apptRemindNHoursBefore,
142 	ZmMsg.apptRemindNDaysBefore,
143 	ZmMsg.apptRemindNDaysBefore,
144 	ZmMsg.apptRemindNDaysBefore,
145 	ZmMsg.apptRemindNDaysBefore,
146 	ZmMsg.apptRemindNWeeksBefore,
147 	ZmMsg.apptRemindNWeeksBefore
148 ];
149 
150 ZmCalendarApp.reminderTimeWarningValues = [-1, 0, 1, 5, 10, 15, 30, 45, 60, 120, 180, 240, 300, 1080, 1440, 2880, 4320, 5760, 10080, 20160];
151 ZmCalendarApp.reminderTimeWarningLabels = [-1, 0, 1, 5, 10, 15, 30, 45, 60, 2, 3, 4, 5, 18, 1, 2, 3, 4, 1, 2];
152 
153 // Construction
154 
155 ZmCalendarApp.prototype._defineAPI =
156 function() {
157 	AjxDispatcher.setPackageLoadFunction("CalendarCore", new AjxCallback(this, this._postLoadCore));
158 	AjxDispatcher.setPackageLoadFunction("Calendar", new AjxCallback(this, this._postLoad, ZmOrganizer.CALENDAR));
159 	AjxDispatcher.registerMethod("GetCalController", ["MailCore","CalendarCore"], new AjxCallback(this, this.getCalController));
160 	AjxDispatcher.registerMethod("GetReminderController", ["MailCore","CalendarCore"], new AjxCallback(this, this.getReminderController));
161 	AjxDispatcher.registerMethod("ShowMiniCalendar", ["MailCore","CalendarCore"], new AjxCallback(this, this.showMiniCalendar));
162 	AjxDispatcher.registerMethod("GetApptComposeController", ["MailCore","CalendarCore", "Calendar", "CalendarAppt"], new AjxCallback(this, this.getApptComposeController));
163 };
164 
165 ZmCalendarApp.prototype._registerSettings =
166 function(settings) {
167 	var settings = settings || appCtxt.getSettings();
168 	settings.registerSetting("CAL_ALWAYS_SHOW_MINI_CAL",	{name: "zimbraPrefCalendarAlwaysShowMiniCal", type: ZmSetting.T_PREF, dataType: ZmSetting.D_BOOLEAN, defaultValue: false, isGlobal:true});
169 	settings.registerSetting("CAL_APPT_VISIBILITY",			{name: "zimbraPrefCalendarApptVisibility", type: ZmSetting.T_PREF, dataType: ZmSetting.D_STRING, defaultValue: "public", isGlobal:true});
170     settings.registerSetting("CAL_EMAIL_REMINDERS_ADDRESS",	{name: "zimbraPrefCalendarReminderEmail", type:ZmSetting.T_PREF});
171     settings.registerSetting("CAL_DEVICE_EMAIL_REMINDERS_ADDRESS",	{name: "zimbraCalendarReminderDeviceEmail", type:ZmSetting.T_PREF});
172     settings.registerSetting("CAL_DEVICE_EMAIL_REMINDERS_ENABLED",	{name: "zimbraFeatureCalendarReminderDeviceEmailEnabled", type:ZmSetting.T_COS, dataType:ZmSetting.D_BOOLEAN, defaultValue:false});
173 	settings.registerSetting("CAL_EXPORT",					{type: ZmSetting.T_PREF, dataType: ZmSetting.D_NONE});
174 	settings.registerSetting("CAL_FIRST_DAY_OF_WEEK",		{name: "zimbraPrefCalendarFirstDayOfWeek", type: ZmSetting.T_PREF, dataType: ZmSetting.D_INT, defaultValue: 0, isGlobal:true});
175 	settings.registerSetting("CAL_FREE_BUSY_ACL",			{type: ZmSetting.T_PREF, defaultValue:ZmSetting.ACL_ALL});
176 	settings.registerSetting("CAL_FREE_BUSY_ACL_USERS",		{type: ZmSetting.T_PREF});
177     settings.registerSetting("CAL_IMPORT",					{type: ZmSetting.T_PREF, dataType: ZmSetting.D_NONE});
178 	settings.registerSetting("CAL_INVITE_ACL",				{type: ZmSetting.T_PREF, defaultValue:ZmSetting.ACL_ALL});
179 	settings.registerSetting("CAL_INVITE_ACL_USERS",		{type: ZmSetting.T_PREF});
180 	settings.registerSetting("CAL_REMINDER_NOTIFY_SOUNDS",	{name: "zimbraPrefCalendarReminderSoundsEnabled", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:true, isGlobal:true});
181 	settings.registerSetting("CAL_REMINDER_NOTIFY_BROWSER",	{name: "zimbraPrefCalendarReminderFlashTitle", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:true, isGlobal:true});
182 	settings.registerSetting("CAL_REMINDER_NOTIFY_TOASTER",	{name: "zimbraPrefCalendarToasterEnabled", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:false, isGlobal:true});
183 	settings.registerSetting("CAL_REMINDER_WARNING_TIME",	{name: "zimbraPrefCalendarApptReminderWarningTime", type: ZmSetting.T_PREF, dataType: ZmSetting.D_INT, defaultValue: 0, isGlobal:true});
184     settings.registerSetting("CAL_SHOW_DECLINED_MEETINGS",  {name: "zimbraPrefCalendarShowDeclinedMeetings", type: ZmSetting.T_PREF,dataType:ZmSetting.D_BOOLEAN, defaultValue:true});
185 	settings.registerSetting("CAL_SHOW_TIMEZONE",			{name: "zimbraPrefUseTimeZoneListInCalendar", type: ZmSetting.T_PREF, dataType: ZmSetting.D_BOOLEAN, defaultValue: false, isGlobal:true});
186 	settings.registerSetting("CAL_USE_QUICK_ADD",			{name: "zimbraPrefCalendarUseQuickAdd", type: ZmSetting.T_PREF, dataType: ZmSetting.D_BOOLEAN, defaultValue: true, isGlobal:true});
187 	settings.registerSetting("CALENDAR_INITIAL_VIEW",		{name: "zimbraPrefCalendarInitialView", type: ZmSetting.T_PREF, defaultValue: ZmSetting.CAL_DAY, isGlobal:true});
188     settings.registerSetting("CAL_WORKING_HOURS",           {name: "zimbraPrefCalendarWorkingHours", type: ZmSetting.T_PREF, defaultValue: ZmCalendarApp.DEFAULT_WORKING_HOURS, isGlobal:true});
189     settings.registerSetting("FREE_BUSY_VIEW_ENABLED",      {name: "zimbraFeatureFreeBusyViewEnabled", type:ZmSetting.T_COS, dataType: ZmSetting.D_BOOLEAN, defaultValue:false});
190 	settings.registerSetting("DELETE_INVITE_ON_REPLY",		{name: "zimbraPrefDeleteInviteOnReply",type: ZmSetting.T_PREF, dataType: ZmSetting.D_BOOLEAN, defaultValue: true, isGlobal:true});
191     settings.registerSetting("ENABLE_APPL_ICAL_DELEGATION", {name: "zimbraPrefAppleIcalDelegationEnabled",type: ZmSetting.T_PREF, dataType: ZmSetting.D_BOOLEAN, defaultValue: false, isGlobal:true});
192 	settings.registerSetting("CAL_AUTO_ADD_INVITES",		{name: "zimbraPrefCalendarAutoAddInvites",type: ZmSetting.T_PREF, dataType: ZmSetting.D_BOOLEAN, defaultValue: true});
193 	settings.registerSetting("CAL_SEND_INV_DENIED_REPLY",	{name: "zimbraPrefCalendarSendInviteDeniedAutoReply",type: ZmSetting.T_PREF, dataType: ZmSetting.D_BOOLEAN, defaultValue: false});
194 	settings.registerSetting("CAL_INV_FORWARDING_ADDRESS",	{name: "zimbraPrefCalendarForwardInvitesTo", type:ZmSetting.T_PREF, dataType:ZmSetting.D_LIST, isGlobal:true});
195 	settings.registerSetting("CAL_SHOW_PAST_DUE_REMINDERS",	{name: "zimbraPrefCalendarShowPastDueReminders", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue: true, isGlobal:true});
196 	settings.registerSetting("CAL_SHOW_CALENDAR_WEEK",		{name: "zimbraPrefShowCalendarWeek", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue: false, isGlobal:true});
197 	settings.registerSetting("CAL_APPT_ALLOW_ATTENDEE_EDIT",    {name: "zimbraPrefCalendarApptAllowAtendeeEdit", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue: true, isGlobal:true});
198 	settings.registerSetting("CAL_RESOURCE_DBL_BOOKING_ALLOWED",	{name: "zimbraCalendarResourceDoubleBookingAllowed", type:ZmSetting.T_COS, dataType:ZmSetting.D_BOOLEAN, defaultValue: true, isGlobal:true});
199 	settings.registerSetting("CAL_SHOW_RESOURCE_TABS",	    {name: "zimbraCalendarShowResourceTabs", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue: true, isGlobal:true});
200     settings.registerSetting("CAL_DEFAULT_APPT_DURATION",   {name: "zimbraPrefCalendarDefaultApptDuration", type:ZmSetting.T_PREF, dataType:ZmSetting.D_LDAP_TIME, defaultValue:ZmCalendarApp.DEFAULT_APPT_DURATION, isGlobal:true});
201     settings.registerSetting("CAL_EXCEPTION_ON_SERIES_TIME_CHANGE",	    {name: "zimbraCalendarKeepExceptionsOnSeriesTimeChange", type:ZmSetting.T_COS, dataType:ZmSetting.D_BOOLEAN, defaultValue: false, isGlobal:true});
202     settings.registerSetting("CAL_LOCATION_FIELDS_DISABLED",{name: "zimbraCalendarLocationDisabledFields", type: ZmSetting.T_COS, dataType: ZmSetting.D_STRING, defaultValue: false, isGlobal:true});
203 };
204 
205 ZmCalendarApp.prototype._registerPrefs =
206 function() {
207 	var sections = {
208 		CALENDAR: {
209 			title: ZmMsg.calendar,
210 			icon: "CalendarApp",
211 			templateId: "prefs.Pages#Calendar",
212 			priority: 80,
213 			precondition: ZmSetting.CALENDAR_ENABLED,
214 			prefs: [
215 				ZmSetting.CAL_ALWAYS_SHOW_MINI_CAL,
216 				ZmSetting.CAL_AUTO_ADD_INVITES,
217 				ZmSetting.CAL_SEND_INV_DENIED_REPLY,
218 				ZmSetting.CAL_APPT_VISIBILITY,
219 				ZmSetting.CAL_EXPORT,
220 				ZmSetting.CAL_FIRST_DAY_OF_WEEK,
221                 ZmSetting.CAL_IMPORT,
222 				ZmSetting.CAL_REMINDER_WARNING_TIME,
223 				ZmSetting.CAL_REMINDER_NOTIFY_SOUNDS,
224 				ZmSetting.CAL_REMINDER_NOTIFY_BROWSER,
225 				ZmSetting.CAL_SHOW_DECLINED_MEETINGS,
226 				ZmSetting.CAL_SHOW_TIMEZONE,
227 				ZmSetting.CAL_USE_QUICK_ADD,
228 				ZmSetting.CALENDAR_INITIAL_VIEW,
229 				ZmSetting.CAL_WORKING_HOURS,
230 				ZmSetting.DELETE_INVITE_ON_REPLY,
231 				ZmSetting.ENABLE_APPL_ICAL_DELEGATION,
232 				ZmSetting.CAL_FREE_BUSY_ACL,
233 				ZmSetting.CAL_FREE_BUSY_ACL_USERS,
234 				ZmSetting.CAL_INVITE_ACL,
235 				ZmSetting.CAL_INVITE_ACL_USERS,
236 				ZmSetting.CAL_REMINDER_NOTIFY_TOASTER,
237 				ZmSetting.CAL_INV_FORWARDING_ADDRESS,
238 				ZmSetting.CAL_SHOW_PAST_DUE_REMINDERS,
239 				ZmSetting.CAL_SHOW_CALENDAR_WEEK,
240                 ZmSetting.CAL_DEFAULT_APPT_DURATION,
241                 ZmSetting.CAL_LOCATION_FIELDS_DISABLED
242 			],
243 			manageDirty: true,
244 			createView: function(parent, section, controller) {
245 				AjxDispatcher.require("Alert");
246 				return new ZmCalendarPrefsPage(parent, section, controller);
247 			}
248 		}
249 	};
250 
251 	for (var id in sections) {
252 		ZmPref.registerPrefSection(id, sections[id]);
253 	}
254 
255 	ZmPref.registerPref("CAL_ALWAYS_SHOW_MINI_CAL", {
256 		displayName:		ZmMsg.alwaysShowMiniCal,
257 		displayContainer:	ZmPref.TYPE_CHECKBOX
258 	});
259 
260     ZmPref.registerPref("CAL_WORKING_HOURS", {
261 		displayContainer:	ZmPref.TYPE_CUSTOM
262 	});
263 
264 	ZmPref.registerPref("CAL_AUTO_ADD_INVITES", {
265 		displayName:		ZmMsg.autoAddInvites,
266 		displayContainer:	ZmPref.TYPE_CHECKBOX
267 	});
268 
269 	ZmPref.registerPref("CAL_SEND_INV_DENIED_REPLY", {
270 		displayName:		ZmMsg.sendInvDeniedAutoReply,
271 		displayContainer:	ZmPref.TYPE_CHECKBOX
272 	});
273 
274 	ZmPref.registerPref("CAL_EMAIL_REMINDERS_ADDRESS", {
275 		displayName:		ZmMsg.emailNotificationsDescription,
276 		displayContainer:	ZmPref.TYPE_INPUT,
277 //		validationFunction: ZmMailApp.validateForwardEmail,
278 		errorMessage:       ZmMsg.invalidEmail,
279 		hint:				ZmMsg.enterEmailAddress
280 	});
281 
282     ZmPref.registerPref("CAL_DEVICE_EMAIL_REMINDERS_ADDRESS", {
283         displayName:		ZmMsg.deviceEmailNotificationsDescription,
284         displayContainer:	ZmPref.TYPE_INPUT,
285 //		validationFunction: ZmMailApp.validateForwardEmail,
286         errorMessage:       ZmMsg.invalidEmail,
287         hint:				ZmMsg.enterEmailAddress
288     });
289 
290 	ZmPref.registerPref("CAL_EXPORT", {
291 		displayName:		ZmMsg.exportToICS,
292 		displayContainer:	ZmPref.TYPE_EXPORT
293 	});
294 
295 	ZmPref.registerPref("CAL_FIRST_DAY_OF_WEEK", {
296 		displayName:		ZmMsg.calendarFirstDayOfWeek,
297 		displayContainer:	ZmPref.TYPE_SELECT,
298 		displayOptions:		AjxDateUtil.WEEKDAY_LONG,
299 		options:			[0,1,2,3,4,5,6]
300 	});
301 
302 	ZmPref.registerPref("CAL_FREE_BUSY_ACL", {
303 		displayContainer:	ZmPref.TYPE_RADIO_GROUP,
304 		displayOptions:		[ZmMsg.freeBusyAllowAll, ZmMsg.freeBusyAllowLocal, ZmMsg.freeBusyAllowDomain, ZmMsg.freeBusyAllowNone, ZmMsg.freeBusyAllowSome],
305 		options:			[ZmSetting.ACL_PUBLIC, ZmSetting.ACL_AUTH, ZmSetting.ACL_DOMAIN, ZmSetting.ACL_NONE, ZmSetting.ACL_USER]
306 	});
307 
308 	ZmPref.registerPref("CAL_FREE_BUSY_ACL_USERS", {
309 		displayContainer:	ZmPref.TYPE_TEXTAREA,
310 		hint: ZmMsg.enterEmailAddresses
311 	});
312 
313 	ZmPref.registerPref("CAL_IMPORT", {
314 		displayName:		ZmMsg.importFromICS,
315 		displayContainer:	ZmPref.TYPE_IMPORT
316 	});
317 
318 	ZmPref.registerPref("CAL_INVITE_ACL", {
319 		displayContainer:	ZmPref.TYPE_RADIO_GROUP,
320 		displayOptions:		[ZmMsg.invitesAllowAll, ZmMsg.invitesAllowLocal, ZmMsg.invitesAllowNone, ZmMsg.invitesAllowSome],
321 		options:			[ZmSetting.ACL_PUBLIC, ZmSetting.ACL_AUTH, ZmSetting.ACL_NONE, ZmSetting.ACL_USER]
322 	});
323 
324 	ZmPref.registerPref("CAL_INVITE_ACL_USERS", {
325 		displayContainer:	ZmPref.TYPE_TEXTAREA,
326 		hint: ZmMsg.enterEmailAddresses
327 	});
328 
329 	ZmPref.registerPref("CAL_REMINDER_WARNING_TIME", {
330 		displayName:		ZmMsg.numberOfMinutes,
331 		displayContainer:	ZmPref.TYPE_SELECT,
332 		displayOptions:		ZmCalendarApp.getReminderTimeWarningDisplayOptions(),
333 		options:            ZmCalendarApp.reminderTimeWarningValues,
334         setFunction:        ZmCalendarApp.setDefaultReminderTimePrefValueOnSave,
335         loadFunction:       ZmCalendarApp.postLoadSetDefaultReminderValue
336 	});
337 
338 	ZmPref.registerPref("CAL_SHOW_DECLINED_MEETINGS", {
339 		displayName:        ZmMsg.showDeclinedMeetings,
340 		displayContainer:   ZmPref.TYPE_CHECKBOX
341 	});
342 
343 	ZmPref.registerPref("CAL_SHOW_TIMEZONE", {
344 		displayName:		ZmMsg.shouldShowTimezone,
345 		displayContainer:	ZmPref.TYPE_CHECKBOX
346 	});
347 
348 	ZmPref.registerPref("CAL_USE_QUICK_ADD", {
349 	 	displayName:		ZmMsg.useQuickAdd,
350 	 	displayContainer:	ZmPref.TYPE_CHECKBOX
351 	 });
352 
353 	ZmPref.registerPref("CALENDAR_INITIAL_VIEW", {
354 		displayName:		ZmMsg.calendarInitialView,
355 		displayContainer:	ZmPref.TYPE_SELECT,
356 		displayOptions:		[ZmMsg.calViewDay, ZmMsg.calViewWorkWeek, ZmMsg.calViewWeek, ZmMsg.calViewMonth, ZmMsg.calViewList],
357         options:			[ZmSetting.CAL_DAY, ZmSetting.CAL_WORK_WEEK, ZmSetting.CAL_WEEK, ZmSetting.CAL_MONTH, ZmSetting.CAL_LIST]
358 	});
359 
360 	ZmPref.registerPref("CAL_REMINDER_NOTIFY_SOUNDS", {
361 		displayName:		ZmMsg.playSound,
362 		displayContainer:	ZmPref.TYPE_CHECKBOX
363 	});
364 
365 	ZmPref.registerPref("CAL_REMINDER_NOTIFY_BROWSER", {
366 		displayName:		ZmMsg.flashBrowser,
367 		displayContainer:	ZmPref.TYPE_CHECKBOX
368 	});
369 
370 	ZmPref.registerPref("DELETE_INVITE_ON_REPLY", {
371 		displayName: ZmMsg.deleteInviteOnReply,
372 		displayContainer:	ZmPref.TYPE_CHECKBOX
373 	});
374 
375 	ZmPref.registerPref("ENABLE_APPL_ICAL_DELEGATION", {
376 		displayName: ZmMsg.enableAppleICalDelegation,
377 		displayContainer:	ZmPref.TYPE_CHECKBOX
378 	});
379 
380 	AjxDispatcher.require("Alert");
381 	var notifyText = ZmDesktopAlert.getInstance().getDisplayText();
382 	ZmPref.registerPref("CAL_REMINDER_NOTIFY_TOASTER", {
383 		displayFunc:		function() { return notifyText; },
384 		precondition:		!!notifyText,
385 		displayContainer:	ZmPref.TYPE_CHECKBOX
386 	});
387 
388 	ZmPref.registerPref("CAL_APPT_VISIBILITY", {
389 		displayName:		ZmMsg.calendarInitialApptVisibility,
390 		displayContainer:	ZmPref.TYPE_SELECT,
391 		displayOptions:		[ZmMsg._public, ZmMsg._private],
392 		options:			[ZmSetting.CAL_VISIBILITY_PUB, ZmSetting.CAL_VISIBILITY_PRIV]
393 	});
394 
395 	ZmPref.registerPref("CAL_INV_FORWARDING_ADDRESS", {
396 		displayName:		ZmMsg.inviteForwardingAddress,
397 		displayContainer:	ZmPref.TYPE_INPUT,
398 		validationFunction: ZmPref.validateEmailList,
399         valueFunction:      ZmPref.string2EmailList,
400 		errorMessage:		ZmMsg.invalidEmail,
401 		hint:				ZmMsg.enterEmailAddress
402 	});
403 
404 	ZmPref.registerPref("CAL_SHOW_PAST_DUE_REMINDERS", {
405 		displayName: ZmMsg.apptPastDueReminderLabel,
406 		displayContainer:	ZmPref.TYPE_CHECKBOX
407 	});
408 
409 	ZmPref.registerPref("CAL_SHOW_CALENDAR_WEEK", {
410 		displayName: ZmMsg.showWeekNumber,
411 		displayContainer:	ZmPref.TYPE_CHECKBOX
412 	});
413 
414     ZmPref.registerPref("CAL_DEFAULT_APPT_DURATION", {
415 		displayName:		ZmMsg.defaultApptDuration,
416 		displayContainer:	ZmPref.TYPE_SELECT,
417 		displayOptions:		["30","60","90","120"],
418 		options:			["1800", "3600", "5400", "7200"]
419 	});
420 };
421 
422 ZmCalendarApp.prototype._registerOperations =
423 function() {
424 	ZmOperation.registerOp(ZmId.OP_CAL_LIST_VIEW, {textKey:"list", tooltipKey:"viewCalListTooltip", image:"CalListView", shortcut:ZmKeyMap.CAL_LIST_VIEW});
425 	ZmOperation.registerOp(ZmId.OP_CAL_REFRESH, {textKey:"refresh", tooltipKey:"calRefreshTooltip", image:"Refresh", shortcut:ZmKeyMap.REFRESH, showImageInToolbar: true});
426 	ZmOperation.registerOp(ZmId.OP_CAL_VIEW_MENU, {textKey:"view", image:"Appointment"}, null,
427 		AjxCallback.simpleClosure(function(parent) {
428 			ZmOperation.addDeferredMenu(ZmCalendarApp.addCalViewMenu, parent);
429 	}));
430 	ZmOperation.registerOp(ZmId.OP_DAY_VIEW, {textKey:"viewDay", tooltipKey:"viewDayTooltip", image:"DayView", shortcut:ZmKeyMap.CAL_DAY_VIEW});
431 	ZmOperation.registerOp(ZmId.OP_EDIT_REPLY_ACCEPT, {textKey:"replyAccept", image:"Check"});
432 	ZmOperation.registerOp(ZmId.OP_EDIT_REPLY_CANCEL);
433 	ZmOperation.registerOp(ZmId.OP_EDIT_REPLY_TENTATIVE, {textKey:"replyTentative", image:"QuestionMark"});
434 	ZmOperation.registerOp(ZmId.OP_EDIT_REPLY_DECLINE, {textKey:"replyDecline", image:"Cancel"});
435 	ZmOperation.registerOp(ZmId.OP_INVITE_REPLY_ACCEPT, {textKey:"editReply", image:"Check"});
436 	ZmOperation.registerOp(ZmId.OP_INVITE_REPLY_DECLINE, {textKey:"editReply", image:"Cancel"});
437 	ZmOperation.registerOp(ZmId.OP_INVITE_REPLY_MENU, {textKey:"editReply", image:"Reply"}, ZmSetting.MAIL_ENABLED,
438 		AjxCallback.simpleClosure(function(parent) {
439 			ZmOperation.addDeferredMenu(ZmCalendarApp.addInviteReplyMenu, parent);
440 	}));
441 	ZmOperation.registerOp(ZmId.OP_INVITE_REPLY_TENTATIVE, {textKey:"editReply", image:"QuestionMark"});
442 	ZmOperation.registerOp(ZmId.OP_MONTH_VIEW, {textKey:"viewMonth", tooltipKey:"viewMonthTooltip", image:"MonthView", shortcut:ZmKeyMap.CAL_MONTH_VIEW});
443 	ZmOperation.registerOp(ZmId.OP_MOUNT_CALENDAR, {textKey:"mountCalendar", image:"GroupSchedule"});
444 	ZmOperation.registerOp(ZmId.OP_NEW_ALLDAY_APPT, {textKey:"newAllDayAppt", tooltipKey:"newAllDayApptTooltip", image:"NewAppointment"});
445 	ZmOperation.registerOp(ZmId.OP_NEW_APPT, {textKey:"newAppt", tooltipKey:"newApptTooltip", image:"NewAppointment", shortcut:ZmKeyMap.NEW_APPT});
446 	ZmOperation.registerOp(ZmId.OP_NEW_CALENDAR, {textKey:"newCalendar", image:"NewAppointment", tooltipKey: "newCalendarTooltip", shortcut:ZmKeyMap.NEW_CALENDAR});
447 	ZmOperation.registerOp(ZmId.OP_ADD_EXTERNAL_CALENDAR, {textKey:"addExternalCalendar", image:"NewAppointment", tooltipKey: "addExternalCalendarTooltip", shortcut:ZmKeyMap.ADD_EXTERNAL_CALENDAR});
448     ZmOperation.registerOp(ZmId.OP_PRINT_CALENDAR, {textKey:"print", tooltipKey:"printTooltip", image:"Print", shortcut:ZmKeyMap.PRINT, textPrecedence:30, showImageInToolbar: true}, ZmSetting.PRINT_ENABLED);
449     ZmOperation.registerOp(ZmId.OP_PROPOSE_NEW_TIME, {textKey:"proposeNewTime", image:"ProposeTime", showTextInToolbar: true, showImageInToolbar: true});
450     ZmOperation.registerOp(ZmId.OP_REINVITE_ATTENDEES, {textKey:"reinviteAttendees", image:"MeetingRequest"});
451     ZmOperation.registerOp(ZmId.OP_FB_VIEW, {textKey:"viewFB", tooltipKey:"viewFBTooltip", image:"GroupSchedule", shortcut:ZmKeyMap.CAL_FB_VIEW});
452 	ZmOperation.registerOp(ZmId.OP_SEARCH_MAIL, {textKey:"searchMail", image:"SearchMail"}, ZmSetting.MAIL_ENABLED);
453 	ZmOperation.registerOp(ZmId.OP_SHARE_CALENDAR, {textKey:"shareCalendar", image:"CalendarFolder"});
454 	ZmOperation.registerOp(ZmId.OP_TODAY, {textKey:"today", tooltipKey:"todayTooltip", image:"Date", shortcut:ZmKeyMap.TODAY});
455 	ZmOperation.registerOp(ZmId.OP_VIEW_APPOINTMENT, {textKey:"viewAppointment", image:"Appointment"});
456 	ZmOperation.registerOp(ZmId.OP_OPEN_APPT_INSTANCE, {textKey:"openApptInstance", image:"Appointment"});
457 	ZmOperation.registerOp(ZmId.OP_OPEN_APPT_SERIES, {textKey:"openApptSeries", image:"Appointment"});
458 	ZmOperation.registerOp(ZmId.OP_DELETE_APPT_INSTANCE, {textKey:"deleteApptInstance", image:"Delete"});
459 	ZmOperation.registerOp(ZmId.OP_DELETE_APPT_SERIES, {textKey:"deleteApptSeries", image:"Delete"});
460 	ZmOperation.registerOp(ZmId.OP_VIEW_APPT_INSTANCE, {textKey:"apptInstance", image:"Appointment"});
461 	ZmOperation.registerOp(ZmId.OP_VIEW_APPT_SERIES, {textKey:"apptSeries", image:"Appointment"});
462 	ZmOperation.registerOp(ZmId.OP_WEEK_VIEW, {textKey:"viewWeek", tooltipKey:"viewWeekTooltip", image:"WeekView", shortcut:ZmKeyMap.CAL_WEEK_VIEW});
463 	ZmOperation.registerOp(ZmId.OP_WORK_WEEK_VIEW, {textKey:"viewWorkWeek", tooltipKey:"viewWorkWeekTooltip", image:"WorkWeekView", shortcut:ZmKeyMap.CAL_WORK_WEEK_VIEW});
464 	ZmOperation.registerOp(ZmId.OP_FORWARD_APPT, {textKey:"forward", tooltipKey:"forward", image:"Forward"});
465 	ZmOperation.registerOp(ZmId.OP_FORWARD_APPT_INSTANCE, {textKey:"forwardInstance", tooltipKey:"forwardInstance", image:"Forward"});
466 	ZmOperation.registerOp(ZmId.OP_FORWARD_APPT_SERIES, {textKey:"forwardSeries", tooltipKey:"forwardSeries", image:"Forward"});
467 	ZmOperation.registerOp(ZmId.OP_DUPLICATE_APPT, {textKey:"createCopy", tooltipKey:"createCopy", image:"Copy"});
468     ZmOperation.registerOp(ZmId.OP_INVITE_ATTENDEES, {textKey:"inviteAttendees", tooltipKey:"inviteAttendees", image:"Group"});
469     ZmOperation.registerOp(ZmId.OP_SEND_INVITE, {textKey:"send", tooltipKey:"sendInvites", image:"MeetingRequest"});
470 };
471 
472 ZmCalendarApp.prototype._registerItems =
473 function() {
474 	ZmItem.registerItem(ZmItem.APPT,
475 						{app:			ZmApp.CALENDAR,
476 						 nameKey:		"appointment",
477 						 icon:			"Appointment",
478 						 soapCmd:		"ItemAction",
479 						 itemClass:		"ZmAppt",
480 						 node:			"appt",
481 						 organizer:		ZmOrganizer.CALENDAR,
482 						 dropTargets:	[ZmOrganizer.TAG, ZmOrganizer.CALENDAR],
483 						 searchType:	"appointment",
484 						 resultsList:
485 	   AjxCallback.simpleClosure(function(search) {
486 		   AjxDispatcher.require(["MailCore", "CalendarCore"]);
487 		   return new ZmApptList(ZmItem.APPT, search);
488 	   }, this)
489 						});
490 
491 	ZmItem.registerItem(ZmItem.RESOURCE,
492 						{app:			ZmApp.CALENDAR,
493 						 itemClass:		"ZmResource",
494 						 node:			"calResource",
495 						 resultsList:
496 		AjxCallback.simpleClosure(function(search) {
497 			AjxDispatcher.require(["MailCore", "CalendarCore"]);
498 			return new ZmResourceList(null, search);
499 		}, this)
500 						});
501 };
502 
503 ZmCalendarApp.prototype._registerOrganizers =
504 function() {
505 	ZmOrganizer.registerOrg(ZmOrganizer.CALENDAR,
506 							{app:				ZmApp.CALENDAR,
507 							 nameKey:			"calendar",
508 							 defaultFolder:		ZmOrganizer.ID_CALENDAR,
509 							 soapCmd:			"FolderAction",
510 							 firstUserId:		256,
511 							 orgClass:			"ZmCalendar",
512 							 orgPackage:		"CalendarCore",
513 							 treeController:	"ZmCalendarTreeController",
514 							 labelKey:			"calendars",
515 							 itemsKey:			"appointments",
516 							 hasColor:			true,
517                              defaultColor:      ZmOrganizer.C_BLUE,
518 							 treeType:			ZmOrganizer.FOLDER,
519 							 views:				["appointment"],
520 							 folderKey:			"calendar",
521 							 mountKey:			"mountCalendar",
522 							 createFunc:		"ZmCalendar.create",
523 							 compareFunc:		"ZmFolder.sortCompareNonMail",
524 							 newOp:				ZmOperation.NEW_CALENDAR,
525 							 displayOrder:		100,
526 							 deferrable:		true,
527 							 childWindow:		true
528 							});
529 };
530 
531 ZmCalendarApp.prototype._setupSearchToolbar =
532 function() {
533     var params = {
534         msgKey:		"appointments",
535         tooltipKey:	"searchAppts",
536         icon:			"Appointment",
537         shareIcon:		"SharedCalendarFolder",
538         id:			ZmId.getMenuItemId(ZmId.SEARCH, ZmId.ITEM_APPOINTMENT)
539     };
540     // always enable appt search for offline
541     if(!appCtxt.isOffline) {
542         params["setting"] = ZmSetting.CALENDAR_ENABLED;
543     }
544 	ZmSearchToolBar.addMenuItem(ZmItem.APPT, params);
545 };
546 
547 ZmCalendarApp.prototype._registerApp =
548 function() {
549 	var newItemOps = {};
550 	newItemOps[ZmOperation.NEW_APPT] = "appointment";
551 
552 	var newOrgOps = {};
553 	newOrgOps[ZmOperation.NEW_CALENDAR] = "calendar";
554 
555 	var actionCodes = {};
556 	actionCodes[ZmKeyMap.NEW_APPT]		= ZmOperation.NEW_APPT;
557 	actionCodes[ZmKeyMap.NEW_CALENDAR]	= ZmOperation.NEW_CALENDAR;
558 	actionCodes[ZmKeyMap.ADD_EXTERNAL_CALENDAR]	= ZmOperation.ADD_EXTERNAL_CALENDAR;
559 
560 	ZmApp.registerApp(ZmApp.CALENDAR,
561 							 {mainPkg:				"Calendar",
562 							  nameKey:				"calendar",
563 							  icon:					"CalendarApp",
564 							  textPrecedence:		60,
565 							  chooserTooltipKey:	"goToCalendar",
566 							  viewTooltipKey:		"displayCalendar",
567 							  defaultSearch:		ZmItem.APPT,
568 							  organizer:			ZmOrganizer.CALENDAR,
569 							  overviewTrees:		[ZmOrganizer.CALENDAR, ZmOrganizer.SEARCH, ZmOrganizer.TAG],
570 							  newItemOps:			newItemOps,
571 							  newOrgOps:			newOrgOps,
572 							  actionCodes:			actionCodes,
573 							  searchTypes:			[ZmItem.APPT],
574 							  gotoActionCode:		ZmKeyMap.GOTO_CALENDAR,
575 							  newActionCode:		ZmKeyMap.NEW_APPT,
576 							  chooserSort:			30,
577 							  defaultSort:			20,
578 							  upsellUrl:			ZmSetting.CALENDAR_UPSELL_URL,
579                               //quickCommandType:		ZmQuickCommand[ZmId.ITEM_APPOINTMENT],
580 							  searchResultsTab:		true
581 							  });
582 };
583 
584 ZmCalendarApp.prototype._getRefreshButtonTooltip =
585 function() {
586 	return ZmMsg.showAllEventsFromSelectedCalendars;
587 };
588 
589 // App API
590 
591 ZmCalendarApp.prototype.startup =
592 function(result) {
593 };
594 
595 ZmCalendarApp.prototype.refresh =
596 function(refresh) {
597 	if (!appCtxt.inStartup) {
598         this.resetOverview(this.getOverviewId());
599 		AjxDispatcher.run("GetCalController").refreshHandler(refresh);
600 	}
601 };
602 
603 ZmCalendarApp.prototype.runRefresh =
604 function() {
605 	appCtxt.getCalManager().getCalViewController().runRefresh();
606 };
607 
608 
609 ZmCalendarApp.prototype.deleteNotify =
610 function(ids, force) {
611 	if (!force && this._deferNotifications("delete", ids)) { return; }
612 	AjxDispatcher.run("GetCalController").notifyDelete(ids);
613 };
614 
615 /**
616  * Checks for the creation of a calendar or a mount point to one, or an
617  * appointment.
618  *
619  * @param {Hash}	creates	a hash of create notifications
620  * 
621  * @private
622  */
623 ZmCalendarApp.prototype.createNotify =
624 function(creates, force) {
625 	if (!creates["folder"] && !creates["appt"] && !creates["link"]) { return; }
626 	if (!force && !this._noDefer && this._deferNotifications("create", creates)) { return; }
627 
628 	var ctlr = AjxDispatcher.run("GetCalController");
629 	for (var name in creates) {
630 		var list = creates[name];
631 		for (var i = 0; i < list.length; i++) {
632 			var create = list[i];
633 			if (appCtxt.cacheGet(create.id)) { continue; }
634 
635 			if (name == "folder") {
636 				this._handleCreateFolder(create, ZmOrganizer.CALENDAR);
637 			} else if (name == "link") {
638 				this._handleCreateLink(create, ZmOrganizer.CALENDAR);
639 			} else if (name == "appt") {
640 				ctlr.notifyCreate(create);
641 			}
642 
643 			if ((name == "folder" || name == "link") && ctlr) {
644 				ctlr._updateCheckedCalendars();
645 			}
646 		}
647 	}
648 };
649 
650 ZmCalendarApp.prototype.modifyNotify =
651 function(modifies, force) {
652 	if (!force && !this._noDefer && this._deferNotifications("modify", modifies)) { return; }
653 	AjxDispatcher.run("GetCalController").notifyModify(modifies);
654 };
655 
656 ZmCalendarApp.prototype.preNotify =
657 function(notify) {
658 	var ctlr = AjxDispatcher.run("GetCalController");
659 	if (ctlr) {
660 		ctlr.preNotify(notify);
661 	}
662 };
663 
664 ZmCalendarApp.prototype.postNotify =
665 function(notify) {
666 	var ctlr = AjxDispatcher.run("GetCalController");
667 	if (ctlr) {
668 		ctlr.postNotify(notify);
669 	}
670 };
671 
672 ZmCalendarApp.prototype.handleOp =
673 function(op) {
674 	if (!appCtxt.isWebClientOffline()) {
675 		switch (op) {
676 			case ZmOperation.NEW_APPT: {
677 				var loadCallback = new AjxCallback(this, this._handleLoadNewAppt);
678 				AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar"], false, loadCallback, null, true);
679 				break;
680 			}
681 			case ZmOperation.NEW_CALENDAR: {
682 				var loadCallback = new AjxCallback(this, this._handleLoadNewCalendar);
683 				AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar"], false, loadCallback, null, true);
684 				break;
685 			}
686 			case ZmOperation.ADD_EXTERNAL_CALENDAR: {
687 				var loadCallback = new AjxCallback(this, this._handleLoadExternalCalendar);
688 				AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar"], false, loadCallback, null, true);
689 				break;
690 			}
691 		}
692 	}
693 };
694 
695 ZmCalendarApp.prototype._handleLoadNewAppt =
696 function() {
697 	Dwt.setLoadingTime("ZmCalendarApp-newAppt");
698 	AjxDispatcher.run("GetCalController").newAppointment(null, null, null, null);
699 	Dwt.setLoadedTime("ZmCalendarApp-newAppt");
700 };
701 
702 ZmCalendarApp.prototype._handleLoadNewCalendar =
703 function() {
704 	appCtxt.getAppViewMgr().popView(true, ZmId.VIEW_LOADING);	// pop "Loading..." page
705 	var dialog = appCtxt.getNewCalendarDialog();
706 	if (!this._newCalendarCb) {
707 		this._newCalendarCb = new AjxCallback(this, this._newCalendarCallback);
708 	}
709 	ZmController.showDialog(dialog, this._newCalendarCb);
710 };
711 
712 ZmCalendarApp.prototype._handleLoadExternalCalendar =
713 function() {
714     appCtxt.getAppViewMgr().popView(true, ZmId.VIEW_LOADING);
715 	var oc = appCtxt.getOverviewController();
716     var tc = oc.getTreeController(ZmOrganizer.CALENDAR);
717     if(tc) {
718         tc._addExternalCalendarListener();
719     }
720 };
721 
722 // Public methods
723 
724 ZmCalendarApp.prototype.launch =
725 function(params, callback) {
726 	this._setLaunchTime(this.toString(), new Date());
727 	var loadCallback = new AjxCallback(this, this._handleLoadLaunch, [params, callback]);
728 	AjxDispatcher.require(["MailCore", "ContactsCore", "CalendarCore", "Calendar"], true, loadCallback, null, true);
729 };
730 
731 ZmCalendarApp.prototype._handleLoadLaunch =
732 function(params, callback) {
733 	var cc = AjxDispatcher.run("GetCalController");
734 	var view = cc.getDefaultViewType();
735 	var sd = null;
736 
737 	params = params || {};
738 	if (params.qsParams) {
739 		var viewArg = params.qsParams.view;
740 		if (viewArg) {
741 			var viewId = ZmCalendarApp.VIEW_FOR_SETTING[viewArg];
742 			if (viewId) {
743 				view = viewId;
744 				var date = params.qsParams.date;
745 				if (date) {
746 					date = AjxDateUtil.parseServerDateTime(date);
747 					if (date && !isNaN(date)) {
748 						sd = new Date((date).setHours(0,0,0,0));
749 					}
750 				}
751 			}
752 		}
753 	}
754 
755 	if (appCtxt.get(ZmSetting.CONTACTS_ENABLED)) {
756 		this.initResources();
757 	}
758     ZmCalendarApp.postLoadSetDefaultReminderValue();
759 	cc.show(view, sd);
760 	this._setLoadedTime(this.toString(), new Date());
761 	if (callback) {
762 		callback.run();
763 	}
764 	this._setRefreshButtonTooltip();
765 };
766 
767 ZmCalendarApp.prototype.getNewButtonProps =
768 function() {
769 	return {
770 		text:		ZmMsg.newAppt,
771 		tooltip:	ZmMsg.createNewAppt,
772 		icon:		"NewAppointment",
773 		iconDis:	"NewAppointmentDis",
774 		defaultId:	ZmOperation.NEW_APPT,
775 		disabled:	!this.containsWritableFolder()
776 	};
777 };
778 
779 ZmCalendarApp.prototype.showSearchResults =
780 function(results, callback) {
781 	// calls ZmSearchController's _handleLoadShowResults
782 	if (callback) {
783 		callback.run(AjxDispatcher.run("GetCalController"));
784 	}
785 };
786 
787 ZmCalendarApp.prototype.activate =
788 function(active, viewId) {
789     this._createDeferredFolders(ZmApp.CALENDAR);
790 	ZmApp.prototype.activate.apply(this, arguments);
791 
792 	if (appCtxt.get(ZmSetting.CALENDAR_ENABLED)) {
793 		var avm = appCtxt.getAppViewMgr();
794 		var show = (active || appCtxt.get(ZmSetting.CAL_ALWAYS_SHOW_MINI_CAL)) && !avm.isHidden(ZmAppViewMgr.C_TREE_FOOTER, viewId);
795 		AjxDispatcher.run("ShowMiniCalendar", show);
796 	}
797 };
798 
799 // Online to Offline or Offline to Online; Called from ZmApp.activate and from ZmOffline.enableApps, disableApps
800 ZmCalendarApp.prototype.resetWebClientOfflineOperations =
801 function() {
802 	ZmApp.prototype.resetWebClientOfflineOperations.apply(this);
803 	var controller = this.getCalController();
804 	if (controller) {
805 		controller._resetToolbarOperations();
806 		controller._clearViewActionMenu();
807 	}
808 };
809 
810 /**
811  * Shows the mini-calendar.
812  *
813  * @param	{Boolean}	show		if <code>true</code>, show the mini-calendar
814  * @param	{int}	delay			the delay (in seconds)
815  */
816 ZmCalendarApp.prototype.showMiniCalendar =
817 function(show, delay) {
818 	var mc = AjxDispatcher.run("GetCalController").getMiniCalendar(delay);
819 	mc.setSkipNotifyOnPage(show && !this._active);
820 	if (!this._active) {
821 		mc.setSelectionMode(DwtCalendar.DAY);
822 	}
823 	appCtxt.getAppViewMgr().displayComponent(ZmAppViewMgr.C_TREE_FOOTER, show);
824 };
825 
826 // common API shared by tasks app
827 /**
828  * Gets the list controller.
829  *
830  * @return	{ZmCalViewController}		the controller
831  *
832  * @see		#getCalController
833  */
834 ZmCalendarApp.prototype.getListController =
835 function() {
836 	return AjxDispatcher.run("GetCalController");
837 };
838 
839 /**
840  * Gets the calendar controller.
841  * 
842  * @return	{ZmCalViewController}		the controller
843  */
844 ZmCalendarApp.prototype.getCalController =
845 function(sessionId, searchResultsController) {
846 	AjxDispatcher.require(["Startup2", "MailCore", "CalendarCore"]);
847 	return this.getSessionController({controllerClass:			"ZmCalViewController",
848 									  sessionId:				sessionId || ZmApp.MAIN_SESSION,
849 									  searchResultsController:	searchResultsController});
850 };
851 
852 /**
853  * Gets the free busy cache.
854  *
855  * @return	{ZmFreeBusyCache} free busy cache object
856  */
857 ZmCalendarApp.prototype.getFreeBusyCache =
858 function() {
859 	if (!this._freeBusyCache) {
860 		AjxDispatcher.require(["MailCore", "CalendarCore"]);
861 		this._freeBusyCache = new ZmFreeBusyCache(this);
862 	}
863 	return this._freeBusyCache;
864 };
865 
866 /**
867  * Gets the reminder controller.
868  *
869  * @return	{ZmReminderController}	the controller
870  */
871 ZmCalendarApp.prototype.getReminderController =
872 function() {
873 	if (!this._reminderController) {
874 		AjxDispatcher.require(["MailCore", "CalendarCore"]);
875 		var calMgr = appCtxt.getCalManager();
876 		this._reminderController = calMgr.getReminderController();
877 		this._reminderController._calController = AjxDispatcher.run("GetCalController");
878 	}
879 	return this._reminderController;
880 };
881 
882 /**
883  * Gets the appointment compose controller.
884  * 
885  * @return	{ZmApptComposeController}	the controller
886  */
887 ZmCalendarApp.prototype.getApptComposeController =
888 function(sessionId) {
889 	AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar", "CalendarAppt"]);
890 	return this.getSessionController({controllerClass:	"ZmApptComposeController",
891 									  sessionId:		sessionId});
892 };
893 
894 ZmCalendarApp.prototype.getSimpleApptComposeController =
895 function() {
896 	AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar", "CalendarAppt"]);
897 	return this.getSessionController({controllerClass:	"ZmSimpleApptComposeController"});
898 };
899 
900 ZmCalendarApp.prototype.getApptViewController =
901 function(sessionId) {
902 	AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar", "CalendarAppt"]);
903 	return this.getSessionController({controllerClass:	"ZmApptController",
904 									  sessionId:		sessionId});
905 };
906 
907 ZmCalendarApp.prototype.initResources =
908 function() {
909 	if (!this._locations) {
910 		this._locations = new ZmResourceList(ZmCalBaseItem.LOCATION);
911 		this._locations.isCanonical = true;
912 	}
913 
914 	if (!this._equipment) {
915 		this._equipment = new ZmResourceList(ZmCalBaseItem.EQUIPMENT);
916 		this._equipment.isCanonical = true;
917 	}
918 };
919 
920 ZmCalendarApp.prototype.loadResources =
921 function() {
922 	this.initResources();
923 
924 	if (appCtxt.get(ZmSetting.GAL_ENABLED)) {
925 		var batchCmd = new ZmBatchCommand();
926 		if (!this._locations.isLoaded) {
927 			batchCmd.add(new AjxCallback(this._locations, this._locations.load));
928 		}
929 		if (!this._equipment.isLoaded) {
930 			batchCmd.add(new AjxCallback(this._equipment, this._equipment.load));
931 		}
932 		if (batchCmd._cmds.length) {
933 			batchCmd.run();
934 		}
935 	}
936 };
937 
938 /**
939  * Gets a list of locations.
940  * 
941  * @return	{ZmResourceList}	the resource list
942  */
943 ZmCalendarApp.prototype.getLocations =
944 function() {
945 	this.initResources();
946 	return this._locations;
947 };
948 
949 /**
950  * Gets a list of equipment.
951  * 
952  * @return	{ZmResourceList}	the resource list
953  */
954 ZmCalendarApp.prototype.getEquipment =
955 function() {
956 	this.initResources();
957 	return this._equipment;
958 };
959 
960 /**
961  * Gets the list of calendar ids for reminders. If calendar packages are not loaded,
962  * gets the list from deferred folder ids.
963  *
964  * @return	{Array}	an array of ids
965  */
966 ZmCalendarApp.prototype.getReminderCalendarFolderIds =
967 function() {
968 	var folderIds = [];
969 	if (AjxDispatcher.loaded("CalendarCore")) {
970 		folderIds = AjxDispatcher.run("GetCalController").getReminderCalendarFolderIds();
971 	} else {
972 		// will be used in reminder dialog
973 		this._folderNames = {};
974 		for (var i = 0; i < this._deferredFolders.length; i++) {
975 			var params = this._deferredFolders[i];
976 			folderIds.push(params.obj.id);
977 			// _folderNames are used when deferred folders are not created
978 			// and calendar name is required. example: calendar name
979 			// requirement in reminder module
980 			this._folderNames[params.obj.id] = params.obj.name;
981 		}
982 	}
983 	return folderIds;
984 };
985 
986 /**
987  * Gets the list of checked calendar ids. If calendar packages are not loaded,
988  * gets the list from deferred folder ids.
989  *
990  * @param	{Boolean}		localOnly	if <code>true</code>, use local calendar only
991  * @return	{Array}	an array of ids
992  */
993 ZmCalendarApp.prototype.getCheckedCalendarFolderIds =
994 function(localOnly, includeTrash) {
995 	var folderIds = [];
996 	if (AjxDispatcher.loaded("CalendarCore")) {
997 		folderIds = AjxDispatcher.run("GetCalController").getCheckedCalendarFolderIds(localOnly, includeTrash);
998 	} else {
999 		// will be used in reminder dialog
1000 		this._folderNames = {};
1001 		for (var i = 0; i < this._deferredFolders.length; i++) {
1002 			var params = this._deferredFolders[i];
1003 			var str = (params && params.obj && params.obj.f) ? params.obj.f : "";
1004 			if (str && (str.indexOf(ZmOrganizer.FLAG_CHECKED) != -1)) {
1005 				if (localOnly && params.obj.zid != null) {
1006 					continue;
1007 				}
1008                 if (params.obj.id == ZmOrganizer.ID_TRASH && !includeTrash) {
1009                     continue;
1010                 }
1011 				folderIds.push(params.obj.id);
1012 				// _folderNames are used when deferred folders are not created
1013 				// and calendar name is required. example: calendar name
1014 				// requirement in reminder module
1015 				this._folderNames[params.obj.id] = params.obj.name;
1016 			}
1017 		}
1018 	}
1019 	return folderIds;
1020 };
1021 
1022 /**
1023  * Gets the name of the calendar with specified id.
1024  *
1025  * @param	{String}	id		the id of the calendar
1026  * @return	{String}	the name
1027  */
1028 ZmCalendarApp.prototype.getCalendarName =
1029 function(id) {
1030 	// _folderNames are used when deferred folders are not created and calendar
1031 	// name is required. example: calendar name requirement in reminder module
1032 	return appCtxt.getById(id) ? appCtxt.getById(id).name : this._folderNames[id];
1033 };
1034 
1035 /**
1036  * Creates a new button with a {@link DwtCalendar} as the menu.
1037  * 
1038  * @param	{DwtComposite}	parent						the parent
1039  * @param	{String}	buttonId 					the button id to fetch inside DOM and append DwtButton to
1040  * @param	{AjxListener}	dateButtonListener			the listener to call when date button is pressed
1041  * @param	{AjxListener}	dateCalSelectionListener	the listener to call when date is selected in {@link DwtCalendar}
1042  */
1043 ZmCalendarApp.createMiniCalButton =
1044 function(parent, buttonId, dateButtonListener, dateCalSelectionListener, reparent) {
1045 	// create button
1046 	var params = {parent:parent};
1047 	if (reparent === false) {
1048 		params.id = buttonId;
1049 	}
1050 	var dateButton = new DwtButton(params);
1051 	dateButton.addDropDownSelectionListener(dateButtonListener);
1052 	//make sure to listen to the tiny left-edge(thats not part of drop-down menu)
1053 	dateButton.addSelectionListener(dateButtonListener);
1054     //to keep the image unchanged on hover
1055     dateButton.setDropDownHovImage(null);
1056 	dateButton.setData(Dwt.KEY_ID, buttonId);
1057 	if (AjxEnv.isIE) {
1058 		dateButton.setSize("20");
1059 	}
1060 
1061 	// create menu for button
1062 	var calMenu = new DwtMenu({parent:dateButton, style:DwtMenu.CALENDAR_PICKER_STYLE});
1063 	calMenu.setSize("150");
1064 	calMenu._table.width = "100%";
1065 	dateButton.setMenu(calMenu, true);
1066 
1067 	// create mini cal for menu for button
1068 	var cal = new DwtCalendar({parent:calMenu});
1069 	cal.setData(Dwt.KEY_ID, buttonId);
1070 	cal.setSkipNotifyOnPage(true);
1071 	var fdow = appCtxt.get(ZmSetting.CAL_FIRST_DAY_OF_WEEK) || 0;
1072 	cal.setFirstDayOfWeek(fdow);
1073 	cal.addSelectionListener(dateCalSelectionListener);
1074 	// add settings change listener on mini cal in case first day of week setting changes
1075 	// safety check since this is static code (may not have loaded calendar)
1076 	var fdowSetting = appCtxt.getSettings().getSetting(ZmSetting.CAL_FIRST_DAY_OF_WEEK);
1077 	if (fdowSetting) {
1078 		var listener = new AjxListener(null, ZmCalendarApp._settingChangeListener, cal);
1079 		fdowSetting.addChangeListener(listener);
1080 	}
1081 
1082 	if (reparent !== false) {
1083 		dateButton.reparentHtmlElement(buttonId);
1084 	}
1085 
1086 	return dateButton;
1087 };
1088 
1089 /**
1090  * Creates a new button with a reminder options as its menu.
1091  * 
1092  * @param	{DwtComposite}	parent						the parent
1093  * @param	{String}	buttonId 					the button id to fetch inside DOM and append DwtButton to
1094  * @param	{AjxListener}	buttonListener			the listener to call when date button is pressed
1095  * @param	{AjxListener}	menuSelectionListener	the listener to call when date is selected in {@link DwtCalendar}
1096  */
1097 ZmCalendarApp.createReminderButton =
1098 function(parent, buttonId, buttonListener, menuSelectionListener) {
1099 	// create button
1100 	var reminderButton = new DwtButton({parent:parent});
1101 	reminderButton.addDropDownSelectionListener(buttonListener);
1102 	reminderButton.setData(Dwt.KEY_ID, buttonId);
1103 	if (AjxEnv.isIE) {
1104 		reminderButton.setSize("20");
1105 	}
1106 
1107 	// create menu for button
1108 	var reminderMenu = new DwtMenu({parent:reminderButton, style:DwtMenu.DROPDOWN_STYLE});
1109 	reminderMenu.setSize("150");
1110 	reminderButton.setMenu(reminderMenu, true);
1111 
1112 	var defaultWarningTime = appCtxt.get(ZmSetting.CAL_REMINDER_WARNING_TIME);
1113 
1114 	for (var i = 0; i < ZmCalendarApp.reminderTimeWarningDisplayMsgs.length; i++) {
1115 		var optLabel = ZmCalendarApp.__formatLabel(ZmCalendarApp.reminderTimeWarningDisplayMsgs[i], ZmCalendarApp.reminderTimeWarningLabels[i]);
1116 		var mi = new DwtMenuItem({parent: reminderMenu, style: DwtMenuItem.NO_STYLE});
1117 		mi.setText(optLabel);
1118 		mi.setData("value",ZmCalendarApp.reminderTimeWarningValues[i]);
1119 		if(menuSelectionListener) mi.addSelectionListener(menuSelectionListener);
1120 	}
1121 
1122 	// reparent and cleanup
1123 	reminderButton.reparentHtmlElement(buttonId);
1124 	delete buttonId;
1125 
1126 	return reminderButton;
1127 };
1128 
1129 /**
1130  * Gets the summary of reminder info from the reminder minutes.
1131  * 
1132  * @param {int}	reminderMinutes		the number of minutes before which reminder should be shown
1133  * @return	{String}		the summary
1134  */
1135 ZmCalendarApp.getReminderSummary =
1136 function(reminderMinutes) {
1137 
1138 	var hoursConvertable = ((reminderMinutes%60) == 0);
1139 	var daysConvertable  = ((reminderMinutes%(60*24)) == 0);
1140 	var weeksConvertable = ((reminderMinutes%(60*24*7)) == 0);
1141 
1142     if (reminderMinutes === -1)	{ return ZmMsg.apptRemindNever; }
1143     if (reminderMinutes === 0)	{ return ZmMsg.apptRemindAtEventTime; }
1144 	if (weeksConvertable)		{ return ZmCalendarApp.__formatLabel(ZmMsg.apptRemindNWeeksBefore, reminderMinutes/(60*24*7)); }
1145 	if (daysConvertable)		{ return ZmCalendarApp.__formatLabel(ZmMsg.apptRemindNDaysBefore, reminderMinutes/(60*24)); }
1146 	if (hoursConvertable)		{ return ZmCalendarApp.__formatLabel(ZmMsg.apptRemindNHoursBefore, reminderMinutes/60); }
1147 
1148 	return ZmCalendarApp.__formatLabel(ZmMsg.apptRemindNMinutesBefore, reminderMinutes);
1149 };
1150 
1151 ZmCalendarApp._settingChangeListener =
1152 function(cal, ev) {
1153 	if (ev.type != ZmEvent.S_SETTING) { return; }
1154 
1155 	var setting = ev.source;
1156 	if (setting.id == ZmSetting.CAL_FIRST_DAY_OF_WEEK) {
1157 		cal.setFirstDayOfWeek(setting.getValue());
1158 	}
1159 };
1160 
1161 ZmCalendarApp.prototype._newCalendarCallback =
1162 function(parent, name, color, url, excludeFb) {
1163 	// REVISIT: Do we really want to close the dialog before we
1164 	//          know if the create succeeds or fails?
1165 	var dialog = appCtxt.getNewCalendarDialog();
1166 	dialog.popdown();
1167 
1168 	var oc = appCtxt.getOverviewController();
1169 	oc.getTreeController(ZmOrganizer.CALENDAR)._doCreate(parent, name, color, url, excludeFb);
1170 };
1171 
1172 /**
1173  * Adds an invite actions submenu for accept/decline/tentative.
1174  *
1175  * @param {ZmButtonToolBar|ZmActionMenu}	parent		the parent widget
1176  * @return	{ZmActionMenu}	the action menu
1177  */
1178 ZmCalendarApp.addInviteReplyMenu =
1179 function(parent) {
1180 	var list = [ZmOperation.EDIT_REPLY_ACCEPT, ZmOperation.EDIT_REPLY_TENTATIVE, ZmOperation.EDIT_REPLY_DECLINE];
1181 	var menu = new ZmActionMenu({parent:parent, menuItems:list});
1182 	parent.setMenu(menu);
1183 	return menu;
1184 };
1185 
1186 /**
1187  * Adds an invite actions submenu for accept/decline/tentative.
1188  *
1189  * @param {ZmButtonToolBar|ZmActionMenu}	parent		the parent widget
1190  * @return	{ZmActionMenu}	the action menu
1191  */
1192 ZmCalendarApp.addCalViewMenu =
1193 function(parent) {
1194 	var list = [
1195 		ZmOperation.DAY_VIEW, ZmOperation.WORK_WEEK_VIEW, ZmOperation.WEEK_VIEW,
1196 		ZmOperation.MONTH_VIEW, ZmOperation.CAL_LIST_VIEW
1197 	];
1198     if(appCtxt.get(ZmSetting.FREE_BUSY_VIEW_ENABLED)) {
1199         list.push(ZmOperation.FB_VIEW);    
1200     }
1201 	var menu = new ZmActionMenu({parent:parent, menuItems:list});
1202 	parent.setMenu(menu);
1203 	return menu;
1204 };
1205 
1206 ZmCalendarApp.__formatLabel =
1207 function(prefLabel, prefValue) {
1208 	prefLabel = prefLabel || "";
1209 	return prefLabel.match(/\{/) ? AjxMessageFormat.format(prefLabel, prefValue) : prefLabel;
1210 };
1211 
1212 /**
1213  * Parses the given string and return reminder info containing units and exact value
1214  *
1215  * @param reminderString 	reminder string eg. "20 minutes before"
1216  * 
1217  * @private
1218  */
1219 ZmCalendarApp.parseReminderString =
1220 function(reminderString) {
1221 	var reminderFormats = {};
1222 	reminderFormats[ZmMsg.apptRemindNDaysBefore]	= ZmCalItem.REMINDER_UNIT_DAYS;
1223 	reminderFormats[ZmMsg.apptRemindNMinutesBefore]	= ZmCalItem.REMINDER_UNIT_MINUTES;
1224 	reminderFormats[ZmMsg.apptRemindNHoursBefore]	= ZmCalItem.REMINDER_UNIT_HOURS;
1225 	reminderFormats[ZmMsg.apptRemindNWeeksBefore]	= ZmCalItem.REMINDER_UNIT_WEEKS;
1226 
1227 	reminderString = AjxStringUtil.trim(reminderString);
1228 	var formattedString = reminderString;
1229 	var reminderValue = formattedString.replace(/\D/g, "");
1230 	reminderValue = AjxStringUtil.trim(reminderValue);
1231     if (reminderString === ZmMsg.apptRemindAtEventTime) {
1232         return {
1233             reminderValue: 0,
1234             reminderUnits: ZmCalItem.REMINDER_UNIT_MINUTES
1235         }
1236     }
1237 
1238 	// junk content returns empty reminder (None)
1239 	if (reminderValue == "") {
1240 		return {
1241 			reminderValue: "",
1242 			reminderUnits: ZmCalItem.REMINDER_NONE
1243 		};
1244 	}
1245 	if (reminderValue.indexOf(" ") >= 0) {
1246 		reminderValue = reminderValue.split(" ")[0];
1247 	}
1248 
1249 	// look for standard reminder formats strings
1250 	for (var pattern in  reminderFormats) {
1251 		var formattedContent = ZmCalendarApp.__formatLabel(pattern, reminderValue);
1252 		if(formattedContent != "" && formattedContent.toLowerCase() == reminderString.toLowerCase()) {
1253             //Fix for bug: 80651 - set and return object to determine before snooze
1254 			return  {reminderValue: reminderValue, reminderUnits: reminderFormats[pattern], before: true};
1255 		}
1256 	}
1257 
1258 	var reminderHours = parseInt(reminderValue);
1259 
1260     // parse the reminder string for singular units like minute, hour, day etc
1261 	var remUnitStrings = {};
1262 	remUnitStrings[ZmCalItem.REMINDER_UNIT_MINUTES] = AjxMsg.minute;
1263 	remUnitStrings[ZmCalItem.REMINDER_UNIT_HOURS] = AjxMsg.hour;
1264 	remUnitStrings[ZmCalItem.REMINDER_UNIT_DAYS] = AjxMsg.day;
1265 	remUnitStrings[ZmCalItem.REMINDER_UNIT_WEEKS] = AjxMsg.week;
1266 
1267 	//look for matching units
1268     // default unit is hours
1269 	var reminderUnits = ZmCalItem.REMINDER_UNIT_HOURS;
1270 
1271 	for(var i in remUnitStrings) {
1272 		if(formattedString.indexOf(remUnitStrings[i]) >= 0) {
1273 			reminderUnits = i;
1274             return {reminderValue: reminderHours ? reminderHours : 0,  reminderUnits: reminderUnits};
1275 		}
1276 	}
1277 
1278     // parse the reminder string for plural units like minutes, hours, days etc
1279     var remUnitPluralStrings = {};
1280     remUnitPluralStrings[ZmCalItem.REMINDER_UNIT_MINUTES] = AjxMsg.minutes;
1281     remUnitPluralStrings[ZmCalItem.REMINDER_UNIT_HOURS] = AjxMsg.hours;
1282     remUnitPluralStrings[ZmCalItem.REMINDER_UNIT_DAYS] = AjxMsg.days;
1283     remUnitPluralStrings[ZmCalItem.REMINDER_UNIT_WEEKS] = AjxMsg.weeks;
1284 
1285     for(var i in remUnitPluralStrings) {
1286         if(formattedString.indexOf(remUnitPluralStrings[i]) >= 0) {
1287             reminderUnits = i;
1288             return {reminderValue: reminderHours ? reminderHours : 0,  reminderUnits: reminderUnits};
1289         }
1290     }
1291     // fall back to hours if no matching string found
1292 	return {reminderValue: reminderHours ? reminderHours : 0,  reminderUnits: reminderUnits};
1293 };
1294 
1295 ZmCalendarApp.convertReminderUnits =
1296 function(reminderValue, reminderUnits) {
1297 	switch (reminderUnits) {
1298 		case ZmCalItem.REMINDER_UNIT_MINUTES:	return reminderValue;
1299 		case ZmCalItem.REMINDER_UNIT_HOURS:		return reminderValue*60;
1300 		case ZmCalItem.REMINDER_UNIT_DAYS:		return reminderValue*60*24;
1301 		case ZmCalItem.REMINDER_UNIT_WEEKS:		return reminderValue*60*24*7;
1302 		default: 								return 0;
1303 	}
1304 };
1305 
1306 ZmCalendarApp.prototype.updateResourceCache =
1307 function(resource) {
1308 	var name = resource.getFullName();
1309 	if (name) {
1310 		this._resByName[name.toLowerCase()] = resource;
1311 	}
1312 	var email = resource.getEmail();
1313 	if (email) {
1314 		this._resByEmail[email.toLowerCase()] = resource;
1315 	}
1316 };
1317 
1318 ZmCalendarApp.prototype._addSettingsChangeListeners =
1319 function() {
1320 
1321     ZmApp.prototype._addSettingsChangeListeners.call(this);
1322 
1323 	if (!this._settingsListener) {
1324 		this._settingsListener = new AjxListener(this, this._settingsChangeListener);
1325 	}
1326 
1327     var settings = appCtxt.getSettings();
1328 	var setting = settings.getSetting(ZmSetting.CAL_ALWAYS_SHOW_MINI_CAL);
1329 	if (setting) {
1330 		setting.addChangeListener(this._settingListener);
1331 	}
1332 	setting = settings.getSetting(ZmSetting.CAL_FIRST_DAY_OF_WEEK);
1333 	if (setting) {
1334 		setting.addChangeListener(this._settingListener);
1335 	}
1336     setting = settings.getSetting(ZmSetting.CAL_WORKING_HOURS);
1337 	if (setting) {
1338 		setting.addChangeListener(this._settingListener);
1339 	}
1340     setting = settings.getSetting(ZmSetting.CAL_SHOW_DECLINED_MEETINGS);
1341 	if (setting) {
1342 		setting.addChangeListener(this._settingListener);
1343 	}
1344 
1345 	var settings = appCtxt.getSettings();
1346 	settings.getSetting(ZmSetting.CAL_SHOW_CALENDAR_WEEK).addChangeListener(this._settingListener);
1347 	settings.addChangeListener(this._settingsListener);
1348 };
1349 
1350 /**
1351  * Settings listener to process changed settings.
1352  */
1353 ZmCalendarApp.prototype._settingChangeListener =
1354 function(ev) {
1355 	if (ev.type != ZmEvent.S_SETTING) { return; }
1356 
1357 	var setting = ev.source;
1358     if (setting.id == ZmSetting.CAL_ALWAYS_SHOW_MINI_CAL) {
1359 		if (setting.getValue()) {
1360 			var avm = appCtxt.getAppViewMgr();
1361 			var show = !avm.isHidden(ZmAppViewMgr.C_TREE_FOOTER, avm.getCurrentViewId());
1362 			AjxDispatcher.run("ShowMiniCalendar", show);
1363 		} else if (!this._active) {
1364 			AjxDispatcher.run("ShowMiniCalendar", false);
1365 		}
1366 	} else if (setting.id == ZmSetting.CAL_FIRST_DAY_OF_WEEK) {
1367 		var controller = AjxDispatcher.run("GetCalController");
1368 		var minical = controller.getMiniCalendar();
1369 
1370 		var firstDayOfWeek = setting.getValue();
1371 		minical.setFirstDayOfWeek(firstDayOfWeek);
1372 
1373 		var date = minical.getDate();
1374 		controller.setDate(date, 0, true);
1375 	}
1376     else if (setting.id == ZmSetting.CAL_WORKING_HOURS) {
1377         var controller = AjxDispatcher.run("GetCalController");
1378 		var viewMgr = controller.getViewMgr();
1379         if(viewMgr) {
1380             viewMgr.layoutWorkingHours();
1381         }
1382     }  else if (setting.id == ZmSetting.CAL_SHOW_DECLINED_MEETINGS) {
1383         var controller = AjxDispatcher.run("GetCalController");
1384         controller.refreshCurrentView();
1385 	}
1386 };
1387 
1388 ZmCalendarApp.prototype._settingsChangeListener =
1389 function(ev) {
1390 	if (ev.type != ZmEvent.S_SETTINGS) { return; }
1391 
1392 	var list = ev.getDetail("settings");
1393 	if (!(list && list.length)) { return; }
1394 
1395 	for (var i = 0; i < list.length; i++) {
1396 		var setting = list[i];
1397 		if (setting.id == ZmSetting.CAL_SHOW_CALENDAR_WEEK) {
1398 			var controller = AjxDispatcher.run("GetCalController").recreateMiniCalendar();
1399 			var calMgr = appCtxt.getCalManager();
1400 			calMgr.highlightMiniCal();
1401 		}
1402 	}
1403 };
1404 
1405 ZmCalendarApp.prototype.showDayView =
1406 function(date) {
1407 	var calController = AjxDispatcher.run("GetCalController");
1408 	var miniCalendar = calController.getMiniCalendar();
1409 	calController.setDate(date, 0, miniCalendar.getForceRollOver());
1410 	if (!calController._viewVisible) {
1411 		calController.show(ZmId.VIEW_CAL_DAY);
1412 	}
1413 };
1414 
1415 ZmCalendarApp.prototype.getDateToolTip =
1416 function(date, getSimpleToolTip) {
1417 	var cc = AjxDispatcher.run("GetCalController");
1418 	return cc.getDayToolTipText(date, null, null, true, getSimpleToolTip);
1419 };
1420 
1421 ZmCalendarApp.prototype.importAppointment =
1422 function(msgId, partId,name) {
1423 	var loadCallback = new AjxCallback(this, this._handleImportAppointment, [msgId, partId, name]);
1424 	AjxDispatcher.require(["MailCore", "CalendarCore","Calendar"], false, loadCallback);
1425 };
1426 
1427 ZmCalendarApp.prototype._handleImportAppointment =
1428 function(msgId, partId, name) {
1429 	if (this._deferredFolders.length != 0) {
1430 		this._createDeferredFolders(ZmApp.CALENDAR);
1431 	}
1432 	var dlg = this._copyToDialog = appCtxt.getChooseFolderDialog();
1433 	var chooseCb = new AjxCallback(this, this._chooserCallback, [msgId, partId, name]);
1434 	ZmController.showDialog(dlg, chooseCb, this._getCopyParams(dlg, msgId, partId));
1435 };
1436 
1437 ZmCalendarApp.prototype._getCopyParams =
1438 function(dlg, msgId, partId) {
1439 	return {
1440 		data:			{msgId:msgId,partId:partId},
1441 		treeIds:		[ZmOrganizer.CALENDAR],
1442 		overviewId:		dlg.getOverviewId(this._name),
1443 		title:			ZmMsg.addToCalendar,
1444 		description:	ZmMsg.targetFolder,
1445 		appName:		ZmApp.CALENDAR
1446 	};
1447 };
1448 
1449 ZmCalendarApp.prototype._chooserCallback =
1450 function(msgId, partId, name, folder) {
1451 
1452 
1453     var jsonObj = {ImportAppointmentsRequest:{_jsns:"urn:zimbraMail"}};
1454     var request = jsonObj.ImportAppointmentsRequest;
1455     request.l = folder.id;
1456     request.ct = "text/calendar";
1457 
1458     var m = request.content = {};
1459     m.mid = msgId;
1460     m.part = partId;
1461 
1462     var params = {
1463         jsonObj: jsonObj,
1464         asyncMode: true,
1465         callback: (new AjxCallback(this, this._handleImportApptResponse, [folder.id])),
1466         errorCallback: (new AjxCallback(this, this._handleImportApptError))
1467     };
1468     appCtxt.getAppController().sendRequest(params);
1469 
1470 };
1471 
1472 ZmCalendarApp.prototype._handleImportApptResponse =
1473 function(folderId,response) {
1474 	appCtxt.getAppController().setStatusMsg(ZmMsg.addedToCalendar);
1475 	appCtxt.getChooseFolderDialog().popdown();
1476 
1477     var ac = window.parentAppCtxt || window.appCtxt;
1478     if(ac.get(ZmSetting.CAL_ALWAYS_SHOW_MINI_CAL)) {
1479         var calMgr = ac.getCalManager();
1480         calMgr.getMiniCalCache().clearCache();
1481         calMgr.highlightMiniCal();
1482     }
1483 };
1484 
1485 ZmCalendarApp.prototype._handleImportApptError =
1486 function(ex) {
1487 	appCtxt.getAppController().setStatusMsg(ZmMsg.errorImportAppt, ZmStatusView.LEVEL_CRITICAL);
1488 };
1489 
1490 /**
1491  * Returns the reminder warning time display options formatted for preferences
1492  * we create preferences reminder button here .
1493  */
1494 ZmCalendarApp.getReminderTimeWarningDisplayOptions = 
1495 function() {
1496 	var returnArr = [];
1497 	for (var i = 0; i < ZmCalendarApp.reminderTimeWarningDisplayMsgs.length; i++) {
1498 		returnArr.push(ZmCalendarApp.__formatLabel(ZmCalendarApp.reminderTimeWarningDisplayMsgs[i], ZmCalendarApp.reminderTimeWarningLabels[i]));
1499 		
1500 	}
1501 	return returnArr;
1502 };
1503 
1504 /**
1505  * On doing save, we modify the request and map zimbraPrefCalendarApptReminderWarningTimevalue
1506  * so that the value of never, 0, is not changed at server.
1507  * If never is selected in reminder dropdown, we map never value -1 to previous value, 0
1508  * and if 'at time of event' is chosen, we map 0 to -1 while constructing request.
1509  **/
1510 
1511 ZmCalendarApp.setDefaultReminderTimePrefValueOnSave =
1512 function(pref, value, list) {
1513     value === 0 ? (value = -1) : (value  === -1 ? value =0 : '');
1514     pref.setValue(value);
1515     list.push(pref);
1516 };
1517 
1518 /**
1519  *  Client side mapping of never is -1 and 'at time of event' is 0.
1520  * If never is chosen in default reminder dropdown, user saves his preferences. We then modify the request
1521  * and set the pref zimbraPrefCalendarApptReminderWarningTimevalue value to 0, to make the behaviour
1522  * backward compatible, as earlier never was mapped to 0. Now, after reload, the value of pref zimbraPrefCalendarApptReminderWarningTimevalue
1523  * in client side i.e ZmSetting.CAL_REMINDER_WARNING_TIME, is 0 as the server returns me this value.
1524  * This was causing issue in the view of reminder option in pref section and while composing a new appt.
1525  * So, here we map default reminder pref to its client side mapping.
1526  * Same thing with 'at time of event'.
1527  */
1528 
1529 ZmCalendarApp.postLoadSetDefaultReminderValue = function() {
1530     /**
1531      * This function is called when after reload, when you click on calendar tab or click on calendar icon
1532      * in preferences. And, after reload, we want to set the value of default reminder pref only one time when
1533      * the calendar tab is clicked or calendar pref is clicked. So, we have used global variable postLoadSetReminderCalled
1534      * as a check for that. Other option for doing that would be to call this function from ZmApp.prototype._postLoad
1535      * and then no need for doing check based on variable postLoadSetReminderCalled, but that be
1536      * something calling during the initial page's load, so I avoided that .
1537      */
1538 
1539     if (ZmCalendarApp.postLoadSetReminderCalled) {
1540         return;
1541     }
1542     var defaultWarningTime = appCtxt.get(ZmSetting.CAL_REMINDER_WARNING_TIME);
1543     if (defaultWarningTime === -1 || defaultWarningTime === 0) { // never or 'at time of event' was chosen in defaultreminderpref dropdown before load
1544         defaultWarningTime === -1 ? (defaultWarningTime = 0) : (defaultWarningTime = -1);
1545         appCtxt.set(ZmSetting.CAL_REMINDER_WARNING_TIME,defaultWarningTime);
1546     }
1547     ZmCalendarApp.postLoadSetReminderCalled = true;
1548 };
1549