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 ZmCalDayView = function(parent, posStyle, controller, dropTgt, view, numDays, readonly, isInviteMessage, isRight) {
 25 	// Usage in ZmInviteMsgView requires a unique id - used in the conversation view,
 26 	// so multiple simultaneous instances
 27     var id = isInviteMessage ? ZmId.getViewId(ZmId.VIEW_CAL_DAY, null, view) : ZmId.VIEW_CAL_DAY;
 28 	ZmCalColView.call(this, parent, posStyle, controller, dropTgt, id, 1, false, readonly, isInviteMessage, isRight);
 29 	this._compactMode = false;
 30 };
 31 
 32 ZmCalDayView.prototype = new ZmCalColView;
 33 ZmCalDayView.prototype.constructor = ZmCalDayView;
 34 
 35 ZmCalDayView.prototype.toString =
 36 function() {
 37 	return "ZmCalDayView";
 38 };
 39 
 40 ZmCalDayView.prototype.setCompactMode =
 41 function(compactMode) {
 42 	this._compactMode = compactMode;
 43 };
 44 
 45 ZmCalDayView.prototype.isCompactMode =
 46 function() {
 47 	return this._compactMode;
 48 };
 49 
 50 ZmCalDayView.prototype.fbStatusBarEnabled =
 51 function(){
 52     return true;
 53 };
 54 
 55 ZmCalDayView.prototype._layout =
 56 function(refreshApptLayout) {
 57 	ZmCalColView.prototype._layout.call(this, refreshApptLayout);
 58 
 59 	if (this._compactMode && !this._closeButton) {
 60 		var btn = this._closeButton = new DwtButton({
 61 			parent:this,
 62 			style: DwtLabel.ALIGN_RIGHT | DwtButton.ALWAYS_FLAT,
 63 			posStyle: DwtControl.ABSOLUTE_STYLE,
 64 			className:"DwtToolbarButton cal_day_expand"
 65 		});
 66 		this._closeButton.setImage("Close");
 67 		this._closeButton.setToolTipContent(ZmMsg.close);
 68 		this._closeButton.setSize(16,16);
 69 		var size= this.getSize();
 70 		this._closeButton.setLocation(size.x-22, 0); // close button at top right corner for compact mode alone
 71 		this._closeButton.addSelectionListener(new AjxListener(this, this._closeDayViewListener));
 72 	}
 73 };
 74 
 75 ZmCalDayView.prototype._closeDayViewListener =
 76 function() {
 77 	if (this._closeDayViewCallback) {
 78 		this._closeDayViewCallback.run();
 79 	}
 80 };
 81 
 82 ZmCalDayView.prototype.setCloseDayViewCallback =
 83 function(callback) {
 84 	this._closeDayViewCallback = callback;
 85 };
 86 
 87 ZmCalDayView.prototype.setSize =
 88 function(width, height) {
 89 	ZmCalColView.prototype.setSize.call(this, width, height);
 90 	if (this._closeButton) {
 91 		this._closeButton.setLocation(width-22, 0);
 92 	}
 93 };
 94 
 95 ZmCalDayView.prototype.layout =
 96 function() {
 97     this._layout(true);
 98 }
 99 
100 ZmCalDayView.prototype._controlListener =
101 function(ev) {
102 	if (!this._compactMode) {
103 		ZmCalColView.prototype._controlListener.call(this, ev);
104 	}
105 };
106 
107 
108 ZmCalDayView.prototype._apptMouseDownAction =
109 function(ev, apptEl) {
110     appt = this.getItemFromElement(apptEl);
111     if (appt.isAllDayEvent()) {
112         return false;
113     } else {
114         return ZmCalBaseView.prototype._apptMouseDownAction.call(this, ev, apptEl, appt);
115     }
116 };
117 
118 ZmCalDayView.prototype._updateDays =
119 function() {
120     // When used from the ZmInviteMsgView, the day view requires a unique id - but
121     // underlying views count on the id being the standard ZmId.VIEW_CAL_DAY.  Since
122     // the use from ZmInviteMsgView is read-only, _updateDays is the only superclass
123     // function that we have to fool by using the standard id.
124     var viewId = this.view;
125     this.view = ZmId.VIEW_CAL_DAY;
126     ZmCalColView.prototype._updateDays.call(this);
127     this.view = viewId;
128 }
129 
130 ZmCalDayTabView = function(parent, posStyle, controller, dropTgt, view, numDays, readonly, isInviteMessage, isRight) {
131 	//ZmCalColView.call(this, parent, posStyle, controller, dropTgt, ZmId.VIEW_CAL_DAY_TAB, 1, false, readonly, isInviteMessage, isRight);
132     ZmCalColView.call(this, parent, posStyle, controller, dropTgt, ZmId.VIEW_CAL_DAY, 1, true);
133 	this._compactMode = false;
134 };
135 
136 ZmCalDayTabView.prototype = new ZmCalColView;
137 ZmCalDayTabView.prototype.constructor = ZmCalDayTabView;
138 
139 ZmCalDayTabView._ALL_DAY_APPT_HEIGHT_PAD = 3;
140 ZmCalDayTabView._UNION_DIV_WIDTH = 0;
141 
142 ZmCalDayTabView._TAB_BORDER_WIDTH = 1;
143 ZmCalDayTabView._TAB_BORDER_MARGIN = 4;
144 ZmCalDayTabView._TAB_SEP_WIDTH = 8;
145 ZmCalDayTabView._TAB_TITLE_MAX_LENGTH = 15;
146 
147 ZmCalDayTabView.ATTR_CAL_ID = "_calid";
148 
149 ZmCalDayTabView.prototype.toString =
150 function() {
151 	return "ZmCalDayTabView";
152 };
153 
154 ZmCalDayTabView.prototype.fbStatusBarEnabled =
155 function(){
156     return true;
157 };
158 
159 ZmCalDayTabView.prototype._createHtml =
160 function(abook) {
161 	this._days = {};
162 	this._columns = [];
163 	this._hours = {};
164 	this._layouts = [];
165 	this._allDayAppts = [];
166     this._allDayRows = [];
167 
168 	this._headerYearId = Dwt.getNextId();
169 	this._yearHeadingDivId = Dwt.getNextId();
170 	this._yearAllDayDivId = Dwt.getNextId();
171 	this._yearAllDayTopBorderId = Dwt.getNextId();
172 	this._yearAllDayBottomBorderId = Dwt.getNextId();
173 	this._leftAllDaySepDivId = Dwt.getNextId();
174 	this._leftApptSepDivId = Dwt.getNextId();
175 
176 	this._allDayScrollDivId = Dwt.getNextId();
177 	this._allDayHeadingDivId = Dwt.getNextId();
178 	this._allDayApptScrollDivId = Dwt.getNextId();
179 	this._allDayDivId = Dwt.getNextId();
180 	this._hoursScrollDivId = Dwt.getNextId();
181 	this._bodyHourDivId = Dwt.getNextId();
182 	this._allDaySepDivId = Dwt.getNextId();
183 	this._bodyDivId = Dwt.getNextId();
184 	this._apptBodyDivId = Dwt.getNextId();
185 	this._newApptDivId = Dwt.getNextId();
186 	this._newAllDayApptDivId = Dwt.getNextId();
187 	this._timeSelectionDivId = Dwt.getNextId();
188     this._curTimeIndicatorHourDivId = Dwt.getNextId();
189     this._curTimeIndicatorGridDivId = Dwt.getNextId();
190     this._startLimitIndicatorDivId = Dwt.getNextId();
191     this._endLimitIndicatorDivId = Dwt.getNextId();
192     this._hourColDivId = Dwt.getNextId();
193 
194     this._unionHeadingDivId = Dwt.getNextId();
195     this._unionAllDayDivId = Dwt.getNextId();
196     this._unionHeadingSepDivId = Dwt.getNextId();
197     this._unionGridScrollDivId = Dwt.getNextId();
198     this._unionGridDivId = Dwt.getNextId();
199     this._unionGridSepDivId = Dwt.getNextId();
200     this._workingHrsFirstDivId = Dwt.getNextId();
201     this._workingHrsSecondDivId = Dwt.getNextId();
202 
203     this._tabsContainerDivId = Dwt.getNextId();
204     this._toggleBtnContainerId = Dwt.getNextId();
205 
206     this._borderLeftDivId = Dwt.getNextId();
207     this._borderRightDivId = Dwt.getNextId();
208     this._borderTopDivId = Dwt.getNextId();
209     this._borderBottomDivId = Dwt.getNextId();
210     this._startLimitIndicatorDivId = Dwt.getNextId();
211     this._endLimitIndicatorDivId = Dwt.getNextId();
212 
213     var html = new AjxBuffer(),
214         // year heading
215 	    inviteMessageHeaderStyle = (this._isInviteMessage && !this._isRight ? "height:26px;" : ""), //override class css in this case, so the header height aligns with the message view on the left
216 	    headerStyle = "position:absolute;" + inviteMessageHeaderStyle,
217         func,
218         ids,
219         types,
220         i;
221 
222 	// div under year
223 	html.append("<div id='", this._yearAllDayDivId, "' name='_yearAllDayDivId' style='position:absolute;'>");
224     html.append("<div id='", this._yearHeadingDivId, "' class='calendar_heading_day_tab' name='_yearHeadingDivId' style='width:100%;height:100%;'>");
225 	html.append("<div id='", this._headerYearId,
226 		"' name='_headerYearId' class=calendar_heading_year_text style='position:absolute; width:", ZmCalColView._HOURS_DIV_WIDTH,"px;'></div>");
227 	html.append("</div>");
228     html.append("</div>");
229 
230 	// sep between year and headings
231 	html.append("<div id='", this._leftAllDaySepDivId, "' name='_leftAllDaySepDivId' class='calendar_day_separator' style='position:absolute'></div>");
232 
233     if (this._scheduleMode) {
234 
235 		// div in all day space
236 		html.append("<div id='", this._unionAllDayDivId, "' name='_unionAllDayDivId' style='position:absolute'>");
237         html.append("<div id='", this._unionHeadingDivId, "' name='_unionHeadingDivId' class=calendar_heading style='position:absolute'>");
238 		html.append("<div class=calendar_heading_year_text style='position:absolute; width:", ZmCalDayTabView._UNION_DIV_WIDTH,"px;'>",ZmMsg.allDay,"</div>");
239 		html.append("</div>");
240         html.append("</div>");
241 
242 		// sep between year and headings
243 		html.append("<div id='", this._unionHeadingSepDivId, "' name='_unionHeadingSepDivId' class='calendar_day_separator' style='position:absolute'></div>");
244 	}
245 
246 	// all day scroll	=============
247 	html.append("<div id='", this._allDayScrollDivId, "' name='_allDayScrollDivId' style='position:absolute; overflow:hidden;'>");
248 	html.append("</div>");
249 	// end of all day scroll ===========
250 
251 	// div holding all day appts
252 	html.append("<div id='", this._allDayApptScrollDivId, "' name='_allDayApptScrollDivId' class='calendar_allday_appt' style='position:absolute'>");
253 	html.append("<div id='", this._allDayDivId, "' name='_allDayDivId' style='position:absolute'>");
254 	html.append("<div id='", this._newAllDayApptDivId, "' name='_newAllDayApptDivId' class='appt-selected' style='position:absolute; display:none;'></div>");
255 	html.append("</div>");
256 	html.append("</div>");
257     // end of div holding all day appts
258 
259 	// sep betwen all day and normal appts
260 	html.append("<div id='", this._allDaySepDivId, "' name='_allDaySepDivId' style='overflow:hidden;position:absolute;'></div>");
261 
262 	// div to hold hours
263 	html.append("<div id='", this._hoursScrollDivId, "' name='_hoursScrollDivId' class=calendar_hour_scroll style='position:absolute;'>");
264 	this._createHoursHtml(html);
265 	html.append("</div>");
266     // end of div to hold hours
267 
268 	// sep between hours and grid
269 	html.append("<div id='", this._leftApptSepDivId, "' name='_leftApptSepDivId' class='calendar_day_separator' style='position:absolute'></div>");
270 
271 	// union grid
272 	if (this._scheduleMode) {
273 		html.append("<div id='", this._unionGridScrollDivId, "' name='_unionGridScrollDivId' class=calendar_union_scroll style='position:absolute;display:none;'>");
274 		html.append("<div id='", this._unionGridDivId, "' name='_unionGridDivId' class='ImgCalendarDayGrid' style='width:100%; height:1008px; position:absolute;'>");
275 		html.append("</div></div>");
276 		// sep between union grid and appt grid
277 		html.append("<div id='", this._unionGridSepDivId, "' name='_unionGridSepDivId' class='calendar_day_separator' style='position:absolute;display:none;'></div>");
278 	}
279 
280 	// grid body
281     // Fix for bug: 66603. Removed horizontal scroll bar from grid body
282 	html.append("<div id='", this._bodyDivId, "' name='_bodyDivId' class=calendar_body style='position:absolute; overflow-x:hidden;'>");
283     html.append("<div id='", this._apptBodyDivId, "' name='_apptBodyDivId' class='ImgCalendarDayGrid' style='width:100%; height:1008px; position:absolute;background-color:#E3E3DC;'>");
284 	html.append("<div id='", this._timeSelectionDivId, "' name='_timeSelectionDivId' class='calendar_time_selection' style='position:absolute; display:none;z-index:10;'></div>");
285 	html.append("<div id='", this._newApptDivId, "' name='_newApptDivId' class='appt-selected' style='position:absolute; display:none;'></div>");
286 
287     html.append("<div id='", this._workingHrsFirstDivId, "' style='position:absolute;background-color:#FFFFFF;'><div class='ImgCalendarDayGrid' id='", this._workingHrsFirstChildDivId, "' style='position:absolute;top:0px;left:0px;overflow:hidden;'></div></div>");
288     html.append("<div id='", this._workingHrsSecondDivId, "' style='position:absolute;background-color:#FFFFFF;'><div class='ImgCalendarDayGrid' id='", this._workingHrsSecondChildDivId, "' style='position:absolute;top:0px;left:0px;overflow:hidden;'></div></div>");
289 
290     html.append("<div id='", this._borderLeftDivId, "' name='_borderLeftDivId' class='ZmDayTabSeparator' style='background-color:#FFFFFF;position:absolute;'></div>");
291     html.append("<div id='", this._borderRightDivId, "' name='_borderRightDivId' class='ZmDayTabSeparator' style='background-color:#FFFFFF;position:absolute;'></div>");
292     html.append("<div id='", this._borderTopDivId, "' name='_borderTopDivId' class='ZmDayTabSeparator' style='background-color:#FFFFFF;position:absolute;'></div>");
293     html.append("<div id='", this._borderBottomDivId, "' name='_borderBottomDivId' class='ZmDayTabSeparator' style='background-color:#FFFFFF;position:absolute;'></div>");
294 	html.append("</div>");
295     // end of grid body
296 
297     //Strip to indicate the current time
298     html.append("<div id='"+this._curTimeIndicatorGridDivId+"' name='_curTimeIndicatorGridDivId' class='calendar_cur_time_indicator_strip' style='position:absolute;background-color:#F16426; height: 1px;'></div>");
299     //arrow to indicate the off-screen appointments
300     html.append("<div id='"+this._startLimitIndicatorDivId+"' class='calendar_start_limit_indicator'><div class='ImgArrowMoreUp'></div></div>");
301     html.append("<div id='"+this._endLimitIndicatorDivId+"' class='calendar_end_limit_indicator'><div class='ImgArrowMoreDown'></div></div>");
302 
303     //html.append("<div id='"+this._curTimeIndicatorGridDivId+"' name='_curTimeIndicatorGridDivId' class='calendar_cur_time_indicator_strip' style='position:absolute;background-color:#F16426; height: 1px;'></div>");
304 	html.append("</div>");
305 
306     // all day headings
307     // Fix for bug: 66603. Separating merge/split button from tab container
308     html.append("<div id='", this._toggleBtnContainerId, "' name='_toggleBtnContainerId' style='position:absolute;bottom:0px;'></div>");
309     // Fix for bug: 66603. Hide the overflow
310 	html.append("<div id='", this._tabsContainerDivId, "' name='_tabsContainerDivId' style='position:absolute;height:45px;bottom:0px;overflow-y:hidden;'>");
311 	html.append("<div id='", this._allDayHeadingDivId, "' name='_allDayHeadingDivId' style='", headerStyle,	"'></div>");
312 	html.append("</div>");
313     // end of all day headings
314 
315 	this.getHtmlElement().innerHTML = html.toString();
316     func = AjxCallback.simpleClosure(ZmCalColView.__onScroll, ZmCalColView, this);
317 	document.getElementById(this._bodyDivId).onscroll = func;
318 	document.getElementById(this._allDayApptScrollDivId).onscroll = func;
319     // Fix for bug: 66603. Attaching a scroll function.
320     document.getElementById(this._tabsContainerDivId).onscroll = func;
321 
322 	ids = [this._apptBodyDivId, this._bodyHourDivId, this._allDayDivId, this._allDaySepDivId];
323 	types = [ZmCalBaseView.TYPE_APPTS_DAYGRID, ZmCalBaseView.TYPE_HOURS_COL, ZmCalBaseView.TYPE_ALL_DAY, ZmCalBaseView.TYPE_DAY_SEP];
324 	for (i = 0; i < ids.length; i++) {
325 		this.associateItemWithElement(null, document.getElementById(ids[i]), types[i], ids[i]);
326 	}
327 	this._scrollToTime(8);
328 };
329 
330 ZmCalDayTabView.prototype._layout =
331 function(refreshApptLayout) {
332 	DBG.println(AjxDebug.DBG2, "ZmCalColView in layout!");
333 	this._updateDays();
334 
335 	var numCols = this._columns.length,
336         sz = this.getSize(),
337         width = sz.x,
338         height = sz.y,
339         hoursWidth = ZmCalColView._HOURS_DIV_WIDTH-1,
340         bodyX = hoursWidth + this._daySepWidth,
341         bodyY,
342         unionX = ZmCalColView._HOURS_DIV_WIDTH,
343         needHorzScroll,
344         scrollFudge,
345         allDayHeadingDiv = document.getElementById(this._allDayHeadingDivId),
346         allDayHeadingDivHeight = Dwt.getSize(allDayHeadingDiv).y,
347         numRows = this._allDayApptsRowLayouts ? (this._allDayApptsRowLayouts.length) : 1,
348         percentageHeight,
349         nearestNoOfRows,
350         allDayScrollHeight,
351         unionSepX;
352 
353 	if (width == 0 || height == 0) { return; }
354 
355     height -= 25;
356 	this._needFirstLayout = false;
357 	bodyX += this._daySepWidth + ZmCalDayTabView._TAB_SEP_WIDTH;
358 
359 	// compute height for hours/grid
360 	this._bodyDivWidth = width - bodyX - ZmCalDayTabView._TAB_SEP_WIDTH;
361 
362 	// size appts divs
363 	this._apptBodyDivHeight = ZmCalColView._DAY_HEIGHT + 1; // extra for midnight to show up
364 	this._apptBodyDivWidth = Math.max(this._bodyDivWidth, this._calcMinBodyWidth(this._bodyDivWidth, numCols));
365 	needHorzScroll = this._apptBodyDivWidth > this._bodyDivWidth;
366 
367 	this._horizontalScrollbar(needHorzScroll);
368 
369 	if (needHorzScroll) this._apptBodyDivWidth -= 18;
370 	scrollFudge = needHorzScroll ? 20 : 0; // need all day to be a little wider then grid
371 
372     if(!this._toggleBtn) {
373         this._setBounds(this._toggleBtnContainerId, 0, Dwt.DEFAULT, hoursWidth+ZmCalDayTabView._UNION_DIV_WIDTH+this._daySepWidth, Dwt.DEFAULT);
374         this._toggleBtn = new DwtButton({parent:this, parentElement: this._toggleBtnContainerId, className: "ZButton ZPicker ZCalToggleBtn"});
375         this._toggleBtn.setText(ZmMsg.calTabsMerge);
376         this._toggleBtn.addListener(DwtEvent.ONCLICK, new AjxListener(this, this._toggleView));
377     }
378 
379 	// column headings
380     // Fix for bug: 66603. Position - X set to 0 to adjust the scrolling and Position - Y to 2px.
381 	Dwt.setBounds(allDayHeadingDiv, 0, 2, this._apptBodyDivWidth, Dwt.DEFAULT);
382 	// div for all day appts
383 	if (this._allDayApptsList && this._allDayApptsList.length > 0) {
384         numRows++;
385     }
386 	this._allDayFullDivHeight = (ZmCalColView._ALL_DAY_APPT_HEIGHT+ZmCalDayTabView._ALL_DAY_APPT_HEIGHT_PAD) * numRows + ZmCalDayTabView._ALL_DAY_APPT_HEIGHT_PAD;
387 
388 	percentageHeight = (this._allDayFullDivHeight/height)*100;
389 	this._allDayDivHeight = this._allDayFullDivHeight;
390 
391 	// if height overflows more than 50% of full height set its height
392 	// to nearest no of rows which occupies less than 50% of total height
393 	if (percentageHeight > 50) {
394 		nearestNoOfRows = Math.floor((0.50*height-ZmCalDayTabView._ALL_DAY_APPT_HEIGHT_PAD)/(ZmCalColView._ALL_DAY_APPT_HEIGHT+ZmCalDayTabView._ALL_DAY_APPT_HEIGHT_PAD));
395 		this._allDayDivHeight = (ZmCalColView._ALL_DAY_APPT_HEIGHT+ZmCalDayTabView._ALL_DAY_APPT_HEIGHT_PAD) * nearestNoOfRows + ZmCalDayTabView._ALL_DAY_APPT_HEIGHT_PAD;
396 	}
397 
398 	this._setBounds(this._allDayApptScrollDivId, bodyX, allDayHeadingDivHeight+ZmCalDayTabView._TAB_BORDER_MARGIN, this._bodyDivWidth, this._allDayDivHeight+ZmCalDayTabView._TAB_BORDER_MARGIN);
399 	this._setBounds(this._allDayDivId, 0, 0, this._apptBodyDivWidth + scrollFudge, this._allDayFullDivHeight+ZmCalDayTabView._TAB_BORDER_MARGIN);
400 
401     // Fix for bug: 66603. Set the position-X, width and height for heading container.
402     this._setBounds(this._tabsContainerDivId, bodyX, Dwt.DEFAULT, this._bodyDivWidth, scrollFudge !== 0 ? 45 : 25);
403 
404 	this._allDayVerticalScrollbar(this._allDayDivHeight != this._allDayFullDivHeight);
405 
406 	// div under year
407 	this._setBounds(this._yearAllDayDivId, 0, ZmCalDayTabView._TAB_BORDER_MARGIN, hoursWidth + ZmCalDayTabView._UNION_DIV_WIDTH + this._daySepWidth-1, this._allDayDivHeight);
408     //this._setBounds(this._yearHeadingDivId, 0, this._daySepWidth-1, hoursWidth + ZmCalDayTabView._UNION_DIV_WIDTH + this._daySepWidth, ZmCalColView._ALL_DAY_APPT_HEIGHT+ZmCalDayTabView._TAB_BORDER_MARGIN+1);
409 	// all day scroll
410 	allDayScrollHeight =  this._allDayDivHeight;
411 	this._setBounds(this._allDayScrollDivId, bodyX, 0, this._bodyDivWidth, allDayScrollHeight);
412 
413 	// horiz separator between all day appts and grid
414 	this._setBounds(this._allDaySepDivId, 0, (this._hideAllDayAppt ? ZmCalColView._DAY_HEADING_HEIGHT : allDayScrollHeight)+2, width, ZmCalColView._ALL_DAY_SEP_HEIGHT);
415 
416 	bodyY =  (this._hideAllDayAppt ? ZmCalColView._DAY_HEADING_HEIGHT : allDayScrollHeight) + ZmCalColView._ALL_DAY_SEP_HEIGHT +  (AjxEnv.isIE ? 0 : 2);
417 
418     // Fix for bug: 66603. Adjusts the height of grid body.
419 	this._bodyDivHeight = height - bodyY - scrollFudge;
420 
421 	// hours
422 	this._setBounds(this._hoursScrollDivId, 0, bodyY, hoursWidth, this._bodyDivHeight);
423 
424 	// vert sep between hours and grid
425 	this._setBounds(this._leftApptSepDivId, hoursWidth, bodyY-ZmCalDayTabView._TAB_BORDER_WIDTH, this._daySepWidth, ZmCalColView._DAY_HEIGHT);
426 
427 	// div for scrolling grid
428 	this._setBounds(this._bodyDivId, bodyX, bodyY, this._bodyDivWidth, this._bodyDivHeight);
429 
430 	this._setBounds(this._apptBodyDivId, 0, -1, this._apptBodyDivWidth, this._apptBodyDivHeight);
431 
432     // sep in all day area
433     unionSepX = unionX + ZmCalDayTabView._UNION_DIV_WIDTH;
434     this._setBounds(this._unionHeadingSepDivId, unionSepX, ZmCalDayTabView._TAB_BORDER_MARGIN, this._daySepWidth-1, allDayScrollHeight+1);
435 
436     // div for scrolling union
437     this._setBounds(this._unionGridScrollDivId, unionX, bodyY, ZmCalDayTabView._UNION_DIV_WIDTH, this._bodyDivHeight);
438     this._setBounds(this._unionGridDivId, 0, -1, ZmCalDayTabView._UNION_DIV_WIDTH, this._apptBodyDivHeight+ZmCalColView._HOUR_HEIGHT);
439 
440     // sep in grid area
441     this._setBounds(this._unionGridSepDivId, unionSepX, bodyY-ZmCalDayTabView._TAB_BORDER_MARGIN, this._daySepWidth, this._apptBodyDivHeight);
442 
443     this._bodyX = bodyX;
444     this.layoutWorkingHours(this.workingHours);
445 	this._layoutAllDayAppts();
446 
447     this._apptBodyDivOffset   = Dwt.toWindow(document.getElementById(this._apptBodyDivId), 0, 0, null, true);
448     this._apptAllDayDivOffset = Dwt.toWindow(document.getElementById(this._allDayDivId), 0, 0, null, true);
449 
450 
451 	this._layoutAppts();
452     this._layoutUnionData();
453 
454 };
455 
456 ZmCalDayTabView.prototype.layoutWorkingHours =
457 function(workingHours){
458     if(!workingHours) {
459         workingHours = ZmCalBaseView.parseWorkingHours(ZmCalBaseView.getWorkingHours());
460         this.workingHours = workingHours;
461     }
462     var numCols = this._columns.length;
463     var dayWidth = this._calcColWidth(this._apptBodyDivWidth - Dwt.SCROLLBAR_WIDTH, numCols);
464 
465     var allDayHeadingDiv = document.getElementById(this._allDayHeadingDivId);
466 	var allDayHeadingDivHeight = Dwt.getSize(allDayHeadingDiv).y;
467 
468     var currentX = 0;
469     var topBorderYPos = AjxEnv.isIE ? ZmCalDayTabView._TAB_BORDER_WIDTH : ZmCalColView._ALL_DAY_SEP_HEIGHT-ZmCalDayTabView._TAB_BORDER_WIDTH;
470 
471 	for (var i = 0; i < numCols; i++) {
472 		var col = this._columns[i];
473 
474 		// position day heading
475 		var day = this._days[col.dayIndex];
476         // Fix for bug: 66603. Adjust position X & Y calendar title bubble
477 		this._setBounds(col.titleId, currentX, Dwt.DEFAULT, dayWidth-ZmCalDayTabView._TAB_BORDER_MARGIN, ZmCalColView._DAY_HEADING_HEIGHT);
478 		col.apptX = currentX + 2 ; //ZZZ
479 		col.apptWidth = dayWidth - 3*this._daySepWidth - ZmCalDayTabView._TAB_SEP_WIDTH;  //ZZZZ
480 		col.allDayX = col.apptX;
481 		col.allDayWidth = dayWidth - ZmCalDayTabView._TAB_SEP_WIDTH; // doesn't include sep
482 
483         //split into half hrs sections
484         var dayIndex = day.date.getDay(),
485             workingHrs = this.workingHours[dayIndex],
486             pos = this.getPostionForWorkingHourDiv(dayIndex, 0);
487 
488         if(this._scheduleMode && day.isWorkingDay) {
489             this.layoutWorkingHoursDiv(col.workingHrsFirstDivId, pos, currentX, dayWidth-ZmCalDayTabView._TAB_BORDER_MARGIN);
490             if( workingHrs.startTime.length >= 2 &&
491                 workingHrs.endTime.length >= 2) {
492 
493                 pos = this.getPostionForWorkingHourDiv(dayIndex, 1);
494                 this.layoutWorkingHoursDiv(col.workingHrsSecondDivId, pos, currentX, dayWidth-ZmCalDayTabView._TAB_BORDER_MARGIN);
495 
496             }
497         }
498         //set tab borders
499         this._setBounds(col.borderTopDivId, currentX+this._bodyX, topBorderYPos, dayWidth-ZmCalDayTabView._TAB_BORDER_MARGIN, Dwt.CLEAR);
500         // Fix for bug: 66603. Adjust position X of title border separator
501         this._setBounds(col.borderBottomDivId, currentX, 0, dayWidth-ZmCalDayTabView._TAB_BORDER_MARGIN, Dwt.CLEAR);
502         this._setBounds(col.borderLeftDivId, currentX, 0, ZmCalDayTabView._TAB_BORDER_WIDTH, this._apptBodyDivHeight);
503         this._setBounds(col.borderRightDivId, currentX+dayWidth-ZmCalDayTabView._TAB_BORDER_WIDTH-ZmCalDayTabView._TAB_BORDER_MARGIN, 0, ZmCalDayTabView._TAB_BORDER_WIDTH, this._apptBodyDivHeight);
504 
505         this._setBounds(col.borderLeftAllDayDivId, currentX, 0, ZmCalDayTabView._TAB_BORDER_WIDTH, this._allDayDivHeight+ZmCalDayTabView._TAB_BORDER_WIDTH+1);
506         this._setBounds(col.borderTopAllDayDivId, currentX, 0, dayWidth-ZmCalDayTabView._TAB_BORDER_MARGIN, Dwt.CLEAR);
507         this._setBounds(col.borderRightAllDayDivId, currentX+dayWidth-ZmCalDayTabView._TAB_BORDER_WIDTH-ZmCalDayTabView._TAB_BORDER_MARGIN, 0, ZmCalDayTabView._TAB_BORDER_WIDTH, this._allDayDivHeight+ZmCalDayTabView._TAB_BORDER_WIDTH+1);
508 
509         currentX += dayWidth;
510 
511         if (i == numCols-1) {
512             //If the border div is last border div add 6 to the width
513             this._setBounds(col.daySepDivId, currentX-ZmCalDayTabView._TAB_BORDER_MARGIN, 0, ZmCalDayTabView._TAB_SEP_WIDTH+6, this._apptBodyDivHeight);
514         }
515         else {
516 		    this._setBounds(col.daySepDivId, currentX-ZmCalDayTabView._TAB_BORDER_MARGIN, 0, ZmCalDayTabView._TAB_SEP_WIDTH-1, this._apptBodyDivHeight);
517         }
518 
519 		currentX += this._daySepWidth;
520 	}
521 };
522 
523 ZmCalDayTabView.prototype._toggleView =
524 function() {
525     if(!this._mergedView) {
526         this._mergedView = true;
527         this._toggleBtn.setText(ZmMsg.calTabsSplit);
528     }
529     else {
530         this._mergedView = false;
531         this._toggleBtn.setText(ZmMsg.calTabsMerge);
532     }
533     this.set(this._list, null, true);
534 };
535 
536 ZmCalDayTabView.prototype._resetCalendarData =
537 function() {
538 	// TODO: optimize: if calendar list is same, skip!
539     var titleParentEl = document.getElementById(this._allDayHeadingDivId),
540         dayParentEl = document.getElementById(this._apptBodyDivId),
541         allDaySepEl = document.getElementById(this._allDaySepDivId),
542         allDayDivEl = document.getElementById(this._allDayDivId),
543         cal,
544          calColor,
545         mergedCal,
546         calMergerdTabColor,
547         col,
548         html,
549         calId,
550         div,
551         i,
552         k;
553 	// remove existing
554 	// TODO: optimize, add/remove depending on new calendar length
555 	if (this._numCalendars > 0) {
556 		for (i = 0; i < this._numCalendars; i++) {
557 			col = this._columns[i];
558 			this._removeNode(col.titleId);
559 			//this._removeNode(col.headingDaySepDivId);
560 			this._removeNode(col.daySepDivId);
561 			this._removeNode(col.borderBottomDivId);
562 			this._removeNode(col.borderLeftDivId);
563 			this._removeNode(col.borderRightDivId);
564 			this._removeNode(col.borderTopDivId);
565 			this._removeNode(col.borderLeftAllDayDivId);
566 			this._removeNode(col.borderTopAllDayDivId);
567 			this._removeNode(col.borderRightAllDayDivId);
568             this._removeNode(col.workingHrsFirstChildDivId);
569 			this._removeNode(col.workingHrsSecondChildDivId);
570             this._removeNode(col.workingHrsFirstDivId);
571 			this._removeNode(col.workingHrsSecondDivId);
572 		}
573 	}
574 
575 	this._calendars = this._controller.getCheckedCalendars();
576 	this._calendars.sort(ZmFolder.sortCompareNonMail);
577 	this._folderIdToColIndex = {};
578 	this._columns = [];
579 	this._numCalendars = this._mergedView ? 1 : this._calendars.length;
580 
581 	this._layoutMap = [];
582 	this._unionBusyData = []; 			//  0-47, one slot per half hour, 48 all day
583 	this._unionBusyDataToolTip = [];	// tool tips
584 
585 	for (i = 0; i < this._numCalendars; i++) {
586         cal = this._calendars[i];
587         calId = cal.id ? cal.id : "";
588 		col = this._columns[i] = {
589 			index: i,
590 			dayIndex: 0,
591 			cal: cal,
592 			titleId: Dwt.getNextId(),
593 			headingDaySepDivId: Dwt.getNextId(),
594 			daySepDivId: Dwt.getNextId(),
595             workingHrsFirstDivId: Dwt.getNextId(),
596             workingHrsSecondDivId: Dwt.getNextId(),
597 			apptX: 0, 		// computed in layout
598 			apptWidth: 0,	// computed in layout
599 			allDayX: 0, 	// computed in layout
600 			allDayWidth: 0,	// computed in layout
601             borderLeftDivId: Dwt.getNextId(),
602             borderRightDivId: Dwt.getNextId(),
603             borderTopDivId: Dwt.getNextId(),
604             borderBottomDivId: Dwt.getNextId(),
605             borderLeftAllDayDivId: Dwt.getNextId(),
606             borderTopAllDayDivId: Dwt.getNextId(),
607             borderRightAllDayDivId: Dwt.getNextId(),
608             workingHrsFirstChildDivId: Dwt.getNextId(),
609             workingHrsSecondChildDivId: Dwt.getNextId()
610 		};
611         calColor = this._mergedView ? "" : cal.rgb;
612 		this._folderIdToColIndex[cal.id] = col;
613 		if (cal.isRemote() && cal.rid && cal.zid) {
614 			this._folderIdToColIndex[cal.zid + ":" + cal.rid] = col;
615 		}
616 
617         this._createDivForColumn(col.workingHrsFirstDivId, dayParentEl, "", "#FFFFFF");
618         div = this._createDivForColumn(col.workingHrsFirstChildDivId, col.workingHrsFirstDivId, "ImgCalendarDayGrid");
619         div.setAttribute(ZmCalDayTabView.ATTR_CAL_ID, calId);
620         this._createDivForColumn(col.workingHrsSecondDivId, dayParentEl, "", "#FFFFFF");
621         div = this._createDivForColumn(col.workingHrsSecondChildDivId, col.workingHrsSecondDivId, "ImgCalendarDayGrid");
622         div.setAttribute(ZmCalDayTabView.ATTR_CAL_ID, calId);
623         this._createDivForColumn(col.borderBottomDivId, titleParentEl, "ZmDayTabSeparator", calColor, calColor);
624 
625         // Fix for bug: 66603. The class adjusts width of calendar title bubbles
626         div = this._createDivForColumn(col.titleId, titleParentEl, this._mergedView ? "" : "ZmCalDayTab ZmCalDayMerged", calColor, calColor);
627         div.style.top = ZmCalDayTabView._TAB_BORDER_WIDTH + 'px';
628 
629         // Fix for bug: 84268. Removed calendar titles from the merged view.
630         if (!this._mergedView) {
631             div.style.top = ZmCalDayTabView._TAB_BORDER_WIDTH + 'px';
632 			div.style.color = AjxUtil.getForegroundColor(calColor);
633             div.innerHTML = cal.getName();
634         }
635 
636         this._createDivForColumn(col.borderLeftAllDayDivId, allDayDivEl, "ZmDayTabSeparator", calColor, calColor);
637         this._createDivForColumn(col.borderTopAllDayDivId, allDayDivEl, "ZmDayTabSeparator", calColor, calColor);
638         this._createDivForColumn(col.borderRightAllDayDivId, allDayDivEl, "ZmDayTabSeparator", calColor, calColor);
639         this._createDivForColumn(col.daySepDivId, dayParentEl, "ZmDayTabMarginDiv");
640 		this._createDivForColumn(col.borderLeftDivId, dayParentEl, "ZmDayTabSeparator", calColor, calColor);
641         this._createDivForColumn(col.borderRightDivId, dayParentEl, "ZmDayTabSeparator", calColor, calColor);
642         this._createDivForColumn(col.borderTopDivId, allDaySepEl, "ZmDayTabSeparator", calColor, calColor);
643 
644 	}
645 };
646 
647 ZmCalDayTabView.prototype._createDivForColumn =
648 function(id, parentEl, className, bgColor, borderColor, position, isSpan, calId) {
649     var div = document.createElement(isSpan ? "span" : "div");
650     div.className = className ? className : "";
651     div.id = id;
652     div.style.position = position ? position : 'absolute';
653     if(bgColor) { div.style.backgroundColor = bgColor; }
654     if(borderColor) { div.style.borderColor = borderColor; }
655     if(parentEl) {
656         parentEl = typeof parentEl === "string" ? document.getElementById(parentEl) : parentEl;
657         parentEl.appendChild(div);
658     }
659     return div;
660 };
661 
662 
663 ZmCalDayTabView.prototype._layoutAllDayAppts =
664 function() {
665 	var rows = this._allDayApptsRowLayouts;
666 	if (!rows) { return; }
667 
668 	var rowY = ZmCalColView._ALL_DAY_APPT_HEIGHT_PAD + 2;
669 	for (var i=0; i < rows.length; i++) {
670 		var row = rows[i];
671 		var num = this._mergedView ? 1 : this._numCalendars;
672 		for (var j=0; j < num; j++) {
673 			var slot = row[j];
674 			if (slot.data) {
675 				var appt = slot.data.appt;
676                 var div = document.getElementById(this._getItemId(appt));
677                 if(div) {
678                     if (!this._mergedView) {
679                         var cal = this._getColForFolderId(appt.folderId);
680                         this._positionAppt(div, cal.allDayX+0, rowY);
681                         this._sizeAppt(div, (cal.allDayWidth + this._daySepWidth) - this._daySepWidth - 1 - ZmCalDayTabView._TAB_SEP_WIDTH,
682                                      ZmCalColView._ALL_DAY_APPT_HEIGHT);
683                     } else {
684                         this._positionAppt(div, this._columns[j].allDayX+0, rowY);
685                         this._sizeAppt(div, ((this._columns[j].allDayWidth + this._daySepWidth) * slot.data.numDays) - this._daySepWidth - 1 - ZmCalDayTabView._TAB_SEP_WIDTH,
686                                      ZmCalColView._ALL_DAY_APPT_HEIGHT);
687                     }
688                 }
689 			}
690 		}
691 		rowY += ZmCalColView._ALL_DAY_APPT_HEIGHT + ZmCalColView._ALL_DAY_APPT_HEIGHT_PAD;
692 	}
693 };
694 
695 ZmCalDayTabView.prototype._getBoundsForAppt =
696 function(appt) {
697 	var sd = appt.startDate;
698 	var endOfDay = new Date(sd);
699 	endOfDay.setHours(23,59,59,999);
700 	var et = Math.min(appt.getEndTime(), endOfDay.getTime());
701 	if (!this._mergedView)
702 		return this._getBoundsForCalendar(sd, et - sd.getTime(), appt.folderId);
703 	else
704 		return this._getBoundsForDate(sd, et - sd.getTime());
705 };
706 
707 ZmCalDayTabView.prototype._getBoundsForDate =
708 function(d, duration, col) {
709 	var durationMinutes = duration / 1000 / 60;
710 	durationMinutes = Math.max(durationMinutes, 22);
711 	var h = d.getHours();
712 	var m = d.getMinutes();
713 	if (col == null) {
714 		var day = this._getDayForDate(d);
715 		col = day ? this._columns[day.index] : null;
716 	}
717 	if (col == null) return null;
718 	return new DwtRectangle(col.apptX, ((h+m/60) * ZmCalColView._HOUR_HEIGHT),
719 					col.apptWidth, (ZmCalColView._HOUR_HEIGHT / 60) * durationMinutes);
720 };
721 
722 ZmCalDayTabView.prototype._resetList =
723 function() {
724 	var list = this.getList();
725 	var size = list ? list.size() : 0;
726 	if (size == 0) return;
727 
728 	for (var i=0; i < size; i++) {
729 		var ao = list.get(i);
730 		var id = this._getItemId(ao);
731 		var appt = document.getElementById(id);
732 		if (appt) {
733 			appt.parentNode.removeChild(appt);
734 			this._data[id] = null;
735 		}
736 	}
737 	//list.removeAll();
738 	this.removeAll();
739 };
740 
741 ZmCalDayTabView.prototype._computeAllDayApptLayout =
742 function() {
743 	var adlist = this._allDayApptsList;
744 	adlist.sort(ZmCalBaseItem.compareByTimeAndDuration);
745 
746 	for (var i=0; i < adlist.length; i++) {
747 		var appt = adlist[i];
748 		var data = this._allDayAppts[appt.getUniqueId()];
749 		if (data) {
750 			var col = this._mergedView ? this._columns[0] : this._getColForFolderId(data.appt.folderId);
751 			if (col)	 this._findAllDaySlot(col.index, data);
752 		}
753 	}
754 };
755 
756 ZmCalDayTabView.prototype._layoutUnionDataDiv =
757 function(gridEl, allDayEl, i, data, numCols) {
758 	var enable = data instanceof Object;
759 	var id = this._unionBusyDivIds[i];
760 	var divEl = null;
761 
762 	if (id == null) {
763 		if (!enable) { return; }
764 		id = this._unionBusyDivIds[i] = Dwt.getNextId();
765 		var divEl = document.createElement("div");
766 		divEl.style.position = 'absolute';
767 		divEl.className = "calendar_sched_union_div";
768 		this.associateItemWithElement(null, divEl, ZmCalBaseView.TYPE_SCHED_FREEBUSY, id, {index:i});
769 
770 		Dwt.setOpacity(divEl, 40);
771 
772 		if (i == 48) {
773 			//have to resize every layout, since all day div height might change
774 			allDayEl.appendChild(divEl);
775 		} else {
776 			// position/size once right here!
777 			Dwt.setBounds(divEl, 1, ZmCalColView._HALF_HOUR_HEIGHT*i+1, ZmCalDayTabView._UNION_DIV_WIDTH-2 , ZmCalColView._HALF_HOUR_HEIGHT-2);
778 			gridEl.appendChild(divEl);
779 		}
780 
781 	} else {
782 		divEl =  document.getElementById(id);
783 	}
784 	// have to relayout each time
785 	//if (i == 48)	Dwt.setBounds(divEl, 1, 1, ZmCalColView._UNION_DIV_WIDTH-2, this._allDayDivHeight-2);
786 
787 	var num = 0;
788 	for (var key in data) num++;
789 
790 	Dwt.setOpacity(divEl, 20 + (60 * (num/numCols)));
791 	Dwt.setVisibility(divEl, enable);
792 };
793 
794 /*
795  * compute appt layout for appts that aren't all day
796  */
797 ZmCalDayTabView.prototype._computeApptLayout =
798 function() {
799 //	DBG.println("_computeApptLayout");
800 //	DBG.timePt("_computeApptLayout: start", true);
801 	var layouts = this._layouts = new Array();
802 	var layoutsDayMap = [];
803 	var layoutsAllDay = [];
804 	var list = this.getList();
805 	if (!list) return;
806 
807 	var size = list.size();
808 	if (size == 0) { return; }
809 
810 	var overlap = null;
811 	var overlappingCol = null;
812 
813 	for (var i=0; i < size; i++) {
814 		var ao = list.get(i);
815 
816 		if (ao.isAllDayEvent()) {
817 			continue;
818 		}
819 
820 		var newLayout = { appt: ao, col: 0, maxcol: -1};
821 
822 		overlap = null;
823 		overlappingCol = null;
824 
825 		var asd = ao.startDate;
826 		var aed = ao.endDate;
827 
828 		var asdDate = asd.getDate();
829 		var aedDate = aed.getDate();
830 
831 		var checkAllLayouts = (asdDate != aedDate);
832 		var layoutCheck = [];
833 
834 		// if a appt starts n end in same day, it should be compared only with
835 		// other appts on same day and with those which span multiple days
836 		if (checkAllLayouts) {
837 			layoutCheck.push(layouts);
838 		} else {
839 			layoutCheck.push(layoutsAllDay);
840 			if (layoutsDayMap[asdDate]!=null) {
841 				layoutCheck.push(layoutsDayMap[asdDate]);
842 			}
843 		}
844 
845 		// look for overlapping appts
846 		for (var k = 0; k < layoutCheck.length; k++) {
847 			for (var j=0; j < layoutCheck[k].length; j++) {
848 				var layout = layoutCheck[k][j];
849 				if (ao.isOverlapping(layout.appt, !this._mergedView)) {
850 					if (overlap == null) {
851 						overlap = [];
852 						overlappingCol = [];
853 					}
854 					overlap.push(layout);
855 					overlappingCol[layout.col] = true;
856 					// while we overlap, update our col
857 					while (overlappingCol[newLayout.col]) {
858 						newLayout.col++;
859 					}
860 				}
861 			}
862 		}
863 
864 		// figure out who is on our right
865 		if (overlap != null) {
866 			for (var c in overlap) {
867 				var l = overlap[c];
868 				if (newLayout.col < l.col) {
869 					if (!newLayout.right) newLayout.right = [l];
870 					else newLayout.right.push(l);
871 				} else {
872 					if (!l.right) l.right = [newLayout];
873 					else l.right.push(newLayout);
874 				}
875 			}
876 		}
877 		layouts.push(newLayout);
878 		if (asdDate == aedDate) {
879 			if(!layoutsDayMap[asdDate]) {
880 				layoutsDayMap[asdDate] = [];
881 			}
882 			layoutsDayMap[asdDate].push(newLayout);
883 		} else {
884 			layoutsAllDay.push(newLayout);
885 		}
886 	}
887 
888 	// compute maxcols
889 	for (var i=0; i < layouts.length; i++) {
890 		this._computeMaxCols(layouts[i], -1);
891 		this._layoutMap[this._getItemId(layouts[i].appt)]  = layouts[i];
892 //		DBG.timePt("_computeApptLayout: computeMaxCol "+i, false);
893 	}
894 
895 	delete layoutsAllDay;
896 	delete layoutsDayMap;
897 	delete layoutCheck;
898 	//DBG.timePt("_computeApptLayout: end", false);
899 };
900