1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2013, 2014, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * Creates the calendar view manager.
 26  * @class
 27  * This class represents the calendar view manager.
 28  * 
 29  * @param {DwtShell}	parent			the element that created this view
 30  * @param {ZmController}		controller		the controller
 31  * @param {DwtDropTarget}	dropTgt			the drop target
 32  * 
 33  * @extends		DwtComposite
 34  */
 35 ZmCalViewMgr = function(parent, controller, dropTgt) {
 36 
 37 	DwtComposite.call(this, {parent:parent, className:"ZmCalViewMgr", posStyle:Dwt.ABSOLUTE_STYLE});
 38 	this.addControlListener(new AjxListener(this, this._controlListener));
 39 
 40 	this._controller = controller;
 41 	this._dropTgt = dropTgt;
 42     this._showNewScheduleView = appCtxt.get(ZmSetting.FREE_BUSY_VIEW_ENABLED);
 43 	// View hash. Holds the various views e.g. day, month, week, etc...
 44 	this._views = {};
 45 	this._date = new Date();
 46 	this._viewFactory = {};
 47 	this._viewFactory[ZmId.VIEW_CAL_DAY]		= ZmCalDayTabView;
 48 	this._viewFactory[ZmId.VIEW_CAL_WORK_WEEK]	= ZmCalWorkWeekView;
 49 	this._viewFactory[ZmId.VIEW_CAL_WEEK]		= ZmCalWeekView;
 50 	this._viewFactory[ZmId.VIEW_CAL_MONTH]		= ZmCalMonthView;
 51 	this._viewFactory[ZmId.VIEW_CAL_LIST]		= ZmCalListView;
 52     this._viewFactory[ZmId.VIEW_CAL_FB]	        = ZmCalNewScheduleView;
 53 
 54     this._viewFactory[ZmId.VIEW_CAL_TRASH]		= ZmApptListView;
 55 };
 56 
 57 ZmCalViewMgr.prototype = new DwtComposite;
 58 ZmCalViewMgr.prototype.constructor = ZmCalViewMgr;
 59 
 60 ZmCalViewMgr._SEP = 5;
 61 
 62 ZmCalViewMgr.MIN_CONTENT_SIZE = 100;
 63 
 64 ZmCalViewMgr.prototype.toString = 
 65 function() {
 66 	return "ZmCalViewMgr";
 67 };
 68 
 69 ZmCalViewMgr.prototype.TEMPLATE = "calendar.Calendar#ZmCalViewMgr";
 70 
 71 ZmCalViewMgr.prototype._subContentShown = false;
 72 ZmCalViewMgr.prototype._subContentInitialized = false;
 73 
 74 ZmCalViewMgr.prototype.getController =
 75 function() {
 76 	return this._controller;
 77 };
 78 
 79 // sets need refresh on all views
 80 ZmCalViewMgr.prototype.setNeedsRefresh = 
 81 function() {
 82 	for (var name in this._views) {
 83 		this._views[name].setNeedsRefresh(true);
 84     }
 85 };
 86 
 87 ZmCalViewMgr.prototype.layoutWorkingHours =
 88 function() {
 89 	for (var name in this._views) {
 90 		if (name == ZmId.VIEW_CAL_DAY ||
 91             name == ZmId.VIEW_CAL_WORK_WEEK ||
 92             name == ZmId.VIEW_CAL_WEEK ||
 93             name == ZmId.VIEW_CAL_FB
 94             )
 95 			this._views[name].layoutWorkingHours();
 96 	}
 97 };
 98 
 99 ZmCalViewMgr.prototype.needsRefresh =
100 function(viewId) {
101 	viewId = viewId || this._currentViewName;
102 	var view = this._views[viewId];
103 	return view.needsRefresh ? view.needsRefresh() : false;
104 };
105 
106 ZmCalViewMgr.prototype.getCurrentView =
107 function() {
108 	return this._views[this._currentViewName];
109 };
110 
111 ZmCalViewMgr.prototype.getCurrentViewName =
112 function() {
113 	return this._currentViewName;
114 };
115 
116 ZmCalViewMgr.prototype.getView =
117 function(viewName) {
118 	return this._views[viewName];
119 };
120 
121 ZmCalViewMgr.prototype.getTitle =
122 function() {
123 	return this.getCurrentView().getTitle();
124 };
125 
126 ZmCalViewMgr.prototype.getDate =
127 function() {
128 	return this._date;
129 };
130 
131 ZmCalViewMgr.prototype.setDate =
132 function(date, duration, roll) {
133 	this._date = new Date(date.getTime());
134 	this._duration = duration;
135 	if (this._currentViewName) {
136 		var view = this._views[this._currentViewName];
137 		view.setDate(date, duration, roll);
138 	}
139 };
140 
141 ZmCalViewMgr.prototype.createView =
142 function(viewName) {
143 	var view = new this._viewFactory[viewName](this, DwtControl.ABSOLUTE_STYLE, this._controller, this._dropTgt);
144 
145 	if (viewName != ZmId.VIEW_CAL_TRASH) {
146 		view.addTimeSelectionListener(new AjxListener(this, this._viewTimeSelectionListener));
147 		view.addDateRangeListener(new AjxListener(this, this._viewDateRangeListener));
148 		view.addViewActionListener(new AjxListener(this, this._viewActionListener));
149 	}
150 	this._views[viewName] = view;
151     if (viewName == ZmId.VIEW_CAL_TRASH) {
152         var controller = this._controller;
153         view.addSelectionListener(new AjxListener(controller, controller._listSelectionListener));
154         view.addActionListener(new AjxListener(controller, controller._listActionListener));
155     }
156 	return view;
157 };
158 
159 ZmCalViewMgr.prototype.getSubContentView = function() {
160     return this._list || this._createSubContent();
161 };
162 
163 ZmCalViewMgr.prototype.getSelTrashCount = function() {
164 
165     var folders  = this._controller.getCheckedCalendars(true);
166     this._multiAccTrashQuery = [];
167     for (var i=0; i< folders.length; i++) {
168         if (folders[i].nId == ZmOrganizer.ID_TRASH) {
169             this._multiAccTrashQuery.push(['inid:', '"', folders[i].getAccount().id, ':', ZmOrganizer.ID_TRASH, '"'].join(""));
170         }
171     }
172     return this._multiAccTrashQuery.length;
173 };
174 
175 ZmCalViewMgr.prototype.setSubContentVisible = function(visible) {
176 
177     if (appCtxt.multiAccounts) {
178         var selCount = this.getSelTrashCount();
179         // if no trash is checked
180         if (selCount < 1) {
181             this._subContentShown = false;
182             this._subContentInitialized = true;
183             this._controller.setCurrentListView(null);
184         } else {
185         // if more one or more trash is checked
186             this._subContentShown = true;
187         }
188     } else if (this._subContentShown != visible) {
189         this._subContentShown = visible;
190         if (!visible) {
191             this._controller.setCurrentListView(null);
192         }
193     }
194     this._layout();
195 };
196 
197 ZmCalViewMgr.prototype._createSubContent = function() {
198     if (!this._subContentShown) return null;
199     if (this._subContentInitialized) return this._list;
200 
201     this._subContentInitialized = true;
202 
203     this._sash = new DwtSash({parent:this,posStyle:Dwt.ABSOLUTE_STYLE,style:DwtSash.VERTICAL_STYLE});
204     this._sash.registerCallback(this._handleSashAdjustment, this);
205     this._list = this.createView(ZmId.VIEW_CAL_TRASH);
206     this._list.set(new AjxVector([]));
207 
208     this._populateTrashListView(this._list);
209     return this._list;
210 };
211 
212 ZmCalViewMgr.prototype._handleSashAdjustment = function(delta) {
213     // sash moved too far up
214     var sashLocation = this._sash.getLocation();
215     if (sashLocation.y + delta < ZmCalViewMgr.MIN_CONTENT_SIZE) {
216         delta = ZmCalViewMgr.MIN_CONTENT_SIZE - sashLocation.y;
217     }
218 
219     // sash moved to0 far down
220     else {
221         var size = this.getSize();
222         if (sashLocation.y + delta > size.y - ZmCalViewMgr.MIN_CONTENT_SIZE) {
223             delta = size.y - ZmCalViewMgr.MIN_CONTENT_SIZE - sashLocation.y;
224         }
225     }
226 
227     // adjust sub-content
228     if (delta != 0) {
229         var listSize = this._list.getSize();
230         this._list.setSize(listSize.x, listSize.y - delta);
231         this._layoutControls(true);
232     }
233 
234     return delta;
235 };
236 
237 ZmCalViewMgr.prototype._populateTrashListView = function(listView) {
238     var params = {
239         searchFor:ZmItem.APPT,
240         limit:20,
241         types:AjxVector.fromArray([ZmId.ITEM_APPOINTMENT]),
242         forceSearch: true,
243 //        noRender: true,
244         callback: new AjxCallback(this, this._populateTrashListViewResults, [listView])
245     };
246 
247     if (appCtxt.multiAccounts) {
248         params.query = this._multiAccTrashQuery.join(" OR ");
249         params.account = appCtxt.accountList.mainAccount.name;
250     } else {
251         params.query = "inid:"+ZmOrganizer.ID_TRASH;
252     }
253     var search = new ZmSearch(params);
254     search.execute(params);
255 };
256 
257 ZmCalViewMgr.prototype._populateTrashListViewResults = function(listView, results) {
258     var data = results && results._data;
259     var apptList = data && data._results && data._results.APPT;
260     listView.set(apptList || new AjxVector([]));
261 };
262 
263 ZmCalViewMgr.prototype.addViewActionListener =
264 function(listener) {
265 	this.addListener(ZmCalBaseView.VIEW_ACTION, listener);
266 };
267 
268 ZmCalViewMgr.prototype.removeViewActionListener = 
269 function(listener) {
270 	this.removeListener(ZmCalBaseView.VIEW_ACTION, listener);
271 };
272 
273 ZmCalViewMgr.prototype.addTimeSelectionListener = 
274 function(listener) {
275 	this.addListener(ZmCalBaseView.TIME_SELECTION, listener);
276 };
277 
278 ZmCalViewMgr.prototype.removeTimeSelectionListener = 
279 function(listener) {
280 	this.removeListener(ZmCalBaseView.TIME_SELECTION, listener);
281 };
282 
283 ZmCalViewMgr.prototype.addDateRangeListener = 
284 function(listener) {
285 	this.addListener(DwtEvent.DATE_RANGE, listener);
286 };
287 
288 ZmCalViewMgr.prototype.removeDateRangeListener = 
289 function(listener) {
290 	this.removeListener(DwtEvent.DATE_RANGE, listener);
291 };
292 
293 ZmCalViewMgr.prototype.setView =
294 function(viewName) {
295 	if (viewName != this._currentViewName) {
296 		if (this._currentViewName) {
297 			this._views[this._currentViewName].setLocation(Dwt.LOC_NOWHERE, Dwt.LOC_NOWHERE);
298 		}
299 		var view = this._views[viewName];
300 		this._currentViewName = viewName;
301 
302 		var vd = view.getDate();
303 		if (vd == null || (view.getDate().getTime() != this._date.getTime())) {
304 			view.setDate(this._date, this._duration, true);
305 		}
306 		this._layout();
307 	}
308 };
309 
310 ZmCalViewMgr.prototype._layout =
311 function() {
312     // create sub-content, if needed
313     var showSubContent = this._subContentShown;
314     if (showSubContent && !this._subContentInitialized) {
315         this._createSubContent();
316 
317         // NOTE: The list maintains its size so we can toggle back and forth
318         var size = this.getSize();
319         this._list.setSize(null, size.y / 3);
320     }
321     // Always re-populate trash list for multi-accounts
322     if (appCtxt.multiAccounts && this._subContentInitialized) {
323         this._populateTrashListView(this._list);
324     }
325 
326     // show sub-content
327     if (this._sash) {
328         this._sash.setVisible(showSubContent);
329         this._list.setVisible(showSubContent);
330     }
331 
332     // layout the controls
333     this._layoutControls();
334 };
335 
336 ZmCalViewMgr.prototype._layoutControls = function(skipSash) {
337     // size sub-content
338     var size = this.getSize();
339     var contentHeight = size.y;
340     if (this._subContentShown) {
341         var listSize = this._list.getSize();
342         var sashSize = this._sash.getSize();
343         var subContentHeight = listSize.y + sashSize.y;
344 
345         contentHeight -= subContentHeight;
346 
347         if (!skipSash) {
348             this._sash.setBounds(0, contentHeight, size.x, sashSize.y);
349         }
350         this._list.setBounds(0, contentHeight+sashSize.y, size.x, listSize.y);
351     }
352 
353     // size content
354     var view = this._views[this._currentViewName];
355     view.setBounds(0, 0, size.x, contentHeight);
356 
357     //need to reset layout for time view renderings
358     if (view instanceof ZmCalBaseView) view.layoutView();
359 };
360 
361 ZmCalViewMgr.prototype._controlListener =
362 function(ev) {
363 	if (ev.oldHeight != ev.newHeight ||
364 		ev.oldWidth != ev.newWidth)
365 	{
366 		this._layout();
367 	}
368 };
369 
370 ZmCalViewMgr.prototype._viewTimeSelectionListener =
371 function(ev) {
372 	this.notifyListeners(ZmCalBaseView.TIME_SELECTION, ev);
373 };
374 
375 
376 ZmCalViewMgr.prototype._viewActionListener =
377 function(ev) {
378 	this.notifyListeners(ZmCalBaseView.VIEW_ACTION, ev);
379 };
380 
381 ZmCalViewMgr.prototype._viewSelectionListener =
382 function(ev) {
383 	//this.notifyListeners(ZmCalBaseView.TIME_SELECTION, ev);
384 };
385 
386 ZmCalViewMgr.prototype._viewDateRangeListener =
387 function(ev) {
388 	// Notify any listeners
389 	if (this.isListenerRegistered(DwtEvent.DATE_RANGE)) {
390 		this.notifyListeners(DwtEvent.DATE_RANGE, ev);
391 	}
392 };
393