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  * Creates the calendar view controller.
 26  * @class
 27  * The strategy for the calendar is to leverage the list view stuff by creating a single
 28  * view (i.e. {@link ZmCalViewMgr}) and have it manage the schedule views (e.g. week, month) and
 29  * a single calendar view (the calendar widget to the right). Each of the schedule views
 30  * will be a list view that are managed by the {@link ZmCalViewMgr}.
 31  * <p>
 32  * To do this we have to trick the {@link ZmListController}. Specifically we have only one toolbar and
 33  * directly manipulate this._toolbar elements to point to a single instance of the toolbar. We also
 34  * directly override the {@link ZmListControl.initializeToolBar} method.
 35  *
 36  * @param {DwtComposite}				container					the containing element
 37  * @param {ZmCalendarApp}				calApp						the handle to the calendar application
 38  * @param {string}						sessionId					the session id
 39  * @param {ZmSearchResultsController}	searchResultsController		containing controller
 40  * 
 41  * @extends		ZmListController
 42  */
 43 ZmCalViewController = function(container, calApp, sessionId, searchResultsController) {
 44 
 45 	ZmListController.apply(this, arguments);
 46     
 47 	var apptListener = this._handleApptRespondAction.bind(this);
 48 	var apptEditListener = this._handleApptEditRespondAction.bind(this);
 49 
 50 	// get view based on op
 51 	ZmCalViewController.OP_TO_VIEW = {};
 52 	ZmCalViewController.OP_TO_VIEW[ZmOperation.DAY_VIEW]		= ZmId.VIEW_CAL_DAY;
 53 	ZmCalViewController.OP_TO_VIEW[ZmOperation.WEEK_VIEW]		= ZmId.VIEW_CAL_WEEK;
 54 	ZmCalViewController.OP_TO_VIEW[ZmOperation.WORK_WEEK_VIEW]	= ZmId.VIEW_CAL_WORK_WEEK;
 55 	ZmCalViewController.OP_TO_VIEW[ZmOperation.MONTH_VIEW]		= ZmId.VIEW_CAL_MONTH;
 56 	ZmCalViewController.OP_TO_VIEW[ZmOperation.CAL_LIST_VIEW]	= ZmId.VIEW_CAL_LIST;
 57 	ZmCalViewController.OP_TO_VIEW[ZmOperation.FB_VIEW]			= ZmId.VIEW_CAL_FB;
 58 
 59 	// get op based on view
 60 	ZmCalViewController.VIEW_TO_OP = {};
 61 	for (var op in ZmCalViewController.OP_TO_VIEW) {
 62 		ZmCalViewController.VIEW_TO_OP[ZmCalViewController.OP_TO_VIEW[op]] = op;
 63 	}
 64 
 65 	this._listeners[ZmOperation.REPLY_ACCEPT]			= apptListener;
 66 	this._listeners[ZmOperation.REPLY_DECLINE]			= apptListener;
 67 	this._listeners[ZmOperation.REPLY_TENTATIVE]		= apptListener;
 68 	this._listeners[ZmOperation.EDIT_REPLY_ACCEPT]		= apptEditListener;
 69 	this._listeners[ZmOperation.EDIT_REPLY_DECLINE]		= apptEditListener;
 70 	this._listeners[ZmOperation.EDIT_REPLY_TENTATIVE]	= apptEditListener;
 71 
 72 	this._listeners[ZmOperation.SHOW_ORIG]				= this._showOrigListener.bind(this);
 73 	this._listeners[ZmOperation.VIEW_APPOINTMENT]		= this._handleMenuViewAction.bind(this);
 74 	this._listeners[ZmOperation.OPEN_APPT_INSTANCE]		= this._handleMenuViewAction.bind(this);
 75 	this._listeners[ZmOperation.OPEN_APPT_SERIES]		= this._handleMenuViewAction.bind(this);
 76 	this._listeners[ZmOperation.TODAY]					= this._todayButtonListener.bind(this);
 77 	this._listeners[ZmOperation.NEW_APPT]				= this._newApptAction.bind(this);
 78 	this._listeners[ZmOperation.NEW_ALLDAY_APPT]		= this._newAllDayApptAction.bind(this);
 79 	this._listeners[ZmOperation.SEARCH_MAIL]			= this._searchMailAction.bind(this);
 80 	this._listeners[ZmOperation.MOVE]					= this._apptMoveListener.bind(this);
 81 	this._listeners[ZmOperation.DELETE_INSTANCE]		= this._deleteListener.bind(this);
 82 	this._listeners[ZmOperation.DELETE_SERIES]			= this._deleteListener.bind(this);
 83 	this._listeners[ZmOperation.FORWARD_APPT]			= this._forwardListener.bind(this);
 84     this._listeners[ZmOperation.PROPOSE_NEW_TIME]		= this._proposeTimeListener.bind(this);
 85     this._listeners[ZmOperation.REINVITE_ATTENDEES]		= this._reinviteAttendeesListener.bind(this);
 86 	this._listeners[ZmOperation.FORWARD_APPT_INSTANCE]	= this._forwardListener.bind(this);
 87 	this._listeners[ZmOperation.FORWARD_APPT_SERIES]	= this._forwardListener.bind(this);
 88 	this._listeners[ZmOperation.REPLY]					= this._replyListener.bind(this);
 89 	this._listeners[ZmOperation.REPLY_ALL]				= this._replyAllListener.bind(this);
 90 	this._listeners[ZmOperation.DUPLICATE_APPT]			= this._duplicateApptListener.bind(this);
 91     this._listeners[ZmOperation.PRINT_CALENDAR]			= this._printCalendarListener.bind(this);
 92 
 93 	this._treeSelectionListener = this._calTreeSelectionListener.bind(this);
 94 	this._maintTimedAction = new AjxTimedAction(this, this._maintenanceAction);
 95 	this._pendingWork = ZmCalViewController.MAINT_NONE;
 96 	this.apptCache = new ZmApptCache(this);
 97 	this._currentViewIdOverride = ZmId.VIEW_CAL;	// public view ID
 98 	
 99 	ZmCalViewController.OPS = [
100 		ZmOperation.DAY_VIEW, ZmOperation.WORK_WEEK_VIEW, ZmOperation.WEEK_VIEW,
101 		ZmOperation.MONTH_VIEW, ZmOperation.CAL_LIST_VIEW
102 	];
103     if (appCtxt.get(ZmSetting.FREE_BUSY_VIEW_ENABLED)) {
104         ZmCalViewController.OPS.push(ZmOperation.FB_VIEW);    
105     }
106 	var viewOpsListener = new AjxListener(this, this._viewActionMenuItemListener);
107 	for (var i = 0; i < ZmCalViewController.OPS.length; i++) {
108 		var op = ZmCalViewController.OPS[i];
109 		this._listeners[op] = viewOpsListener;
110 	}
111 	
112 	this._errorCallback = this._handleError.bind(this);
113 
114 	// needed by ZmCalListView:
115 	if (this.supportsDnD()) {
116 		this._dragSrc = new DwtDragSource(Dwt.DND_DROP_MOVE);
117 		this._dragSrc.addDragListener(this._dragListener.bind(this));
118 	}
119 	
120 	this._clearCacheFolderMap = {};
121 	this._apptSessionId = {};
122 };
123 
124 ZmCalViewController.prototype = new ZmListController;
125 ZmCalViewController.prototype.constructor = ZmCalViewController;
126 
127 ZmCalViewController.prototype.isZmCalViewController = true;
128 ZmCalViewController.prototype.toString = function() { return "ZmCalViewController"; };
129 
130 ZmCalViewController.DEFAULT_APPOINTMENT_DURATION = 30*60*1000;
131 
132 // maintenance needed on views and/or minical
133 ZmCalViewController.MAINT_NONE 		= 0x0; // no work to do
134 ZmCalViewController.MAINT_MINICAL 	= 0x1; // minical needs refresh
135 ZmCalViewController.MAINT_VIEW 		= 0x2; // view needs refresh
136 ZmCalViewController.MAINT_REMINDER	= 0x4; // reminders need refresh
137 
138 // get view based on op
139 ZmCalViewController.ACTION_CODE_TO_VIEW = {};
140 ZmCalViewController.ACTION_CODE_TO_VIEW[ZmKeyMap.CAL_DAY_VIEW]			= ZmId.VIEW_CAL_DAY;
141 ZmCalViewController.ACTION_CODE_TO_VIEW[ZmKeyMap.CAL_WEEK_VIEW]			= ZmId.VIEW_CAL_WEEK;
142 ZmCalViewController.ACTION_CODE_TO_VIEW[ZmKeyMap.CAL_WORK_WEEK_VIEW]	= ZmId.VIEW_CAL_WORK_WEEK;
143 ZmCalViewController.ACTION_CODE_TO_VIEW[ZmKeyMap.CAL_MONTH_VIEW]		= ZmId.VIEW_CAL_MONTH;
144 ZmCalViewController.ACTION_CODE_TO_VIEW[ZmKeyMap.CAL_LIST_VIEW]			= ZmId.VIEW_CAL_LIST;
145 ZmCalViewController.ACTION_CODE_TO_VIEW[ZmKeyMap.CAL_FB_VIEW]		    = ZmId.VIEW_CAL_FB;
146 
147 ZmCalViewController.CHECKED_STATE_KEY = "CalendarsChecked";
148 
149 // Zimlet hack
150 ZmCalViewController.prototype.postInitListeners =
151 function () {
152 	if (ZmZimlet.listeners && ZmZimlet.listeners["ZmCalViewController"]) {
153 		for(var ix in ZmZimlet.listeners["ZmCalViewController"]) {
154 			if(ZmZimlet.listeners["ZmCalViewController"][ix] instanceof AjxListener) {
155 				this._listeners[ix] = ZmZimlet.listeners["ZmCalViewController"][ix];
156 			} else {
157 				this._listeners[ix] = new AjxListener(this, ZmZimlet.listeners["ZmCalViewController"][ix]);
158 			}
159 		}
160 	}
161 };
162 
163 ZmCalViewController.getDefaultViewType =
164 function() {
165 	var setting = appCtxt.get(ZmSetting.CALENDAR_INITIAL_VIEW);
166     //This if loop has to be removed once bug 68708 is fixed
167     if(setting === "schedule"){
168         setting = ZmSetting.CAL_DAY;//Assigning day view as default view instead of schedule view as it is removed
169     }
170 	return ZmCalendarApp.VIEW_FOR_SETTING[setting] || ZmId.VIEW_CAL_WORK_WEEK;
171 };
172 ZmCalViewController.prototype.getDefaultViewType = ZmCalViewController.getDefaultViewType;
173 
174 /**
175  * Gets the first day of week setting.
176  * 
177  * @return	{int}	the first day of week index
178  */
179 ZmCalViewController.prototype.firstDayOfWeek =
180 function() {
181 	return appCtxt.get(ZmSetting.CAL_FIRST_DAY_OF_WEEK) || 0;
182 };
183 
184 ZmCalViewController.prototype.setCurrentViewId =
185 function(viewId) {
186     if (viewId != ZmId.VIEW_CAL) {
187         // Only store a real view id; VIEW_CAL is a placeholder used to identity the
188         // constellation of calendar views to the AppViewMgr.  Cal views are handled
189         // by their own ZmCalViewMgr.
190         ZmController.prototype.setCurrentViewId.call(this, viewId);
191     }
192 };
193 
194 ZmCalViewController.prototype.show =
195 function(viewId, startDate, skipMaintenance) {
196 	AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar"]);
197 	DBG.println(AjxDebug.DBG1, "ZmCalViewController.show, viewId = " + viewId);
198 	if (!viewId || viewId == ZmId.VIEW_CAL) {
199 		viewId = this.getCurrentViewType() || this.getDefaultViewType();
200 	}
201 
202 	if (!this._viewMgr) {
203 		this.createViewMgr(startDate);
204 		this._setup(viewId); // TODO: Needed? Wouldn't this be handled by next conditional?
205 	}
206 
207 	if (!this._viewMgr.getView(viewId)) {
208 		this._setup(viewId);
209 	}
210 
211 	var previousView = this._viewMgr.getCurrentView();
212 	var hadFocus = previousView && previousView.hasFocus();
213 
214 	this._viewMgr.setView(viewId);
215 	DBG.timePt("setup and set view");
216 
217 	var elements = this.getViewElements(ZmId.VIEW_CAL, this._viewMgr);
218 
219 	// we need to mark the current view _before_ actually setting the view,
220 	// otherwise we won't get the tab group added if the view already exists
221 	this._currentViewId = this._currentViewType = this._viewMgr.getCurrentViewName();
222 
223     this._setView({ view:		ZmId.VIEW_CAL,
224 					viewType:	this._currentViewType,
225 					elements:	elements,
226 					isAppView:	true});
227 
228     this.setCurrentListView(null);
229     var currentView = this._listView[this._currentViewId] = this._viewMgr.getCurrentView();
230 	this._resetToolbarOperations(viewId);
231 
232 
233 	DBG.timePt("switching selection mode and tooltips");
234 
235     switch(viewId) {
236         case ZmId.VIEW_CAL_DAY:
237             this._miniCalendar.setSelectionMode(DwtCalendar.DAY);
238             this._navToolBar[ZmId.VIEW_CAL].setToolTip(ZmOperation.PAGE_BACK, ZmMsg.previousDay);
239             this._navToolBar[ZmId.VIEW_CAL].setToolTip(ZmOperation.PAGE_FORWARD, ZmMsg.nextDay);
240             break;
241         case ZmId.VIEW_CAL_WORK_WEEK:
242             this._miniCalendar.setSelectionMode(DwtCalendar.WORK_WEEK);
243             this._navToolBar[ZmId.VIEW_CAL].setToolTip(ZmOperation.PAGE_BACK, ZmMsg.previousWorkWeek);
244             this._navToolBar[ZmId.VIEW_CAL].setToolTip(ZmOperation.PAGE_FORWARD, ZmMsg.nextWorkWeek);
245             break;
246         case ZmId.VIEW_CAL_WEEK:
247             this._miniCalendar.setSelectionMode(DwtCalendar.WEEK);
248             this._navToolBar[ZmId.VIEW_CAL].setToolTip(ZmOperation.PAGE_BACK, ZmMsg.previousWeek);
249             this._navToolBar[ZmId.VIEW_CAL].setToolTip(ZmOperation.PAGE_FORWARD, ZmMsg.nextWeek);
250             break;
251         case ZmId.VIEW_CAL_MONTH:
252             // use day until month does something
253             this._miniCalendar.setSelectionMode(DwtCalendar.DAY);
254             this._navToolBar[ZmId.VIEW_CAL].setToolTip(ZmOperation.PAGE_BACK, ZmMsg.previousMonth);
255             this._navToolBar[ZmId.VIEW_CAL].setToolTip(ZmOperation.PAGE_FORWARD, ZmMsg.nextMonth);
256             break;
257         case ZmId.VIEW_CAL_LIST:
258             this._miniCalendar.setSelectionMode(DwtCalendar.DAY);
259             break;
260     }
261     if (viewId == ZmId.VIEW_CAL_LIST) {
262 		this._navToolBar[ZmId.VIEW_CAL].setVisible(false);
263 	} else {
264         if(viewId!=ZmId.VIEW_CAL_MONTH){this._viewMgr.getView(viewId).initializeTimeScroll();}
265 		this._navToolBar[ZmId.VIEW_CAL].setVisible(true);
266 		var navText = viewId == ZmId.VIEW_CAL_MONTH
267 			? currentView.getShortCalTitle()
268 			: currentView.getCalTitle();
269 		this._navToolBar[ZmId.VIEW_CAL].setText(navText);
270 		DBG.println(AjxDebug.DBG1, "ZmCalViewController.show, skipMaintenance = " + skipMaintenance);
271 		if (!skipMaintenance) {
272 			var work = ZmCalViewController.MAINT_VIEW;
273 			if (window.inlineCalSearchResponse) {
274 				this.processInlineCalSearch();
275 				window.inlineCalSearchResponse = null;
276 				work = ZmCalViewController.MAINT_MINICAL|ZmCalViewController.MAINT_VIEW|ZmCalViewController.MAINT_REMINDER;
277 			}
278 			this._scheduleMaintenance(work);
279 		}
280         if (!this.isSearchResults) {
281             this.updateTimeIndicator(viewId);
282         }
283 		DBG.timePt("scheduling maintenance");
284 	}
285 
286 	if (hadFocus) {
287 		currentView.focus();
288 	}
289 
290 	// do this last
291 	if (!this._calTreeController) {
292 		this._calTreeController = appCtxt.getOverviewController().getTreeController(ZmOrganizer.CALENDAR);
293 		if (this._calTreeController) {
294 			if (appCtxt.multiAccounts) {
295 				var overviews = this._app.getOverviewContainer().getOverviews();
296 				for (var i in overviews) {
297 					this._calTreeController.addSelectionListener(i, this._treeSelectionListener);
298 				}
299 			} else {
300 				this._calTreeController.addSelectionListener(this._app.getOverviewId(), this._treeSelectionListener);
301 			}
302 			var calTree = appCtxt.getFolderTree();
303 			if (calTree) {
304 				calTree.addChangeListener(new AjxListener(this, this._calTreeChangeListener));
305 			}
306 		}
307 		DBG.timePt("getting tree controller", true);
308 	}
309 };
310 
311 /**
312  * Updates the time indicator strip.
313  *
314  */
315 ZmCalViewController.prototype.updateTimeIndicator =
316 function(viewId) {
317     viewId = viewId || this.getCurrentViewType();
318     this._viewMgr.getView(viewId).startIndicatorTimer(this.isSearchResults);
319 };
320 
321 /**
322  * Gets the calendar tree controller.
323  *
324  * @return	{ZmCalendarTreeController}		the controller
325  */
326 ZmCalViewController.prototype.getCalTreeController =
327 function() {
328 	return this._calTreeController;
329 };
330 
331 ZmCalViewController.prototype.createViewMgr =
332 function(startDate) {
333 	var newDate = startDate || (this._miniCalendar ? this._miniCalendar.getDate() : new Date());
334 
335 	if (!this._miniCalendar) {
336 		this._createMiniCalendar(newDate);
337 	}
338 
339 	this._viewMgr = new ZmCalViewMgr(this._container, this, this._dropTgt);
340 	this._viewMgr.setDate(newDate);
341 	this._viewMgr.addTimeSelectionListener(new AjxListener(this, this._timeSelectionListener));
342 	this._viewMgr.addDateRangeListener(new AjxListener(this, this._dateRangeListener));
343 	this._viewMgr.addViewActionListener(new AjxListener(this, this._viewActionListener));
344 	DBG.timePt("created view manager");
345 };
346 
347 /**
348  * Gets the checked calendars.
349  * 
350  * @param	{String}	overviewId		the overview id
351  * @return	{Array}		an array of {ZmCalendar} objects
352  */
353 ZmCalViewController.prototype.getCheckedCalendars =
354 function(includeTrash) {
355 	if (!this._checkedCalendars) {
356 		this._updateCheckedCalendars();
357 	}
358     return includeTrash ? this._checkedCalendarsAll : this._checkedCalendars;
359 };
360 
361 ZmCalViewController.prototype.getMainAccountCheckedCalendarIds =
362 function() {
363     if (!this._checkedAccountCalendarIds) {
364         // Generate the checked calendar data
365         this._updateCheckedCalendars();
366     }
367     // If there are any account calendar ids, the 0th should be the main account - copy its checked calendars
368     return (this._checkedAccountCalendarIds.length == 0) ? [] : this._checkedAccountCalendarIds[0].slice(0);
369 }
370 
371 // Called whether starting on or offline
372 ZmCalViewController.prototype.getOfflineSearchCalendarIds =
373 function() {
374     if (!this._offlineSearchCalendarIds) {
375 
376         var checkedCalendarHash = null;
377         if (appCtxt.isWebClientOffline()) {
378             checkedCalendarHash = this._retrieveCalendarCheckedState();
379         }
380 
381         // Generate the checked calendar data - this will also store the calendar entries, if we are starting up online
382         this._updateCheckedCalendars(checkedCalendarHash);
383     }
384     return (this._offlineSearchCalendarIds);
385 }
386 
387 ZmCalViewController.prototype._storeCalendarCheckedState =
388 function() {
389     // if !offline, store which calendars are currently checked, in localStorage.  This will be updated
390     // while running, and reloaded if the app starts in offline mode.
391     var calendarCheckedState = this._checkedCalendarIds.join(":");
392     localStorage.setItem(ZmCalendarApp.CHECKED_STATE_KEY, calendarCheckedState);
393 }
394 
395 
396 ZmCalViewController.prototype._retrieveCalendarCheckedState =
397 function() {
398     // Starting up offline, retrieve the stored calendar checked state
399     var checkedStateText = localStorage.getItem(ZmCalendarApp.CHECKED_STATE_KEY);
400     var checkedCalendarFolderIds = checkedStateText.split(":");
401     var checkedCalendarHash = {};
402     for (var i = 0; i < checkedCalendarFolderIds.length; i++) {
403         checkedCalendarHash[checkedCalendarFolderIds[i]] = true;
404     }
405     return checkedCalendarHash;
406 }
407 
408 
409 /**
410  * Gets the unchecked calendar folder ids for a owner email
411  *
412  * @param	{String}	ownerEmailId		email id of the owner account
413  * @return	{Array}		an array of folder ids
414  */
415 ZmCalViewController.prototype.getUncheckedCalendarIdsByOwner =
416 function(ownerEmailId) {
417 	var dataTree = appCtxt.getTree(ZmId.ORG_CALENDAR, appCtxt.getActiveAccount()),
418         calendars = dataTree.getByType(ZmId.ORG_CALENDAR),
419         len = calendars.length,
420         calIds = [],
421         calendar,
422         i;
423 
424     for (i=0; i<len; i++) {
425         calendar = calendars[i];
426         if(!calendar.isChecked && calendar.owner && calendar.owner == ownerEmailId) {
427             calIds.push(calendar.id);
428         }
429     }
430     return calIds;
431 };
432 
433 /**
434  * Gets the checked calendar folder ids.
435  * 
436  * @param	{Boolean}	localOnly		if <code>true</code>, include local calendars only
437  * @return	{Array}		an array of folder ids
438  */
439 ZmCalViewController.prototype.getCheckedCalendarFolderIds =
440 function(localOnly, includeTrash) {
441 	if (!this._checkedCalendarIds) {
442 		this.getCheckedCalendars(includeTrash);
443 		if (!this._checkedCalendarIds) {
444 			return [ZmOrganizer.ID_CALENDAR];
445 		}
446 	}
447     // TODO: Do we also need to handle includeTrash here?
448 	return localOnly
449 		? this._checkedLocalCalendarIds
450 		: this._checkedCalendarIds;
451 };
452 
453 /**
454  * Gets the checked calendar folder ids.
455  *
456  * @param	{Boolean}	localOnly		if <code>true</code>, include local calendars only
457  * @return	{Array}		an array of folder ids
458  */
459 ZmCalViewController.prototype.getOwnedCalendarIds =
460 function(email, includeTrash) {
461     var i,
462         cal,
463         calendars,
464         calIds = [];
465     if(!this._calTreeController) {
466         this._calTreeController = appCtxt.getOverviewController().getTreeController(ZmOrganizer.CALENDAR);
467     }
468 
469     calendars = this._calTreeController.getOwnedCalendars(this._app.getOverviewId(), email);
470     for (i = 0; i < calendars.length; i++) {
471         cal = calendars[i];
472         if (cal) {
473             if(!includeTrash && (cal.nId == ZmFolder.ID_TRASH)) { continue; }
474             calIds.push(appCtxt.multiAccounts ? cal.id : cal.nId);
475         }
476     }
477 
478 	return calIds;
479 };
480 
481 /**
482  * Gets the calendar folder ids used to create reminders.
483  * All local calendars and shared calendars with the reminder flag set, checked or unchecked.
484  * Does not include the trash folder.
485  *
486  * @return	{Array}		an array of folder ids
487  */
488 ZmCalViewController.prototype.getReminderCalendarFolderIds =
489 function() {
490 	if (!this._reminderCalendarIds) {
491 		this._updateCheckedCalendars();
492 		if (!this._reminderCalendarIds) {
493 			return [ZmOrganizer.ID_CALENDAR];
494 		}
495 	}
496 	return this._reminderCalendarIds;
497 };
498 
499 /**
500  * Gets the checked organizers.
501  *
502  * @return {Array} array of {ZmOrganizer}
503  */
504 ZmCalViewController.prototype.getCheckedOrganizers = function(includeTrash, acct) {
505     var controller = appCtxt.getOverviewController();
506     var overviewId = appCtxt.getApp(ZmApp.CALENDAR).getOverviewId(acct);
507     var treeId = ZmOrganizer.CALENDAR;
508     var treeView = controller.getTreeView(overviewId, treeId);
509     var organizers = treeView.getSelected();
510     if (!includeTrash) {
511         for (var i = 0; i < organizers.length; i++) {
512             if (organizers[i].id == ZmOrganizer.ID_TRASH) {
513                 organizers.splice(i, 1);
514                 break;
515             }
516         }
517     }
518     return organizers;
519 };
520 
521 /**
522  * Gets the checked organizer IDs.
523  *
524  * @return {Array} array of strings
525  */
526 ZmCalViewController.prototype.getCheckedOrganizerIds = function() {
527     return AjxUtil.map(this.getCheckedOrganizers(), ZmCalViewController.__map_id);
528 };
529 ZmCalViewController.__map_id = function(item) {
530     return item.id;
531 };
532 
533 ZmCalViewController.prototype._updateCheckedCalendars =
534 function(checkedCalendarHash) {
535     var allCalendars = [];
536 	if (this._calTreeController) {
537 		if (appCtxt.multiAccounts) {
538 			var overviews = this._app.getOverviewContainer().getOverviews();
539 			for (var i in overviews) {
540 				allCalendars = allCalendars.concat(this._calTreeController.getCalendars(i));
541 			}
542 		} else {
543 			// bug fix #25512 - avoid race condition
544 			if (!this._app._overviewPanelContent) {
545 				this._app.setOverviewPanelContent(true);
546 			}
547 			allCalendars = this._calTreeController.getCalendars(this._app.getOverviewId());
548 		}
549 	} else {
550 		this._app._createDeferredFolders(ZmApp.CALENDAR);
551         var ac = window.parentAppCtxt || window.appCtxt;
552 		var list = ac.accountList.visibleAccounts;
553 		for (var i = 0; i < list.length; i++) {
554 			var acct = list[i];
555 			if (!ac.get(ZmSetting.CALENDAR_ENABLED, null, acct)) { continue; }
556 
557 			var folderTree = ac.getFolderTree(acct);
558 			var calendars = folderTree && folderTree.getByType(ZmOrganizer.CALENDAR);
559 			if (calendars) {
560 				for (var j = 0; j < calendars.length; j++) {
561 					// bug: 43067: skip the default calendar for caldav based accounts
562 					if (acct.isCalDavBased() && calendars[j].nId == ZmOrganizer.ID_CALENDAR) {
563 						continue;
564 					}
565 					allCalendars.push(calendars[j]);
566 				}
567 			}
568 		}
569 	}
570 
571     this._checkedCalendars = [];
572     this._checkedCalendarIds = [];
573     this._checkedLocalCalendarIds = [];
574     this._checkedAccountCalendarIds = [];
575     this._offlineSearchCalendarIds = [];
576     this._reminderCalendarIds = [];
577     var checkedAccountCalendarIds = {};
578     var trashFolder = null;
579     for (var i = 0; i < allCalendars.length; i++) {
580         var cal = allCalendars[i];
581         if (!cal.noSuchFolder) {
582             this._offlineSearchCalendarIds.push(cal.id);
583         }
584         if (!cal.noSuchFolder && (cal.id != ZmOrganizer.ID_TRASH) &&
585             (cal.isRemote && !cal.isRemote())) {
586             this._reminderCalendarIds.push(cal.id);
587         }
588         var checked = cal.isChecked;
589         if (checkedCalendarHash) {
590             // LocalStorage values passed in upon offline startup, use those
591             checked = checkedCalendarHash[cal.id] ? true : false;
592             cal.isChecked = checked;
593         }
594         if (checked) {
595             if (cal.id == ZmOrganizer.ID_TRASH) {
596                 trashFolder = cal;
597             } else {
598                 this._checkedCalendars.push(cal);
599             }
600             if (!cal.noSuchFolder) {
601                 this._checkedCalendarIds.push(cal.id);
602                 if (cal.isRemote && !cal.isRemote()) {
603                     this._checkedLocalCalendarIds.push(cal.id);
604                 }
605                 var acctId = (appCtxt.multiAccounts) ? cal.getAccount().id : appCtxt.accountList.mainAccount.id;
606                 if (!checkedAccountCalendarIds[acctId]) {
607                     checkedAccountCalendarIds[acctId] = [];
608                 }
609                 checkedAccountCalendarIds[acctId].push(cal.id);
610             }
611         }
612     }
613 
614     this._checkedCalendarsAll = trashFolder ? this._checkedCalendars.concat(trashFolder) : this._checkedCalendars;
615 
616     // convert hash to local array
617     for (var i in checkedAccountCalendarIds) {
618         this._checkedAccountCalendarIds.push(checkedAccountCalendarIds[i]);
619     }
620 
621     if (!checkedCalendarHash) {
622         // Not the initial call, update the stored calendar info.  Note this will be called when calendars are
623         // checked/unchecked by the user (online or offline), and when notifications modifying the calendars
624         // are received.
625         this._storeCalendarCheckedState();
626     }
627 
628     // return list of checked calendars
629     return this._checkedCalendars;
630 };
631 
632 ZmCalViewController.prototype._calTreeSelectionListener =
633 function(ev) {
634 	if (ev.detail != DwtTree.ITEM_CHECKED) { return; }
635 
636     // NOTE: This isn't called by the cal tree controller in all cases so
637     // NOTE: we need to make sure the checked calendar list is up-to-date.
638 	this._updateCheckedCalendars();
639 
640 	if (!this._calItemStatus) {
641 		this._calItemStatus = {};
642 	}
643 
644 	if (ev.item) {
645         var organizer = ev.item.getData && ev.item.getData(Dwt.KEY_OBJECT);
646         if (organizer && organizer.nId == ZmOrganizer.ID_TRASH) {
647             var found = false;
648             var acct = organizer.getAccount();
649             var organizers = this.getCheckedOrganizers(true, acct);
650             for (var i = 0; i < organizers.length; i++) {
651                 var id = organizers[i].nId;
652                 if (id == ZmOrganizer.ID_TRASH) {
653                     found = true;
654                     break;
655                 }
656             }
657             this._viewMgr.setSubContentVisible(found);
658             return;
659         }
660 		ev.items = [ ev.item ];
661 	}
662 	if (ev.items && ev.items.length) {
663 		for (var i = 0; i < ev.items.length; i++) {
664 			var item = ev.items[i];
665 			this.__addCalItemStatus(item, item.getChecked());
666 		}
667 	}
668 
669 	// update calendar state on time delay to avoid race condition
670 	if (!this._updateCalItemStateActionId) {
671 		this._updateCalItemStateActionId = AjxTimedAction.scheduleAction(new AjxTimedAction(this, this._updateCalItemState), 1200);
672 	}
673 };
674 
675 ZmCalViewController.prototype.__addCalItemStatus =
676 function(item, checked) {
677 	item.setChecked(checked);
678 	var organizer = item.getData(Dwt.KEY_OBJECT);
679 	if (organizer && organizer.type == ZmOrganizer.CALENDAR) {
680         AjxDebug.println(AjxDebug.CALENDAR, " ---------------- calendar " +  organizer.name + " [" + organizer.id + "] is " + (checked ? "checked" : "unchecked"));
681 		this._calItemStatus[organizer.id] = {item: organizer, checked: checked};
682 	}
683 
684 	// bug 6410
685 	var items = item.getItems();
686 	for (var i = 0; i < items.length; i++) {
687 		item = items[i];
688 		this.__addCalItemStatus(item, checked);
689 	}
690 };
691 
692 ZmCalViewController.prototype._updateCalItemState =
693 function() {
694 	if (!this._calItemStatus) { return; }
695 
696 	var accountName = appCtxt.isOffline && appCtxt.accountList.mainAccount.name;
697 	var batchCmd = new ZmBatchCommand(null, accountName, true);
698 	var itemCount = 0;
699     for (var i in this._calItemStatus) {
700         var info = this._calItemStatus[i];
701         if (info.item) {
702             var calendar = info.item;
703             //If, Remote Calendars & not mount-points, dont send check/uncheck requests
704             if ((calendar.isRemote() && (!calendar.isMountpoint || !calendar.zid))) {
705                 calendar.isChecked = info.checked;
706                 calendar.checkedCallback(info.checked);
707                 this._handleCheckedCalendarRefresh(calendar);
708             } else {
709                 batchCmd.add(new AjxCallback(calendar, calendar.checkAction, [info.checked]));
710                 itemCount++;
711             }
712         }
713     }
714 
715     if (appCtxt.multiAccounts) {
716         this.apptCache.clearCache();
717     }
718 
719     if (itemCount > 0) {
720         if (appCtxt.isWebClientOffline()) {
721             // The offlineCallback gets bound inside batchCmd.run to this
722             var offlineCallback = this._handleOfflineCalendarCheck.bind(this, batchCmd);
723             batchCmd.run(null, null, offlineCallback);
724         } else {
725             this._calItemStatus = {};
726             batchCmd.run();
727         }
728     }
729 
730 	this._updateCalItemStateActionId = null;
731 };
732 
733 // Update the check change locally (a folder and its subfolders) and store the batch command into the request queue
734 ZmCalViewController.prototype._handleOfflineCalendarCheck =
735 function(batchCmd) {
736 
737     // Json batchCommand created in _updaetCalItemState above.  Must be Json to be stored in offline request queue
738     var jsonObj = batchCmd.getRequestBody();
739 
740     this.apptCache.clearCache();
741     // Apply the changes locally
742     var calendarIds = [];
743     for (var i in this._calItemStatus) {
744         var info = this._calItemStatus[i];
745         if (info.item) {
746             var calendar = info.item;
747             calendarIds.push(calendar.id);
748             // Remote non-mountpoint has aleady been processed for local display - ignore it
749             if (!(calendar.isRemote() && (!calendar.isMountpoint || !calendar.zid))) {
750                 calendar.isChecked = info.checked;
751                 calendar.checkedCallback(info.checked);
752                 this._handleCheckedCalendarRefresh(calendar);
753             }
754         }
755     }
756      this._calItemStatus = {};
757 
758     // Store the request for playback
759     var jsonObjCopy = $.extend(true, {}, jsonObj);  //Always clone the object.  ?? Needed here ??
760     // This should be the only BatchCommand queued for calendars for replay (for the FolderAction, checked).
761     jsonObjCopy.methodName = "BatchRequest";
762     // Modify the id to thwart ZmOffline._handleResponseSendOfflineRequest, which sends a DELETE
763     // notification for the id - so a single calendar would get deleted in the UI
764     jsonObjCopy.id = "C" + calendarIds.join(":");
765     var value = {
766         update:          true,
767         methodName:      jsonObjCopy.methodName,
768         id:              jsonObjCopy.id,
769         value:           jsonObjCopy
770     };
771 
772     // No callback - we apply the check changes, whether or not the request goes into the queue successfully
773     ZmOfflineDB.setItemInRequestQueue(value);
774 };
775 
776 
777 ZmCalViewController.prototype._handleCheckedCalendarRefresh =
778 function(calendar){
779 	this._updateCheckedCalendars();
780 	this._refreshAction(true);
781 };
782 
783 ZmCalViewController.prototype._calTreeChangeListener =
784 function(ev) {
785 	if (ev.event == ZmEvent.E_DELETE) {
786 		this._updateCheckedCalendars();
787 	}
788     if (ev.event == ZmEvent.E_MODIFY) {
789         var details = ev.getDetails(),
790             fields = details ? details.fields : null;
791 
792         if (fields && (fields[ZmOrganizer.F_COLOR] || fields[ZmOrganizer.F_RGB])) {
793             this._refreshAction(true);
794         }
795 	}
796 };
797 
798 ZmCalViewController.prototype.getApptCache =
799 function(){
800     return this.apptCache;
801 };
802     
803 /**
804  * Gets the calendar by folder id.
805  * 
806  * @param	{String}	folderId		the folder id
807  * @return	{ZmCalendar}	the calendar
808  */
809 ZmCalViewController.prototype.getCalendar =
810 function(folderId) {
811 	return appCtxt.getById(folderId);
812 };
813 
814 /**
815  * Gets the calendar for the specified account.
816  *
817  * @param   {Hash}      params              Param hash
818  * @param	{Boolean}	params.includeLinks	if <code>true</code>, include links
819  * @param   {Boolean}   params.onlyWritable if <code>true</code> only writable calendars
820  * @param	{ZmAccount}	params.account		the account
821  * @return	{Array}	an array of {ZmCalendar} objects
822  */
823 ZmCalViewController.prototype.getCalendars =
824 function(params) {
825 	params = params || {};
826 	var account = params.account;
827 	var includeLinks = params.includeLinks;
828 	var onlyWritable = params.onlyWritable;
829 	this._updateCheckedCalendars();
830 	var calendars = [];
831 	var organizers = appCtxt.getFolderTree(account).getByType(ZmOrganizer.CALENDAR);
832 	for (var i = 0; i < organizers.length; i++) {
833 		var organizer = organizers[i];
834 		if ((organizer.zid && !includeLinks) ||
835 			(appCtxt.multiAccounts &&
836 			 organizer.nId == ZmOrganizer.ID_CALENDAR &&
837 			 organizer.getAccount().isCalDavBased()))
838 		{
839 			continue;
840 		}
841 
842 		if (account && organizer.getAccount() != account) { continue; }
843 
844 		if (onlyWritable && organizer.isReadOnly()) continue;
845 
846 		calendars.push(organizer);
847 	}
848 	calendars.sort(ZmCalViewController.__BY_NAME);
849 	return calendars;
850 };
851 
852 ZmCalViewController.__BY_NAME =
853 function(a, b) {
854 	return a.name.localeCompare(b.name);
855 };
856 
857 // todo: change to currently "selected" calendar
858 ZmCalViewController.prototype.getDefaultCalendarFolderId =
859 function() {
860 	return ZmOrganizer.ID_CALENDAR;
861 };
862 
863 /**
864  * Gets the calendar color.
865  * 
866  * @param	{String}	folderId		the folder id
867  * @return	{String}	the color
868  */
869 ZmCalViewController.prototype.getCalendarColor =
870 function(folderId) {
871 	if (!folderId) { return ZmOrganizer.DEFAULT_COLOR[ZmOrganizer.CALENDAR]; }
872 	var cal = this.getCalendar(folderId);
873 	return cal ? cal.color : ZmOrganizer.DEFAULT_COLOR[ZmOrganizer.CALENDAR];
874 };
875 
876 ZmCalViewController.prototype._refreshButtonListener =
877 function(ev) {
878 	//Return if a search is already in progress
879 	if (this.searchInProgress) {
880 		return;
881 	}
882     this.setCurrentListView(null);
883     
884 	// bug fix #33830 - force sync for calendar refresh
885 	if (appCtxt.isOffline) {
886 		appCtxt.accountList.syncAll();
887 	}
888 
889 	var app = appCtxt.getApp(ZmApp.CALENDAR);
890 	// reset possibly set user query
891 	this._userQuery = null;
892 	if (app === appCtxt.getCurrentApp()) {
893 		var sc = appCtxt.getSearchController();
894 		sc.setSearchField("");
895 		sc.getSearchToolbar().blur();
896 	}
897 	app.currentSearch = null;
898 	app.currentQuery = null;
899 	this._refreshMaintenance = true;
900     this.searchInProgress = false;
901 	this._refreshAction(false);
902 
903 	var overview = appCtxt.getOverviewController().getOverview(app.getOverviewId());
904 	if (overview) {
905 		overview.clearSelection();
906 	}
907 
908 };
909 
910 // Move button has been pressed, show the dialog.
911 ZmCalViewController.prototype._apptMoveListener =
912 function(ev) {
913 	var items = this.getSelection();
914 	var divvied = (items.length > 1) ? this._divvyItems(items) : null;
915 
916 	if (divvied && divvied.readonly.length > 0) {
917 		var dlg = appCtxt.getMsgDialog();
918 		var list = [];
919 		if (divvied.normal.length > 0) {
920 			list = list.concat(divvied.normal);
921 		}
922 		if (divvied.recurring.length > 0) {
923 			list = list.concat(this._dedupeSeries(divvied.recurring));
924 		}
925 		var callback = (list.length > 0)
926 			? (new AjxCallback(this, this._moveListener, [ev, list])) : null;
927 		var listener = new AjxListener(this, this._handleReadonlyOk, [callback, dlg]);
928 		dlg.setButtonListener(DwtDialog.OK_BUTTON, listener);
929 		dlg.setMessage(ZmMsg.moveReadonly);
930 		dlg.popup();
931 	}
932 	else {
933 		this._moveListener(ev, this._dedupeSeries(items));
934 	}
935 };
936 
937 ZmCalViewController.prototype._isItemMovable =
938 function(item, isShiftKey, folder) {
939 	return (!isShiftKey && !folder.isReadOnly() && !appCtxt.isWebClientOffline());
940 };
941 
942 ZmCalViewController.prototype._moveCallback =
943 function(folder) {
944 	if (this.isMovingBetwAccounts(this._pendingActionData, folder.id)) {
945         var dlg = appCtxt.getMsgDialog();
946         dlg.setMessage(ZmMsg.orgChange, DwtMessageDialog.WARNING_STYLE);
947         dlg.popup();
948         return false;
949     }else if (this.isMovingBetwAcctsAsAttendee(this._pendingActionData, folder.id)) {
950 		var dlg = appCtxt.getMsgDialog();
951 		dlg.setMessage(ZmMsg.apptInviteOnly, DwtMessageDialog.WARNING_STYLE);
952 		dlg.popup();
953 	} else {
954 		this._doMove(this._pendingActionData, folder, null, false);
955 		this._clearDialog(appCtxt.getChooseFolderDialog());
956 		this._pendingActionData = null;
957 	}
958 };
959 
960 ZmCalViewController.prototype._changeOrgCallback =
961 function(dlg, folder) {
962 	dlg.popdown();
963 	ZmListController.prototype._moveCallback.call(this, folder);
964 };
965 
966 ZmCalViewController.prototype._doTag =
967 function(items, tag, doTag) {
968 
969 	var list = this._getTaggableItems(items);
970 
971 	if (doTag) {
972 		if (list.length > 0 && list.length == items.length) {
973 			// there are items to tag, and all are taggable
974 			ZmListController.prototype._doTag.call(this, list, tag, doTag);
975 		} else {
976 			var msg;
977 			var dlg = appCtxt.getMsgDialog();
978 			if (list.length > 0 && list.length < items.length) {
979 				// there are taggable and nontaggable items
980 				var listener = new AjxListener(this, this._handleDoTag, [dlg, list, tag, doTag]);
981 				dlg.setButtonListener(DwtDialog.OK_BUTTON, listener);
982 				msg = ZmMsg.tagReadonly;
983 			} else if (list.length == 0) {
984 				// no taggable items
985 				msg = ZmMsg.nothingToTag;
986 			}
987 			dlg.setMessage(msg);
988 			dlg.popup();
989 		}
990 	} else if (list.length > 0) {
991 		ZmListController.prototype._doTag.call(this, list, tag, doTag);
992 	}
993 };
994 
995 ZmCalViewController.prototype._doRemoveAllTags =
996 function(items) {
997 	var list = this._getTaggableItems(items);
998 	ZmListController.prototype._doRemoveAllTags.call(this, list);
999 };
1000 
1001 ZmCalViewController.prototype._handleDoTag =
1002 function(dlg, list, tag, doTag) {
1003 	dlg.popdown();
1004 	ZmListController.prototype._doTag.call(this, list, tag, doTag);
1005 };
1006 
1007 ZmCalViewController.prototype._getTaggableItems =
1008 function(items) {
1009 	var divvied = (items.length > 1) ? this._divvyItems(items) : null;
1010 
1011 	if (divvied && (divvied.readonly.length > 0 || divvied.shared.length > 0)) {
1012 		// get a list of items that are "taggable"
1013 		items = [];
1014 		for (var i in divvied) {
1015 			// we process read only appts b/c it can also mean any appt where
1016 			// i'm not the organizer but still resides in my local folder.
1017 			if (i == "shared") { continue; }
1018 
1019 			var list = divvied[i];
1020 			for (var j = 0; j < list.length; j++) {
1021 				var appt = list[j];
1022 				var calendar = appt.getFolder();
1023 				if (calendar && !calendar.isRemote()) {
1024 					items.push(appt);
1025 				}
1026 			}
1027 		}
1028 	}
1029 
1030 	return items;
1031 };
1032 
1033 ZmCalViewController.prototype._getToolBarOps =
1034 function() {
1035     var toolbarOptions = [
1036 		ZmOperation.DELETE, ZmOperation.SEP, ZmOperation.MOVE_MENU,
1037 		ZmOperation.TAG_MENU,
1038 		ZmOperation.SEP,
1039 		ZmOperation.PRINT_CALENDAR,
1040 		ZmOperation.SEP,
1041 		ZmOperation.TODAY,
1042         ZmOperation.FILLER,
1043         ZmOperation.DAY_VIEW,
1044         ZmOperation.WORK_WEEK_VIEW,
1045         ZmOperation.WEEK_VIEW,
1046 		ZmOperation.MONTH_VIEW,
1047         ZmOperation.CAL_LIST_VIEW
1048 	];
1049     if( appCtxt.get(ZmSetting.FREE_BUSY_VIEW_ENABLED) ){
1050         toolbarOptions.push(ZmOperation.FB_VIEW);
1051     }
1052     return toolbarOptions;
1053 };
1054 
1055 /* This method is called from ZmListController._setup. We control when this method is called in our
1056  * show method. We ensure it is only called once i.e the first time show is called
1057  */
1058 ZmCalViewController.prototype._initializeToolBar =
1059 function(viewId) {
1060 	if (this._toolbar[ZmId.VIEW_CAL]) { return; }
1061 
1062 	ZmListController.prototype._initializeToolBar.call(this, ZmId.VIEW_CAL_DAY);
1063 	var toolbar = this._toolbar[ZmId.VIEW_CAL_DAY];
1064 
1065 	// Set the other view toolbar entries to point to the Day view entry. Hack
1066 	// to fool the ZmListController into thinking there are multiple toolbars
1067     this._toolbar[ZmId.VIEW_CAL_FB] = this._toolbar[ZmId.VIEW_CAL_WEEK] =
1068 	this._toolbar[ZmId.VIEW_CAL_WORK_WEEK] = this._toolbar[ZmId.VIEW_CAL_MONTH] =
1069 	this._toolbar[ZmId.VIEW_CAL_LIST] = this._toolbar[ZmId.VIEW_CAL_DAY];
1070 
1071 	this._toolbar[ZmId.VIEW_CAL] = toolbar;
1072 
1073 	// Setup the toolbar stuff
1074 	toolbar.enable([ZmOperation.PAGE_BACK, ZmOperation.PAGE_FORWARD], true);
1075 	toolbar.enable([ZmOperation.WEEK_VIEW, ZmOperation.MONTH_VIEW, ZmOperation.DAY_VIEW], true);
1076 
1077 	// We have style sheets in place to position the navigation toolbar at the
1078 	// center of the main toolbar. The filler is usually at that location, so
1079 	// to ensure that the semantic order matches the visual order as close as
1080 	// possible, we position the navigation toolbar after it.
1081 	var pos = AjxUtil.indexOf(this._getToolBarOps(), ZmOperation.FILLER) + 1;
1082 
1083 	var tb = new ZmNavToolBar({
1084 		parent: toolbar,
1085 		index: pos,
1086 		className: "ZmNavToolbar ZmCalendarNavToolbar",
1087 		context: ZmId.VIEW_CAL,
1088 		posStyle: Dwt.ABSOLUTE_STYLE
1089 	});
1090 	this._setNavToolBar(tb, ZmId.VIEW_CAL);
1091 
1092 	var printButton = toolbar.getButton(ZmOperation.PRINT_CALENDAR);
1093 	if (printButton) {
1094 		printButton.setToolTipContent(ZmMsg.printCalendar);
1095 	}
1096 
1097 	toolbar.getButton(ZmOperation.DELETE).setToolTipContent(ZmMsg.hardDeleteTooltip);
1098 
1099 	appCtxt.notifyZimlets("initializeToolbar", [this._app, toolbar, this, viewId], {waitUntilLoaded:true});
1100 };
1101 
1102 ZmCalViewController.prototype._initializeTabGroup = function(viewId) {
1103 	if (this._tabGroups[viewId]) {
1104 		return;
1105 	}
1106 
1107 	ZmListController.prototype._initializeTabGroup.apply(this, arguments);
1108 
1109 	// this is kind of horrible, but since list views don't normally have
1110 	// children, we can't do this the right way by having a tab group in
1111 	// ZmCalListView
1112 	if (viewId === ZmId.VIEW_CAL_LIST) {
1113 		var view = this._view[viewId];
1114 		var topTabGroup = view._getSearchBarTabGroup();
1115 
1116 		this._tabGroups[viewId].addMemberBefore(topTabGroup, view);
1117 	}
1118 }
1119 
1120 ZmCalViewController.prototype.runRefresh =
1121 function() {
1122 	this._refreshButtonListener();
1123 };
1124 
1125 ZmCalViewController.prototype._setView =
1126 function(params) {
1127 	if (!this.isSearchResults) {
1128 		ZmListController.prototype._setView.apply(this, arguments);
1129 	}
1130 };
1131 
1132 ZmCalViewController.prototype._setViewContents =
1133 function(viewId) {
1134 	// Ignore since this will always be ZmId.VIEW_CAL as we are fooling
1135 	// ZmListController (see our show method)
1136 };
1137 
1138 ZmCalViewController.prototype._getTagMenuMsg =
1139 function(num) {
1140 	return AjxMessageFormat.format(ZmMsg.tagAppts, num);
1141 };
1142 
1143 ZmCalViewController.prototype._createNewView =
1144 function(viewId) {
1145 	return this._viewMgr.createView(viewId);
1146 };
1147 
1148 // Switch to selected view.
1149 ZmCalViewController.prototype._viewActionMenuItemListener =
1150 function(ev) {
1151 	Dwt.setLoadingTime("ZmCalItemView");
1152 	if (appCtxt.multiAccounts) {
1153 		this.apptCache.clearCache();
1154 	}
1155 	var id = ev.item.getData(ZmOperation.KEY_ID);
1156     var viewType = ZmCalViewController.OP_TO_VIEW[id];
1157     //Check if the view is current calendar view
1158     if (this.getCurrentViewType() === viewType) {
1159         return;
1160     }
1161 	this.show(viewType);
1162 };
1163 
1164 // Switch to selected view.
1165 ZmCalViewController.prototype._viewMenuItemListener =
1166 function(ev) {
1167 
1168 };
1169 
1170 /**
1171  * Creates the mini-calendar widget that sits below the overview.
1172  * 
1173  * @param {Date}		date		the date to highlight (defaults to today)
1174  * @private
1175  */
1176 ZmCalViewController.prototype._createMiniCalendar =
1177 function(date) {
1178 	var calMgr = appCtxt.getCalManager();
1179 	if (calMgr._miniCalendar == null) {
1180 		calMgr._createMiniCalendar(date);
1181 		this._miniCalendar = calMgr.getMiniCalendar();
1182 	} else {
1183 		this._miniCalendar = calMgr.getMiniCalendar();
1184 		if (date != null) {
1185 			this._miniCalendar.setDate(date, true);
1186 		}
1187 	}
1188 	this._minicalMenu = calMgr._miniCalMenu;
1189 	this._miniCalDropTarget = calMgr._miniCalDropTarget;
1190 };
1191 
1192 ZmCalViewController.prototype.recreateMiniCalendar =
1193 function() {
1194 	var calMgr = appCtxt.getCalManager();
1195 	if (calMgr._miniCalendar != null) {
1196 		var mc = calMgr.getMiniCalendar();
1197 		var el = mc.getHtmlElement();
1198 		var date = mc.getDate();
1199 		if(el) {
1200 			el.parentNode.removeChild(el);
1201 		}
1202 		this._miniCalendar = null;
1203 		calMgr._miniCalendar = null;
1204 		calMgr._createMiniCalendar(date);
1205 		this._miniCalendar = calMgr.getMiniCalendar();
1206 		this._createMiniCalendar();
1207 	}
1208 };
1209 
1210 ZmCalViewController.prototype._miniCalDropTargetListener =
1211 function(ev) {
1212 
1213 	if (appCtxt.isWebClientOffline()) return;
1214 
1215 	var data = ((ev.srcData.data instanceof Array) && ev.srcData.data.length == 1)
1216 	? ev.srcData.data[0] : ev.srcData.data;
1217 
1218 	// use shiftKey to create new Tasks if enabled. NOTE: does not support Contacts yet
1219 	var shiftKey = appCtxt.get(ZmSetting.TASKS_ENABLED) && ev.uiEvent.shiftKey;
1220 
1221 	if (ev.action == DwtDropEvent.DRAG_ENTER) {
1222 		// Hack: in some instances ZmContact is reported as being an Array of
1223 		//       length 1 (as well as a ZmContact) under FF1.5
1224 		if (data instanceof Array && data.length > 1) {
1225 			var foundValid = false;
1226 			for (var i = 0; i < data.length; i++) {
1227 				if (!shiftKey && (data[i] instanceof ZmContact)) {
1228 					if (data[i].isGroup() && data[i].getGroupMembers().good.size() > 0) {
1229 						foundValid = true;
1230 					} else {
1231 						var email = data[i].getEmail();
1232 						if (email && email != "")
1233 							foundValid = true;
1234 					}
1235 				} else {
1236 					// theres other stuff besides contacts in here.. bail
1237 					ev.doIt = false;
1238 					return;
1239 				}
1240 			}
1241 
1242 			// if not a single valid email was found in list of contacts, bail
1243 			if (!foundValid) {
1244 				ev.doIt = false;
1245 				return;
1246 			}
1247 		} else {
1248 			if (!this._miniCalDropTarget.isValidTarget(data)) {
1249 				ev.doIt = false;
1250 				return;
1251 			}
1252 
1253 			// If dealing with a contact, make sure it has a valid email address
1254 			if (!shiftKey && data.isZmContact) {
1255 				if (data.isGroup() && !data.isDistributionList()) {
1256 					ev.doIt = (data.getGroupMembers().good.size() > 0);
1257 				} else {
1258 					var email = data.getEmail();
1259 					ev.doIt = (email != null && email != "");
1260 				}
1261 			}
1262 		}
1263 	} else if (ev.action == DwtDropEvent.DRAG_DROP) {
1264 		var dropDate = this._miniCalendar.getDndDate();
1265 
1266 		if (dropDate) {
1267 			// bug fix #5088 - reset time to next available slot
1268 			var now = new Date();
1269 			dropDate.setHours(now.getHours());
1270 			dropDate.setMinutes(now.getMinutes());
1271 			dropDate = AjxDateUtil.roundTimeMins(dropDate, 30);
1272 
1273 			if ((data instanceof ZmContact) ||
1274 				((data instanceof Array) && data[0] instanceof ZmContact))
1275 			{
1276 				this.newApptFromContact(data, dropDate);
1277 			}
1278 			else
1279 			{
1280 				if (shiftKey) {
1281 					AjxDispatcher.require(["TasksCore", "Tasks"]);
1282 					appCtxt.getApp(ZmApp.TASKS).newTaskFromMailItem(data, dropDate);
1283 				} else {
1284 					this.newApptFromMailItem(data, dropDate);
1285 				}
1286 			}
1287 		}
1288 	}
1289 };
1290 
1291 /**
1292  * This method will create a new appointment from a conversation/mail message. In the case
1293  * of a conversation, the appointment will be populated by the first message in the
1294  * conversation (or matched msg in the case of a search). This method is asynchronous and
1295  * will return immediately if the mail message must load in the background.
1296  *
1297  * @param {ZmConv|ZmMailMsg}	mailItem the may either be a conversation of message
1298  * @param {Date}	date 	the date/time for the appointment
1299  */
1300 ZmCalViewController.prototype.newApptFromMailItem =
1301 function(mailItem, date) {
1302 	var subject = mailItem.subject || "";
1303 	if (mailItem instanceof ZmConv) {
1304 		mailItem = mailItem.getFirstHotMsg();
1305 	}
1306 	mailItem.load({getHtml:false, markRead: true, forceLoad: true, noTruncate: true,
1307 	               callback:new AjxCallback(this, this._msgLoadedCallback, [mailItem, date, subject])});
1308 };
1309 
1310 ZmCalViewController.prototype._msgLoadedCallback =
1311 function(mailItem, date, subject) {
1312 	var newAppt = this._newApptObject(date, null, null, mailItem);
1313 	newAppt.setFromMailMessage(mailItem, subject);
1314 	
1315 	 if (appCtxt.get(ZmSetting.GROUP_CALENDAR_ENABLED)) {
1316         var addAttendeeDlg = this._attAttendeeDlg = appCtxt.getYesNoMsgDialog();
1317         addAttendeeDlg.reset();
1318         addAttendeeDlg.setMessage(ZmMsg.addRecipientstoAppt, DwtMessageDialog.WARNING_STYLE, ZmMsg.addAttendees);
1319         addAttendeeDlg.registerCallback(DwtDialog.YES_BUTTON, this._addAttendeeYesCallback, this, [newAppt]);
1320         addAttendeeDlg.registerCallback(DwtDialog.NO_BUTTON, this._addAttendeeNoCallback, this, [newAppt]);
1321         addAttendeeDlg.popup();
1322     }
1323     else {
1324         this.newAppointment(newAppt, ZmCalItem.MODE_NEW, true);
1325     }
1326 };
1327 
1328 ZmCalViewController.prototype._addAttendeeYesCallback =
1329 function(newAppt) {
1330     this._attAttendeeDlg.popdown();
1331     this.newAppointment(newAppt, ZmCalItem.MODE_NEW, true);
1332 };
1333 
1334 ZmCalViewController.prototype._addAttendeeNoCallback =
1335 function(newAppt) {
1336     this._attAttendeeDlg.popdown();
1337     newAppt.setAttendees(null,ZmCalBaseItem.PERSON);
1338     this.newAppointment(newAppt, ZmCalItem.MODE_NEW, true);
1339 };
1340 
1341 
1342 /**
1343  * This method will create a new appointment from a contact.
1344  *
1345  * @param {ZmContact}		contact the contact
1346  * @param {Date}	date 	the date/time for the appointment
1347  */
1348 ZmCalViewController.prototype.newApptFromContact =
1349 function(contact, date) {
1350 	var emails = [];
1351 	var list = AjxUtil.toArray(contact);
1352 	for (var i = 0; i < list.length; i++) {
1353 		var item = list[i];
1354 		if (item.isGroup() && !item.isDistributionList()) {
1355 			var members = item.getGroupMembers().good.getArray();
1356 			for (var j = 0; j < members.length; j++) {
1357 				var e = members[j].address;
1358 				if (e && e !== "") {
1359 					emails.push(e);
1360 				}
1361 			}
1362 		}
1363 		else {
1364 			// grab the first valid email address for this contact
1365 			var e = item.getEmail();
1366 			if (e && e !== "") {
1367 				emails.push(e);
1368 			}
1369 		}
1370 	}
1371 
1372 	if (emails.length > 0) {
1373 		var newAppt = this._newApptObject(date);
1374 		newAppt.setAttendees(emails, ZmCalBaseItem.PERSON);
1375 		this.newAppointment(newAppt, ZmCalItem.MODE_NEW);
1376 	}
1377 };
1378 
1379 /**
1380  * This method will create a new appointment from an email address.
1381  *
1382  * @param {String}	emailAddr	the email address
1383  * @param {Date}	date 	the date/time for the appointment
1384  */
1385 ZmCalViewController.prototype.newApptFromEmailAddr =
1386 function(emailAddr, date) {
1387 	if (!emailAddr || emailAddr == "") {return; }
1388 
1389 	var newAppt = this._newApptObject(date);
1390 	newAppt.setAttendees(emailAddr, ZmCalBaseItem.PERSON);
1391 	this.newAppointment(newAppt, ZmCalItem.MODE_NEW);
1392 };
1393 
1394 ZmCalViewController.prototype.getMiniCalendar =
1395 function(delay) {
1396 	if (!this._miniCalendar) {
1397 		this._createMiniCalendar(null, delay);
1398 	}
1399 	return this._miniCalendar;
1400 };
1401 
1402 ZmCalViewController.prototype._todayButtonListener =
1403 function(ev) {
1404     this.setCurrentListView(null);
1405 	this.setDate(new Date(), 0, false);
1406 };
1407 
1408 ZmCalViewController.prototype._newApptAction =
1409 function(ev) {
1410 	var d = this._minicalMenu ? this._minicalMenu.__detail : null;
1411 
1412 	if (d != null) {
1413 		delete this._minicalMenu.__detail;
1414 	} else {
1415 		d = this._viewMgr ? this._viewMgr.getDate() : null;
1416 	}
1417 
1418 	// Bug 15686, eshum
1419 	// Uses the selected timeslot if possible.
1420 	var curr = this._viewVisible ? this._viewMgr.getDate() : new Date(); //new Date();
1421 	if (d == null) {
1422 		d = curr;
1423 	} else {
1424 		// bug fix #4693 - set the current time since it will be init'd to midnite
1425 		d.setHours(curr.getHours());
1426 		d.setMinutes(curr.getMinutes());
1427 	}
1428 	//bug:44423, Action Menu needs to select appropriate Calendar
1429 	var calendarId = null;
1430 	if (this._viewActionMenu && this._viewActionMenu._calendarId) {
1431 		calendarId = this._viewActionMenu._calendarId;
1432 		this._viewActionMenu._calendarId = null;
1433 	}
1434 
1435 	var loadCallback = new AjxCallback(this, this._handleLoadNewApptAction, [d, calendarId]);
1436 	AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar"], false, loadCallback, null, true);
1437 };
1438 
1439 ZmCalViewController.prototype._handleLoadNewApptAction =
1440 function(d, calendarId) {
1441 	appCtxt.getAppViewMgr().popView(true, ZmId.VIEW_LOADING);	// pop "Loading..." page
1442 	this.newAppointmentHelper(d, null, calendarId);
1443 };
1444 
1445 ZmCalViewController.prototype._searchMailAction =
1446 function(ev) {
1447 	var d = this._minicalMenu ? this._minicalMenu.__detail : null;
1448 	if (d != null) {
1449 		delete this._minicalMenu.__detail;
1450 		appCtxt.getSearchController().dateSearch(d, ZmId.SEARCH_MAIL);
1451 	}
1452 };
1453 
1454 ZmCalViewController.prototype._newAllDayApptAction =
1455 function(ev) {
1456 	var d = this._minicalMenu ? this._minicalMenu.__detail : null;
1457 	if (d != null) delete this._minicalMenu.__detail;
1458 	else d = this._viewMgr ? this._viewMgr.getDate() : null;
1459 	if (d == null) d = new Date();
1460 
1461 	//bug:44423, Action Menu needs to select appropriate Calendar
1462 	var calendarId = null;
1463 	if (this._viewActionMenu && this._viewActionMenu._calendarId) {
1464 		calendarId = this._viewActionMenu._calendarId;
1465 		this._viewActionMenu._calendarId = null;
1466 	}
1467 
1468 	var loadCallback = new AjxCallback(this, this._handleLoadNewAllDayApptAction, [d, calendarId]);
1469 	AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar"], false, loadCallback, null, true);
1470 };
1471 
1472 ZmCalViewController.prototype._handleLoadNewAllDayApptAction =
1473 function(d, calendarId) {
1474 	appCtxt.getAppViewMgr().popView(true, ZmId.VIEW_LOADING);	// pop "Loading..." page
1475 	this.newAllDayAppointmentHelper(d, null, calendarId);
1476 };
1477 
1478 ZmCalViewController.prototype._postShowCallback =
1479 function() {
1480 	ZmController.prototype._postShowCallback.call(this);
1481 	this._viewVisible = true;
1482 	if (this._viewMgr.needsRefresh()) {
1483 		this._scheduleMaintenance(ZmCalViewController.MAINT_MINICAL|ZmCalViewController.MAINT_VIEW);
1484 	}
1485     //this._app.setOverviewPanelContent();
1486 };
1487 
1488 ZmCalViewController.prototype._postHideCallback =
1489 function() {
1490     if (appCtxt.multiAccounts) {
1491         var ovc = this._app.getOverviewContainer();
1492         var overviews = ovc.getOverviews();
1493         var overview;
1494         for (var ov in overviews) {
1495             ovc.getOverview(ov).zShow(false);
1496         }
1497     } else {
1498         overview =  this._app.getOverview();
1499         overview.zShow(false);
1500     }
1501     this._viewVisible = false;
1502 };
1503 
1504 ZmCalViewController.prototype.isCurrent =
1505 function() {
1506     var currentView = this._viewMgr && this._viewMgr.getCurrentViewName();
1507     return (this._currentViewId === currentView);
1508 };
1509 
1510 ZmCalViewController.prototype._paginate =
1511 function(viewId, forward) {
1512     this.setCurrentListView(null);
1513 	var view = this._listView[viewId];
1514 	var field = view.getRollField();
1515 	var d = new Date(this._viewMgr.getDate());
1516 	d = AjxDateUtil.roll(d, field, forward ? 1 : -1);
1517 	this.setDate(d, 0, true);
1518     this._viewMgr.getView(viewId).checkIndicatorNeed(viewId,d);
1519 };
1520 
1521 /**
1522  * Sets the date.
1523  * 
1524  * @param	{Date}		date		the date
1525  * @param	{int}		duration	the duration
1526  * @param	{Boolean}	roll		if <code>true</code>, roll
1527  */
1528 ZmCalViewController.prototype.setDate =
1529 function(date, duration, roll) {
1530 	AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar"]);
1531 	// set mini-cal first so it will cache appts we might need
1532 	if (this._miniCalendar.getDate() == null ||
1533 		this._miniCalendar.getDate().getTime() != date.getTime())
1534 	{
1535 		this._miniCalendar.setDate(date, true, roll);
1536 	}
1537 	if (this._viewMgr != null) {
1538 		this._viewMgr.setDate(date, duration, roll);
1539 		var viewId = this._viewMgr.getCurrentViewName();
1540 		var currentView = this._viewMgr.getCurrentView();
1541 		var title = currentView.getCalTitle();
1542 		Dwt.setTitle([ZmMsg.zimbraTitle, ": ", title].join(""));
1543 		if (!roll &&
1544 			this._currentViewType == ZmId.VIEW_CAL_WORK_WEEK &&
1545 			!currentView.workingHours[date.getDay()].isWorkingDay &&
1546 			(date.getDay() == 0 || date.getDay() == 6))
1547 		{
1548 			this.show(ZmId.VIEW_CAL_WEEK);
1549 		}
1550 		if (ZmId.VIEW_CAL_MONTH == this._currentViewType) {
1551 			title = this._viewMgr.getCurrentView().getShortCalTitle();
1552 		}
1553         if (ZmId.VIEW_CAL_FB == this._currentViewType && roll && appCtxt.get(ZmSetting.FREE_BUSY_VIEW_ENABLED)) {
1554             currentView._navDateChangeListener(date);
1555 		}
1556 		this._navToolBar[ZmId.VIEW_CAL].setText(title);
1557 	}
1558 };
1559 
1560 ZmCalViewController.prototype._dateSelectionListener =
1561 function(ev) {
1562 	this.setDate(ev.detail, 0, ev.force);
1563 };
1564 
1565 ZmCalViewController.prototype._miniCalSelectionListener =
1566 function(ev) {
1567 	if (ev.item instanceof DwtCalendar) {
1568 		var loadCallback = new AjxCallback(this, this._handleLoadMiniCalSelection, [ev]);
1569 		AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar"], false, loadCallback, null, true);
1570 	}
1571 };
1572 
1573 ZmCalViewController.prototype._handleLoadMiniCalSelection =
1574 function(ev) {
1575 	this.setDate(ev.detail, 0, ev.item.getForceRollOver());
1576 	if (!this._viewVisible) {
1577 		this.show();
1578 	}
1579 };
1580 
1581 ZmCalViewController.prototype.newApptObject =
1582 function(startDate, duration, folderId, mailItem) {
1583 	return this._newApptObject(startDate, duration, folderId, mailItem);
1584 };
1585 
1586 ZmCalViewController.prototype._newApptObject =
1587 function(startDate, duration, folderId, mailItem) {
1588 	var newAppt = new ZmAppt();
1589 	newAppt.setStartDate(AjxDateUtil.roundTimeMins(startDate, 30));
1590 	newAppt.setEndDate(newAppt.getStartTime() + (duration ? duration : ZmCalViewController.DEFAULT_APPOINTMENT_DURATION));
1591 	newAppt.resetRepeatWeeklyDays();
1592 	newAppt.resetRepeatMonthlyDayList();
1593 	newAppt.resetRepeatYearlyMonthsList(startDate.getMonth()+1);
1594 	newAppt.resetRepeatCustomDayOfWeek();
1595     var defaultWarningTime = appCtxt.get(ZmSetting.CAL_REMINDER_WARNING_TIME);
1596 	if (defaultWarningTime) {
1597 		newAppt.setReminderMinutes(defaultWarningTime);
1598 	}
1599 
1600 	if (folderId) {
1601 		newAppt.setFolderId(folderId);
1602 	} else {
1603 		// get folderId from mail message if being created off of one
1604 		if (appCtxt.multiAccounts && mailItem) {
1605 			newAppt.setFolderId(mailItem.getAccount().getDefaultCalendar().id);
1606 		} else {
1607 			// bug: 27646 case where only one calendar is checked
1608 			var checkedFolderIds = this.getCheckedCalendarFolderIds();
1609 			if (checkedFolderIds && checkedFolderIds.length == 1) {
1610 				var calId = checkedFolderIds[0];
1611 				var cal = appCtxt.getById(calId);
1612 				// don't use calendar if feed, or remote and don't have write perms
1613 				if (cal) {
1614 					var share = cal.getMainShare();
1615 					var skipCal = (cal.isFeed() || (cal.link && share && !share.isWrite()));
1616 					if (cal && !skipCal) {
1617 						newAppt.setFolderId(calId);
1618 					}
1619 				}
1620 			} else if (appCtxt.multiAccounts) {
1621 				// calendar app has no notion of "active" app, so always set to default calendar
1622 				this.defaultAccount = appCtxt.isFamilyMbox ? this.mainAccount : this.visibleAccounts[1];
1623 				newAppt.setFolderId(calId);
1624 			}
1625 		}
1626 	}
1627 	newAppt.setPrivacy((appCtxt.get(ZmSetting.CAL_APPT_VISIBILITY) == ZmSetting.CAL_VISIBILITY_PRIV)?"PRI" :"PUB");
1628 	return newAppt;
1629 };
1630 
1631 ZmCalViewController.prototype._timeSelectionListener =
1632 function(ev) {
1633     if (!this._app.containsWritableFolder()) {
1634         return;
1635     }
1636 	var view = this._viewMgr.getCurrentView();
1637 	if (view.getSelectedItems().size() > 0) {
1638 		view.deselectAll();
1639 		this._resetOperations(this._toolbar[ZmId.VIEW_CAL_DAY], 0);
1640 	}
1641 	this.setDate(ev.detail, 0, ev.force);
1642 
1643 	// popup the edit dialog
1644 	if (ev._isDblClick){
1645 		this._apptFromView = view;
1646 		var appt = this._newApptObject(ev.detail);
1647 		appt.setAllDayEvent(ev.isAllDay);
1648 		if (ev.folderId) {
1649 			appt.setFolderId(ev.folderId);
1650 		}
1651 		this._showQuickAddDialog(appt, ev.shiftKey);
1652 	}
1653 };
1654 
1655 ZmCalViewController.prototype._printCalendarListener =
1656 function(ev) {
1657 	var url,
1658 	    viewId = this._viewMgr.getCurrentViewName(),
1659         printDialog = this._printDialog,
1660         wHrs = ZmCalBaseView.parseWorkingHours(ZmCalBaseView.getWorkingHours()),
1661         curDate = this._viewMgr.getDate() || new Date();
1662 
1663     if(!printDialog) {
1664         printDialog = this.createPrintDialog();
1665     }
1666 
1667     var org = ZmApp.ORGANIZER[this._app._name] || ZmOrganizer.FOLDER;
1668     var params = {
1669                 overviewId: appCtxt.getOverviewId(["ZmCalPrintDialog", this._app._name], null),
1670                 treeIds: [org],
1671                 treeStyle: DwtTree.CHECKEDITEM_STYLE,
1672                 appName: this._app._name,
1673                 currentViewId: viewId,
1674                 workHours: wHrs[curDate.getDay()],
1675                 currentDate: curDate,
1676                 timeRange: this.getViewMgr().getView(viewId).getTimeRange()
1677             };
1678 
1679     printDialog.popup(params);
1680 
1681     this._printDialog = printDialog;
1682     /*
1683 	if (viewId == ZmId.VIEW_CAL_LIST)
1684 	{
1685 		var ids = [];
1686 		var list = this.getSelection();
1687 		for (var i = 0; i < list.length; i++) {
1688 			ids.push(list[i].invId);
1689 		}
1690         url = ["/h/printappointments?id=", ids.join(','), "&tz=", AjxTimezone.getServerId(AjxTimezone.DEFAULT)];
1691         if(appCtxt.isOffline) {
1692             if (ids.length == 1) {
1693                 var appt = this.getSelection()[0];
1694                 url.push("&acct=", appt.getFolder().getAccount().name);
1695             }
1696             url.push("&zd=", "true");
1697         }
1698         url = url.join("");
1699     } else {
1700 		var date = this._viewMgr
1701 			? this._viewMgr.getDate()
1702 			: (new Date());
1703 
1704 		var day = (date.getDate() < 10)
1705 			? ('0' + date.getDate())
1706 			: date.getDate();
1707 
1708 		var month = date.getMonth() + 1;
1709 		if (month < 10) {
1710 			month = '0' + month;
1711 		}
1712 
1713 		var view;
1714 		switch (viewId) {
1715 			case ZmId.VIEW_CAL_DAY: 		view = "day"; break;
1716 			case ZmId.VIEW_CAL_WORK_WEEK:	view = "workWeek"; break;
1717 			case ZmId.VIEW_CAL_WEEK:		view = "week"; break;
1718 			case ZmId.VIEW_CAL_SCHEDULE:	view = "schedule"; break;
1719 			default:						view = "month"; break;				// default is month
1720 		}
1721 
1722 		var folderIds = this.getCheckedCalendarFolderIds();
1723 		var l = folderIds.join(",");
1724 
1725 		url = [
1726 			"/h/printcalendar?view=", view,
1727 			"&l=", l,
1728 			"&date=", date.getFullYear(), month, day,
1729 			"&tz=",AjxTimezone.getServerId(AjxTimezone.DEFAULT)
1730 		].join("");
1731 	} */
1732 
1733 	//window.open(appContextPath+url, "_blank");
1734 };
1735 
1736 ZmCalViewController.prototype.createPrintDialog =
1737 function() {
1738     var pd,
1739         params = {},
1740         curDate = this._viewMgr.getDate() || new Date();
1741 
1742     //params.calendars = this.getCalTreeController().getOwnedCalendars(this._app.getOverviewId(), appCtxt.getActiveAccount().getEmail());
1743     params.parent = this._shell;
1744     pd = new ZmCalPrintDialog(params);
1745     return pd;
1746 };
1747 
1748 ZmCalViewController.prototype._printListener =
1749 function(ev) {
1750     var ids = [];
1751     var list = this.getSelection();
1752     if (list.length == 0) {
1753         // Calendar list view is anomalous - on a right click (action menu) when resetOperations
1754         // is called, getSelection returns 1 item, but the selection actually only occurs if it was a
1755         // left click.  So we can get here with no selections.  Use the appt associated with the menu.
1756         var actionMenu = this.getActionMenu();
1757         var appt = actionMenu.__appt;
1758         if (appt) {
1759             ids.push(appt.invId);
1760         }
1761         // bug:68735 if no selection is made in the calendar, open up the print dialog
1762         else{
1763             this._printCalendarListener(ev);
1764         }
1765 
1766     }
1767     else {
1768         for (var i = 0; i < list.length; i++) {
1769             ids.push(list[i].invId);
1770         }
1771     }
1772     if (ids.length == 0) return;
1773 
1774     var url = ["/h/printappointments?id=", ids.join(','), "&tz=", AjxTimezone.getServerId(AjxTimezone.DEFAULT)];
1775     if(appCtxt.isOffline) {
1776         if (ids.length == 1) {
1777             var appt = this.getSelection()[0];
1778             url.push("&acct=", appt.getFolder().getAccount().name);
1779         }
1780         url.push("&zd=", "true");
1781     }
1782     url = url.join("");
1783 
1784     window.open(appContextPath+url, "_blank");
1785 };
1786 
1787 ZmCalViewController.prototype._deleteListener =
1788 function(ev) {
1789 	var op = (ev && ev.item instanceof DwtMenuItem)
1790 		? ev.item.parent.getData(ZmOperation.KEY_ID) : null;
1791 	this._doDelete(this.getSelection(), null, null, op);
1792 };
1793 
1794 ZmCalViewController.prototype._replyListener =
1795 function(ev) {
1796 	var op = (ev && ev.item instanceof DwtMenuItem)
1797 		? ev.item.parent.getData(ZmOperation.KEY_ID) : null;
1798 	var items = this.getSelection();
1799 	if (items && items.length)
1800 		this._replyAppointment(items[0], false);
1801 };
1802 
1803 ZmCalViewController.prototype._replyAllListener =
1804 function(ev) {
1805 	var op = (ev && ev.item instanceof DwtMenuItem)
1806 		? ev.item.parent.getData(ZmOperation.KEY_ID) : null;
1807 	var items = this.getSelection();
1808 	if (items && items.length)
1809 		this._replyAppointment(items[0], true);
1810 };
1811 
1812 ZmCalViewController.prototype._forwardListener =
1813 function(ev) {
1814 	var op = (ev && ev.item instanceof DwtMenuItem)
1815 		? ev.item.parent.getData(ZmOperation.KEY_ID) : null;
1816 	this._doForward(this.getSelection(), op);
1817 };
1818 
1819 ZmCalViewController.prototype._duplicateApptListener =
1820 function(ev) {
1821 	var op = (ev && ev.item instanceof DwtMenuItem)
1822 		? ev.item.parent.getData(ZmOperation.KEY_ID) : null;
1823 	var items = this.getSelection();
1824 	var appt = items[0];
1825 	var isException = (appt.isRecurring() && op == ZmOperation.VIEW_APPT_INSTANCE);
1826 	this.duplicateAppt(appt, {isException: isException});
1827 };
1828 
1829 ZmCalViewController.prototype.duplicateAppt =
1830 function(appt, params) {
1831 	Dwt.setLoadingTime("ZmCalendarApp-cloneAppt");
1832 	var clone = ZmAppt.quickClone(appt);
1833 	var mode = ZmCalItem.MODE_EDIT;
1834 	if (appt.isRecurring()) {
1835 		mode = params.isException ? ZmCalItem.MODE_COPY_SINGLE_INSTANCE : ZmCalItem.MODE_EDIT_SERIES;  //at first I also created a MODE_COPY_SERIES but I'm afraid of the impact and regressions. So keep it as "edit".
1836 	}
1837 	clone.getDetails(mode, new AjxCallback(this, this._duplicateApptContinue, [clone, ZmCalItem.MODE_NEW, params]));
1838 	Dwt.setLoadedTime("ZmCalendarApp-cloneAppt");
1839 };
1840 
1841 ZmCalViewController.prototype._duplicateApptContinue =
1842 function(appt, mode, params) {
1843 	if(params.isException) appt.clearRecurrence();
1844 	if(params.startDate) appt.setStartDate(params.startDate);
1845 	if(params.endDate) appt.setEndDate(params.endDate);
1846 
1847     if (!appt.isOrganizer() || appt.isReadOnly()) {  //isOrganizer means current user is the organizer of the appt. (which is not the case if the appt is on a shared folder, even if the current user has admin or manager rights (i.e. not read only)
1848           var origOrganizer=appt.organizer;
1849           var myEmail=appt.getFolder().getAccount().getEmail();
1850           appt.replaceAttendee(myEmail,origOrganizer);
1851           appt.organizer=myEmail;
1852           appt.isOrg=true;
1853           if(appt.isShared()) {
1854             appt.isSharedCopy = true;
1855 			if (!appt.getFolderId()) { //not sure why the following line is done, but if the appt is of a certain folder, it should be kept that folder in the copy.
1856             	appt.setFolderId(ZmOrganizer.ID_CALENDAR);
1857 			}
1858           }
1859           var dlg = appCtxt.getMsgDialog();
1860 		  var callback = new AjxCallback(this, this.newAppointment,[appt,mode,true]);
1861 		  var listener = new AjxListener(this, this._handleReadonlyOk, [callback, dlg]);
1862 		  dlg.setButtonListener(DwtDialog.OK_BUTTON, listener);
1863 		  dlg.setMessage(ZmMsg.confirmApptDuplication);
1864 		  dlg.popup();
1865     }
1866     else {
1867 		appt.organizer = null; // Let the organizer be set according to the choise in the form. (the calendar).
1868 	    this.newAppointment(appt, mode, true);
1869     }
1870 };
1871 
1872 ZmCalViewController.prototype._proposeTimeListener =
1873 function(ev) {
1874 	var op = ev.item.parent.getData(ZmOperation.KEY_ID);
1875 
1876 	var items = this.getSelection();
1877 
1878 	// listview cannot handle proposing time for multiple items at once
1879 	if (this._viewMgr.getCurrentViewName() == ZmId.VIEW_CAL_LIST && items.length > 1) {
1880 		return;
1881 	}
1882 
1883 	var mode = ZmCalItem.MODE_EDIT;
1884 	if (op == ZmOperation.VIEW_APPT_INSTANCE || op == ZmOperation.VIEW_APPT_SERIES) {
1885 		mode = (op == ZmOperation.VIEW_APPT_INSTANCE)
1886 			? ZmCalItem.MODE_EDIT_SINGLE_INSTANCE
1887 			: ZmCalItem.MODE_EDIT_SERIES;
1888 	}
1889 
1890 	var appt = items[0];
1891 	var clone = ZmAppt.quickClone(appt);
1892 	clone.setProposeTimeMode(true);
1893 	clone.getDetails(mode, new AjxCallback(this, this._proposeTimeContinue, [clone, mode]));
1894 };
1895 
1896 ZmCalViewController.prototype._proposeTimeContinue =
1897 function(appt, mode) {
1898 	appt.setViewMode(mode);
1899 	AjxDispatcher.run("GetApptComposeController").proposeNewTime(appt);
1900 };
1901 
1902 ZmCalViewController.prototype._reinviteAttendeesListener =
1903 function(ev) {
1904     var items = this.getSelection();
1905     // listview cannot handle reinvite for multiple items at once
1906     if (this._viewMgr.getCurrentViewName() == ZmId.VIEW_CAL_LIST && items.length > 1) {
1907         return;
1908     }
1909     // Get the details.  Otherwise, on send, any missing information will be deleted server side
1910     var detailsSuccessCallback = this._sendAppt.bind(this, items[0]);
1911     items[0].getDetails(null, detailsSuccessCallback, this._errorCallback);
1912 };
1913 
1914 ZmCalViewController.prototype._sendAppt =
1915 function(appt) {
1916     var mode = appt.viewMode;
1917     appt.viewMode =  ZmCalItem.MODE_EDIT;
1918 
1919     var callback = new AjxCallback(this, this._handleResponseSave, appt);
1920     var errorCallback = new AjxCallback(this, this._handleErrorSave, appt);
1921     appt.send(null, callback, errorCallback);
1922 
1923     appt.viewMode = mode;
1924 };
1925 
1926 ZmCalViewController.prototype._handleResponseSave =
1927 function(response) {
1928     appCtxt.setStatusMsg(ZmMsg.apptSent);
1929 };
1930 
1931 ZmCalViewController.prototype._handleErrorSave =
1932 function(calItem, ex) {
1933     var status = calItem.processErrorSave(ex);
1934     var handled = false;
1935 
1936     if (status.continueSave) {
1937         this._sendAppt(calItem);
1938         handled = true;
1939     } else {
1940         if (status.errorMessage) {
1941             // Handled the error, display the error message
1942             handled = true;
1943             var dialog = appCtxt.getMsgDialog();
1944             dialog.setMessage(status.errorMessage, DwtMessageDialog.CRITICAL_STYLE);
1945             dialog.popup();
1946         }
1947         appCtxt.notifyZimlets("onSaveApptFailure", [this, calItem, ex]);
1948     }
1949     return handled;
1950 };
1951 
1952 ZmCalViewController.prototype._doForward =
1953 function(items, op) {
1954 	// listview cannot handle forwarding multiple items at once
1955 	if (this._viewMgr.getCurrentViewName() == ZmId.VIEW_CAL_LIST && items.length > 1) {
1956 		return;
1957 	}
1958 
1959 	// since base view has multiple selection turned off, always select first item
1960 	var appt = items[0];
1961 	var mode = ZmCalItem.MODE_FORWARD;
1962 	if (op == ZmOperation.VIEW_APPT_INSTANCE || op == ZmOperation.VIEW_APPT_SERIES) {
1963 		mode = (op == ZmOperation.VIEW_APPT_INSTANCE)
1964 			? ZmCalItem.MODE_FORWARD_SINGLE_INSTANCE
1965 			: ZmCalItem.MODE_FORWARD_SERIES;
1966 	}
1967 	this._forwardAppointment(appt, mode);
1968 };
1969 
1970 /**
1971  * Override the ZmListController method.
1972  * 
1973  * @private
1974  */
1975 ZmCalViewController.prototype._doDelete =
1976 function(items, hardDelete, attrs, op) {
1977 
1978     var isTrash = false;
1979 
1980 	// listview can handle deleting multiple items at once
1981     if(items.length>0){
1982         var calendar = items[0].getFolder();
1983         isTrash = calendar && calendar.nId==ZmOrganizer.ID_TRASH;
1984     }
1985 
1986 	if ((this._viewMgr.getCurrentViewName() == ZmId.VIEW_CAL_LIST || isTrash) && items.length > 1) {
1987 		var divvied = this._divvyItems(items);
1988 
1989 		// first attempt to deal with read-only appointments
1990 		if (divvied.readonly.length > 0) {
1991 			var dlg = appCtxt.getMsgDialog();
1992 			var callback = (divvied.recurring.length > 0)
1993 				? this._showTypeDialog.bind(this, [divvied.recurring, ZmCalItem.MODE_DELETE])
1994 				: null;
1995 			var listener = new AjxListener(this, this._handleReadonlyOk, [callback, dlg]);
1996 			dlg.setButtonListener(DwtDialog.OK_BUTTON, listener);
1997 			dlg.setMessage(ZmMsg.deleteReadonly);
1998 			dlg.popup();
1999 		}
2000 		else if (divvied.recurring.length > 0) {
2001 			this._showTypeDialog(divvied.recurring, ZmCalItem.MODE_DELETE);
2002 		}
2003 		else {
2004 			this._promptDeleteApptList(divvied.normal);
2005 		}
2006 	}
2007 	else {
2008 		// since base view has multiple selection turned off, always select first item
2009 		var appt = items[0];
2010 		if (op == ZmOperation.VIEW_APPT_INSTANCE || op == ZmOperation.VIEW_APPT_SERIES) {
2011 			var mode = (op == ZmOperation.VIEW_APPT_INSTANCE)
2012 				? ZmCalItem.MODE_DELETE_INSTANCE
2013 				: ZmCalItem.MODE_DELETE_SERIES;
2014 			this._promptDeleteAppt(appt, mode);
2015 		} else {
2016 			this._deleteAppointment(appt);
2017 		}
2018 	}
2019 };
2020 
2021 ZmCalViewController.prototype._handleReadonlyOk =
2022 function(callback, dlg) {
2023 	dlg.popdown();
2024 	if (callback) {
2025 		callback.run();
2026 	}
2027 };
2028 
2029 ZmCalViewController.prototype._handleMultiDelete =
2030 function(deleteList, mode) {
2031 	var batchCmd = new ZmBatchCommand(true, null, true);
2032 
2033 	// first, get details for each appointment
2034 	for (var i = 0; i < deleteList.length; i++) {
2035 		var appt = deleteList[i];
2036 		var args = [mode, null, null, null, null];
2037 		batchCmd.add(new AjxCallback(appt, appt.getDetails, args));
2038 	}
2039 	batchCmd.run(this._handleGetDetails.bind(this, deleteList, mode));
2040 };
2041 
2042 ZmCalViewController.prototype._handleGetDetails =
2043 function(deleteList, mode) {
2044 	var batchCmd = new ZmBatchCommand(true, null, true);
2045 	for (var i = 0; i < deleteList.length; i++) {
2046 		var appt = deleteList[i];
2047 		var args = [mode, null, null, null];
2048 		batchCmd.add(new AjxCallback(appt, appt.cancel, args));
2049 	}
2050 	batchCmd.run();
2051 	var summary = ZmList.getActionSummary({
2052 		actionTextKey:  'actionDelete',
2053 		numItems:       batchCmd.size(),
2054 		type:           ZmItem.APPT
2055 	});
2056 	appCtxt.setStatusMsg(summary);
2057 	appCtxt.notifyZimlets("onAppointmentDelete", deleteList);//notify Zimlets on delete
2058 };
2059 
2060 ZmCalViewController.prototype.getSelection =
2061 function() {
2062     return ZmListController.prototype.getSelection.call(this, this.getCurrentListView());
2063 };
2064 
2065 ZmCalViewController.prototype.getSelectionCount = function() {
2066     return ZmListController.prototype.getSelectionCount.call(this, this.getCurrentListView());
2067 };
2068 
2069 ZmCalViewController.prototype._divvyItems =
2070 function(items) {
2071 
2072 	var normal = [];
2073 	var readonly = [];
2074 	var recurring = [];
2075 	var shared = [];
2076 
2077 	for (var i = 0; i < items.length; i++) {
2078 		var appt = items[i];
2079 		if (appt.type != ZmItem.APPT) { continue; }
2080 
2081 		if (appt.isFolderReadOnly()) {
2082 			readonly.push(appt);
2083 		}
2084 		else if (appt.isRecurring() && !appt.isException) {
2085 			recurring.push(appt);
2086 		}
2087 		else {
2088 			normal.push(appt);
2089 		}
2090 
2091 		// keep a separate list of shared items. This means "recurring" and
2092 		// "normal" can contain shared items as well.
2093 		var calendar = appt.getFolder();
2094 		if (calendar && calendar.isRemote()) {
2095 			shared.push(appt);
2096 		}
2097 	}
2098 
2099 	return {normal:normal, readonly:readonly, recurring:recurring, shared:shared};
2100 };
2101 
2102 ZmCalViewController.prototype._promptDeleteApptList =
2103 function(deleteList) {
2104 	if (deleteList.length === 0) {
2105 		return;
2106 	}
2107 	var calendar = deleteList[0].getFolder();
2108 	var isTrash = calendar && calendar.nId == ZmOrganizer.ID_TRASH;
2109 	var msg = (isTrash) ? ZmMsg.confirmCancelApptListPermanently : ZmMsg.confirmCancelApptList;
2110 	var callback = this._handleMultiDelete.bind(this, deleteList, ZmCalItem.MODE_DELETE);
2111 	appCtxt.getConfirmationDialog().popup(msg, callback);
2112 };
2113 
2114 ZmCalViewController.prototype._promptDeleteAppt =
2115 function(appt, mode) {
2116     if(!appt){
2117         return;
2118     }
2119 	if (appt instanceof Array) {
2120 		this._continueDelete(appt, mode);
2121 	} else {
2122 		if (appt.isOrganizer()) {
2123 			if (mode == ZmCalItem.MODE_DELETE_SERIES) {
2124 				this._promptDeleteFutureInstances(appt, mode);
2125 			} else {
2126 				this._promptCancelReply(appt, mode);
2127 			}
2128 		} else {
2129 			this._promptDeleteNotify(appt, mode);
2130 		}
2131 	}
2132 };
2133 ZmCalViewController.prototype._confirmDeleteApptDialog =
2134 function(){
2135 
2136     if (!ZmCalViewController._confirmDialog) {
2137         var editMessageButton =new DwtDialog_ButtonDescriptor(DwtDialog.YES_BUTTON, ZmMsg.editMessage , DwtDialog.ALIGN_LEFT);
2138         var buttons = [ DwtDialog.NO_BUTTON, DwtDialog.CANCEL_BUTTON ];
2139         var extraButtons = [editMessageButton];
2140         ZmCalViewController._confirmDialog = new DwtConfirmDialog(this._shell, null, "CNF_DEL_SENDEDIT", buttons, extraButtons);
2141         var noButton = ZmCalViewController._confirmDialog.getButton(DwtDialog.NO_BUTTON);
2142         noButton.setText(ZmMsg.sendCancellation); // Changing the text for No button
2143 	}
2144 	return ZmCalViewController._confirmDialog;
2145 };
2146 
2147 ZmCalViewController.prototype._promptCancelReply =
2148 function(appt, mode) {
2149 	var cancelNoReplyCallback = new AjxCallback(this, this._continueDelete, [appt, mode]);
2150     var confirmDialog;
2151     var calendar = appt && appt.getFolder();
2152     var isTrash = calendar && calendar.nId==ZmOrganizer.ID_TRASH;
2153 
2154     // Display traditional Yes/No Dialog if
2155     // - If appt has no attendees
2156     // - appt is from trash
2157     // - appt is saved but not sent
2158 	if (!isTrash && appt.otherAttendees && !appt.inviteNeverSent && appCtxt.get(ZmSetting.MAIL_ENABLED)) {
2159         confirmDialog = this._confirmDeleteApptDialog();
2160         var cancelReplyCallback = new AjxCallback(this, this._continueDeleteReply, [appt, mode]);
2161 
2162         confirmDialog.setTitle(ZmMsg.confirmDeleteApptTitle);
2163 		confirmDialog.popup(ZmMsg.confirmCancelApptReply, cancelReplyCallback, cancelNoReplyCallback);
2164 	} else {
2165         confirmDialog = appCtxt.getConfirmationDialog("CNF_DEL_YESNO");
2166 		var msg = isTrash ? ZmMsg.confirmPermanentCancelAppt : ZmMsg.confirmCancelAppt;
2167 
2168 		if (appt.isRecurring() && !appt.isException && !isTrash) {
2169 	    	msg = (mode == ZmCalItem.MODE_DELETE_INSTANCE) ? AjxMessageFormat.format(ZmMsg.confirmCancelApptInst, AjxStringUtil.htmlEncode(appt.name)) :  ZmMsg.confirmCancelApptSeries; 
2170 		}
2171         confirmDialog.setTitle(ZmMsg.confirmDeleteApptTitle);
2172 		confirmDialog.popup(msg, cancelNoReplyCallback);
2173 	}
2174 };
2175 
2176 ZmCalViewController.prototype._promptDeleteFutureInstances =
2177 function(appt, mode) {
2178 
2179     if (appt.getAttendees(ZmCalBaseItem.PERSON).length>0) {
2180         this._delFutureInstNotifyDlgWithAttendees = this._delFutureInstNotifyDlgWithAttendees ?
2181             this._delFutureInstNotifyDlgWithAttendees : (new ZmApptDeleteNotifyDialog({
2182 			    parent: this._shell,
2183 			    title: AjxMsg.confirmTitle,
2184 			    confirmMsg: ZmMsg.confirmCancelApptSeries,
2185 			    choiceLabel1: ZmMsg.confirmCancelApptWholeSeries,
2186 			    choiceLabel2 : ZmMsg.confirmCancelApptFutureInstances,
2187                 choice2WarningMsg : ZmMsg.deleteApptWarning
2188 		    }));
2189         this._delFutureInstNotifyDlg = this._delFutureInstNotifyDlgWithAttendees;
2190     }
2191     else{
2192             this._delFutureInstNotifyDlgWithoutAtteendees = this._delFutureInstNotifyDlgWithoutAtteendees ?
2193                 this._delFutureInstNotifyDlgWithoutAtteendees : (new ZmApptDeleteNotifyDialog({
2194 			        parent: this._shell,
2195 			        title: ZmMsg.confirmDeleteApptTitle,
2196 			        confirmMsg: ZmMsg.confirmCancelApptSeries,
2197 			        choiceLabel1: ZmMsg.confirmDeleteApptWholeSeries,
2198                     choiceLabel2 : ZmMsg.confirmDeleteApptFutureInstances,
2199                     choice2WarningMsg : ZmMsg.deleteApptWarning
2200 		        }));
2201         this._delFutureInstNotifyDlg = this._delFutureInstNotifyDlgWithoutAtteendees;
2202     }
2203 	this._delFutureInstNotifyDlg.popup(new AjxCallback(this, this._deleteFutureInstYesCallback, [appt,mode]));
2204 };
2205 
2206 ZmCalViewController.prototype._deleteFutureInstYesCallback =
2207 function(appt, mode) {
2208 	var deleteWholeSeries = this._delFutureInstNotifyDlg.isDefaultOptionChecked();
2209 	if (!deleteWholeSeries) {
2210 		appt.setCancelFutureInstances(true);
2211 	}
2212 
2213 	var cancelNoReplyCallback = new AjxCallback(this, this._continueDelete, [appt, mode]);
2214 
2215     var confirmDialog = this._confirmDeleteApptDialog();
2216 	if (appt.otherAttendees && !appt.inviteNeverSent && appCtxt.get(ZmSetting.MAIL_ENABLED)) {
2217 		var cancelReplyCallback = new AjxCallback(this, this._continueDeleteReply, [appt, mode]);
2218 		confirmDialog.popup(ZmMsg.sendCancellationConfirmation, cancelReplyCallback, cancelNoReplyCallback);
2219 	} else {
2220 		this._continueDelete(appt, mode);
2221 	}
2222 };
2223 
2224 ZmCalViewController.prototype._promptDeleteNotify =
2225 function(appt, mode) {
2226 	if (!this._deleteNotifyDialog) {
2227 		this._deleteNotifyDialog = new ZmApptDeleteNotifyDialog({
2228 			parent: this._shell,
2229 			title: AjxMsg.confirmTitle,
2230 			confirmMsg: "",
2231             choiceLabel1: ZmMsg.dontNotifyOrganizer,
2232 			choiceLabel2 : ZmMsg.notifyOrganizer
2233 		});
2234 	}
2235     if(this._deleteMode != mode){
2236         var msg = ZmMsg.confirmCancelAppt;
2237         if(appt.isRecurring()){
2238             msg = (mode == ZmCalItem.MODE_DELETE_INSTANCE)
2239     			? AjxMessageFormat.format(ZmMsg.confirmCancelApptInst, AjxStringUtil.htmlEncode(appt.name))
2240     			: ZmMsg.confirmCancelApptSeries;
2241         }
2242         var msgDiv = document.getElementById(this._deleteNotifyDialog._confirmMessageDivId);
2243         msgDiv.innerHTML = msg;
2244         this._deleteMode = mode;
2245     }
2246 	this._deleteNotifyDialog.popup(new AjxCallback(this, this._deleteNotifyYesCallback, [appt,mode]));
2247 };
2248 
2249 ZmCalViewController.prototype._deleteNotifyYesCallback =
2250 function(appt, mode) {
2251 	var notifyOrg = !this._deleteNotifyDialog.isDefaultOptionChecked();
2252 	if (notifyOrg) {
2253 		this._cancelBeforeDelete(appt, mode);
2254 	} else {
2255 		this._continueDelete(appt, mode);
2256 	}
2257 };
2258 
2259 ZmCalViewController.prototype._cancelBeforeDelete =
2260 function(appt, mode) {
2261 	var type = ZmOperation.REPLY_DECLINE;
2262 	var respCallback = new AjxCallback(this, this._cancelBeforeDeleteContinue, [appt, type, mode]);
2263 	appt.getDetails(null, respCallback, this._errorCallback);
2264 };
2265 
2266 ZmCalViewController.prototype._cancelBeforeDeleteContinue =
2267 function(appt, type, mode) {
2268 	var msgController = this._getMsgController();
2269 	msgController.setMsg(appt.message);
2270 	// poke the msgController
2271 	var instanceDate = mode == ZmCalItem.MODE_DELETE_INSTANCE ? new Date(appt.uniqStartTime) : null;
2272     var delContCallback = new AjxCallback(this, this._continueDelete, [appt, mode]);
2273 	msgController._sendInviteReply(type, appt.compNum || 0, instanceDate, appt.getRemoteFolderOwner(),false,null,null,delContCallback);
2274 };
2275 
2276 
2277 ZmCalViewController.prototype._deleteAppointment =
2278 function(appt) {
2279 	if (!appt) { return; }
2280 
2281     var calendar = appt.getFolder();
2282     var isTrash =  calendar && calendar.nId == ZmOrganizer.ID_TRASH;
2283 
2284 	if (appt.isRecurring() && !isTrash && !appt.isException) {
2285 		this._showTypeDialog(appt, ZmCalItem.MODE_DELETE);
2286 	} else {
2287 		this._promptDeleteAppt(appt, ZmCalItem.MODE_DELETE);
2288 	}
2289 };
2290 
2291 ZmCalViewController.prototype._continueDeleteReply =
2292 function(appt, mode) {
2293 	var action = ZmOperation.REPLY_CANCEL;
2294 	var respCallback = new AjxCallback(this, this._continueDeleteReplyRespondAction, [appt, action, mode]);
2295 	appt.getDetails(mode, respCallback, this._errorCallback);
2296 };
2297 
2298 ZmCalViewController.prototype._continueDeleteReplyRespondAction =
2299 function(appt, action, mode) {
2300 	var msgController = this._getMsgController();
2301 	var msg = appt.message;
2302 	msg._appt = appt;
2303 	msg._mode = mode;
2304 	msgController.setMsg(msg);
2305 	var instanceDate = mode == ZmCalItem.MODE_DELETE_INSTANCE ? new Date(appt.uniqStartTime) : null;
2306 	msg._instanceDate = instanceDate;
2307 	msgController._editInviteReply(action, 0, instanceDate);
2308 };
2309 
2310 ZmCalViewController.prototype._continueDelete =
2311 function(appt, mode) {
2312 	if (appt instanceof Array) {
2313 		// if list of appointments, de-dupe the same series appointments
2314 		var deleteList = (mode === ZmCalItem.MODE_DELETE_SERIES) ? this._dedupeSeries(appt) : appt;
2315 		this._handleMultiDelete(deleteList, mode);
2316 	}
2317 	else {
2318 		var respCallback = new AjxCallback(this, this._handleResponseContinueDelete, appt);
2319 		appt.cancel(mode, null, respCallback, this._errorCallback);
2320 	}
2321 };
2322 
2323 ZmCalViewController.prototype._handleResponseContinueDelete =
2324 function(appt) {
2325 
2326     var currentView = appCtxt.getCurrentView();
2327     if (currentView && currentView.isZmApptView) {
2328         currentView.close();
2329     }
2330 
2331 	var summary = ZmList.getActionSummary({
2332 		actionTextKey:  'actionDelete',
2333 		numItems:       1,
2334 		type:           ZmItem.APPT
2335 	});
2336 	appCtxt.setStatusMsg(summary);
2337 	appCtxt.notifyZimlets("onAppointmentDelete", [appt]);//notify Zimlets on delete 
2338 };
2339 
2340 /**
2341  * This method takes a list of recurring appointments and returns a list of
2342  * unique appointments (removes instances)
2343  *
2344  * @param list		[Array]		List of *recurring* appointments
2345  * 
2346  * @private
2347  */
2348 ZmCalViewController.prototype._dedupeSeries =
2349 function(list) {
2350 	var unique = [];
2351 	var deduped = {};
2352 	for (var i = 0; i < list.length; i++) {
2353 		var appt = list[i];
2354 		if (!deduped[appt.id]) {
2355 			deduped[appt.id] = true;
2356 			unique.push(appt);
2357 		}
2358 	}
2359 	return unique;
2360 };
2361 
2362 ZmCalViewController.prototype._getMoveParams =
2363 function(dlg) {
2364 	var params = ZmListController.prototype._getMoveParams.apply(this, arguments);
2365 	var omit = {};
2366 	var folderTree = appCtxt.getFolderTree();
2367 	if (!folderTree) { return params; }
2368 
2369 	var folders = folderTree.getByType(ZmOrganizer.CALENDAR);
2370 	for (var i = 0; i < folders.length; i++) {
2371 		var folder = folders[i];
2372 		if (folder.link && folder.isReadOnly()) {
2373 			omit[folder.id] = true;
2374 		}
2375 	}
2376 	params.omit = omit;
2377 	params.description = ZmMsg.targetCalendar;
2378 
2379 	return params;
2380 };
2381 
2382 ZmCalViewController.prototype._getMoveDialogTitle =
2383 function(num) {
2384 	return AjxMessageFormat.format(ZmMsg.moveAppts, num);
2385 };
2386 
2387 /**
2388  * Shows a dialog for handling recurring appointments. User must choose to
2389  * perform the action on the instance of the series of a recurring appointment.
2390  *
2391  * @param appt		[ZmAppt]	This can be a single appt object or a *list* of appts
2392  * @param mode		[Integer]	Constant describing what kind of appointments we're dealing with
2393  * 
2394  * @private
2395  */
2396 ZmCalViewController.prototype._showTypeDialog =
2397 function(appt, mode) {
2398 	if (this._typeDialog == null) {
2399 		AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar", "CalendarAppt"]);
2400 		this._typeDialog = new ZmCalItemTypeDialog({
2401 			id: 'CAL_ITEM_TYPE_DIALOG',
2402 			parent: this._shell
2403 		});
2404 		this._typeDialog.registerCallback(DwtDialog.OK_BUTTON, this._typeOkListener, this);
2405 		this._typeDialog.registerCallback(DwtDialog.CANCEL_BUTTON, this._typeCancelListener, this);
2406 	}
2407 	this._typeDialog.initialize(appt, mode, ZmItem.APPT);
2408 	this._typeDialog.popup();
2409 };
2410 
2411 ZmCalViewController.prototype.showApptReadOnlyView =
2412 function(appt, mode) {
2413 	var clone = ZmAppt.quickClone(appt);
2414 	clone.getDetails(mode, new AjxCallback(this, this._showApptReadOnlyView, [clone, mode]));
2415 };
2416 
2417 ZmCalViewController.prototype._showApptReadOnlyView =
2418 function(appt, mode) {
2419 	var controller = this._app.getApptViewController(this._apptSessionId[appt.invId]);
2420 	this._apptSessionId[appt.invId] = controller.getSessionId();
2421 	controller.show(appt, mode);
2422 };
2423 
2424 ZmCalViewController.prototype._showQuickAddDialog =
2425 function(appt, shiftKey) {
2426     if(appCtxt.isExternalAccount() || appCtxt.isWebClientOffline()) { return; }
2427 	// find out if we really should display the quick add dialog
2428 	var useQuickAdd = appCtxt.get(ZmSetting.CAL_USE_QUICK_ADD);
2429 	if ((useQuickAdd && !shiftKey) || (!useQuickAdd && shiftKey)) {
2430 		if (AjxTimezone.TIMEZONE_CONFLICT || AjxTimezone.DEFAULT_RULE.autoDetected) {
2431 			var timezonePicker = this.getTimezonePicker();
2432 			var callback = new AjxCallback(this, this.handleTimezoneSelectionQuickAdd, [appt, shiftKey]);
2433 			timezonePicker.setCallback(callback);
2434 			timezonePicker.popup();
2435 		} else {
2436 			this._showQuickAddDialogContinue(appt, shiftKey);
2437 		}
2438 	} else {
2439 		this.newAppointment(appt);
2440 	}
2441 };
2442 
2443 ZmCalViewController.prototype._showQuickAddDialogContinue =
2444 function(appt, shiftKey) {
2445 	if (this._quickAddDialog == null) {
2446 		AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar", "CalendarAppt"]);
2447 		this._quickAddDialog = new ZmApptQuickAddDialog(this._shell);
2448 		this._quickAddDialog.setButtonListener(DwtDialog.OK_BUTTON, new AjxListener(this, this._quickAddOkListener));
2449 		this._quickAddDialog.addSelectionListener(ZmApptQuickAddDialog.MORE_DETAILS_BUTTON, new AjxListener(this, this._quickAddMoreListener));
2450 	}
2451 	this._quickAddDialog.initialize(appt);
2452 	this._quickAddDialog.popup();
2453 };
2454 
2455 ZmCalViewController.prototype.newAppointmentHelper =
2456 function(startDate, optionalDuration, folderId, shiftKey) {
2457 	var appt = this._newApptObject(startDate, optionalDuration, folderId);
2458 	this._showQuickAddDialog(appt, shiftKey);
2459 };
2460 
2461 ZmCalViewController.prototype.newAllDayAppointmentHelper =
2462 function(startDate, endDate, folderId, shiftKey) {
2463 	var appt = this._newApptObject(startDate, null, folderId);
2464 	if (endDate) {
2465 		appt.setEndDate(endDate);
2466 	}
2467 	appt.setAllDayEvent(true);
2468 	appt.freeBusy = "F";
2469 	this._showQuickAddDialog(appt, shiftKey);
2470 };
2471 
2472 ZmCalViewController.prototype.newAppointment =
2473 function(newAppt, mode, isDirty, startDate) {
2474 	AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar"]);
2475 	var sd = startDate || (this._viewVisible ? this._viewMgr.getDate() : new Date());
2476 	var appt = newAppt || this._newApptObject(sd, (appCtxt.get(ZmSetting.CAL_DEFAULT_APPT_DURATION) * 1000));  //bug:50121 added appt duration as configurable from preference
2477 
2478     //certain views can set attendees before creating appointment
2479     if(this._viewVisible && this._viewMgr.getCurrentView().getAtttendees) {
2480         var attendees = this._viewMgr.getCurrentView().getAtttendees();
2481         if(attendees && attendees.length > 0) appt.setAttendees(attendees, ZmCalBaseItem.PERSON);
2482     }
2483 
2484     if (AjxTimezone.TIMEZONE_CONFLICT || AjxTimezone.DEFAULT_RULE.autoDetected) {
2485 		var timezonePicker = this.getTimezonePicker();
2486 		var callback = new AjxCallback(this, this.handleTimezoneSelection, [appt, mode, isDirty]);
2487 		timezonePicker.setCallback(callback);
2488 		timezonePicker.popup();
2489 	} else {
2490 		this._app.getApptComposeController().show(appt, mode, isDirty);
2491 	}
2492 };
2493 
2494 ZmCalViewController.prototype.handleTimezoneSelection =
2495 function(appt, mode, isDirty, serverId) {
2496 	this.updateTimezoneInfo(appt, serverId);
2497 	this._app.getApptComposeController().show(appt, mode, isDirty);
2498 };
2499 
2500 ZmCalViewController.prototype.handleTimezoneSelectionQuickAdd =
2501 function(appt, shiftKey, serverId) {
2502 	this.updateTimezoneInfo(appt, serverId);
2503 	this._showQuickAddDialogContinue(appt, shiftKey);
2504 };
2505 
2506 ZmCalViewController.prototype.updateTimezoneInfo =
2507 function(appt, serverId) {
2508 	appt.setTimezone(serverId);
2509 	appCtxt.set(ZmSetting.DEFAULT_TIMEZONE, serverId);
2510 	AjxTimezone.TIMEZONE_CONFLICT = false;
2511 	this.updateDefaultTimezone(serverId);
2512 	var settings = appCtxt.getSettings();
2513 	var tzSetting = settings.getSetting(ZmSetting.DEFAULT_TIMEZONE);
2514 	settings.save([tzSetting], new AjxCallback(this, this._timezoneSaveCallback));
2515 };
2516 
2517 ZmCalViewController.prototype.updateDefaultTimezone =
2518 function(serverId) {
2519 	for (var i =0; i < AjxTimezone.MATCHING_RULES.length; i++) {
2520 		if (AjxTimezone.MATCHING_RULES[i].serverId == serverId) {
2521 			AjxTimezone.DEFAULT_RULE = AjxTimezone.MATCHING_RULES[i];
2522 			AjxTimezone.DEFAULT = AjxTimezone.getClientId(AjxTimezone.DEFAULT_RULE.serverId);
2523 			break;
2524 		}
2525 	}
2526 };
2527 
2528 ZmCalViewController.prototype._timezoneSaveCallback =
2529 function() {
2530 	appCtxt.setStatusMsg(ZmMsg.timezonePrefSaved);
2531 };
2532 
2533 ZmCalViewController.prototype.getTimezonePicker =
2534 function() {
2535 	if (!this._timezonePicker) {
2536 		this._timezonePicker = appCtxt.getTimezonePickerDialog();
2537 	}
2538 	return this._timezonePicker;
2539 };
2540 
2541 /**
2542  * Edits the appointment.
2543  * 
2544  * @param	{ZmAppt}		appt		the appointment
2545  * @param	{constant}		mode		the mode
2546  */
2547 ZmCalViewController.prototype.editAppointment =
2548 function(appt, mode) {
2549 	Dwt.setLoadingTime("ZmCalendarApp-editAppt");
2550 	AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar"]);
2551 	if (mode != ZmCalItem.MODE_NEW) {
2552 		var clone = ZmAppt.quickClone(appt);
2553 		clone.getDetails(mode, new AjxCallback(this, this._showApptComposeView, [clone, mode]));
2554 	} else {
2555 		this._app.getApptComposeController().show(appt, mode);
2556 	}
2557 	Dwt.setLoadedTime("ZmCalendarApp-editAppt");
2558 };
2559 
2560 ZmCalViewController.prototype._replyAppointment =
2561 function(appt, all) {
2562 	Dwt.setLoadingTime("ZmCalendarApp-replyAppt");
2563 	AjxDispatcher.require(["MailCore", "Mail"]);
2564     var clone = ZmAppt.quickClone(appt);
2565 	var respCallback = new AjxCallback(this, this._replyDetailsHandler, [clone, all]);
2566 	clone.getDetails(null, respCallback, this._errorCallback, true, true);
2567 	Dwt.setLoadedTime("ZmCalendarApp-replyAppt");
2568 };
2569 
2570 ZmCalViewController.prototype._replyDetailsHandler =
2571 function(appt, all, result) {
2572 
2573 	var msg = new ZmMailMsg(-1, null, true);
2574 	var orig = appt.message;
2575 	if (orig) {
2576 		var inviteHtml = orig.getInviteDescriptionContent(ZmMimeTable.TEXT_HTML);
2577         if (inviteHtml) {
2578             var htmlContent = inviteHtml.getContent();
2579             htmlContent && msg.setInviteDescriptionContent(ZmMimeTable.TEXT_HTML, htmlContent);
2580         }
2581 
2582 		var inviteText = orig.getInviteDescriptionContent(ZmMimeTable.TEXT_PLAIN);
2583         if (inviteText) {
2584             var textContent = inviteText.getContent();
2585             textContent && msg.setInviteDescriptionContent(ZmMimeTable.TEXT_PLAIN, textContent);
2586         }
2587         if (htmlContent || textContent) {
2588             msg._loaded = true;
2589         }
2590 
2591 		msg.invite = orig.invite;
2592 		msg.id = orig.id;
2593         msg.date = orig.date;
2594 	}
2595 	msg.setSubject(appt.name);
2596 
2597 	var organizer = appt.getOrganizer();
2598 	var organizerAddress = AjxEmailAddress.parse(organizer);
2599 	var self = appCtxt.getActiveAccount().name;
2600 	msg.getHeaderStr = AjxCallback.returnFalse; // Real ugly hack to prevent headers from showing in the message
2601 
2602     var isOrganizer = appt.isOrganizer();
2603     var folder = appt.getFolder();
2604     var isRemote = folder ? folder.isRemote() : false;
2605 	if (!isOrganizer || isRemote) {
2606 		organizerAddress.setType(AjxEmailAddress.FROM);
2607 		msg.addAddress(organizerAddress);
2608 	}
2609 
2610 	if (all) {
2611 		var omit = AjxUtil.arrayAsHash([ self, organizerAddress.getAddress() ]),
2612 			attendees = appt.getAttendees(ZmCalBaseItem.PERSON);
2613 
2614 		this._addAttendeesToReply(attendees, msg, ZmCalItem.ROLE_REQUIRED, AjxEmailAddress.FROM, omit);
2615 		this._addAttendeesToReply(attendees, msg, ZmCalItem.ROLE_OPTIONAL, AjxEmailAddress.CC, omit);
2616 	}
2617 	
2618 	var data = {action: all ? ZmOperation.CAL_REPLY_ALL : ZmOperation.CAL_REPLY, msg: msg};
2619 	AjxDispatcher.run("GetComposeController").doAction(data);
2620 };
2621 
2622 ZmCalViewController.prototype._addAttendeesToReply = function(attendees, msg, role, addrType, omit) {
2623 
2624 	var attendeesByRole = ZmApptViewHelper.getAttendeesArrayByRole(attendees, role),
2625 		ln = attendeesByRole.length, i, addr;
2626 
2627 	for (i = 0; i < ln; i++) {
2628 		addr = attendeesByRole[i].getAttendeeText(ZmCalBaseItem.PERSON);
2629 		if (addr && !omit[addr]) {
2630 			var addrObj = new AjxEmailAddress(addr);
2631 			addrObj.setType(addrType);
2632 			msg.addAddress(addrObj);
2633 		}
2634 	}
2635 };
2636 
2637 ZmCalViewController.prototype._forwardAppointment =
2638 function(appt, mode) {
2639 	Dwt.setLoadingTime("ZmCalendarApp-fwdAppt");
2640 	AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar"]);
2641 	if (mode != ZmCalItem.MODE_NEW) {
2642 		var clone = ZmAppt.quickClone(appt);
2643 		clone.getDetails(mode, new AjxCallback(this, this._showApptForwardComposeView, [clone, mode]));
2644 	} else {
2645 		this._showApptForwardComposeView(appt, mode);
2646 	}
2647 	Dwt.setLoadedTime("ZmCalendarApp-fwdAppt");
2648 };
2649 
2650 ZmCalViewController.prototype._showAppointmentDetails =
2651 function(appt) {
2652 	// if we have an appointment, go get all the details.
2653 	if (!appt.__creating) {
2654 		var calendar = appt.getFolder();
2655 		var isSynced = Boolean(calendar.url);
2656 		if (appt.isRecurring()) {
2657 			// prompt user to edit instance vs. series if recurring but not for exception and from edit mode
2658 			if (appt.isException || appt.editViewMode) {
2659 				var mode = appt.editViewMode || ZmCalItem.MODE_EDIT_SINGLE_INSTANCE;
2660 				if (appt.isReadOnly() || calendar.isReadOnly() || isSynced || appCtxt.isWebClientOffline()) {
2661 					this.showApptReadOnlyView(appt, mode);
2662 				} else {
2663 					this.editAppointment(appt, mode);
2664 				}
2665 			} else {
2666 				this._showTypeDialog(appt, ZmCalItem.MODE_EDIT);
2667 			}
2668 		} else {
2669 			// if simple appointment, no prompting necessary
2670             var isTrash = calendar.nId == ZmOrganizer.ID_TRASH;
2671 			if (appt.isReadOnly() || calendar.isReadOnly() || isSynced || isTrash || appCtxt.isWebClientOffline()) {
2672                 var mode = appt.isRecurring() ? (appt.isException ? ZmCalItem.MODE_EDIT_SINGLE_INSTANCE : ZmCalItem.MODE_EDIT_SERIES) : ZmCalItem.MODE_EDIT;
2673 				this.showApptReadOnlyView(appt, mode);
2674 			} else {
2675 				this.editAppointment(appt, ZmCalItem.MODE_EDIT);
2676 			}
2677 		}
2678 	} else {
2679 		this.newAppointment(appt);
2680 	}
2681 };
2682 
2683 ZmCalViewController.prototype._typeOkListener =
2684 function(ev) {
2685 	if (this._typeDialog) {
2686 		this._typeDialog.popdown();
2687 	}
2688 
2689 	if (this._typeDialog.mode == ZmCalItem.MODE_DELETE)
2690 		this._promptDeleteAppt(this._typeDialog.calItem, this._typeDialog.isInstance() ? ZmCalItem.MODE_DELETE_INSTANCE : ZmCalItem.MODE_DELETE_SERIES);
2691 	else
2692 		this._performApptAction(this._typeDialog.calItem, this._typeDialog.mode, this._typeDialog.isInstance());
2693 };
2694 
2695 ZmCalViewController.prototype._performApptAction =
2696 function(appt, mode, isInstance) {
2697 	if (mode == ZmCalItem.MODE_DELETE) {
2698 		var delMode = isInstance ? ZmCalItem.MODE_DELETE_INSTANCE : ZmCalItem.MODE_DELETE_SERIES;
2699 		if (appt instanceof Array) {
2700 			this._continueDelete(appt, delMode);
2701 		} else {
2702 			if (appt.isOrganizer()) {
2703 				this._continueDelete(appt, delMode);
2704 			} else {
2705 				this._promptDeleteNotify(appt, delMode);
2706 			}
2707 		}
2708 	}
2709 	else if (mode == ZmAppt.MODE_DRAG_OR_SASH) {
2710 		var viewMode = isInstance ? ZmCalItem.MODE_EDIT_SINGLE_INSTANCE : ZmCalItem.MODE_EDIT_SERIES;
2711 		var state = this._updateApptDateState;
2712 		var args = [state.appt, viewMode, state.startDateOffset, state.endDateOffset, state.callback, state.errorCallback];
2713 		var respCallback = new AjxCallback(this, this._handleResponseUpdateApptDate, args);
2714 		delete this._updateApptDateState;
2715 		appt.getDetails(viewMode, respCallback, state.errorCallback);
2716 	}
2717 	else {
2718 		var editMode = isInstance ? ZmCalItem.MODE_EDIT_SINGLE_INSTANCE : ZmCalItem.MODE_EDIT_SERIES;
2719 		var calendar = appt.getFolder();
2720 		var isSynced = Boolean(calendar.url);
2721 
2722 		if (appt.isReadOnly() || calendar.isReadOnly() || isSynced || appCtxt.isWebClientOffline()) {
2723 			this.showApptReadOnlyView(appt, editMode);
2724 		} else {
2725 			this.editAppointment(appt, editMode);
2726 		}
2727 	}
2728 };
2729 
2730 ZmCalViewController.prototype._typeCancelListener =
2731 function(ev) {
2732 	if (this._typeDialog.mode == ZmAppt.MODE_DRAG_OR_SASH) {
2733 		// we cancel the drag/sash, refresh view
2734 		this._refreshAction(true);
2735 	}
2736 
2737 	this._typeDialog.popdown();
2738 };
2739 
2740 ZmCalViewController.prototype._quickAddOkListener =
2741 function(ev) {
2742     var isValid = this._quickAddDialog.isValid();
2743     var appt = this._quickAddDialog.getAppt();
2744     var closeCallback = this._quickAddCallback.bind(this, true);
2745     var errorCallback = this._quickAddCallback.bind(this, false);
2746 
2747     var locations = appt.getAttendees(ZmCalBaseItem.LOCATION);
2748     // Send if any locations attendees are specified.  This will send invites
2749     var action = (locations.length == 0) ? ZmCalItemComposeController.SAVE_CLOSE :
2750                                            ZmCalItemComposeController.SEND;
2751     this._saveSimpleAppt(isValid, appt, action, closeCallback, errorCallback);
2752 }
2753 
2754 ZmCalViewController.prototype._saveSimpleAppt =
2755 function(isValid, appt, action, closeCallback, errorCallback, cancelCallback) {
2756 	try {
2757 		if (isValid && appt) {
2758             if (appt.getFolder() && appt.getFolder().noSuchFolder) {
2759                 throw AjxMessageFormat.format(ZmMsg.errorInvalidFolder, appt.getFolder().name);
2760             }
2761             if (!this._simpleComposeController) {
2762                 // Create a compose controller, used for saving the quick add
2763                 // appt and modifications made via ZmCalColView drag and drop, in
2764                 // order to trigger permission and resource checks.
2765                 this._simpleComposeController = this._app.getSimpleApptComposeController();
2766             }
2767             this._simpleComposeController.doSimpleSave(appt, action, closeCallback,
2768                                                        errorCallback, cancelCallback);
2769         }
2770 	} catch(ex) {
2771 		if (typeof ex == "string") {
2772 			var errorDialog = new DwtMessageDialog({parent:this._shell});
2773 			var msg = ex ? AjxMessageFormat.format(ZmMsg.errorSavingWithMessage, ex) : ZmMsg.errorSaving;
2774 			errorDialog.setMessage(msg, DwtMessageDialog.CRITICAL_STYLE);
2775 			errorDialog.popup();
2776 		}else{
2777             ZmController.handleScriptError(ex);
2778         }
2779 	}
2780 };
2781 
2782 ZmCalViewController.prototype._quickAddCallback =
2783 function(success) {
2784     if (success) {
2785         this._quickAddDialog.popdown();
2786     }
2787     appCtxt.setStatusMsg(success ? ZmMsg.apptCreated : ZmMsg.apptCreationError);
2788 };
2789 
2790 ZmCalViewController.prototype._quickAddMoreListener =
2791 function(ev) {
2792 	var appt = this._quickAddDialog.getAppt();
2793 	if (appt) {
2794 		this._quickAddDialog.popdown();
2795 		this.newAppointment(appt, ZmCalItem.MODE_NEW_FROM_QUICKADD, this._quickAddDialog.isDirty());
2796 	}
2797 };
2798 
2799 ZmCalViewController.prototype._showApptComposeView =
2800 function(appt, mode) {
2801 	this._app.getApptComposeController().show(appt, mode);
2802 };
2803 
2804 ZmCalViewController.prototype._showApptForwardComposeView =
2805 function(appt, mode) {
2806 	/*if(!appt.isOrganizer()) { */
2807 	appt.name = ZmMsg.fwd + ": " + appt.name;
2808 	//}
2809 	this._app.getApptComposeController().show(appt, mode);
2810 };
2811 
2812 /**
2813  * appt - appt to change
2814  * startDate - new date or null to leave alone
2815  * endDate - new or null to leave alone
2816  * changeSeries - if recurring, change the whole series
2817  *
2818  * TODO: change this to work with _handleException, and take callback so view can
2819  *       restore appt location/size on failure
2820  *       
2821  * @private
2822  */
2823 ZmCalViewController.prototype.dndUpdateApptDate =
2824 function(appt, startDateOffset, endDateOffset, callback, errorCallback, ev) {	
2825 	appt.dndUpdate = true;
2826 	if (!appt.isRecurring()) {
2827 		var viewMode = ZmCalItem.MODE_EDIT;
2828 		var respCallback = new AjxCallback(this, this._handleResponseUpdateApptDate, [appt, viewMode, startDateOffset, endDateOffset, callback, errorCallback]);
2829 		appt.getDetails(viewMode, respCallback, errorCallback);
2830 	}
2831 	else {
2832 		if (ev.shiftKey || ev.altKey) {
2833 			var viewMode = ev.altKey ? ZmCalItem.MODE_EDIT_SERIES : ZmCalItem.MODE_EDIT_SINGLE_INSTANCE;
2834 			var respCallback = new AjxCallback(this, this._handleResponseUpdateApptDate, [appt, viewMode, startDateOffset, endDateOffset, callback, errorCallback]);
2835 			appt.getDetails(viewMode, respCallback, errorCallback);
2836 		}
2837 		else {
2838 			this._updateApptDateState = {appt:appt, startDateOffset: startDateOffset, endDateOffset: endDateOffset, callback: callback, errorCallback: errorCallback };
2839 			if (appt.isException) {
2840 				this._performApptAction(appt, ZmAppt.MODE_DRAG_OR_SASH, true);
2841 			} else {
2842 				this._showTypeDialog(appt, ZmAppt.MODE_DRAG_OR_SASH);
2843 			}
2844 		}
2845 	}
2846 	appCtxt.notifyZimlets("onApptDrop", [appt, startDateOffset, endDateOffset]);		
2847 };
2848 
2849 ZmCalViewController.prototype._handleResponseUpdateApptDate =
2850 function(appt, viewMode, startDateOffset, endDateOffset, callback, errorCallback, result) {
2851 	// skip prompt if no attendees
2852 	if (appt.inviteNeverSent || !appt.otherAttendees) {
2853 		this._handleResponseUpdateApptDateSave.apply(this, arguments);
2854 		return;
2855 	}
2856 
2857 	// NOTE: We copy the arguments into an array because arguments
2858 	//       is *not* technically an array. So if anyone along the
2859 	//       line considers it such it will blow up -- this prevents
2860 	//       that at the expense of having to keep this array and
2861 	//       the actual argument list in sync.
2862 	var args = [appt, viewMode, startDateOffset, endDateOffset, callback, errorCallback, result];
2863 	var edit = new AjxCallback(this, this._handleResponseUpdateApptDateEdit, args);
2864 	var save = new AjxCallback(this, this._handleResponseUpdateApptDateSave, args);
2865 	var ignore = new AjxCallback(this, this._handleResponseUpdateApptDateIgnore, args);
2866 
2867 	var dialog = appCtxt.getConfirmationDialog();
2868 	dialog.popup(ZmMsg.confirmModifyApptReply, edit, save, ignore);
2869 };
2870 
2871 ZmCalViewController.prototype._handleResponseUpdateApptDateEdit =
2872 function(appt, viewMode, startDateOffset, endDateOffset, callback, errorCallback, result) {
2873 	var clone = ZmAppt.quickClone(appt);
2874     clone.editViewMode = viewMode;
2875 	if (startDateOffset) clone.setStartDate(new Date(clone.getStartTime() + startDateOffset));
2876 	if (endDateOffset) clone.setEndDate(new Date(clone.getEndTime() + endDateOffset));
2877 	this._showAppointmentDetails(clone);
2878 };
2879 ZmCalViewController.prototype._handleResponseUpdateApptDateEdit2 =
2880 function(appt, action, mode, startDateOffset, endDateOffset) {
2881 	if (startDateOffset) appt.setStartDate(new Date(appt.getStartTime() + startDateOffset));
2882 	if (endDateOffset) appt.setEndDate(new Date(appt.getEndTime() + endDateOffset));
2883 	this._continueDeleteReplyRespondAction(appt, action, mode);
2884 };
2885 
2886 ZmCalViewController.prototype._handleResponseUpdateApptDateSave =
2887 function(appt, viewMode, startDateOffset, endDateOffset, callback, errorCallback, result) {
2888          var isExceptionAllowed = appCtxt.get(ZmSetting.CAL_EXCEPTION_ON_SERIES_TIME_CHANGE);
2889          var showWarning = appt.isRecurring() && appt.hasEx && appt.getAttendees(ZmCalBaseItem.PERSON) && !isExceptionAllowed && viewMode==ZmCalItem.MODE_EDIT_SERIES;
2890          if(showWarning){
2891             var respCallback = new AjxCallback(this, this._handleResponseUpdateApptDateSaveContinue, [appt, viewMode, startDateOffset, endDateOffset, callback, errorCallback, result]);
2892             this._showExceptionWarning(respCallback);
2893          }
2894          else{
2895              this._handleResponseUpdateApptDateSaveContinue(appt, viewMode, startDateOffset, endDateOffset, callback, errorCallback, result);
2896          }
2897 };
2898 
2899 ZmCalViewController.prototype._handleResponseUpdateApptDateSaveContinue =
2900 function(appt, viewMode, startDateOffset, endDateOffset, callback, errorCallback, result) {
2901     try {
2902         // NOTE: If the appt was already populated (perhaps by
2903 		//       dragging it once, canceling the change, and then
2904 		//       dragging it again), then the result will be null.
2905 		if (result) {
2906 			result.getResponse();
2907 		}
2908 		var apptStartDate = appt.startDate;
2909 		var apptEndDate   = appt.endDate;
2910 	    var apptDuration = appt.getDuration();
2911 
2912 		appt.setViewMode(viewMode);
2913 		if (startDateOffset) {
2914 			var sd = (viewMode == ZmCalItem.MODE_EDIT_SINGLE_INSTANCE) ? appt.getUniqueStartDate() : new Date(appt.getStartTime());
2915 			appt.setStartDate(new Date(sd.getTime() + startDateOffset));
2916 			appt.resetRepeatWeeklyDays();
2917 		}
2918 		if (endDateOffset) {
2919 			var endDateTime;
2920 
2921 			if (viewMode === ZmCalItem.MODE_EDIT_SINGLE_INSTANCE) {
2922 				// For some reason the recurring all day events have their end date set to the next day while the normal all day events don't.
2923 				// For e.g. an event with startDate 9th June and endDate 10th June when dragged to the next day has varying results:
2924 				// -> Regular all day event has end Date as 9th June which with endDateOffset results in 10th June as new endDate
2925 				// -> Recurring all day event has end Date as 10th June which with endDateOffset results in 11th June as new endDate
2926 				// To tackle this the new End date is now calculated from the new start date and appt duration and equivalent of 1 day is subtracted from it.
2927 				// TODO: Need a better solution for this -> investigate why the end date difference is present in the first place.
2928 				if (appt.allDayEvent) {
2929 					endDateTime = appt.getStartTime() + apptDuration - AjxDateUtil.MSEC_PER_DAY;
2930 				}
2931 				else {
2932 					endDateTime = appt.getUniqueEndDate().getTime() + endDateOffset;
2933 				}
2934 			}
2935 			else {
2936 				endDateTime = appt.getEndTime()  + endDateOffset;
2937 			}
2938 			appt.setEndDate(new Date(endDateTime));
2939 		}
2940 
2941 		if (viewMode == ZmCalItem.MODE_EDIT_SINGLE_INSTANCE) {
2942 			//bug: 32231 - use proper timezone while creating exceptions
2943 			appt.setOrigTimezone(AjxTimezone.getServerId(AjxTimezone.DEFAULT));
2944 		}
2945 
2946 		if(!appt.getTimezone()) appt.setTimezone(AjxTimezone.getServerId(AjxTimezone.DEFAULT));
2947 		var respCallback    = new AjxCallback(this, this._handleResponseUpdateApptDateSave2, [callback]);
2948 		var respErrCallback = new AjxCallback(this, this._handleResponseUpdateApptDateSave2, [errorCallback, appt, apptStartDate, apptEndDate]);
2949 		appCtxt.getShell().setBusy(true);
2950 
2951         var action = appt.inviteNeverSent ? ZmCalItemComposeController.SAVE_CLOSE :
2952                                             ZmCalItemComposeController.SEND;
2953         this._saveSimpleAppt(true, appt, action, respCallback, respErrCallback, respErrCallback);
2954 	} catch (ex) {
2955 		appCtxt.getShell().setBusy(false);
2956 		if (ex.msg) {
2957 			this.popupErrorDialog(AjxMessageFormat.format(ZmMsg.mailSendFailure, ex.msg));
2958 		} else {
2959 			this.popupErrorDialog(ZmMsg.errorGeneric, ex);
2960 		}
2961 		if (errorCallback) errorCallback.run(ex);
2962 	}
2963 	if (callback) callback.run(result);
2964 }
2965 
2966 ZmCalViewController.prototype._showExceptionWarning = function(yesCB,noCB) {
2967           var dialog = appCtxt.getYesNoMsgDialog();
2968 		  dialog.setMessage(ZmMsg.recurrenceUpdateWarning, DwtMessageDialog.WARNING_STYLE);
2969           dialog.registerCallback(DwtDialog.YES_BUTTON, this._handleExceptionWarningResponse, this,[dialog,yesCB]);
2970           dialog.registerCallback(DwtDialog.NO_BUTTON, this._handleExceptionWarningResponse,this,[dialog,noCB]);
2971 		  dialog.popup();
2972 }
2973 
2974 ZmCalViewController.prototype._handleExceptionWarningResponse = function(dialog,respCallback) {
2975           if(respCallback){respCallback.run();}
2976           else{this._refreshAction(true);}
2977           if(dialog){
2978               dialog.popdown();
2979           }
2980 }
2981 
2982 ZmCalViewController.prototype._handleResponseUpdateApptDateSave2 =
2983 function(callback, appt, apptStartDate, apptEndDate) {
2984     // Appt passed in for cancel/failure.  Restore the start and endDates
2985     if (appt) {
2986         appt.setStartDate(apptStartDate);
2987         appt.setEndDate(apptEndDate);
2988         appt.resetRepeatWeeklyDays();
2989     }
2990     if (callback) callback.run();
2991     appCtxt.getShell().setBusy(false);
2992 
2993 
2994 };
2995 
2996 ZmCalViewController.prototype._handleResponseUpdateApptDateIgnore =
2997 function(appt, viewMode, startDateOffset, endDateOffset, callback, errorCallback, result) {
2998 	this._refreshAction(true);
2999 	if (callback) callback.run(result);
3000 };
3001 
3002 /**
3003  * Gets the day tool tip text.
3004  * 
3005  * @param	{Date}		date		the date
3006  * @param	{Boolean}	noheader	if <code>true</code>, do not include tool tip header
3007  * @param	{AjxCallback}	callback		the callback
3008  * @param	{Boolean}	getSimpleToolTip	 if <code>true</code>,show only plain text in tooltip for all day events.
3009  * Multi day "appts/allday events" would be shown by just one entry showing final start/end date&time.
3010  */
3011 ZmCalViewController.prototype.getDayToolTipText =
3012 function(date, noheader, callback, isMinical, getSimpleToolTip) {
3013 	try {
3014 		var start = new Date(date.getTime());
3015 		start.setHours(0, 0, 0, 0);
3016 		var startTime = start.getTime();
3017 		var end = start.getTime() + AjxDateUtil.MSEC_PER_DAY;
3018 		var params = {start:startTime, end:end, fanoutAllDay:true};
3019 		if(callback) {
3020 			params.callback = new AjxCallback(this, this._handleToolTipSearchResponse, [start, noheader, callback, isMinical]);
3021 			this.getApptSummaries(params);
3022 		} else {
3023 			var result = this.getApptSummaries(params);
3024 			return ZmApptViewHelper.getDayToolTipText(start, result, this, noheader, null, isMinical, getSimpleToolTip);
3025 		}
3026 	} catch (ex) {
3027 		DBG.println(ex);
3028 		return "<b>" + ZmMsg.errorGettingAppts + "</b>";
3029 	}
3030 };
3031 
3032 ZmCalViewController.prototype._handleToolTipSearchResponse =
3033 function(start, noheader, callback, isMinical, result) {
3034 	try {
3035 		var tooltip = ZmApptViewHelper.getDayToolTipText(start, result, this, noheader, null, isMinical);
3036 		callback.run(tooltip);
3037 	} catch (ex) {
3038 		DBG.println(ex);
3039 		callback.run("<b>" + ZmMsg.errorGettingAppts + "</b>");
3040 	}
3041 };
3042 
3043 ZmCalViewController.prototype.getUserStatusToolTipText =
3044 function(start, end, noheader, email, emptyMsg, calIds) {
3045 	try {
3046         if(!calIds) {
3047             calIds = [];
3048             if (this._calTreeController) {
3049                 var calendars = this._calTreeController.getOwnedCalendars(this._app.getOverviewId(),email);
3050                 for (var i = 0; i < calendars.length; i++) {
3051                     var cal = calendars[i];
3052                     if (cal && (cal.nId != ZmFolder.ID_TRASH)) {
3053                         calIds.push(appCtxt.multiAccounts ? cal.id : cal.nId);
3054                     }
3055                 }
3056             }
3057 
3058             if ((calIds.length == 0) || !email) {
3059                 return "<b>" + ZmMsg.statusFree + "</b>";
3060             }
3061         }
3062 
3063 		var startTime = start.getTime();
3064 		var endTime = end.getTime();
3065 
3066 		var dayStart = new Date(start.getTime());
3067 		dayStart.setHours(0, 0, 0, 0);
3068 
3069 		var dayEnd = new Date(dayStart.getTime() + AjxDateUtil.MSEC_PER_DAY);
3070 
3071 		// to avoid frequent request to server we cache the appt for the entire
3072 		// day first before getting the appts for selected time interval
3073 		this.getApptSummaries({start:dayStart.getTime(), end:dayEnd.getTime(), fanoutAllDay:true, folderIds: calIds});
3074 
3075 		var result = this.getApptSummaries({start:startTime, end:endTime, fanoutAllDay:true, folderIds: calIds});
3076 
3077 		return ZmApptViewHelper.getDayToolTipText(start, result, this, noheader, emptyMsg);
3078 	} catch (ex) {
3079 		DBG.println(ex);
3080 		return "<b>" + ZmMsg.meetingStatusUnknown + "</b>";
3081 	}
3082 };
3083 
3084 ZmCalViewController.prototype._miniCalDateRangeListener =
3085 function(ev) {
3086 	this._scheduleMaintenance(ZmCalViewController.MAINT_MINICAL);
3087 };
3088 
3089 ZmCalViewController.prototype._dateRangeListener =
3090 function(ev) {
3091 	ev.item.setNeedsRefresh(true);
3092 	this._scheduleMaintenance(ZmCalViewController.MAINT_VIEW);
3093 };
3094 
3095 ZmCalViewController.prototype.processInlineCalSearch =
3096 function() {
3097 	var srchResponse = window.inlineCalSearchResponse;
3098 	if (!srchResponse) { return; }
3099 
3100 	var params = srchResponse.search;
3101 	var response = srchResponse.Body;
3102 
3103 	if (params instanceof Array) {
3104 		params = params[0];
3105 	}
3106 
3107 	var viewId = this._currentViewId || this.getDefaultViewType();
3108 	var fanoutAllDay = (viewId == ZmId.VIEW_CAL_MONTH);
3109 	var searchParams = {start: params.s, end: params.e, fanoutAllDay: fanoutAllDay, callback: null};
3110 	searchParams.folderIds = params.l ? params.l.split(",") : []; 
3111 	searchParams.query = "";
3112 
3113 	var miniCalParams = this.getMiniCalendarParams();
3114 	miniCalParams.folderIds = searchParams.folderIds;
3115 
3116 	this.apptCache.setSearchParams(searchParams);
3117 	this.apptCache.processBatchResponse(response.BatchResponse, searchParams, miniCalParams);
3118 };
3119 
3120 ZmCalViewController.prototype.setCurrentView =
3121 function(view) {
3122 	// do nothing
3123 };
3124 
3125 ZmCalViewController.prototype._resetNavToolBarButtons =
3126 function(view) {
3127 	this._navToolBar[ZmId.VIEW_CAL].enable([ZmOperation.PAGE_BACK, ZmOperation.PAGE_FORWARD], true);
3128 };
3129 
3130 ZmCalViewController.prototype._resetToolbarOperations =
3131 function(viewId) {
3132 	ZmListController.prototype._resetToolbarOperations.call(this);
3133 };
3134 
3135 ZmCalViewController.prototype._setNavToolbarPosition =
3136 function(navToolbar, currentViewName) {
3137     if(!navToolbar || !currentViewName) { return; }
3138     navToolbar.setVisible(currentViewName != ZmId.VIEW_CAL_LIST);
3139 };
3140 
3141 ZmCalViewController.prototype._resetOperations =
3142 function(parent, num) {
3143     if (!parent) return;
3144 
3145 	parent.enableAll(true);
3146 	var currViewName = this._viewMgr.getCurrentViewName();
3147     parent.enable(ZmOperation.TAG_MENU, appCtxt.get(ZmSetting.TAGGING_ENABLED) && !appCtxt.isWebClientOffline());
3148 
3149 	if (currViewName == ZmId.VIEW_CAL_LIST && num > 1) { return; }
3150 
3151     this._setNavToolbarPosition(this._navToolBar[ZmId.VIEW_CAL], currViewName);
3152 
3153     var appt = this.getSelection()[0];
3154     var calendar = appt && appt.getFolder();
3155     var isTrash = calendar && calendar.nId == ZmOrganizer.ID_TRASH;
3156     num = ( isTrash && this.getCurrentListView() ) ? this.getCurrentListView().getSelectionCount() : num ;
3157     var isReadOnly = calendar ? calendar.isReadOnly() : false;
3158     var isSynced = Boolean(calendar && calendar.url);
3159     var isShared = calendar ? calendar.isRemote() : false;
3160     var disabled = isSynced || isReadOnly || (num == 0) || appCtxt.isWebClientOffline();
3161     var isPrivate = appt && appt.isPrivate() && calendar.isRemote() && !calendar.hasPrivateAccess();
3162     var isForwardable = !isTrash && calendar && !calendar.isReadOnly() && appCtxt.get(ZmSetting.GROUP_CALENDAR_ENABLED) && !appCtxt.isWebClientOffline();
3163     var isReplyable = !isTrash && appt && (num == 1) && !appCtxt.isWebClientOffline();
3164     var isTrashMultiple = isTrash && (num && num>1);
3165 
3166     parent.enable([ZmOperation.REPLY, ZmOperation.REPLY_ALL], (isReplyable && !isTrashMultiple));
3167     parent.enable(ZmOperation.TAG_MENU, ((!isReadOnly && !isSynced && num > 0) || isTrashMultiple) && !appCtxt.isWebClientOffline());
3168     parent.enable(ZmOperation.VIEW_APPOINTMENT, !isPrivate && !isTrashMultiple);
3169     parent.enable([ZmOperation.FORWARD_APPT, ZmOperation.FORWARD_APPT_INSTANCE, ZmOperation.FORWARD_APPT_SERIES], isForwardable
3170                   && !isTrashMultiple  && !appCtxt.isWebClientOffline());
3171     parent.enable(ZmOperation.PROPOSE_NEW_TIME, !isTrash && (appt && !appt.isOrganizer()) && !isTrashMultiple && !appCtxt.isWebClientOffline());
3172     parent.enable(ZmOperation.SHOW_ORIG, num == 1 && appt && appt.getRestUrl() != null && !isTrashMultiple  && !appCtxt.isWebClientOffline());
3173 
3174     parent.enable([ZmOperation.DELETE, ZmOperation.MOVE, ZmOperation.MOVE_MENU], !disabled || isTrashMultiple);
3175 
3176     parent.enable(ZmOperation.VIEW_APPT_INSTANCE,!isTrash);
3177 
3178     var apptAccess = ((appt && appt.isPrivate() && calendar.isRemote()) ? calendar.hasPrivateAccess() : true );
3179     parent.enable(ZmOperation.DUPLICATE_APPT,apptAccess && !isTrashMultiple && this._app.containsWritableFolder() && !appCtxt.isWebClientOffline());
3180     parent.enable(ZmOperation.SHOW_ORIG,apptAccess && !isTrashMultiple  && !appCtxt.isWebClientOffline());
3181 
3182 	/*if (currViewName == ZmId.VIEW_CAL_LIST) {
3183 		parent.enable(ZmOperation.PRINT_CALENDAR, num > 0);
3184 	} */
3185 	parent.enable(ZmOperation.PRINT_CALENDAR, !appCtxt.isWebClientOffline());
3186 
3187 	// disable button for current view
3188 	var op = ZmCalViewController.VIEW_TO_OP[currViewName];
3189 	// setSelected on a Toolbar; Do nothing for an ActionMenu
3190 	if (op && parent.setSelected) {
3191 		parent.setSelected(op);
3192 	}
3193 
3194     if (appCtxt.isWebClientOffline()) {
3195         // Disable the list view when offline
3196         //parent.enable(ZmCalViewController.VIEW_TO_OP[ZmOperation.CAL_LIST_VIEW], false);
3197         parent.enable(ZmOperation.CAL_LIST_VIEW, false);
3198     }
3199 
3200     //this._resetQuickCommandOperations(parent);
3201 };
3202 
3203 ZmCalViewController.prototype._listSelectionListener =
3204 function(ev) {
3205 
3206 	ZmListController.prototype._listSelectionListener.call(this, ev);
3207     // to avoid conflicts on opening a readonly appointment in readonly view
3208 	if (ev.detail == DwtListView.ITEM_SELECTED) {
3209 		this._viewMgr.getCurrentView()._apptSelected();
3210 	} else if (ev.detail == DwtListView.ITEM_DBL_CLICKED) {
3211 		var appt = ev.item;
3212 		if (appt.isPrivate() && appt.getFolder().isRemote() && !appt.getFolder().hasPrivateAccess()) {
3213 			var msgDialog = appCtxt.getMsgDialog();
3214 			msgDialog.setMessage(ZmMsg.apptIsPrivate, DwtMessageDialog.INFO_STYLE);
3215 			msgDialog.popup();
3216 		} else {
3217 			// open a appointment view
3218 			this._apptIndexShowing = this._list.indexOf(appt);
3219 			this._apptFromView = this._viewMgr.getCurrentView();
3220 			this._showAppointmentDetails(appt);
3221 		}
3222 	}
3223 };
3224 
3225 ZmCalViewController.prototype.getViewMgr =
3226 function(){
3227     return this._viewMgr;
3228 }
3229 
3230 ZmCalViewController.prototype._handleMenuViewAction =
3231 function(ev) {
3232 	var actionMenu = this.getActionMenu();
3233 	var appt = actionMenu.__appt;
3234 	delete actionMenu.__appt;
3235 
3236     var orig = appt.getOrig();
3237     appt = orig && orig.isMultiDay() ? orig : appt;
3238 	var calendar = appt.getFolder();
3239     var isTrash = calendar && calendar.nId == ZmOrganizer.ID_TRASH;
3240 	var isSynced = Boolean(calendar.url);
3241 	var mode = ZmCalItem.MODE_EDIT;
3242 	var menuItem = ev.item;
3243 	var menu = menuItem.parent;
3244 	var id = menu.getData(ZmOperation.KEY_ID);
3245 	switch(id) {
3246 		case ZmOperation.VIEW_APPT_INSTANCE:	mode = ZmCalItem.MODE_EDIT_SINGLE_INSTANCE; break;
3247 		case ZmOperation.VIEW_APPT_SERIES:		mode = ZmCalItem.MODE_EDIT_SERIES; break;
3248 	}
3249 	
3250 	if (appt.isReadOnly() || isSynced || isTrash || appCtxt.isWebClientOffline()) {
3251 		var clone = ZmAppt.quickClone(appt);
3252 		var callback = new AjxCallback(this, this._showApptReadOnlyView, [clone, mode]);
3253 		clone.getDetails(mode, callback, this._errorCallback);
3254 	} else {
3255 		this.editAppointment(appt, mode);
3256 	}
3257 };
3258 
3259 ZmCalViewController.prototype._handleApptRespondAction =
3260 function(ev) {
3261 	var appt = this.getSelection()[0];
3262     if(!appt){return;}
3263 	var type = ev.item.getData(ZmOperation.KEY_ID);
3264 	var op = ev.item.parent.getData(ZmOperation.KEY_ID);
3265 	var respCallback = new AjxCallback(this, this._handleResponseHandleApptRespondAction, [appt, type, op, null]);
3266 	appt.getDetails(null, respCallback, this._errorCallback);
3267 };
3268 
3269 ZmCalViewController.prototype._handleResponseHandleApptRespondAction =
3270 function(appt, type, op, callback) {
3271 	var msgController = this._getMsgController();
3272 	msgController.setMsg(appt.message);
3273 	// poke the msgController
3274 	var instanceDate = op == ZmOperation.VIEW_APPT_INSTANCE ? new Date(appt.uniqStartTime) : null;
3275 
3276     if(type == ZmOperation.REPLY_DECLINE) {
3277         var promptCallback = new AjxCallback(this, this._sendInviteReply, [type, instanceDate]);
3278         this._promptDeclineNotify(appt, promptCallback, callback);
3279     }else {
3280         msgController._sendInviteReply(type, appt.compNum || 0, instanceDate, appt.getRemoteFolderOwner(), false, null, null, callback);
3281     }
3282 };
3283 
3284 ZmCalViewController.prototype._promptDeclineNotify =
3285 function(appt, promptCallback, callback) {
3286 	if (!this._declineNotifyDialog) {
3287 		var msg = ZmMsg.confirmDeclineAppt;
3288 		this._declineNotifyDialog = new ZmApptDeleteNotifyDialog({
3289 			parent: this._shell,
3290 			title: AjxMsg.confirmTitle,
3291 			confirmMsg: msg,
3292 			choiceLabel1: ZmMsg.dontNotifyOrganizer,
3293 			choiceLabel2 : ZmMsg.notifyOrganizer
3294 		});
3295 	}
3296 	this._declineNotifyDialog.popup(new AjxCallback(this, this._declineNotifyYesCallback, [appt, promptCallback, callback]));
3297 };
3298 
3299 ZmCalViewController.prototype._declineNotifyYesCallback =
3300 function(appt, promptCallback, callback) {
3301 	var notifyOrg = !this._declineNotifyDialog.isDefaultOptionChecked();
3302     if(promptCallback) promptCallback.run(appt, notifyOrg, callback);
3303 };
3304 
3305 
3306 ZmCalViewController.prototype._sendInviteReply =
3307 function(type, instanceDate, appt, notifyOrg, callback) {
3308     var msgController = this._getMsgController();
3309     msgController._sendInviteReply(type, appt.compNum || 0, instanceDate, appt.getRemoteFolderOwner(), !notifyOrg, null, null, callback);
3310 };
3311 
3312 ZmCalViewController.prototype._handleApptEditRespondAction =
3313 function(ev) {
3314 	var appt = this.getSelection()[0];
3315 	var id = ev.item.getData(ZmOperation.KEY_ID);
3316 	var op = ev.item.parent.parent.parent.getData(ZmOperation.KEY_ID);
3317 	var respCallback = new AjxCallback(this, this._handleResponseHandleApptEditRespondAction, [appt, id, op]);
3318 	appt.getDetails(null, respCallback, this._errorCallback);
3319 };
3320 
3321 ZmCalViewController.prototype._handleResponseHandleApptEditRespondAction =
3322 function(appt, id, op) {
3323 	var msgController = this._getMsgController();
3324 	var msg = appt.message;
3325 	msg.subject = msg.subject || appt.name;
3326 	if (!msg.getAddress(AjxEmailAddress.REPLY_TO)) {
3327 		msg.setAddress(AjxEmailAddress.REPLY_TO, new AjxEmailAddress(appt.organizer));
3328 	}
3329 	msgController.setMsg(msg);
3330 
3331 	// poke the msgController
3332 	switch (id) {
3333 		case ZmOperation.EDIT_REPLY_ACCEPT: 	id = ZmOperation.REPLY_ACCEPT; break;
3334 		case ZmOperation.EDIT_REPLY_DECLINE: 	id = ZmOperation.REPLY_DECLINE; break;
3335 		case ZmOperation.EDIT_REPLY_TENTATIVE: 	id = ZmOperation.REPLY_TENTATIVE; break;
3336 	}
3337 	var instanceDate = op == ZmOperation.VIEW_APPT_INSTANCE ? new Date(appt.uniqStartTime) : null;
3338 	msgController._editInviteReply(id, 0, instanceDate, appt.getRemoteFolderOwner());
3339 };
3340 
3341 ZmCalViewController.prototype._handleError =
3342 function(ex) {
3343 	if (ex.code == 'mail.INVITE_OUT_OF_DATE' ||	ex.code == 'mail.NO_SUCH_APPT') {
3344 		var msgDialog = appCtxt.getMsgDialog();
3345 		msgDialog.registerCallback(DwtDialog.OK_BUTTON, this._handleError2, this, [msgDialog]);
3346 		msgDialog.setMessage(ZmMsg.apptOutOfDate, DwtMessageDialog.INFO_STYLE);
3347 		msgDialog.popup();
3348 		return true;
3349 	}
3350 	return false;
3351 };
3352 
3353 ZmCalViewController.prototype._handleError2 =
3354 function(msgDialog) {
3355 	msgDialog.unregisterCallback(DwtDialog.OK_BUTTON);
3356 	msgDialog.popdown();
3357 	this._refreshAction(false);
3358 };
3359 
3360 ZmCalViewController.prototype._initCalViewMenu =
3361 function(menu) {
3362 	for (var i = 0; i < ZmCalViewController.OPS.length; i++) {
3363 		var op = ZmCalViewController.OPS[i];
3364 		menu.addSelectionListener(op, this._listeners[op]);
3365         menu.enable(op, ((op != ZmOperation.CAL_LIST_VIEW) || !appCtxt.isWebClientOffline()));
3366 	}
3367 };
3368 
3369 /**
3370  * Overrides ZmListController.prototype._getViewActionMenuOps
3371  * 
3372  * @private
3373  */
3374 ZmCalViewController.prototype._getViewActionMenuOps =
3375 function () {
3376     if(this._app.containsWritableFolder()) {
3377         return [ZmOperation.NEW_APPT, ZmOperation.NEW_ALLDAY_APPT,
3378 			    ZmOperation.SEP,
3379 			    ZmOperation.TODAY, ZmOperation.CAL_VIEW_MENU];
3380     }
3381     else {
3382         return [ZmOperation.TODAY,
3383                 ZmOperation.CAL_VIEW_MENU];
3384     }
3385 };
3386 
3387 /**
3388  * Overrides ZmListController.prototype._initializeActionMenu
3389  * 
3390  * @private
3391  */
3392 ZmCalViewController.prototype._initializeActionMenu =
3393 function() {
3394 	this._actionMenu = this._createActionMenu(this._getActionMenuOps());
3395 	if (appCtxt.get(ZmSetting.TAGGING_ENABLED)) {
3396 		this._setupTagMenu(this._actionMenu);
3397 	}
3398 
3399 	var recurrenceModeOps = this._getRecurrenceModeOps();
3400 	var params = {parent: this._shell, menuItems: recurrenceModeOps};
3401 	this._recurrenceModeActionMenu = new ZmActionMenu(params);
3402 	for (var i = 0; i < recurrenceModeOps.length; i++) {
3403 		var recurrenceMode = recurrenceModeOps[i];
3404 		var modeItem = this._recurrenceModeActionMenu.getMenuItem(recurrenceMode);
3405 		var menuOpsForMode = this._getActionMenuOps(recurrenceMode);
3406 		var actionMenuForMode = this._createActionMenu(menuOpsForMode, modeItem, recurrenceMode);
3407 		if (appCtxt.get(ZmSetting.TAGGING_ENABLED)) {
3408 			this._setupTagMenu(actionMenuForMode);
3409 		}
3410 		modeItem.setMenu(actionMenuForMode);
3411 		// NOTE: Target object for listener is menu item
3412 		var menuItemListener = this._recurrenceModeMenuPopup.bind(modeItem);
3413 		modeItem.addListener(AjxEnv.isIE ? DwtEvent.ONMOUSEENTER : DwtEvent.ONMOUSEOVER, menuItemListener);
3414 	}
3415 	this._recurrenceModeActionMenu.addPopdownListener(this._menuPopdownListener);
3416 };
3417 
3418 ZmCalViewController.prototype._createActionMenu =
3419 function(menuItems, parentMenuItem, context) {
3420 	var params = {parent: parentMenuItem || this._shell, menuItems: menuItems, context: this._getMenuContext() + (context ? "_" + context : "")};
3421 	var actionMenu = new ZmActionMenu(params);
3422 	menuItems = actionMenu.opList;
3423 	for (var i = 0; i < menuItems.length; i++) {
3424 		var menuItem = menuItems[i];
3425 		if (menuItem == ZmOperation.INVITE_REPLY_MENU) {
3426 			var menu = actionMenu.getOp(ZmOperation.INVITE_REPLY_MENU).getMenu();
3427 			menu.addSelectionListener(ZmOperation.EDIT_REPLY_ACCEPT, this._listeners[ZmOperation.EDIT_REPLY_ACCEPT]);
3428 			menu.addSelectionListener(ZmOperation.EDIT_REPLY_TENTATIVE, this._listeners[ZmOperation.EDIT_REPLY_TENTATIVE]);
3429 			menu.addSelectionListener(ZmOperation.EDIT_REPLY_DECLINE, this._listeners[ZmOperation.EDIT_REPLY_DECLINE]);
3430 		} else if (menuItem == ZmOperation.CAL_VIEW_MENU) {
3431 			var menu = actionMenu.getOp(ZmOperation.CAL_VIEW_MENU).getMenu();
3432 			this._initCalViewMenu(menu);
3433 		}
3434 		if (this._listeners[menuItem]) {
3435 			actionMenu.addSelectionListener(menuItem, this._listeners[menuItem]);
3436 		}
3437 	}
3438 	actionMenu.addPopdownListener(this._menuPopdownListener);
3439 	return actionMenu;
3440 };
3441 
3442 /**
3443  * The <code>this</code> in this method is the menu item.
3444  * 
3445  * @private
3446  */
3447 ZmCalViewController.prototype._recurrenceModeMenuPopup =
3448 function(ev) {
3449 	if (!this.getEnabled()) { return; }
3450 
3451 	var menu = this.getMenu();
3452 	var opId = this.getData(ZmOperation.KEY_ID);
3453 	menu.setData(ZmOperation.KEY_ID, opId);
3454 };
3455 
3456 /**
3457  * Overrides ZmListController.prototype._getActionMenuOptions
3458  * 
3459  * @private
3460  */
3461 ZmCalViewController.prototype._getActionMenuOps =
3462 function(recurrenceMode) {
3463 
3464 	var deleteOp = ZmOperation.DELETE;
3465 	var viewOp = ZmOperation.VIEW_APPOINTMENT;
3466 	var forwardOp = ZmOperation.FORWARD_APPT;
3467 
3468 	if (recurrenceMode == ZmOperation.VIEW_APPT_INSTANCE) {
3469 		deleteOp = ZmOperation.DELETE_INSTANCE;
3470 		viewOp = ZmOperation.OPEN_APPT_INSTANCE;
3471 		forwardOp = ZmOperation.FORWARD_APPT_INSTANCE;
3472 	} else if (recurrenceMode == ZmOperation.VIEW_APPT_SERIES) {
3473 		deleteOp = ZmOperation.DELETE_SERIES;
3474 		viewOp = ZmOperation.OPEN_APPT_SERIES;
3475 		forwardOp = ZmOperation.FORWARD_APPT_SERIES;
3476 	}
3477 	
3478 	var retVal = [viewOp,
3479 	      		ZmOperation.PRINT,
3480 	      		ZmOperation.SEP,
3481 	    		ZmOperation.REPLY_ACCEPT,
3482 	    		ZmOperation.REPLY_TENTATIVE,
3483 	    		ZmOperation.REPLY_DECLINE,
3484 	    		ZmOperation.INVITE_REPLY_MENU,
3485                 ZmOperation.PROPOSE_NEW_TIME,
3486                 ZmOperation.REINVITE_ATTENDEES,
3487 	    		ZmOperation.DUPLICATE_APPT,
3488 	    		ZmOperation.SEP,
3489 	    		ZmOperation.REPLY,
3490 	    		ZmOperation.REPLY_ALL,
3491 	    		forwardOp,
3492 	    		deleteOp,
3493 	    		ZmOperation.SEP];
3494 	if (recurrenceMode != ZmOperation.VIEW_APPT_INSTANCE) {
3495 		retVal.push(ZmOperation.MOVE);
3496 	}
3497 	retVal.push(ZmOperation.TAG_MENU);
3498 	retVal.push(ZmOperation.SHOW_ORIG);
3499     //retVal.push(ZmOperation.QUICK_COMMANDS);
3500 	return retVal;
3501 };
3502 
3503 ZmCalViewController.prototype._getRecurrenceModeOps =
3504 function() {
3505 	return [ZmOperation.VIEW_APPT_INSTANCE, ZmOperation.VIEW_APPT_SERIES];
3506 };
3507 
3508 ZmCalViewController.prototype._enableActionMenuReplyOptions =
3509 function(appt, actionMenu) {
3510 
3511     if (!(appt && actionMenu)) {
3512         return;
3513     }
3514     var isExternalAccount = appCtxt.isExternalAccount();
3515     var isFolderReadOnly = appt.isFolderReadOnly();
3516 	var isShared = appt.isShared();
3517 
3518 	//Note - isOrganizer means the Calendar is the organizer of the appt. i.e. the appointment was created on this calendar.
3519 	//This could be "true" even if the current user is just a sharee of the calendar.
3520 	var isTheCalendarOrganizer = appt.isOrganizer();
3521 
3522 	// find the checked calendar for this appt
3523 	var calendar;
3524 	var folderId = appt.getLocalFolderId();
3525 	var calendars = this.getCheckedCalendars(true);
3526 	for (var i = 0; i < calendars.length; i++) {
3527 		if (calendars[i].id == folderId) {
3528 			calendar = calendars[i];
3529 			break;
3530 		}
3531 	}
3532     //bug:68452 if its a trash folder then its not present in the calendars array
3533     if (!calendar){
3534         calendar = appt.getFolder();
3535     }
3536 	var share = calendar && calendar.link ? calendar.getMainShare() : null;
3537 	var workflow = share ? share.isWorkflow() : true;
3538     var isTrash = calendar && calendar.nId == ZmOrganizer.ID_TRASH;
3539 	var isPrivate = appt.isPrivate() && calendar.isRemote() && !calendar.hasPrivateAccess();
3540     var isReplyable = !isTrash && appt.otherAttendees;
3541 	var isForwardable = !isTrash && calendar && !calendar.isReadOnly() && appCtxt.get(ZmSetting.GROUP_CALENDAR_ENABLED);
3542 
3543 	//don't show for organizer calendar, or readOnly attendee calendar.
3544 	var showAcceptDecline = !isTheCalendarOrganizer && !isFolderReadOnly;
3545 	actionMenu.setItemVisible(ZmOperation.REPLY_ACCEPT, showAcceptDecline);
3546 	actionMenu.setItemVisible(ZmOperation.REPLY_DECLINE, showAcceptDecline);
3547 	actionMenu.setItemVisible(ZmOperation.REPLY_TENTATIVE, showAcceptDecline);
3548 	actionMenu.setItemVisible(ZmOperation.INVITE_REPLY_MENU, showAcceptDecline);
3549 	actionMenu.setItemVisible(ZmOperation.PROPOSE_NEW_TIME, showAcceptDecline);
3550     actionMenu.setItemVisible(ZmOperation.REINVITE_ATTENDEES, !isTrash && isTheCalendarOrganizer && !isFolderReadOnly && !appt.inviteNeverSent && appt.otherAttendees);
3551     actionMenu.setItemVisible(ZmOperation.TAG_MENU, appCtxt.get(ZmSetting.TAGGING_ENABLED));
3552 
3553     // Initially enabling all the options in the action menu. And then selectively disabling unsupported options for special users.
3554     actionMenu.enableAll(true);
3555 
3556 	//enable/disable specific actions, only if we actually show them. (we don't show for organizer or shared calendar)
3557 	if (showAcceptDecline) {
3558 		var enabled = isReplyable && workflow && !isPrivate && !isExternalAccount;
3559         actionMenu.enable(ZmOperation.REPLY_ACCEPT, enabled && appt.ptst != ZmCalBaseItem.PSTATUS_ACCEPT);
3560         actionMenu.enable(ZmOperation.REPLY_DECLINE, enabled && appt.ptst != ZmCalBaseItem.PSTATUS_DECLINED);
3561         actionMenu.enable(ZmOperation.REPLY_TENTATIVE, enabled && appt.ptst != ZmCalBaseItem.PSTATUS_TENTATIVE);
3562         actionMenu.enable(ZmOperation.INVITE_REPLY_MENU, enabled);
3563 		// edit reply menu
3564 		var mi = enabled && actionMenu.getMenuItem(ZmOperation.INVITE_REPLY_MENU);
3565 		var replyMenu = mi && mi.getMenu();
3566 		if (replyMenu) {
3567 			replyMenu.enable(ZmOperation.EDIT_REPLY_ACCEPT,	appt.ptst != ZmCalBaseItem.PSTATUS_ACCEPT);
3568 			replyMenu.enable(ZmOperation.EDIT_REPLY_DECLINE, appt.ptst != ZmCalBaseItem.PSTATUS_DECLINED);
3569 			replyMenu.enable(ZmOperation.EDIT_REPLY_TENTATIVE, appt.ptst != ZmCalBaseItem.PSTATUS_TENTATIVE);
3570 		}
3571 	}
3572 
3573     actionMenu.enable([ZmOperation.FORWARD_APPT, ZmOperation.FORWARD_APPT_INSTANCE, ZmOperation.FORWARD_APPT_SERIES], isForwardable);
3574 	var myEmail = appt.getFolder().getAccount().getEmail();
3575 	//the last condition in the following is for the somewhat corner case user1 invites user2, and user1 is looking at user2's calendar as a share.
3576 	actionMenu.enable(ZmOperation.REPLY, isReplyable && !isTheCalendarOrganizer && myEmail !== appt.organizer); //the organizer can't reply just to himself
3577 	actionMenu.enable(ZmOperation.REPLY_ALL, isReplyable);
3578 
3579     var disabledOps;
3580     if(appCtxt.isWebClientOffline()) {
3581          disabledOps = [ZmOperation.REINVITE_ATTENDEES,
3582             ZmOperation.PROPOSE_NEW_TIME,
3583             ZmOperation.REPLY,
3584             ZmOperation.REPLY_ALL,
3585             ZmOperation.DUPLICATE_APPT,
3586             ZmOperation.DELETE,
3587             ZmOperation.DELETE_INSTANCE,
3588             ZmOperation.DELETE_SERIES,
3589             ZmOperation.FORWARD_APPT,
3590             ZmOperation.FORWARD_APPT_INSTANCE,
3591             ZmOperation.FORWARD_APPT_SERIES,
3592             ZmOperation.SHOW_ORIG,
3593             ZmOperation.DUPLICATE_APPT,
3594 			ZmOperation.MOVE,
3595 			ZmOperation.PRINT,
3596 			ZmOperation.TAG_MENU,
3597             ZmOperation.MOVE_MENU];
3598         actionMenu.enable(disabledOps, false);
3599     } else {
3600         if(isExternalAccount) {
3601             disabledOps = [ZmOperation.REINVITE_ATTENDEES,
3602                 ZmOperation.PROPOSE_NEW_TIME,
3603                 ZmOperation.REPLY,
3604                 ZmOperation.REPLY_ALL,
3605                 ZmOperation.DUPLICATE_APPT,
3606                 ZmOperation.DELETE,
3607                 ZmOperation.DELETE_INSTANCE,
3608                 ZmOperation.DELETE_SERIES];
3609 
3610             actionMenu.enable(disabledOps, false);
3611         }
3612 
3613         // bug:71007 Disabling unsupported options for shared calendar with view only rights
3614         if (isFolderReadOnly) {
3615             disabledOps = [ZmOperation.REINVITE_ATTENDEES,
3616                            ZmOperation.PROPOSE_NEW_TIME,
3617                            ZmOperation.DELETE,
3618                            ZmOperation.DELETE_INSTANCE,
3619                            ZmOperation.DELETE_SERIES,
3620                            ZmOperation.MOVE,
3621                            ZmOperation.TAG_MENU,
3622                            ZmOperation.MOVE_MENU];
3623 
3624             actionMenu.enable(disabledOps, false);
3625         }
3626     }
3627 
3628 	var del = actionMenu.getMenuItem(ZmOperation.DELETE);
3629 	if (del && !isTrash) {
3630 		del.setText((isTheCalendarOrganizer && appt.otherAttendees) ? ZmMsg.cancel : ZmMsg.del);
3631 		var isSynced = Boolean(calendar.url);
3632 		del.setEnabled(!calendar.isReadOnly() && !isSynced && !isPrivate && !appCtxt.isWebClientOffline());
3633 	}
3634 
3635 	// recurring action menu options
3636 	if (this._recurrenceModeActionMenu && !isTrash) {
3637 		this._recurrenceModeActionMenu.enable(ZmOperation.VIEW_APPT_SERIES, !appt.exception);
3638 	}
3639 };
3640 
3641 ZmCalViewController.prototype._listActionListener =
3642 function(ev) {
3643 	ZmListController.prototype._listActionListener.call(this, ev);
3644 	var count = this.getSelectionCount();
3645 	var appt = ev.item;
3646 	var actionMenu = this.getActionMenu();
3647 	actionMenu.enableAll(count === 1);
3648 	if (count > 1) {
3649 		var isExternalAccount = appCtxt.isExternalAccount();
3650   		var isFolderReadOnly = appt.isFolderReadOnly();
3651 		var enabled = !isExternalAccount && !isFolderReadOnly;
3652 		actionMenu.enable([ZmOperation.TAG_MENU, ZmOperation.MOVE_MENU, ZmOperation.MOVE, ZmOperation.DELETE], enabled);
3653 		actionMenu.popup(0, ev.docX, ev.docY);
3654 		return;
3655 	}
3656     var calendar = appt && appt.getFolder();
3657     var isTrash = calendar && calendar.nId == ZmOrganizer.ID_TRASH;
3658 	var menu = (appt.isRecurring() && !appt.isException && !isTrash) ? this._recurrenceModeActionMenu : actionMenu;
3659 	this._enableActionMenuReplyOptions(appt, menu);
3660 	var op = (menu == actionMenu) && appt.exception ? ZmOperation.VIEW_APPT_INSTANCE : null;
3661 	actionMenu.__appt = appt;
3662 	menu.setData(ZmOperation.KEY_ID, op);
3663 
3664 	if (appt.isRecurring() && !appt.isException && !isTrash) {
3665 		var menuItem = menu.getMenuItem(ZmOperation.VIEW_APPT_INSTANCE);
3666 		this._setTagMenu(menuItem.getMenu());
3667 		this._enableActionMenuReplyOptions(appt, menuItem.getMenu());
3668 		menuItem = menu.getMenuItem(ZmOperation.VIEW_APPT_SERIES);
3669 		this._setTagMenu(menuItem.getMenu());
3670 		this._enableActionMenuReplyOptions(appt, menuItem.getMenu());
3671 	}
3672 	menu.popup(0, ev.docX, ev.docY);
3673 };
3674 
3675 ZmCalViewController.prototype._clearViewActionMenu =
3676 function() {
3677     // This will cause it to regenerate next time its accessed
3678     this._viewActionMenu = null;
3679 }
3680 
3681 ZmCalViewController.prototype._viewActionListener =
3682 function(ev) {
3683 	if (!this._viewActionMenu) {
3684 		var menuItems = this._getViewActionMenuOps();
3685 		if (!menuItems) return;
3686 		var overrides = {};
3687 		overrides[ZmOperation.TODAY] = {textKey:"todayGoto"};
3688 		var params = {parent:this._shell, menuItems:menuItems, overrides:overrides};
3689 		this._viewActionMenu = new ZmActionMenu(params);
3690 		menuItems = this._viewActionMenu.opList;
3691 		for (var i = 0; i < menuItems.length; i++) {
3692 			var menuItem = menuItems[i];
3693 			if (menuItem == ZmOperation.CAL_VIEW_MENU) {
3694 				var menu = this._viewActionMenu.getOp(ZmOperation.CAL_VIEW_MENU).getMenu();
3695 				this._initCalViewMenu(menu);
3696 			} else if (this._listeners[menuItem]) {
3697 				this._viewActionMenu.addSelectionListener(menuItem, this._listeners[menuItem]);
3698                 if ((menuItem == ZmOperation.NEW_APPT) || (menuItem == ZmOperation.NEW_ALLDAY_APPT)) {
3699                     this._viewActionMenu.enable(menuItem, !appCtxt.isWebClientOffline());
3700                 }
3701 			}
3702 		}
3703         this._viewActionMenu.addPopdownListener(this._menuPopdownListener);
3704 	}
3705 
3706 	if ( this._viewVisible && this._currentViewType == ZmId.VIEW_CAL_DAY) {
3707         var calId = ev.target.getAttribute(ZmCalDayTabView.ATTR_CAL_ID);
3708         if(!calId) {
3709             var view = this._viewMgr.getView(this._currentViewId);
3710             var gridLoc = Dwt.toWindow(ev.target, ev.elementX, ev.elementY, ev.target, true);
3711             var col = view._getColFromX(gridLoc.x);
3712             calId = (col && col.cal) ? col.cal.id : null;
3713         }
3714         this._viewActionMenu._calendarId = calId;
3715 	} else {
3716 		this._viewActionMenu._calendarId = null;
3717 	}
3718 
3719 	this._viewActionMenu.__view = ev.item;
3720 	this._viewActionMenu.popup(0, ev.docX, ev.docY);
3721 };
3722 
3723 ZmCalViewController.prototype._dropListener =
3724 function(ev) {
3725 	var view = this._listView[this._currentViewId];
3726 	var div = view.getTargetItemDiv(ev.uiEvent);
3727 	var item = div ? view.getItemFromElement(div) : null;
3728 
3729 	// only tags can be dropped on us *if* we are not readonly
3730 	if (ev.action == DwtDropEvent.DRAG_ENTER) {
3731 		if (item && item.type == ZmItem.APPT) {
3732 			var calendar = item.getFolder();
3733 			var isReadOnly = calendar ? calendar.isReadOnly() : false;
3734 			var isSynced = Boolean(calendar && calendar.url);
3735 			if (isSynced || isReadOnly  || appCtxt.isWebClientOffline()) {
3736 				ev.doIt = false; // can't tag a GAL or shared contact
3737 				view.dragSelect(div);
3738 				return;
3739 			}
3740 		}
3741 	}
3742 
3743 	ZmListController.prototype._dropListener.call(this, ev);
3744 };
3745 
3746 ZmCalViewController.prototype.sendRequest =
3747 function(soapDoc) {
3748 	try {
3749 		return appCtxt.getAppController().sendRequest({soapDoc: soapDoc});
3750 	} catch (ex) {
3751 		// do nothing
3752 		return null;
3753 	}
3754 };
3755 
3756 /**
3757  * Caller is responsible for exception handling. caller should also not modify
3758  * appts in this list directly.
3759  *
3760  * @param	{Hash}		params		a hash of parameters
3761  * @param	{long}	params.start 			the start time (in milliseconds)
3762  * @param	{long}	params.end				the end time (in milliseconds)
3763  * @param	{Boolean}	params.fanoutAllDay	if <code>true</code>, 
3764  * @param	{Array}	params.folderIds		the list of calendar folder Id's (null means use checked calendars in overview)
3765  * @param	{AjxCallback}	params.callback		the callback triggered once search results are returned
3766  * @param	{Boolean}	params.noBusyOverlay	if <code>true</code>, do not show veil during search
3767  * 
3768  * @private
3769  */
3770 ZmCalViewController.prototype.getApptSummaries =
3771 function(params) {
3772 	if (!params.folderIds) {
3773 		params.folderIds = this.getCheckedCalendarFolderIds();
3774 	}
3775 	params.query = this._userQuery;
3776 
3777 	return this.apptCache.getApptSummaries(params);
3778 };
3779 
3780 ZmCalViewController.prototype.handleUserSearch =
3781 function(params, callback) {
3782 	AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar"]);
3783 	//Calendar search is defaulted to list view when searched from the calendar app.
3784 	if (params.origin === ZmId.SEARCH) {
3785 		this.show(ZmId.VIEW_CAL_LIST, null, true);
3786 	}
3787 	else {
3788 		this.show(null, null, true);
3789 	}
3790 
3791 	this.apptCache.clearCache();
3792 	this._viewMgr.setNeedsRefresh();
3793 	this._userQuery = params.query;
3794 
3795 	// set start/end date boundaries
3796 	var view = this._listView[this._currentViewId];
3797 
3798 	if (view) {
3799 		var rt = view.getTimeRange();
3800 		params.start = rt.start;
3801 		params.end = rt.end;
3802 	} else if (this._miniCalendar) {
3803 		var calRange = this._miniCalendar.getDateRange();
3804 		params.start = calRange.start.getTime();
3805 		params.end = calRange.end.getTime();
3806 	} else {
3807 		// TODO - generate start/end based on current month?
3808 	}
3809 
3810 	params.fanoutAllDay = view && view._fanoutAllDay();
3811 	params.callback = this._searchResponseCallback.bind(this, callback);
3812 
3813 	this.getApptSummaries(params);
3814 };
3815 
3816 ZmCalViewController.prototype._searchResponseCallback =
3817 function(callback, list, userQuery, result) {
3818 
3819 	this.show(null, null, true);	// always make sure a calendar view is being shown
3820 	this._userQuery = userQuery;	// cache the user-entered search query
3821 
3822 	this._maintGetApptCallback(ZmCalViewController.MAINT_VIEW, this._listView[this._currentViewId], list, true, userQuery);
3823 
3824 	if (callback) {
3825 		callback.run(result);
3826 	}
3827 };
3828 
3829 // TODO: appt is null for now. we are just clearing our caches...
3830 ZmCalViewController.prototype.notifyCreate =
3831 function(create) {
3832 	// mark folders which needs to be cleared
3833 	if (create && create.l) {
3834 		var cal = appCtxt.getById(create.l);
3835 		if (cal && cal.id) {
3836 			this._clearCacheFolderMap[cal.id] = true;
3837 		}
3838 	}
3839 };
3840 
3841 ZmCalViewController.prototype.notifyDelete =
3842 function(ids) {
3843     if (this._modifyAppts) {
3844         var apptList = this._viewMgr.getSubContentView().getApptList();
3845         for (var i = 0; i < ids.length; i++) {
3846             var appt = ZmCalViewController.__AjxVector_getById(apptList, ids[i])
3847             if (appt) {
3848                 apptList.remove(appt);
3849                 this._modifyAppts.removes++;
3850             }
3851         }
3852     }
3853 
3854 	if (this._clearCache) { return; }
3855 
3856 	this._clearCache = this.apptCache.containsAnyId(ids);
3857 	this.handleEditConflict(ids);
3858 };
3859 
3860 ZmCalViewController.prototype.handleEditConflict =
3861 function(ids) {
3862 	//handling a case where appt is edited and related calendar is deleted
3863 	if (appCtxt.getCurrentViewId() == ZmId.VIEW_APPOINTMENT) {
3864 		var view = appCtxt.getCurrentView();
3865 		var appt = view.getAppt(true);
3866 		var calendar = appt && appt.getFolder();
3867 		var idStr = ","+ ids+",";
3868 		if (idStr.indexOf("," + calendar.id + ",") >= 0) {
3869 			this._app.getApptComposeController()._closeView();
3870 		}
3871 	}
3872 };
3873 
3874 ZmCalViewController.prototype.notifyModify =
3875 function(modifies) {
3876     var apptObjs = modifies.appt;
3877     if (apptObjs && this._viewMgr) {
3878         var listView = this._viewMgr.getSubContentView();
3879         if (listView) {
3880             for (var i = 0; i < apptObjs.length; i++) {
3881                 var apptObj = apptObjs[i];
3882                 // case 1: item moved *into* Trash
3883                 if (apptObj.l == ZmOrganizer.ID_TRASH) {
3884                     this._modifyAppts.adds++;
3885                     ZmAppt.loadByUid(apptObj.uid, new AjxCallback(this, this._updateSubContent));
3886                 }
3887                 // case 2: item moved *from* Trash
3888                 else {
3889                     var apptList = listView.getApptList();
3890                     var appt = ZmCalViewController.__AjxVector_getById(apptList, apptObj.id)
3891                     if (appt) {
3892                         apptList.remove(appt);
3893                         this._modifyAppts.removes++;
3894                         // TODO: was this appointment moved to a checked calendar w/in the specified range?
3895                     }
3896                 }
3897                 // case 3: neither, ignore
3898             }
3899         }
3900     }
3901 
3902 	if (this._clearCache) { return; }
3903 
3904 	// if any of the ids are in the cache then...
3905 	for (var name in modifies) {
3906 		var list = modifies[name];
3907 		this._clearCache = this._clearCache || this.apptCache.containsAnyItem(list);
3908 	}
3909 };
3910 
3911 ZmCalViewController.__AjxVector_getById = function(vector, id) {
3912     var array = vector.getArray();
3913     for (var i = 0; i < array.length; i++) {
3914         var item = array[i];
3915         if (item.id == id) return item;
3916     }
3917     return null;
3918 };
3919 
3920 ZmCalViewController.prototype._updateSubContent = function(appt) {
3921     if (appt) {
3922         var listView = this._viewMgr.getSubContentView();
3923         if (listView) {
3924             if (!listView.hasItem(appt.id)) {
3925                 listView.getApptList().add(appt);
3926             }
3927             listView.setNeedsRefresh(true);
3928             if (!this._modifyAppts || --this._modifyAppts.adds <= 0) {
3929                 listView.refresh();
3930                 this._clearCache = true;
3931                 this.refreshHandler();
3932             }
3933         }
3934     }
3935 };
3936 
3937 // this gets called before all the above notify* methods get called
3938 ZmCalViewController.prototype.preNotify = function(notify) {
3939     DBG.println(AjxDebug.DBG2, "ZmCalViewController: preNotify");
3940     this._modifyAppts = null;
3941     if (notify.deleted || (notify.modified && notify.modified.appt)) {
3942         var listView = this._viewMgr && this._viewMgr.getSubContentView();
3943         if (listView) {
3944             this._modifyAppts = { adds: 0, removes:0 };
3945         }
3946     }
3947 };
3948 
3949 // this gets called after all the above notify* methods get called
3950 ZmCalViewController.prototype.postNotify =
3951 function(notify) {
3952 	DBG.println(AjxDebug.DBG2, "ZmCalViewController: postNotify: " + this._clearCache);
3953 
3954     // update the trash list all at once
3955     if (this._modifyAppts) {
3956         var removes = this._modifyAppts.removes;
3957         var adds = this._modifyAppts.adds;
3958         if (removes || adds) {
3959             var listView = this._viewMgr.getSubContentView();
3960             if (listView) {
3961                 listView.setNeedsRefresh(true);
3962                 listView.refresh();
3963                 this._clearCache = true;
3964             }
3965         }
3966         this._modifyAppts.removes = 0;
3967     }
3968 
3969 	var clearCacheByFolder = false;
3970 	
3971 	for (var i in this._clearCacheFolderMap) {
3972 		DBG.println("clear cache :" + i);
3973 		this.apptCache.clearCache(i);
3974 		clearCacheByFolder = true;
3975 	}
3976 	this._clearCacheFolderMap = {};
3977 
3978 	//offline client makes batch request for each account configured causing
3979 	//refresh overlap clearing calendar
3980 	var timer = (appCtxt.isOffline && this.searchInProgress) ? 1000 : 0;
3981 	
3982 	if (this._clearCache) {
3983 		var act = new AjxTimedAction(this, this._refreshAction);
3984 		AjxTimedAction.scheduleAction(act, timer);
3985 		this._clearCache = false;
3986 	} else if(clearCacheByFolder) {
3987 		var act = new AjxTimedAction(this, this._refreshAction, [true]);
3988 		AjxTimedAction.scheduleAction(act, timer);
3989 	}
3990 };
3991 
3992 ZmCalViewController.prototype.setNeedsRefresh =
3993 function(refresh) {
3994 	if (this._viewMgr != null && this._viewMgr.setNeedsRefresh) {
3995 		this._viewMgr.setNeedsRefresh(refresh);
3996 	}
3997 };
3998 
3999 // returns true if moving given appt from local to remote folder or vice versa
4000 ZmCalViewController.prototype.isMovingBetwAccounts =
4001 function(appts, newFolderId) {
4002 	appts = (!(appts instanceof Array)) ? [appts] : appts;
4003 	var isMovingBetw = false;
4004 	for (var i = 0; i < appts.length; i++) {
4005 		var appt = appts[i];
4006 		if (!appt.isReadOnly() && appt._orig && appt.otherAttendees && !appCtxt.isWebClientOffline()) {
4007 			var origFolder = appt._orig.getFolder();
4008 			var newFolder = appCtxt.getById(newFolderId);
4009 			if (origFolder && newFolder) {
4010 				if ((origFolder.id != newFolderId) &&
4011 					((origFolder.link && !newFolder.link) || (!origFolder.link && newFolder.link)))
4012 				{
4013 					isMovingBetw = true;
4014 					break;
4015 				}
4016 			}
4017 		}
4018 	}
4019 
4020 	return isMovingBetw;
4021 };
4022 
4023 // returns true if moving given read-only invite appt between accounts
4024 ZmCalViewController.prototype.isMovingBetwAcctsAsAttendee =
4025 function(appts, newFolderId) {
4026 	appts = (!(appts instanceof Array)) ? [appts] : appts;
4027 	var isMovingBetw = false;
4028 	for (var i = 0; i < appts.length; i++) {
4029 		var appt = appts[i];
4030 		if (appt.isReadOnly() && appt._orig) {
4031 			var origFolder = appt._orig.getFolder();
4032 			var newFolder = appCtxt.getById(newFolderId);
4033 			if (origFolder && newFolder) {
4034 				if ((origFolder.id != newFolderId) && origFolder.owner != newFolder.owner)
4035 				{
4036 					isMovingBetw = true;
4037 					break;
4038 				}
4039 			}
4040 		}
4041 	}
4042 
4043 	return isMovingBetw;
4044 };
4045 
4046 // this gets called when we get a refresh block from the server
4047 ZmCalViewController.prototype.refreshHandler =
4048 function() {
4049 	var act = new AjxTimedAction(this, this._refreshAction);
4050 	AjxTimedAction.scheduleAction(act, 0);
4051 };
4052 
4053 ZmCalViewController.prototype._refreshAction =
4054 function(dontClearCache) {
4055 	DBG.println(AjxDebug.DBG1, "ZmCalViewController: _refreshAction: " + dontClearCache);
4056 	var forceMaintenance = false;
4057 	// reset cache
4058 	if (!dontClearCache) {
4059 		this.apptCache.clearCache();
4060 		forceMaintenance = true;
4061 	}
4062 
4063 	if (this._viewMgr != null) {
4064 		// mark all views as dirty
4065 		this._viewMgr.setNeedsRefresh(true);
4066 		this._scheduleMaintenance(ZmCalViewController.MAINT_MINICAL|ZmCalViewController.MAINT_VIEW|ZmCalViewController.MAINT_REMINDER, forceMaintenance);
4067 	} else if (this._miniCalendar != null) {
4068 		this._scheduleMaintenance(ZmCalViewController.MAINT_MINICAL|ZmCalViewController.MAINT_REMINDER, forceMaintenance);
4069 	} else {
4070 		this._scheduleMaintenance(ZmCalViewController.MAINT_REMINDER, forceMaintenance);
4071 	}
4072 };
4073 
4074 ZmCalViewController.prototype._maintErrorHandler =
4075 function(params) {
4076 	// TODO: resched work?
4077 };
4078 
4079 ZmCalViewController.prototype._maintGetApptCallback =
4080 function(work, view, list, skipMiniCalUpdate, query) {
4081 	if (list instanceof ZmCsfeException) {
4082 		this.searchInProgress = false;
4083 		this._handleError(list, new AjxCallback(this, this._maintErrorHandler));
4084 		return;
4085 	}
4086 	DBG.println(AjxDebug.DBG2, "ZmCalViewController: _maintGetApptCallback: " + work);
4087 	this._userQuery = query;
4088 
4089 	if (work & ZmCalViewController.MAINT_VIEW) {
4090 		this._list = new ZmApptList();
4091 		this._list.getVector().addList(list);
4092 		var sel = view.getSelection();
4093 		view.set(list);
4094 
4095 		// For bug 27221, reset toolbar after refresh
4096 		if (sel && sel.length > 0) {
4097 			var id = sel[0].id;
4098 			for (var i = 0; i < this._list.size(); i++) {
4099 				if (this._list.getArray()[i].id == id) {
4100 					view.setSelection(this._list.getArray[i],true);
4101 					break;
4102 				}
4103 			}
4104 		}
4105 		this._resetToolbarOperations();
4106 	}
4107 
4108 	if (work & ZmCalViewController.MAINT_REMINDER) {
4109 		this._app.getReminderController().refresh();
4110 	}
4111 
4112 	this.searchInProgress = false;
4113 
4114 	// initiate schedule action for pending work (process queued actions)
4115 	if (this._pendingWork != ZmCalViewController.MAINT_NONE) {
4116 		var newWork = this._pendingWork;
4117 		this._pendingWork = ZmCalViewController.MAINT_NONE;
4118 		this._scheduleMaintenance(newWork);
4119 	}
4120 };
4121 
4122 
4123 ZmCalViewController.prototype.refreshCurrentView =
4124  function() {
4125     var currentView = this.getCurrentView();
4126     if (currentView) {
4127         currentView.setNeedsRefresh(true);
4128         this._scheduleMaintenance(ZmCalViewController.MAINT_VIEW);
4129     }
4130 }
4131 
4132 
4133 ZmCalViewController.prototype._scheduleMaintenance =
4134 function(work, forceMaintenance) {
4135 
4136 	DBG.println(AjxDebug.DBG1, "ZmCalViewController._scheduleMaintenance, work = " + work + ", forceMaintenance = " + forceMaintenance);
4137 	DBG.println(AjxDebug.DBG1, "ZmCalViewController._scheduleMaintenance, searchInProgress = " + this.searchInProgress + ", pendingWork = " + this._pendingWork);
4138 	this.onErrorRecovery = new AjxCallback(this, this._errorRecovery, [work, forceMaintenance]);
4139 
4140 	// schedule timed action
4141 	if ((!this.searchInProgress || forceMaintenance) &&
4142 		(this._pendingWork == ZmCalViewController.MAINT_NONE))
4143 	{
4144 		this._pendingWork |= work;
4145 		AjxTimedAction.scheduleAction(this._maintTimedAction, 0);
4146 	}
4147 	else
4148 	{
4149 		this._pendingWork |= work;
4150 	}
4151 };
4152 
4153 ZmCalViewController.prototype.resetSearchFlags =
4154 function() {
4155     this.searchInProgress = false;
4156     this._pendingWork = ZmCalViewController.MAINT_NONE;
4157 };
4158 
4159 ZmCalViewController.prototype._errorRecovery =
4160 function(work, forceMaintenance) {
4161 	var maintainMiniCal = (work & ZmCalViewController.MAINT_MINICAL);
4162 	var maintainView = (work & ZmCalViewController.MAINT_VIEW);
4163 	var maintainRemainder = (work & ZmCalViewController.MAINT_REMINDER);
4164 	if (maintainMiniCal || maintainView || maintainRemainder) {
4165 		this.getCurrentView().setNeedsRefresh(true);
4166 	}
4167 	this._scheduleMaintenance(work, forceMaintenance);
4168 };
4169 
4170 ZmCalViewController.prototype._maintenanceAction =
4171 function() {
4172 	DBG.println(AjxDebug.DBG1, "ZmCalViewController._maintenanceAction, work = " + this._pendingWork);
4173  	var work = this._pendingWork;
4174 	this.searchInProgress = true;
4175 	this._pendingWork = ZmCalViewController.MAINT_NONE;
4176 
4177 	var maintainMiniCal = (work & ZmCalViewController.MAINT_MINICAL);
4178 	var maintainView = (work & ZmCalViewController.MAINT_VIEW);
4179 	var maintainRemainder = (work & ZmCalViewController.MAINT_REMINDER);
4180 	
4181 	if (work == ZmCalViewController.MAINT_REMINDER) {
4182 		this._app.getReminderController().refresh();
4183 		this.searchInProgress = false;
4184 	}
4185 	else if (maintainMiniCal || maintainView || maintainRemainder) {
4186         // NOTE: It's important that we go straight to the view!
4187 		var view = this._viewMgr ? this._viewMgr.getView(this._currentViewId) : null;
4188 		var needsRefresh =  view && view.needsRefresh();
4189 		DBG.println(AjxDebug.DBG1, "ZmCalViewController._maintenanceAction, view = " + this._currentViewId + ", needsRefresh = " + needsRefresh);
4190 		if (needsRefresh) {
4191             var rt = view.getTimeRange();
4192 			var params = {
4193 				start: rt.start,
4194 				end: rt.end,
4195 				fanoutAllDay: view._fanoutAllDay(),
4196 				callback: (new AjxCallback(this, this._maintGetApptCallback, [work, view])),
4197 				accountFolderIds: ([].concat(this._checkedAccountCalendarIds)), // pass in a copy, not a reference
4198 				query: this._userQuery
4199 			};
4200 
4201 			var reminderParams;
4202 			if (maintainRemainder) {
4203 				reminderParams = this._app.getReminderController().getRefreshParams();
4204 				reminderParams.callback = null;
4205 			}
4206 
4207 			DBG.println(AjxDebug.DBG1, "ZmCalViewController._maintenanceAction, do apptCache.batchRequest");
4208 			this.apptCache.batchRequest(params, this.getMiniCalendarParams(), reminderParams);
4209 			view.setNeedsRefresh(false);
4210 		} else {
4211 			this.searchInProgress = false;
4212 		}
4213 	}
4214 };
4215 
4216 ZmCalViewController.prototype.getKeyMapName =
4217 function() {
4218 	return ZmKeyMap.MAP_CALENDAR;
4219 };
4220 
4221 ZmCalViewController.prototype.handleKeyAction =
4222 function(actionCode) {
4223 	DBG.println(AjxDebug.DBG3, "ZmCalViewController.handleKeyAction");
4224 	var isWebClientOffline = appCtxt.isWebClientOffline();
4225 
4226 	switch (actionCode) {
4227 
4228 		case ZmKeyMap.CAL_DAY_VIEW:
4229 		case ZmKeyMap.CAL_WEEK_VIEW:
4230 		case ZmKeyMap.CAL_WORK_WEEK_VIEW:
4231 		case ZmKeyMap.CAL_MONTH_VIEW:
4232 			this.show(ZmCalViewController.ACTION_CODE_TO_VIEW[actionCode]);
4233 			break;
4234 
4235 		case ZmKeyMap.CAL_LIST_VIEW:
4236 			if (!isWebClientOffline) {
4237 				this.show(ZmCalViewController.ACTION_CODE_TO_VIEW[actionCode]);
4238 			}
4239 			break;
4240 
4241         case ZmKeyMap.CAL_FB_VIEW:
4242             if(appCtxt.get(ZmSetting.FREE_BUSY_VIEW_ENABLED) && !isWebClientOffline) {
4243                 this.show(ZmCalViewController.ACTION_CODE_TO_VIEW[actionCode]);
4244             }
4245 			break;
4246 
4247 		case ZmKeyMap.TODAY:
4248 			this._todayButtonListener();
4249 			break;
4250 
4251 		case ZmKeyMap.REFRESH:
4252 			this._refreshButtonListener();
4253 			break;
4254 
4255 		case ZmKeyMap.QUICK_ADD:
4256 			if (appCtxt.get(ZmSetting.CAL_USE_QUICK_ADD) && !isWebClientOffline) {
4257 				var date = this._viewMgr ? this._viewMgr.getDate() : new Date();
4258 				this.newAppointmentHelper(date, ZmCalViewController.DEFAULT_APPOINTMENT_DURATION);
4259 			}
4260 			break;
4261 
4262 		case ZmKeyMap.EDIT:
4263 			if (!isWebClientOffline) {
4264 				var appt = this.getSelection()[0];
4265 				if (appt) {
4266 					var ev = new DwtSelectionEvent();
4267 					ev.detail = DwtListView.ITEM_DBL_CLICKED;
4268 					ev.item = appt;
4269 					this._listSelectionListener(ev);
4270 				}
4271 			}
4272 			break;
4273 
4274 		case ZmKeyMap.CANCEL:
4275             var currentView = this._viewMgr.getCurrentView();
4276             if ((this._currentViewType == ZmId.VIEW_CAL_WORK_WEEK) ||
4277                 (this._currentViewType == ZmId.VIEW_CAL_WEEK) ||
4278                 (this._currentViewType == ZmId.VIEW_CAL_MONTH)) {
4279                 // Abort - restore location and Mouse up
4280                 var data = DwtMouseEventCapture.getTargetObj();
4281                 if (data) {
4282                     currentView._restoreApptLoc(data);
4283                     currentView._cancelNewApptDrag(data);
4284                     data.startDate = data.appt ? data.appt._orig.startDate : null;
4285                     ZmCalBaseView._apptMouseUpHdlr(null);
4286                 }
4287             }
4288 			break;
4289 
4290 		default:
4291 			return ZmListController.prototype.handleKeyAction.call(this, actionCode);
4292 	}
4293 	return true;
4294 };
4295 
4296 ZmCalViewController.prototype._getDefaultFocusItem =
4297 function() {
4298 	return this._toolbar[ZmId.VIEW_CAL];
4299 };
4300 
4301 /**
4302  * Returns a reference to the singleton message controller, used to send mail (in our case,
4303  * invites and their replies). If mail is disabled, we create our own ZmMsgController so that
4304  * we don't load the mail package.
4305  * 
4306  * @private
4307  */
4308 ZmCalViewController.prototype._getMsgController =
4309 function() {
4310 	if (!this._msgController) {
4311 		if (appCtxt.get(ZmSetting.MAIL_ENABLED)) {
4312 			this._msgController = AjxDispatcher.run("GetMsgController");
4313 		} else {
4314 			AjxDispatcher.require("Mail");
4315 			this._msgController = new ZmMsgController(this._container, this._app);
4316 		}
4317 	}
4318 	return this._msgController;
4319 };
4320 
4321 ZmCalViewController.prototype.getMiniCalendarParams =
4322 function() {
4323 	var dr = this.getMiniCalendar().getDateRange();
4324 	return {
4325 		start: dr.start.getTime(),
4326 		end: dr.end.getTime(),
4327 		fanoutAllDay: true,
4328 		noBusyOverlay: true,
4329 		folderIds: this.getCheckedCalendarFolderIds(),
4330 		tz: AjxTimezone.DEFAULT
4331 	};
4332 };
4333 
4334 ZmCalViewController.prototype.getMiniCalCache =
4335 function() {
4336 	if (!this._miniCalCache) {
4337 		this._miniCalCache = new ZmMiniCalCache(this);
4338 	}
4339 	return this._miniCalCache;
4340 };
4341 
4342 /**
4343  * Gets the calendar name.
4344  * 
4345  * @param	{String}	folderId		the folder id
4346  * @return	{String}	the name
4347  */
4348 ZmCalViewController.prototype.getCalendarName =
4349 function(folderId) {
4350 	var cal = appCtxt.getById(folderId);
4351 	return cal && cal.getName();
4352 };
4353 
4354 ZmCalViewController.prototype._checkItemCount =
4355 function() {
4356 	// No-op since this view doesn't do virtual paging.
4357 };
4358 
4359 
4360 ZmCalViewController.prototype._showOrigListener =
4361 function(ev) {
4362 	var actionMenu = this.getActionMenu();
4363 	var appt = actionMenu && actionMenu.__appt;
4364 	if (appt) {
4365 		setTimeout(this._showApptSource.bind(this, appt), 100); // Other listeners are focusing the main window, so delay the window opening for just a bit
4366 	}
4367 };
4368 
4369 ZmCalViewController.prototype._showApptSource =
4370 function(appt) {
4371     var apptFetchUrl = appCtxt.get(ZmSetting.CSFE_MSG_FETCHER_URI)
4372                     + "&id=" + AjxStringUtil.urlComponentEncode(appt.id || appt.invId)
4373                     +"&mime=text/plain&noAttach=1&icalAttach=none";
4374     // create a new window w/ generated msg based on msg id
4375     window.open(apptFetchUrl, "_blank", "menubar=yes,resizable=yes,scrollbars=yes");
4376 };
4377 
4378 ZmCalViewController.prototype.getAppointmentByInvite =
4379 function(invite, callback) {
4380 
4381 	var apptId = invite.getAppointmentId();
4382 	if(apptId) {
4383 		var jsonObj = {GetAppointmentRequest:{_jsns:"urn:zimbraMail"}};
4384 		var request = jsonObj.GetAppointmentRequest;
4385 		request.id = apptId;
4386 		request.includeContent = "1";
4387 
4388 		var accountName = (appCtxt.multiAccounts ? appCtxt.accountList.mainAccount.name : null);
4389 
4390 		appCtxt.getAppController().sendRequest({
4391 			jsonObj: jsonObj,
4392 			asyncMode: true,
4393 			callback: (new AjxCallback(this, this._getApptItemInfoHandler, [invite, callback])),
4394 			errorCallback: (new AjxCallback(this, this._getApptItemInfoErrorHandler, [invite, callback])),
4395 			accountName: accountName
4396 		});
4397 	}
4398 };
4399 
4400 ZmCalViewController.prototype._getApptItemInfoHandler =
4401 function(invite, callback, result) {
4402 	var resp = result.getResponse();
4403 	resp = resp.GetAppointmentResponse;
4404 
4405 	var apptList = new ZmApptList();
4406 	var apptNode = resp.appt[0];
4407 	var appt = ZmAppt.createFromDom(apptNode, {list: apptList}, null);
4408 
4409 	var invites = apptNode.inv;
4410 	appt.setInvIdFromProposedInvite(invites, invite);
4411 
4412 	if(callback) {
4413 		callback.run(appt);
4414 	}
4415 };
4416 
4417 ZmCalViewController.prototype._getApptItemInfoErrorHandler =
4418 function(invite, callback, result) {
4419 	var msgDialog = appCtxt.getMsgDialog();
4420 	msgDialog.reset();
4421 	msgDialog.setMessage(ZmMsg.unableToAcceptTime, DwtMessageDialog.INFO_STYLE);
4422 	msgDialog.popup();
4423 	return true;
4424 };
4425 
4426 ZmCalViewController.prototype.acceptProposedTime =
4427 function(apptId, invite, proposeTimeCallback) {
4428 
4429 	this._proposedTimeCallback = proposeTimeCallback;
4430 
4431 	if(apptId) {
4432 		var callback = new AjxCallback(this, this._acceptProposedTimeContinue, [invite]);
4433 		this.getAppointmentByInvite(invite, callback);
4434 	}else {
4435 		var msgDialog = appCtxt.getMsgDialog();
4436 		msgDialog.reset();
4437 		msgDialog.setMessage(ZmMsg.unableToAcceptTime, DwtMessageDialog.INFO_STYLE);
4438 		msgDialog.popup();
4439 	}
4440 };
4441 
4442 ZmCalViewController.prototype._acceptProposedTimeContinue =
4443 function(invite, appt) {
4444 	var mode = appt.isRecurring() ? (appt.isException ? ZmCalItem.MODE_EDIT_SINGLE_INSTANCE : ZmCalItem.MODE_EDIT_SERIES) : ZmCalItem.MODE_EDIT;
4445 	appt.setProposedInvite(invite);
4446 	appt.setProposedTimeCallback(this._proposedTimeCallback);
4447 	this.editAppointment(appt, mode);
4448 };
4449 
4450 ZmCalViewController.prototype.proposeNewTime =
4451 function(msg) {
4452 	var newAppt = this.newApptObject(new Date(), null, null, msg),
4453         mode = ZmCalItem.MODE_EDIT,
4454         apptId = msg.invite.getAppointmentId();
4455 	newAppt.setProposeTimeMode(true);
4456 	newAppt.setFromMailMessageInvite(msg);
4457     if (apptId) {
4458 		var msgId = msg.id;
4459 		var inx = msg.id.indexOf(":");
4460 		var accountId = null;
4461 		if (inx !== -1) {
4462 			accountId = msgId.substr(0, inx);
4463 			msgId = msgId.substr(inx + 1);
4464 		}
4465 		var invId = [apptId, msgId].join("-");
4466 		if (accountId) {
4467 			invId = [accountId, invId].join(":");
4468 		}
4469 		newAppt.invId = invId;
4470         newAppt.getDetails(mode, new AjxCallback(this, this.proposeNewTimeContinue, [newAppt, mode]));
4471     }
4472     else {
4473         newAppt.setViewMode(mode);
4474         AjxDispatcher.run("GetApptComposeController").proposeNewTime(newAppt);
4475     }
4476 };
4477 
4478 ZmCalViewController.prototype.proposeNewTimeContinue =
4479 function(newAppt, mode) {
4480     newAppt.setViewMode(mode);
4481 	AjxDispatcher.run("GetApptComposeController").proposeNewTime(newAppt);
4482 };
4483 
4484 ZmCalViewController.prototype.forwardInvite =
4485 function(msg) {
4486 	var invite = msg.invite;
4487 	var apptId = invite.getAppointmentId();
4488 
4489 	if(apptId && invite.isOrganizer()) {
4490 		var callback = new AjxCallback(this, this.forwardInviteContinue, [invite, msg]);
4491 		this.getAppointmentByInvite(invite, callback);
4492 	}else {
4493 		var newAppt = this.newApptObject(new Date(), null, null, msg);
4494 		newAppt.setForwardMode(true);
4495 		newAppt.setFromMailMessageInvite(msg);
4496 		AjxDispatcher.run("GetApptComposeController").forwardInvite(newAppt);
4497 	}
4498 };
4499 
4500 ZmCalViewController.prototype.forwardInviteContinue =
4501 function(invite, msg, appt) {
4502 	appt.setForwardMode(true);
4503 	appt.setFromMailMessageInvite(msg);
4504 	var mode = appt.isRecurring() ? (appt.isException ? ZmCalItem.MODE_EDIT_SINGLE_INSTANCE : ZmCalItem.MODE_EDIT_SERIES) : ZmCalItem.MODE_EDIT;
4505 	var clone = ZmAppt.quickClone(appt);
4506 	clone.getDetails(mode, new AjxCallback(this, this._forwardInviteCompose, [clone]));
4507 };
4508 
4509 ZmCalViewController.prototype._forwardInviteCompose =
4510 function (appt) {
4511 	AjxDispatcher.run("GetApptComposeController").forwardInvite(appt);
4512 };
4513 
4514 ZmCalViewController.prototype.getFreeBusyInfo =
4515 function(startTime, endTime, emailList, callback, errorCallback, noBusyOverlay) {
4516 	var soapDoc = AjxSoapDoc.create("GetFreeBusyRequest", "urn:zimbraMail");
4517 	soapDoc.setMethodAttribute("s", startTime);
4518 	soapDoc.setMethodAttribute("e", endTime);
4519 	soapDoc.setMethodAttribute("uid", emailList);
4520 
4521 	var acct = (appCtxt.multiAccounts)
4522 		? this._composeView.getApptEditView().getCalendarAccount() : null;
4523 
4524 	return appCtxt.getAppController().sendRequest({
4525 		soapDoc: soapDoc,
4526 		asyncMode: true,
4527 		callback: callback,
4528 		errorCallback: errorCallback,
4529 		noBusyOverlay: noBusyOverlay,
4530 		accountName: (acct ? acct.name : null)
4531 	});
4532 };
4533 
4534 ZmCalViewController.prototype.getWorkingInfo =
4535 function(startTime, endTime, emailList, callback, errorCallback, noBusyOverlay) {
4536    var soapDoc = AjxSoapDoc.create("GetWorkingHoursRequest", "urn:zimbraMail");
4537    soapDoc.setMethodAttribute("s", startTime);
4538    soapDoc.setMethodAttribute("e", endTime);
4539    soapDoc.setMethodAttribute("name", emailList);
4540 
4541    var acct = (appCtxt.multiAccounts)
4542        ? this._composeView.getApptEditView().getCalendarAccount() : null;
4543 
4544    return appCtxt.getAppController().sendRequest({
4545        soapDoc: soapDoc,
4546        asyncMode: true,
4547        callback: callback,
4548        errorCallback: errorCallback,
4549        noBusyOverlay: noBusyOverlay,
4550        accountName: (acct ? acct.name : null)
4551    });
4552 };
4553 
4554 /**
4555  * Sets the current view.
4556  *
4557  * @param {ZmListView} view	the view
4558  */
4559 ZmCalViewController.prototype.setCurrentListView = function(view) {
4560     if (!view) {
4561         return;
4562     }
4563 
4564     if (this._currentListView != view) {
4565         var hadFocus = this._currentListView && this._currentListView.hasFocus();
4566 
4567         this._currentListView = view;
4568         this._resetToolbarOperations();
4569 
4570         if (hadFocus) {
4571             this._currentListView.focus();
4572         }
4573     }
4574 };
4575 
4576 ZmCalViewController.prototype.getCurrentListView = function() {
4577     return this._currentListView || this.getCurrentView();
4578 };
4579 
4580 /**
4581  * Creates appointment as configured in the out-of-office preference
4582  */
4583 ZmCalViewController.prototype.createAppointmentFromOOOPref=
4584 function(startDate, endDate, allDay, respCallback){
4585        var newAppt = new ZmAppt();
4586        newAppt.setAllDayEvent(allDay);
4587 	   newAppt.setStartDate(startDate);
4588 	   newAppt.setEndDate(endDate);
4589        newAppt.name = ZmMsg.outOfOffice;
4590        newAppt.freeBusy = (appCtxt.get(ZmSetting.VACATION_CALENDAR_TYPE)=="BUSY")?"B":"O";
4591        newAppt.message = appCtxt.get(ZmSetting.VACATION_MSG);
4592        newAppt.convertToLocalTimezone();
4593        newAppt.save(null, respCallback);
4594 };
4595