1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 ZmCalColView = function(parent, posStyle, controller, dropTgt, view, numDays, scheduleMode, readonly, isInviteMessage, isRight) {
 25 	if (arguments.length == 0) { return; }
 26 
 27 	view = view || ZmId.VIEW_CAL_DAY;
 28 	// set before call to parent
 29 	this._scheduleMode = scheduleMode;
 30     var workingHours = ZmCalBaseView.parseWorkingHours(ZmCalBaseView.getWorkingHours());
 31     if (!numDays && view === ZmId.VIEW_CAL_WORK_WEEK) {
 32         // Edge Case:   Work week is selected but all the days are configured as non working days,
 33         //              Fall back to week view by faking all the days are working days with same start and end time
 34         for (var i=0; i<workingHours.length; i++) {
 35             if (!workingHours[i].isWorkingDay) {
 36                 workingHours[i].isWorkingDay = true;
 37             }
 38         }
 39         numDays = 7;
 40         var msgDlg = appCtxt.getMsgDialog();
 41         msgDlg.setMessage(ZmMsg.emptyWorkingHoursWarning, DwtMessageDialog.WARNING_STYLE);
 42         msgDlg.popup();
 43         var listener = msgDlg.popdown.bind(msgDlg);
 44         msgDlg.setButtonListener(DwtDialog.OK_BUTTON, listener);
 45     }
 46     this.workingHours = workingHours;
 47 
 48 	this.numDays = numDays || 1;
 49 	this._daySepWidth = 2;														// width of separator between days
 50 	this._columns = [];
 51 	this._layoutMap = [];
 52 	this._unionBusyDivIds = [];													// div ids for layingout union
 53     this._fbBarEnabled = this.fbStatusBarEnabled();
 54 
 55 	//we need special alignment for this case.
 56 	this._isInviteMessage = isInviteMessage;
 57 	this._isRight = isRight;
 58 
 59 	ZmCalBaseView.call(this, parent, "calendar_view", posStyle, controller, view, readonly);
 60 	var element = this.getHtmlElement();
 61 	// clear the onClick event handler.  Otherwise accessibility code will
 62 	// generate spurious mouse up/down events
 63 	this._setEventHdlrs([DwtEvent.ONCLICK], true, element);
 64 
 65 	this.setDropTarget(dropTgt);
 66 	this.setScrollStyle(DwtControl.CLIP);
 67 	this._needFirstLayout = true;
 68 
 69     this._isValidIndicatorDuration = true;
 70 };
 71 
 72 ZmCalColView.prototype = new ZmCalBaseView;
 73 ZmCalColView.prototype.constructor = ZmCalColView;
 74 
 75 ZmCalColView.DRAG_THRESHOLD = 4;
 76 
 77 // min width before we'll turn on horizontal scrollbars
 78 ZmCalColView.MIN_COLUMN_WIDTH = 120;
 79 // max number of all day appts before we turn on vertical scrollbars
 80 ZmCalColView.MAX_ALLDAY_APPTS = 4;
 81 
 82 ZmCalColView.HALF_HOUR_HEIGHT = 21;
 83 
 84 ZmCalColView._OPACITY_APPT_NORMAL = 100;
 85 ZmCalColView._OPACITY_APPT_DECLINED = 20;
 86 ZmCalColView._OPACITY_APPT_TENTATIVE = 60;
 87 ZmCalColView._OPACITY_APPT_DND = 70;
 88 
 89 ZmCalColView._OPACITY_APPT_FREE = 40;
 90 ZmCalColView._OPACITY_APPT_BUSY = 100;
 91 ZmCalColView._OPACITY_APPT_TENTATIVE = 60;
 92 
 93 ZmCalColView._HOURS_DIV_WIDTH = parseInt(ZmMsg.COLUMN_WIDTH_HOURS); // width of div holding hours text (1:00am, etc); defaults to 55
 94 ZmCalColView._UNION_DIV_WIDTH = 40; // width of div holding union in sched view
 95 ZmCalColView._FBBAR_DIV_WIDTH = 10;
 96 
 97 ZmCalColView._ALL_DAY_SEP_HEIGHT = 5; // height of separator between all day appts and body
 98 
 99 ZmCalColView._SCROLL_PRESSURE_FUDGE = 10; // pixels for scroll pressure around top/bottom
100 
101 ZmCalColView._DAY_HEADING_HEIGHT = 20;
102 ZmCalColView._ALL_DAY_APPT_HEIGHT = 20;
103 ZmCalColView._ALL_DAY_APPT_HEIGHT_PAD = 3; // space between all day appt rows
104 ZmCalColView._APPT_X_FUDGE = 0; // due to border stuff
105 ZmCalColView._APPT_Y_FUDGE = -1; // ditto
106 ZmCalColView._APPT_WIDTH_FUDGE = (AjxEnv.isIE ? 0 : 0); // due to border stuff
107 ZmCalColView._APPT_HEIGHT_FUDGE = (AjxEnv.isIE ? 0 : 0); // ditto
108 
109 ZmCalColView._HOUR_HEIGHT = 42;
110 ZmCalColView._HALF_HOUR_HEIGHT = ZmCalColView._HOUR_HEIGHT/2;
111 ZmCalColView._15_MINUTE_HEIGHT = ZmCalColView._HOUR_HEIGHT/4;
112 ZmCalColView._DAY_HEIGHT = ZmCalColView._HOUR_HEIGHT*24;
113 
114 ZmCalColView._STATUS_FREE       = "F";
115 ZmCalColView._STATUS_TENTATIVE  = "T";
116 ZmCalColView._STATUS_BUSY       = "B";
117 ZmCalColView._STATUS_OOO        = "O";
118 
119 ZmCalColView.prototype.toString =
120 function() {
121 	return "ZmCalColView";
122 };
123 
124 ZmCalColView.prototype.fbStatusBarEnabled =
125 function(){
126     return false;
127 };
128 
129 ZmCalColView.prototype.getRollField =
130 function() {
131 	switch(this.view) {
132 		case ZmId.VIEW_CAL_WORK_WEEK:
133 		case ZmId.VIEW_CAL_WEEK:
134 			return AjxDateUtil.WEEK;
135 			break;
136 		case ZmId.VIEW_CAL_DAY:
137 		default:
138 			return AjxDateUtil.DAY;
139 			break;
140 	}
141 };
142 
143 ZmCalColView.prototype.dragSelect =
144 function(div) {
145 	// do nothing
146 };
147 
148 ZmCalColView.prototype.dragDeselect =
149 function(div) {
150 	// do nothing
151 };
152 
153 ZmCalColView.prototype.setIsRight =
154 function(isRight) {
155 	this._isRight = isRight;
156 };
157 
158 ZmCalColView.prototype._dateUpdate =
159 function(rangeChanged) {
160 	this._selectDay(this._date);
161 	this._clearSelectedTime();
162 	this._updateSelectedTime();
163 };
164 
165 ZmCalColView.prototype._selectDay =
166 function(date) {
167 	if (this._numDays == 1 || this._scheduleMode) return;
168 	var day = this._getDayForDate(date);
169 	if (day != null) {
170 		var col = this._columns[day.index];
171 		if (this._selectedDay) {
172 	 		var te = document.getElementById(this._selectedCol.titleId);
173 	 		te.className = this._selectedDay.isToday ? 'calendar_heading_day_today' : 'calendar_heading_day';
174 		}
175 		this._selectedDay = day;
176 		this._selectedCol = col;
177 		var te = document.getElementById(col.titleId);
178  		te.className = day.isToday ? 'calendar_heading_day_today-selected' : 'calendar_heading_day-selected';
179 	}
180 };
181 
182 ZmCalColView.prototype._clearSelectedTime =
183 function() {
184 	var e = document.getElementById(this._timeSelectionDivId);
185 	if (e) Dwt.setVisible(e, false);
186 };
187 
188 ZmCalColView.prototype._updateSelectedTime =
189 function() {
190 	var t = this._date.getTime();
191 	if (t < this._timeRangeStart || t >= this._timeRangeEnd)
192 		return;
193 
194 	var e = document.getElementById(this._timeSelectionDivId);
195 	if (!e) return;
196 
197 	var bounds = this._getBoundsForDate(this._date,  AjxDateUtil.MSEC_PER_HALF_HOUR);
198 	if (bounds == null) return;
199 	var snap = this._snapXY(bounds.x, bounds.y, 30);
200 	if (snap == null) return;
201 
202 	Dwt.setLocation(e, snap.x, snap.y);
203 	Dwt.setSize(e, bounds.width, bounds.height);
204 	Dwt.setOpacity(e, 40);
205 	Dwt.setVisible(e, true);
206 };
207 
208 ZmCalColView.prototype._removeNode =
209 function(id) {
210 	var node = document.getElementById(id);
211 	if (node) node.parentNode.removeChild(node);
212 };
213 
214 ZmCalColView.prototype._updateUnionDataHash =
215 function(index, folderId) {
216 	var hash = this._unionBusyData[index];
217 	if (!hash) hash = this._unionBusyData[index] = {};
218 	hash[folderId] = 1;
219 };
220 
221 ZmCalColView.prototype._updateUnionData =
222 function(appt) {
223 	if (appt.isAllDayEvent()) {
224 		this._updateUnionDataHash(48, appt.folderId);
225 	} else {
226 		var em = appt.endDate.getMinutes();
227 		var eh = appt.endDate.getHours();
228 		var startIndex = (appt.startDate.getHours()*2) + (appt.startDate.getMinutes() < 30 ? 0 : 1);
229 		var endIndex = ((eh ? eh : 24) *2) + (em == 0 ? 0 : (em <= 30 ? 1 : 2));
230 		if (startIndex == endIndex) endIndex++;
231 		for (var i=startIndex; i < endIndex; i++) {
232 			this._updateUnionDataHash(i, appt.folderId);
233 		}
234 	}
235 };
236 
237 ZmCalColView.prototype.addAppt =
238 function(appt) {
239 	ZmCalBaseView.prototype.addAppt.call(this, appt);
240 	if (this._scheduleMode) {
241 		this._updateUnionData(appt);
242 	}
243 };
244 
245 ZmCalColView.prototype._resetCalendarData =
246 function() {
247 	// TODO: optimize: if calendar list is same, skip!
248 
249 	// remove existing
250 	// TODO: optimize, add/remove depending on new calendar length
251 	if (this._numCalendars > 0) {
252 		for (var i = 0; i < this._numCalendars; i++) {
253 			var col = this._columns[i];
254 			this._removeNode(col.titleId);
255 			this._removeNode(col.headingDaySepDivId);
256 			this._removeNode(col.daySepDivId);
257 		}
258 	}
259 
260 	this._calendars = this._controller.getCheckedCalendars();
261 	this._calendars.sort(ZmFolder.sortCompareNonMail);
262 	this._folderIdToColIndex = {};
263 	this._columns = [];
264 	this._numCalendars = this._calendars.length;
265 
266 	this._layoutMap = [];
267 	this._unionBusyData = []; 			//  0-47, one slot per half hour, 48 all day
268 	this._unionBusyDataToolTip = [];	// tool tips
269 
270 	var titleParentEl = document.getElementById(this._allDayHeadingDivId);
271 	var headingParentEl = document.getElementById(this._allDayScrollDivId);
272 	var dayParentEl = document.getElementById(this._apptBodyDivId);
273 
274 	for (var i = 0; i < this._numCalendars; i++) {
275 		var col = this._columns[i] = {
276 			index: i,
277 			dayIndex: 0,
278 			cal: this._calendars[i],
279 			titleId: Dwt.getNextId(),
280 			headingDaySepDivId: Dwt.getNextId(),
281 			daySepDivId: Dwt.getNextId(),
282             workingHrsFirstDivId: Dwt.getNextId(),
283             workingHrsSecondDivId: Dwt.getNextId(),
284 			apptX: 0, 		// computed in layout
285 			apptWidth: 0,	// computed in layout
286 			allDayX: 0, 	// computed in layout
287 			allDayWidth: 0	// computed in layout
288 		};
289 		var cal = this._calendars[i];
290 		this._folderIdToColIndex[cal.id] = col;
291 		if (cal.isRemote() && cal.rid && cal.zid) {
292 			this._folderIdToColIndex[cal.zid + ":" + cal.rid] = col;
293 		}
294 
295 		var div = document.createElement("div");
296 		div.style.position = 'absolute';
297 		div.className = "calendar_heading_day";
298 		div.id = col.titleId;
299 		var calName = AjxStringUtil.htmlEncode(cal.getName());
300 		if (appCtxt.multiAccounts) {
301 			var acct = cal.getAccount();
302 			div.innerHTML = [
303 				"<center><table border=0><tr><td>",
304 				calName,
305 				"</td><td>[",
306 				"<td>",
307 				AjxImg.getImageSpanHtml(acct.getIcon(), "width:18px"),
308 				"</td><td>",
309 				AjxStringUtil.htmlEncode(acct.getDisplayName()),
310 				"]</td></tr></table></center>"
311 			].join("");
312 		} else {
313 			div.innerHTML = calName;
314 		}
315         if(titleParentEl) {
316 		    titleParentEl.appendChild(div);
317         }
318 		div = document.createElement("div");
319 		div.className = "calendar_day_separator";
320 		div.style.position = 'absolute';
321 		div.id = col.headingDaySepDivId;
322         if(headingParentEl) {
323 		    headingParentEl.appendChild(div);
324         }
325 
326 		div = document.createElement("div");
327 		div.className = "calendar_day_separator";
328 		div.style.position = 'absolute';
329 		div.id = col.daySepDivId;
330         if(dayParentEl) {
331 		    dayParentEl.appendChild(div);
332         }
333 	}
334 };
335 
336 ZmCalColView.prototype._preSet =
337 function() {
338 	if (this._scheduleMode) {
339 		this._resetCalendarData(); // cal must be first
340 	}
341 	this._layoutMap = [];
342 	this._resetAllDayData();
343 };
344 
345 ZmCalColView.prototype._postSet =
346 function() {
347 	this._computeApptLayout();
348 	this._computeAllDayApptLayout();
349 	if (!this._needFirstLayout) {
350 		this._layoutAppts();
351 	}
352 	this._layout();
353 	this._scrollToTime(8);
354 
355 	if(this._list && this._list.size() > 0) {
356 		AjxDebug.println(AjxDebug.CALENDAR, " ---------------- ZmCalColView::set - calendar is blank");
357 		AjxDebug.println(AjxDebug.CALENDAR, " list size :" + this._list.size());
358 	}
359 
360 	if(this._fbBarEnabled){
361 		this._layoutFBBar();
362 	}
363 
364 	this._checkForOffscreenAppt();
365 	Dwt.setLoadedTime("ZmCalItemView");
366 };
367 
368 ZmCalColView._inSyncScroll = false;
369 
370 ZmCalColView.prototype._syncScroll =
371 function(resetLeft) {
372 	if (ZmCalColView._inSyncScroll) { return; }
373 
374 	ZmCalColView._inSyncScroll = true;
375 	try {
376 		var bodyElement = document.getElementById(this._bodyDivId),
377 		    hourElement = document.getElementById(this._hoursScrollDivId),
378 		    alldayElement = document.getElementById(this._allDayScrollDivId),
379 		    unionGridScrollElement = document.getElementById(this._unionGridScrollDivId),
380 			alldayApptElement = document.getElementById(this._allDayApptScrollDivId);
381 
382 		hourElement.scrollTop = bodyElement.scrollTop;
383 		hourElement.scrollLeft = bodyElement.scrollLeft;
384 		if (resetLeft) bodyElement.scrollLeft = 0;
385 		alldayElement.scrollLeft = bodyElement.scrollLeft;
386 		alldayApptElement.scrollLeft = bodyElement.scrollLeft;
387 		if (unionGridScrollElement) unionGridScrollElement.scrollTop = bodyElement.scrollTop;
388         this._checkForOffscreenAppt(bodyElement);
389 	} catch (ex) {
390 		 ZmController.handleScriptError(ex, true);
391 	} finally {
392 		 ZmCalColView._inSyncScroll = false;
393 	}
394 };
395 
396 ZmCalColView.prototype._horizontalScrollbar =
397 function(enable) {
398 	var bodyElement = document.getElementById(this._bodyDivId);
399 	bodyElement.className = enable ? "calendar_body_hscroll" : "calendar_body";
400 	if (enable != this._horzEnabled) {
401 		this._horzEnabled = enable;
402 		this._syncScroll(true);
403 	}
404 };
405 
406 ZmCalColView.prototype._allDayVerticalScrollbar =
407 function(enable) {
408 	var el = document.getElementById(this._allDayApptScrollDivId);
409 	el.className = enable ? "calendar_allday_appt_vert" : "calendar_allday_appt";
410 	if (enable != this._vertEnabled) {
411 		this._vertEnabled = enable;
412 		this._syncScroll(true);
413 	}
414 };
415 
416 ZmCalColView.prototype._allDayScrollToBottom =
417 function() {
418 	var el = document.getElementById(this._allDayApptScrollDivId);
419 	el.scrollTop = this._allDayFullDivHeight;
420 };
421 
422 ZmCalColView.prototype._scrollToTime =
423 function(hour) {
424 	hour = hour || 8; // default to 8am
425 
426 	if (!this._autoScrollDisabled) {
427 		var bodyElement = document.getElementById(this._bodyDivId);
428         if (!bodyElement) { return; }
429 		bodyElement.scrollTop = ZmCalColView._HOUR_HEIGHT*hour - 10;
430 		this._syncScroll();
431 	} else {
432 		this._autoScrollDisabled = false;
433 	}
434 };
435 
436 ZmCalColView.prototype._updateTitle =
437 function() {
438 	var dayFormatter = DwtCalendar.getDayFormatter();
439 
440 	if (this.numDays == 1) {
441 		var colFormatter = DwtCalendar.getDateFormatter();
442 		var date = this._date;
443 		this._title = this._scheduleMode
444 			? colFormatter.format(date)
445 			: dayFormatter.format(date);
446 	} else {
447 		var first = this._days[0].date;
448 		var last = this._days[this.numDays-1].date;
449 		this._title = [
450 			dayFormatter.format(first), " - ", dayFormatter.format(last)
451 		].join("");
452 	}
453 };
454 
455 ZmCalColView.prototype._dayTitle =
456 function(date) {
457 	var formatter = this.numDays == 1
458 		? DwtCalendar.getDateLongFormatter()
459 		: DwtCalendar.getDateFormatter();
460 	return formatter.format(date);
461 };
462 
463 ZmCalColView.prototype._updateDays =
464 function() {
465 	var d = new Date(this._date.getTime());
466     d.setHours(0,0,0,0);
467 
468     //counter to track DST adjustments
469     var daylightAdjustment = false;
470 
471     //handle daylight shifting the day e.g. Santiago  Oct 10, 2010 00:00 shifted to Oct 9 2010 23:00
472     if(d.getHours() != 0) {
473         AjxDateUtil.rollToNextDay(d);
474         daylightAdjustment = true;
475     }
476 
477 	var dow;
478 
479 	switch(this.view) {
480 		case ZmId.VIEW_CAL_WORK_WEEK:
481 			/*dow = d.getDay();
482 			if (dow == 0)
483 				d.setDate(d.getDate()+1);
484 			else if (dow != 1)
485 				d.setDate(d.getDate()-(dow-1));
486 			break; */
487 		case ZmId.VIEW_CAL_WEEK:
488 			var fdow = this.firstDayOfWeek();
489 			dow = d.getDay();
490 			if (dow != fdow) {
491 				d.setDate(d.getDate()-((dow+(7-fdow))%7));
492 			}
493 			break;
494 		case ZmId.VIEW_CAL_DAY:
495 		default:
496 			/* nothing */
497 			break;
498 	}
499 
500     //handling the case where start day of week shifted due to DST
501     if(d.getHours() != 0 && !daylightAdjustment) {
502         AjxDateUtil.rollToNextDay(d);
503         daylightAdjustment = true;
504     }
505 
506 	this._dateToDayIndex = new Object();
507 
508 	var today = new Date();
509 	today.setHours(0,0,0,0);
510 
511 	var lastDay = this.numDays - 1;
512     var j = 0;
513 	for (var i=0; i < 7; i++) {
514         var wHrs = this.workingHours[d.getDay()];
515         var isWorkingDay = wHrs && wHrs.isWorkingDay ? wHrs.isWorkingDay : false;
516         if (this.view === ZmId.VIEW_CAL_WEEK    ||
517             this.view === ZmId.VIEW_CAL_DAY     ||
518             this._scheduleMode === true         ||
519             isWorkingDay === true ) {
520 
521             var day = this._days[j] = {};
522             day.index = j;
523             day.date = new Date(d);
524             day.endDate = new Date(d);
525             day.endDate.setHours(23,59,59,999);
526             day.isToday = day.date.getTime() == today.getTime();
527             day.isWorkingDay = isWorkingDay;
528             this._dateToDayIndex[this._dayKey(day.date)] = day;
529             if (!this._scheduleMode && this._columns[j]) {
530                 var id = this._columns[j].titleId;
531                 this._calendarTodayHeaderDivId=day.isToday?id:this._calendarTodayHeaderDivId;
532                 var te = document.getElementById(id);
533                 if (te) {
534                     te.innerHTML = this._dayTitle(d);
535                     this.associateItemWithElement(null, te, ZmCalBaseView.TYPE_DAY_HEADER, id, {dayIndex:j});
536                     te.className = day.isToday ? 'calendar_heading_day_today' : 'calendar_heading_day';
537                 }
538             }
539             j++;
540         }
541 		var oldDate = d.getDate();
542 		d.setDate(d.getDate() + 1);
543 		if (oldDate == d.getDate()) {
544 			// daylight saving problem
545 			d.setHours(0,0,0,0);
546 			d.setTime(d.getTime() + AjxDateUtil.MSEC_PER_DAY);
547 		}
548 
549         //handling the case where first day got shifted due to DST
550         if(daylightAdjustment) {
551             d.setHours(0,0,0,0);
552             daylightAdjustment = false;
553         }
554 	}
555 	var te = document.getElementById(this._headerYearId);
556     if(te) {
557 	    te.innerHTML = this._days[0].date.getFullYear();
558     }
559 };
560 
561 ZmCalColView.prototype._resetAllDayData =
562 function() {
563 	this._allDayAppts = {};
564 	this._allDayApptsList = [];
565 	this._allDayApptsRowLayouts = [];
566 	this._addAllDayApptRowLayout();
567 };
568 
569 /**
570  * we don't want allday appts that span days to be fanned out
571  */
572 ZmCalColView.prototype._fanoutAllDay =
573 function(appt) {
574 	return false;
575 };
576 
577 ZmCalColView.prototype._getDivForAppt =
578 function(appt) {
579 	return document.getElementById(appt.isAllDayEvent() ? this._allDayDivId : this._apptBodyDivId);
580 };
581 
582 
583 
584 // for the new appt when drag selecting time grid
585 ZmCalColView.prototype._populateNewApptHtml =
586 function(div, allDay, folderId) {
587 	if (folderId == null) {
588 		folderId = this._controller.getDefaultCalendarFolderId();
589 	}
590 	var color = ZmCalendarApp.COLORS[this._controller.getCalendarColor(folderId)];
591 	var prop = allDay ? "_newAllDayApptColor" : "_newApptColor";
592 	if (this[prop] && this[prop] == color) {
593 		return div;
594 	}
595 
596 	this[prop] = color;
597 	div.style.position = 'absolute';
598 	Dwt.setSize(div, 10, 10);// will be resized
599 	div.className = this._getStyle(null, true);
600 	Dwt.setOpacity(div, ZmCalColView._OPACITY_APPT_DND);
601 	var calendar = appCtxt.getById(folderId);
602 	//var headerColor = calendar.rgb ? AjxColor.deepen(AjxColor.darken(calendar.rgb,ZmCalBaseView.headerColorDelta)) : "";
603 	var bodyColor = calendar.rgb ? AjxColor.deepen(AjxColor.lighten(calendar.rgb,ZmCalBaseView.bodyColorDelta)) : "";
604 	var subs = {
605 		id: div.id,
606 		newState: "",
607 		headerColor: calendar.rgb ? "" : (color + "Light"),
608 		bodyColor: calendar.rgb ? "" : (color + "Bg"),
609 		headerStyle: calendar.rgb ? "background-color: "+bodyColor+";" : "",
610 		name: AjxStringUtil.htmlEncode(ZmMsg.newAppt),
611 		starttime: "",
612 		endtime: "",
613 		location: "",
614 		status: ""
615 	};
616     var template;
617     var gradient = Dwt.createLinearGradientCss("#FFFFFF", bodyColor, "v");
618     if (allDay) {
619         template = "calendar_appt_allday";
620         if (gradient) {
621             subs.headerStyle = gradient;
622         }
623     } else {
624         template = "calendar_appt";
625         if (gradient) {
626             subs.bodyStyle   = gradient;
627             subs.headerStyle = null;
628         }
629     }
630 	div.innerHTML = AjxTemplate.expand("calendar.Calendar#"+template, subs);
631 	return div;
632 };
633 
634 ZmCalColView.prototype._createItemHtml = function(appt) {
635 
636 	if (this.view === ZmId.VIEW_CAL_WORK_WEEK) {
637 		var availableStartTime = this.getAvailableStartTime(appt);
638 		if (!availableStartTime) {
639 			return;
640 		}
641 	}
642 
643     var isAllDay = appt.isAllDayEvent();
644 	if (isAllDay) {
645 		var dataId = appt.getUniqueId();
646 		var startTime = availableStartTime || Math.max(appt.getStartTime(), this._timeRangeStart);
647 
648 		this._allDayAppts[dataId] = {
649 		    appt:       appt,
650 			startTime:  startTime
651 		};
652 		this._allDayApptsList.push(appt);
653 	}
654 
655 	var apptWidth = 10,
656 	    apptHeight = 10,
657 	    apptX = 0,
658 	    apptY = 0,
659 	    layout = this._layoutMap[this._getItemId(appt)];
660 
661 	// set up DIV
662 	var div = document.createElement("div");
663 
664     Dwt.setPosition(div, Dwt.ABSOLUTE_STYLE);
665     Dwt.setCursor(div, 'default');
666 	Dwt.setSize(div, apptWidth, apptHeight);
667 	if (layout) {
668 		div.style.left = apptX + 'px';
669 		div.style.top = apptY + 'px';
670 	}
671 	div.className = this._getStyle();
672     if (this.view === ZmId.VIEW_CAL_FB) {
673         Dwt.setScrollStyle(div, Dwt.CLIP);
674     }
675 
676 	this.associateItemWithElement(appt, div, ZmCalBaseView.TYPE_APPT);
677 
678 	var isNew = (appt.ptst === ZmCalBaseItem.PSTATUS_NEEDS_ACTION),
679 	    id = this._getItemId(appt),
680 	    calendar = appCtxt.getById(appt.folderId),
681 	    isRemote = Boolean(calendar.url),
682 	    is30 = appt._orig.getDuration() <= AjxDateUtil.MSEC_PER_HALF_HOUR,
683 	    is60 = appt._orig.getDuration() <= AjxDateUtil.MSEC_PER_HOUR,
684 		apptName = AjxStringUtil.htmlEncode(appt.getName());
685 
686 	// normalize location
687 	var location = appt.getLocation();
688 	location = location && location.length && !is60 ? "<div class='appt_location'>" + AjxStringUtil.htmlEncode(appt.getLocation()) + "</div>" : null;
689 
690 	if ((is30 || isAllDay) && this.view !== ZmId.VIEW_CAL_DAY) {
691         // fit as much of appt name as we can in one row, use ... if we have to truncate
692         apptName = isAllDay ? apptName : appt.getDurationText(true, true) + " - " + apptName;
693         var apptBounds = this._getBoundsForAppt(appt),
694             apptWidth = apptBounds && apptBounds.width;
695 
696         if (apptWidth > 30) {
697             apptName = AjxStringUtil.fitString(apptName, apptWidth - 15);
698         }
699 	}
700 
701     var tagNames  = appt.getVisibleTags(),
702         tagIcon = appt.getTagImageFromNames(tagNames);
703 
704     // If the tag icon is returned blank image reset the tag icon
705     if (tagIcon === "Blank_16") {
706         tagIcon = "";
707     }
708 
709     var colors = ZmApptViewHelper.getApptColor(isNew, calendar, tagNames, "body"),
710 	    bodyStyle = ZmCalBaseView._toColorsCss(colors.appt),
711         fba = isNew ? ZmCalBaseItem.PSTATUS_NEEDS_ACTION : appt.fba;
712 
713 	var subs = {
714 		id:             id,
715 		newState:       isNew ? "_new" : "",
716 		headerStyle:    bodyStyle,
717 		name:           apptName,
718 		starttime:      appt.getDurationText(true, true),
719 		endtime:        !appt._fanoutLast && (appt._fanoutFirst || appt._fanoutNum > 0) ? "" : ZmCalBaseItem._getTTHour(appt.endDate),
720 		location:       location,
721 		status:         appt.isOrganizer() ? "" : appt.getParticipantStatusStr(),
722 		icon:           appt.isPrivate() ? "ReadOnly" : null,
723 		tagIcon:        tagIcon,
724 		hideTime:       is60,
725 		showAsColor :   ZmApptViewHelper._getShowAsColorFromId(fba),
726         boxBorder:      ZmApptViewHelper.getBoxBorderFromId(fba),
727         isDraft:        appt.isDraft,
728         otherAttendees: appt.otherAttendees,
729         isException:    appt.isException,
730         isRecurring:    appt.isRecurring()
731 	};
732 
733 	var template,
734         colorParam,
735         clearParam,
736         bs;
737 
738 	if (appt.isAllDayEvent()) {
739         colorParam = "headerStyle";
740 		template = "calendar_appt_allday";
741 		if (!this.isStartInView(appt._orig)) {
742             bs = "border-left:none;";
743         }
744 		if (!this.isEndInView(appt._orig)) {
745             bs += "border-right:none;";
746         }
747 		if (bs) {
748             subs.bodyStyle = bs;
749         }
750 	}
751     else if (this.view == ZmId.VIEW_CAL_FB) {
752         template = "calendar_fb_appt";
753     }
754     else if (is30) {
755         colorParam = "headerStyle";
756 		template = "calendar_appt_30";
757 	}
758     else if (appt._fanoutNum > 0) {
759         colorParam = "bodyStyle";
760 		template   = "calendar_appt_bottom_only";
761 	}
762     else {
763         colorParam = "bodyStyle";
764         clearParam = "headerStyle";
765 		template   = "calendar_appt";
766 	}
767     // Currently header/bodyStyles are only used for coloring.  Replace with a gradient
768     // if supported by the browser
769     ZmApptViewHelper.setupCalendarColor(true, colors, tagNames, subs, colorParam, clearParam, 1, 1);
770 
771 	div.innerHTML = AjxTemplate.expand("calendar.Calendar#" + template, subs);
772 
773     // Set opacity on the table element that is colored with the gradient.  Needed for IE
774     var tableEl = Dwt.getDescendant(div, id + "_tableBody"),
775         opacity = ZmCalBaseView.getApptOpacity(appt);
776     if (tableEl) {
777         Dwt.setOpacity(tableEl, opacity);
778     }
779     else {
780         Dwt.setOpacity(div, opacity);
781     }
782 
783 	// if (we can edit this appt) then create sash....
784 	if (!appt.isReadOnly() && !appt.isAllDayEvent() && !isRemote && this.view !== ZmId.VIEW_CAL_FB) {
785 		if (appt._fanoutLast || (!appt._fanoutFirst && (!appt._fanoutNum))) {
786 			var bottom = document.createElement("div");
787 			this.associateItemWithElement(null, bottom, ZmCalBaseView.TYPE_APPT_BOTTOM_SASH, appt.id + "-bs");
788 			bottom.className = 'appt_bottom_sash';
789 			div.appendChild(bottom);
790 		}
791 
792 		if (appt._fanoutFirst || (!appt._fanoutLast && (!appt._fanoutNum))) {
793 			var top = document.createElement("div");
794 			this.associateItemWithElement(null, top, ZmCalBaseView.TYPE_APPT_TOP_SASH, appt.id + "-ts");
795 			top.className = 'appt_top_sash';
796 			div.appendChild(top);
797 		}
798 	}
799 
800 	return div;
801 };
802 
803 
804 // TODO: i18n
805 ZmCalColView.prototype._createHoursHtml =
806 function(html) {
807 
808 	html.append("<div style='position:absolute; top:-8; width:", ZmCalColView._HOURS_DIV_WIDTH, "px;' id='", this._bodyHourDivId, "'>");
809 
810 	var formatter = DwtCalendar.getHourFormatter();
811     var curDate = new Date();
812 	var date = new Date();
813 	date.setHours(0, 0, 0, 0);
814     var timeTDWidth = ZmCalColView._HOURS_DIV_WIDTH - (this._fbBarEnabled ? ZmCalColView._FBBAR_DIV_WIDTH : 0 );
815     html.append("<table class=calendar_grid_day_table>");
816 	for (var h=0; h < 25; h++) {
817 		html.append("<tr><td class=calendar_grid_body_time_td style='height:",
818 		ZmCalColView._HOUR_HEIGHT ,"px; width:", timeTDWidth, "px'><div id='"+this._hourColDivId+"_"+h+"' class=calendar_grid_body_time_text>");
819 		date.setHours(h);
820 		html.append(h > 0 && h < 24 ? AjxStringUtil.htmlEncode(formatter.format([h, date])) : " ");
821 		html.append("</div>");
822         html.append("</td>");
823         if(this._fbBarEnabled){
824             html.append("<td class=calendar_grid_body_fbbar_td style='height:",ZmCalColView._HOUR_HEIGHT ,"px; width:", ZmCalColView._FBBAR_DIV_WIDTH,"px; border-left:1px solid #A7A194;'> </td>");
825         }
826         html.append("</tr>");
827 	}
828 	html.append("</table>");
829     html.append("<div id='"+this._curTimeIndicatorHourDivId+"' class='calendar_cur_time_indicator_arr'><div class='calendar_hour_arrow_indicator'>→</div></div>");
830     html.append( "</div>");
831 };
832 
833 
834 ZmCalColView.prototype._createHtml =
835 function(abook) {
836 	this._days = {};
837 	this._columns = [];
838 	this._hours = {};
839 	this._layouts = [];
840 	this._allDayAppts = [];
841 
842 	var html = new AjxBuffer();
843 
844 	this._headerYearId = Dwt.getNextId();
845 	this._yearHeadingDivId = Dwt.getNextId();
846 	this._yearAllDayDivId = Dwt.getNextId();
847 	this._leftAllDaySepDivId = Dwt.getNextId();
848 	this._leftApptSepDivId = Dwt.getNextId();
849 
850 	this._allDayScrollDivId = Dwt.getNextId();
851 	this._allDayHeadingDivId = Dwt.getNextId();
852 	this._allDayApptScrollDivId = Dwt.getNextId();
853 	this._allDayDivId = Dwt.getNextId();
854 	this._hoursScrollDivId = Dwt.getNextId();
855 	this._bodyHourDivId = Dwt.getNextId();
856 	this._allDaySepDivId = Dwt.getNextId();
857 	this._allDaySepSashDivId = Dwt.getNextId();
858 	this._bodyDivId = Dwt.getNextId();
859 	this._apptBodyDivId = Dwt.getNextId();
860 	this._newApptDivId = Dwt.getNextId();
861 	this._newAllDayApptDivId = Dwt.getNextId();
862 	this._timeSelectionDivId = Dwt.getNextId();
863     this._curTimeIndicatorHourDivId = Dwt.getNextId();
864     this._curTimeIndicatorGridDivId = Dwt.getNextId();
865     this._hourColDivId = Dwt.getNextId();
866     this._startLimitIndicatorDivId = Dwt.getNextId();
867     this._endLimitIndicatorDivId = Dwt.getNextId();
868     // Fix for bug: 66603. Reference to parent container of _allDayHeadingDivId
869     this._tabsContainerDivId = Dwt.getNextId();
870 
871 
872 	if (this._scheduleMode) {
873 		this._unionHeadingDivId = Dwt.getNextId();
874 		this._unionAllDayDivId = Dwt.getNextId();
875 		this._unionHeadingSepDivId = Dwt.getNextId();
876 		this._unionGridScrollDivId = Dwt.getNextId();
877 		this._unionGridDivId = Dwt.getNextId();
878 		this._unionGridSepDivId = Dwt.getNextId();
879         this._workingHrsFirstDivId = Dwt.getNextId();
880         this._workingHrsFirstChildDivId = Dwt.getNextId();
881         this._workingHrsSecondDivId = Dwt.getNextId();
882         this._workingHrsSecondChildDivId = Dwt.getNextId();
883 	}
884 
885 	this._allDayRows = [];
886 
887 	if (!this._scheduleMode) {
888 		for (var i =0; i < this.numDays; i++) {
889 			this._columns[i] = {
890 				index: i,
891 				dayIndex: i,
892 				titleId: Dwt.getNextId(),
893 				headingDaySepDivId: Dwt.getNextId(),
894 				daySepDivId: Dwt.getNextId(),
895                 workingHrsFirstDivId: Dwt.getNextId(),
896                 workingHrsFirstChildDivId: Dwt.getNextId(),
897                 workingHrsSecondDivId: Dwt.getNextId(),
898                 workingHrsSecondChildDivId: Dwt.getNextId(),
899 				apptX: 0, // computed in layout
900 				apptWidth: 0,// computed in layout
901 				allDayX: 0, // computed in layout
902 				allDayWidth: 0// computed in layout
903 			};
904 		}
905 	}
906 
907 	// year heading
908 	var 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
909 	var headerStyle = "position:absolute;" + inviteMessageHeaderStyle;
910 	
911 	html.append("<div id='", this._yearHeadingDivId, "' class='calendar_heading' style='", headerStyle,	"'>");
912 	html.append("<div id='", this._headerYearId,
913 		"' class=calendar_heading_year_text style='position:absolute; width:", ZmCalColView._HOURS_DIV_WIDTH,"px;'></div>");
914 	html.append("</div>");
915 
916 	// div under year
917 	html.append("<div id='", this._yearAllDayDivId, "' style='position:absolute'></div>");
918 
919 	// sep between year and headings
920 	html.append("<div id='", this._leftAllDaySepDivId, "' class='calendar_day_separator' style='position:absolute'></div>");
921 
922 	if (this._scheduleMode) {
923 		// "All" heading
924 		html.append("<div id='", this._unionHeadingDivId, "' class=calendar_heading style='position:absolute'>");
925 		html.append("<div class=calendar_heading_year_text style='position:absolute; width:", ZmCalColView._UNION_DIV_WIDTH,"px;'>",ZmMsg.all,"</div>");
926 		html.append("</div>");
927 
928 		// div in all day space
929 		html.append("<div id='", this._unionAllDayDivId, "' style='position:absolute'></div>");
930 
931 		// sep between year and headings
932 		html.append("<div id='", this._unionHeadingSepDivId, "' class='calendar_day_separator' style='position:absolute'></div>");
933 	}
934 
935 	// all day scroll	=============
936 	html.append("<div id='", this._allDayScrollDivId, "' style='position:absolute; overflow:hidden;'>");
937 
938 	// all day headings
939     // Fix for bug: 66603. Adding a container to calendar headings
940     html.append("<div id='", this._tabsContainerDivId, "' name='_tabsContainerDivId' style='position:absolute;height:25px;bottom:0px;top:0px'>");
941 	html.append("<div id='", this._allDayHeadingDivId, "' class='calendar_heading' style='", headerStyle,	"'>");
942 	if (!this._scheduleMode) {
943 		for (var i =0; i < this.numDays; i++) {
944 			html.append("<div id='", this._columns[i].titleId, "' class='calendar_heading_day' style='position:absolute;'></div>");
945 		}
946 	}
947 	html.append("</div>");
948     // Fix for bug: 66603
949     html.append("</div>");
950 
951 	// divs to separate day headings
952 	if (!this._scheduleMode) {
953 		for (var i =0; i < this.numDays; i++) {
954 			html.append("<div id='", this._columns[i].headingDaySepDivId, "' class='calendar_day_separator' style='position:absolute'></div>");
955 		}
956 	}
957 	html.append("</div>");
958 	// end of all day scroll ===========
959 
960 	// div holding all day appts
961 	html.append("<div id='", this._allDayApptScrollDivId, "' class='calendar_allday_appt' style='position:absolute'>");
962 	html.append("<div id='", this._allDayDivId, "' style='position:absolute'>");
963 	html.append("<div id='", this._newAllDayApptDivId, "' class='appt-selected' style='position:absolute; display:none;'></div>");
964 	html.append("</div>");
965 	html.append("</div>");
966 
967 	// sep betwen all day and normal appts
968 	html.append("<div id='", this._allDaySepDivId, "' class=calendar_header_allday_separator style='overflow:hidden;position:absolute;'><div id='", this._allDaySepSashDivId, "' class='calendar_header_allday_separator_sash open'></div></div>");
969 
970 	// div to hold hours
971 	html.append("<div id='", this._hoursScrollDivId, "' class=calendar_hour_scroll style='position:absolute;'>");
972 	this._createHoursHtml(html);
973 	html.append("</div>");
974 
975 	// sep between hours and grid
976 	html.append("<div id='", this._leftApptSepDivId, "' class='calendar_day_separator' style='position:absolute'></div>");
977 
978 	// union grid
979 	if (this._scheduleMode) {
980 		html.append("<div id='", this._unionGridScrollDivId, "' class=calendar_union_scroll style='position:absolute'>");
981 		html.append("<div id='", this._unionGridDivId, "' class='ImgCalendarDayGrid' style='width:100%; height:1008px; position:absolute;'>");
982 		html.append("</div></div>");
983 		// sep between union grid and appt grid
984 		html.append("<div id='", this._unionGridSepDivId, "' class='calendar_day_separator' style='position:absolute'></div>");
985 	}
986 
987 	// grid body
988 	html.append("<div id='", this._bodyDivId, "' class=calendar_body style='position:absolute'>");
989     html.append("<div id='", this._apptBodyDivId, "' class='ImgCalendarDayGrid' style='width:100%; height:1008px; position:absolute;background-color:#E3E3DC;'>");
990 	html.append("<div id='", this._timeSelectionDivId, "' class='calendar_time_selection' style='position:absolute; display:none;z-index:10;'></div>");
991 	html.append("<div id='", this._newApptDivId, "' class='appt-selected' style='position:absolute; display:none;'></div>");
992 	if (!this._scheduleMode) {
993 		for (var i =0; i < this.numDays; i++) {
994 		  html.append("<div id='", this._columns[i].daySepDivId, "' class='calendar_day_separator' style='position:absolute'></div>");
995 		  html.append("<div id='", this._columns[i].workingHrsFirstDivId, "' style='position:absolute;background-color:#FFFFFF;'><div id='", this._columns[i].workingHrsFirstChildDivId, "' class='ImgCalendarDayGrid' style='position:absolute;top:0px;left:0px;overflow:hidden;'></div></div>");
996 		  html.append("<div id='", this._columns[i].workingHrsSecondDivId, "' style='position:absolute;background-color:#FFFFFF;'><div id='", this._columns[i].workingHrsSecondChildDivId, "' class='ImgCalendarDayGrid' style='position:absolute;top:0px;left:0px;overflow:hidden;'></div></div>");
997 		}
998 	}
999     else {
1000         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>");
1001         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>");
1002     }
1003 
1004 
1005 	html.append("</div>");
1006     //Strip to indicate the current time
1007     html.append("<div id='"+this._curTimeIndicatorGridDivId+"' class='calendar_cur_time_indicator_container'><div class='calendar_cur_time_indicator_strip'></div></div>");
1008     html.append("<div id='"+this._startLimitIndicatorDivId+"' class='calendar_start_limit_indicator'><div class='ImgArrowMoreUp'></div></div>");
1009     html.append("<div id='"+this._endLimitIndicatorDivId+"' class='calendar_end_limit_indicator'><div class='ImgArrowMoreDown'></div></div>");
1010 	html.append("</div>");
1011 
1012 	this.getHtmlElement().innerHTML = html.toString();
1013 
1014     var func = AjxCallback.simpleClosure(ZmCalColView.__onScroll, ZmCalColView, this);
1015 	document.getElementById(this._bodyDivId).onscroll = func;
1016 	document.getElementById(this._allDayApptScrollDivId).onscroll = func;
1017     // Fix for bug: 66603. Adding a handler to enable scrolling.
1018     document.getElementById(this._tabsContainerDivId).onscroll = func;
1019 
1020 	var ids = [this._apptBodyDivId, this._bodyHourDivId, this._allDayDivId, this._allDaySepDivId];
1021 	var types = [ZmCalBaseView.TYPE_APPTS_DAYGRID, ZmCalBaseView.TYPE_HOURS_COL, ZmCalBaseView.TYPE_ALL_DAY, ZmCalBaseView.TYPE_DAY_SEP];
1022 	for (var i = 0; i < ids.length; i++) {
1023 		this.associateItemWithElement(null, document.getElementById(ids[i]), types[i], ids[i]);
1024 	}
1025 	this._scrollToTime(8);
1026 };
1027 
1028 ZmCalColView.prototype.updateTimeIndicator = function(force) {
1029 	this._updateTimeIndicator(force);
1030 	return this.setTimer(1);
1031 }
1032 
1033 
1034 ZmCalColView.prototype._updateTimeIndicator = function(force) {
1035     var curDate = new Date();
1036     var hr  = curDate.getHours();
1037     var min = curDate.getMinutes();
1038     var curHourDiv = document.getElementById(this._hourColDivId + "_" + hr);
1039     if (!curHourDiv) {
1040         return;
1041     }
1042 
1043     var curTimeHourIndicator = document.getElementById(this._curTimeIndicatorHourDivId);
1044 	var currentTopPosition = Math.round((ZmCalColView._HOUR_HEIGHT/60)*min)+parseInt(curHourDiv.offsetParent.offsetTop);
1045     Dwt.setLocation(curTimeHourIndicator, curHourDiv.offsetParent.offsetLeft, currentTopPosition - 5);
1046     var calendarStrip = document.getElementById(this._curTimeIndicatorGridDivId);
1047     Dwt.setVisibility(calendarStrip,true);
1048     var todayColDiv = document.getElementById(this._calendarTodayHeaderDivId);
1049     if (todayColDiv && (force || this._isValidIndicatorDuration)) {
1050         Dwt.setBounds(calendarStrip, todayColDiv.offsetLeft, currentTopPosition, todayColDiv.offsetWidth, null);
1051     } else {
1052 		Dwt.setVisibility(calendarStrip,false);
1053 	}
1054 };
1055 
1056 
1057 ZmCalColView.prototype.startIndicatorTimer=function(force){
1058    if(force || !this._indicatorTimer){
1059     this._indicatorTimer = this.updateTimeIndicator(force);
1060    }
1061 };
1062 
1063 ZmCalColView.prototype.checkIndicatorNeed=function(viewId,startDate){
1064    var isValidView = (viewId == ZmId.VIEW_CAL_WORK_WEEK || viewId == ZmId.VIEW_CAL_WEEK || viewId == ZmId.VIEW_CAL_DAY);
1065    if(startDate!=null && isValidView){
1066         var today = new Date();
1067         var todayTime = today.getTime();
1068         startDate.setHours(0,0,0,0);
1069         var sTime = startDate.getTime();
1070         var endDate = AjxDateUtil.roll(startDate,AjxDateUtil.DAY,1);
1071         endDate.setHours(23,59,59,999);
1072         var endTime = endDate.getTime();
1073         if(!(todayTime>=sTime && todayTime<=endTime)){
1074             this._isValidIndicatorDuration = false;
1075             var calendarStrip = document.getElementById(this._curTimeIndicatorGridDivId);
1076             Dwt.setVisibility(calendarStrip,false);
1077         }else{
1078             this._isValidIndicatorDuration = true;
1079             this.updateTimeIndicator();
1080         }
1081    }else{
1082        this._isValidIndicatorDuration = true;
1083    }
1084 };
1085 
1086 /*
1087 *   Checks whether any offscreen appointment exists, and indicates according to the direction it gets hidden.
1088  */
1089 ZmCalColView.prototype._checkForOffscreenAppt=function(bodyElement){
1090     var topExceeds = false;
1091     var bottomExceeds = false;
1092     if(!bodyElement){bodyElement = document.getElementById(this._bodyDivId);}
1093     if(!bodyElement) { return; }
1094     var height = bodyElement.offsetHeight;
1095     var top = bodyElement.scrollTop;
1096     var appt;
1097 
1098     if(this._list && this._list.size()>0){
1099         var apptArray = this._list.getArray();
1100         for(var i=0;i<apptArray.length;i++){
1101             appt = apptArray[i];
1102             if (!appt) { continue; }
1103             var layoutParams = apptArray[i].getLayoutInfo();
1104             if(!topExceeds){topExceeds=(layoutParams && layoutParams.y<(top));}
1105             if(!bottomExceeds){bottomExceeds=(layoutParams && layoutParams.y>(height+top));}
1106             if(topExceeds && bottomExceeds){break;}
1107         }
1108     }
1109 
1110     var topIndicator = document.getElementById(this._startLimitIndicatorDivId);
1111     Dwt.setVisibility(topIndicator,topExceeds);
1112     var bottomIndicator = document.getElementById(this._endLimitIndicatorDivId);
1113     Dwt.setVisibility(bottomIndicator,bottomExceeds);
1114 
1115     if(topExceeds){
1116         topIndicator.style.top=bodyElement.scrollTop+"px";
1117     }
1118 
1119     if(bottomExceeds){
1120         bottomIndicator.style.top = ((bodyElement.offsetHeight+bodyElement.scrollTop+8)-(bottomIndicator.offsetHeight))+"px";
1121     }
1122 };
1123 
1124 ZmCalColView.__onScroll = 
1125 function(myView) {
1126     if(this.__scrollActionId) {  // Fix for Bug 84928
1127 	AjxTimedAction.cancelAction(this.__scrollActionId);
1128 	delete this.__scrollActionId;
1129     } 
1130     this.__scrollActionId = AjxTimedAction.scheduleAction(new AjxTimedAction(myView,myView._syncScroll), 30); 
1131 };
1132 
1133 ZmCalColView.prototype._computeMaxCols =
1134 function(layout, max) {
1135 	//DBG.println("compute max cols for "+layout.appt.id+" col="+layout.col);
1136 	if (layout.maxDone) return layout.maxcol;
1137 	layout.maxcol = Math.max(layout.col, layout.maxcol, max);
1138 	if (layout.right) {
1139 		for (var r = 0; r < layout.right.length; r++) {
1140 			layout.maxcol = Math.max(layout.col, this._computeMaxCols(layout.right[r], layout.maxcol));
1141 		}
1142 	}
1143 	//DBG.println("max cols for "+layout.appt.id+" was: "+layout.maxcol);
1144 	layout.maxDone = true;
1145 	return layout.maxcol;
1146 };
1147 
1148 /*
1149  * compute appt layout for appts that aren't all day
1150  */
1151 ZmCalColView.prototype._computeApptLayout =
1152 function() {
1153 //	DBG.println("_computeApptLayout");
1154 //	DBG.timePt("_computeApptLayout: start", true);
1155 	var layouts = this._layouts = new Array();
1156 	var layoutsDayMap = [];
1157 	var layoutsAllDay = [];
1158 	var list = this.getList();
1159 	if (!list) return;
1160 
1161 	var size = list.size();
1162 	if (size == 0) { return; }
1163 
1164 	var overlap = null;
1165 	var overlappingCol = null;
1166 
1167 	for (var i=0; i < size; i++) {
1168 		var ao = list.get(i);
1169 
1170 		if (!ao || ao.isAllDayEvent()) {
1171 			continue;
1172 		}
1173 
1174 		var newLayout = { appt: ao, col: 0, maxcol: -1};
1175 
1176 		overlap = null;
1177 		overlappingCol = null;
1178 
1179 		var asd = ao.startDate;
1180 		var aed = ao.endDate;
1181 
1182 		var asdDate = asd.getDate();
1183 		var aedDate = aed.getDate();
1184 
1185 		var checkAllLayouts = (asdDate != aedDate);
1186 		var layoutCheck = [];
1187 
1188 		// if a appt starts n end in same day, it should be compared only with
1189 		// other appts on same day and with those which span multiple days
1190 		if (checkAllLayouts) {
1191 			layoutCheck.push(layouts);
1192 		} else {
1193 			layoutCheck.push(layoutsAllDay);
1194 			if (layoutsDayMap[asdDate]!=null) {
1195 				layoutCheck.push(layoutsDayMap[asdDate]);
1196 			}
1197 		}
1198 
1199 		// look for overlapping appts
1200 		for (var k = 0; k < layoutCheck.length; k++) {
1201 			for (var j=0; j < layoutCheck[k].length; j++) {
1202 				var layout = layoutCheck[k][j];
1203 				if (ao.isOverlapping(layout.appt, this._scheduleMode)) {
1204 					if (overlap == null) {
1205 						overlap = [];
1206 						overlappingCol = [];
1207 					}
1208 					overlap.push(layout);
1209 					overlappingCol[layout.col] = true;
1210 					// while we overlap, update our col
1211 					while (overlappingCol[newLayout.col]) {
1212 						newLayout.col++;
1213 					}
1214 				}
1215 			}
1216 		}
1217 
1218 		// figure out who is on our right
1219 		if (overlap != null) {
1220 			for (var c = 0; c < overlap.length; c++) {
1221 				var l = overlap[c];
1222 				if (newLayout.col < l.col) {
1223 					if (!newLayout.right) newLayout.right = [l];
1224 					else newLayout.right.push(l);
1225 				} else {
1226 					if (!l.right) l.right = [newLayout];
1227 					else l.right.push(newLayout);
1228 				}
1229 			}
1230 		}
1231 		layouts.push(newLayout);
1232 		if (asdDate == aedDate) {
1233 			if(!layoutsDayMap[asdDate]) {
1234 				layoutsDayMap[asdDate] = [];
1235 			}
1236 			layoutsDayMap[asdDate].push(newLayout);
1237 		} else {
1238 			layoutsAllDay.push(newLayout);
1239 		}
1240 	}
1241 
1242 	// compute maxcols
1243 	for (var i=0; i < layouts.length; i++) {
1244 		this._computeMaxCols(layouts[i], -1);
1245 		this._layoutMap[this._getItemId(layouts[i].appt)]  = layouts[i];
1246 //		DBG.timePt("_computeApptLayout: computeMaxCol "+i, false);
1247 	}
1248 	
1249 	delete layoutsAllDay;
1250 	delete layoutsDayMap;
1251 	delete layoutCheck;
1252 	//DBG.timePt("_computeApptLayout: end", false);
1253 };
1254 
1255 /*
1256  * add a new all day appt row layout slot and return it
1257  */
1258 ZmCalColView.prototype._addAllDayApptRowLayout =
1259 function() {
1260 	var data = [];
1261 	var num = this._columns.length;
1262 	for (var i=0; i < num; i++) {
1263 		// free is set to true if slot is available, false otherwise
1264 		// appt is set to the _allDayAppts data in the first slot only (if appt spans days)
1265 		data[i] = { free: true, data: null };
1266 	}
1267 	this._allDayApptsRowLayouts.push(data);
1268 	return data;
1269 };
1270 
1271 /**
1272  * take the appt data in reserve the slots
1273  */
1274 ZmCalColView.prototype._fillAllDaySlot =
1275 function(row, colIndex, data) {
1276 	for (var j=0; j < data.numDays; j++) {
1277 		var col = colIndex + j;
1278 		if (col == row.length) break;
1279 		row[col].data = j==0 ? data : null;
1280 		row[col].free = false;
1281 	}
1282 };
1283 
1284 /**
1285  * find a slot and fill it in, adding new rows if needed
1286  */
1287 ZmCalColView.prototype._findAllDaySlot =
1288 function(colIndex, data) {
1289 	if (data.appt) {
1290 		var appt = data.appt;
1291 		var startTime = appt.getStartTime();
1292 		var endTime = appt.getEndTime();
1293 		data.numDays = 1;
1294         if (startTime != endTime) {
1295             data.numDays = this._calcNumDays(startTime, endTime);
1296         }
1297         if (startTime < data.startTime) {
1298             data.numDays -= this._calcNumDays(startTime, data.startTime);
1299         }
1300 	}
1301 	var rows = this._allDayApptsRowLayouts;
1302 	var row = null;
1303 	for (var i=0; i < rows.length; i++) {
1304 		row = rows[i];
1305 		for (var j=0; j < data.numDays; j++) {
1306 			var col = colIndex + j;
1307 			if (col == row.length) break;
1308 			if (!row[col].free) {
1309 				row = null;
1310 				break;
1311 			}
1312 		}
1313 		if (row != null)	break;
1314 	}
1315 	if (row == null) {
1316 		row = this._addAllDayApptRowLayout();
1317 	}
1318 
1319 	this._fillAllDaySlot(row, colIndex, data);
1320 };
1321 
1322 ZmCalColView.prototype._calcNumDays =
1323 function(startTime, endTime) {
1324     return Math.round((endTime-startTime) / AjxDateUtil.MSEC_PER_DAY);
1325 }
1326 // Calculate the offset in days from the 0th column date.  Used for
1327 // multi-day appt dragging.
1328 ZmCalColView.prototype._calcOffsetFromZeroColumn =
1329 function(time) {
1330     var dayIndex = this._columns[0].dayIndex;
1331     var day = this._days[dayIndex];
1332     return Math.round((time-day.date.getTime()) / AjxDateUtil.MSEC_PER_DAY);
1333 }
1334 
1335 /*
1336  * compute layout info for all day appts
1337  */
1338 ZmCalColView.prototype._computeAllDayApptLayout =
1339 function() {
1340 	var adlist = this._allDayApptsList;
1341 	adlist.sort(ZmCalBaseItem.compareByTimeAndDuration);
1342 
1343 	for (var i=0; i < adlist.length; i++) {
1344 		var appt = adlist[i];
1345 		var data = this._allDayAppts[appt.getUniqueId()];
1346 		if (data) {
1347 			var col = this._scheduleMode ? this._getColForFolderId(data.appt.folderId) : this._getDayForDate(new Date(data.startTime));
1348 			if (col)	 this._findAllDaySlot(col.index, data);
1349 		}
1350 	}
1351 };
1352 
1353 ZmCalColView.prototype._layoutAllDayAppts =
1354 function() {
1355 	var rows = this._allDayApptsRowLayouts;
1356 	if (!rows) { return; }
1357 
1358 	var rowY = ZmCalColView._ALL_DAY_APPT_HEIGHT_PAD + 2;
1359 	for (var i=0; i < rows.length; i++) {
1360 		var row = rows[i];
1361 		var num = this._scheduleMode ? this._numCalendars : this.numDays;
1362 		for (var j=0; j < num; j++) {
1363 			var slot = row[j];
1364 			if (slot.data) {
1365 				var appt = slot.data.appt;
1366                 var div = document.getElementById(this._getItemId(appt));
1367                 if(div) {
1368                     if (this._scheduleMode) {
1369                         var cal = this._getColForFolderId(appt.folderId);
1370                         this._positionAppt(div, cal.allDayX+0, rowY);
1371                         this._sizeAppt(div, ((cal.allDayWidth + this._daySepWidth) * slot.data.numDays) - this._daySepWidth - 1,
1372                                      ZmCalColView._ALL_DAY_APPT_HEIGHT);
1373                     } else {
1374                         this._positionAppt(div, this._columns[j].allDayX+0, rowY);
1375                         this._sizeAppt(div, ((this._columns[j].allDayWidth + this._daySepWidth) * slot.data.numDays) - this._daySepWidth - 1,
1376                                      ZmCalColView._ALL_DAY_APPT_HEIGHT);
1377                     }
1378                 }
1379 			}
1380 		}
1381 		rowY += ZmCalColView._ALL_DAY_APPT_HEIGHT + ZmCalColView._ALL_DAY_APPT_HEIGHT_PAD;
1382 	}
1383 };
1384 
1385 
1386 ZmCalColView._getApptWidthPercent =
1387 function(numCols) {
1388 	switch(numCols) {
1389 		case 1: return 1;
1390 		case 2: return 0.8;
1391 		case 3: return 0.6;
1392 		case 4: return 0.4;
1393 		default: return 0.4;
1394 	}
1395 };
1396 
1397 ZmCalColView.prototype._positionAppt =
1398 function(apptDiv, x, y) {
1399     if(!apptDiv) { return; }
1400 	// position overall div
1401 	Dwt.setLocation(apptDiv, x + ZmCalColView._APPT_X_FUDGE, y + ZmCalColView._APPT_Y_FUDGE);
1402 };
1403 
1404 ZmCalColView.prototype._sizeAppt =
1405 function(apptDiv, w, h) {
1406     if(!apptDiv) { return; }
1407 	// set outer as well as inner
1408 	var fw = w + ZmCalColView._APPT_WIDTH_FUDGE; // no fudge for you
1409 	var fh = h;
1410 	Dwt.setSize(apptDiv, fw >= 0 ? fw : 0, fh >= 0 ? fh : 0);
1411 
1412 	// get the inner div that should be sized and set its width/height
1413 	var apptBodyDiv = document.getElementById(apptDiv.id + "_body");
1414 	if (apptBodyDiv != null) {
1415 		fw = w + ZmCalColView._APPT_WIDTH_FUDGE;
1416 		fh = h + ZmCalColView._APPT_HEIGHT_FUDGE;
1417 		Dwt.setSize(	apptBodyDiv, fw >= 0 ? fw : 0, fh >= 0 ? fh : 0);
1418 	}
1419 };
1420 
1421 ZmCalColView.prototype._layoutAppt =
1422 function(ao, apptDiv, x, y, w, h) {
1423 	// record to restore after dnd/sash
1424 	if (ao) ao._layout = {x: x, y: y, w: w, h: h};
1425 	this._positionAppt(apptDiv, x, y);
1426 	this._sizeAppt(apptDiv, w, h);
1427 };
1428 
1429 ZmCalColView.prototype._layoutAppts =
1430 function() {
1431 	// for starting x and width
1432 	var data = this._hours[0];
1433 
1434 	for (var i=0; i < this._layouts.length; i++) {
1435 		var layout = this._layouts[i];
1436 		var apptDiv = document.getElementById(this._getItemId(layout.appt));
1437 		if (apptDiv) {
1438 			layout.bounds = this._getBoundsForAppt(layout.appt);
1439             if (!layout.bounds) { continue; }
1440 			var w = Math.floor(layout.bounds.width*ZmCalColView._getApptWidthPercent(layout.maxcol+1));
1441 			var xinc = layout.maxcol ? ((layout.bounds.width - w) / layout.maxcol) : 0; // n-1
1442 			var x = xinc * layout.col + (layout.bounds.x);
1443 			this._layoutAppt(layout.appt, apptDiv, x, layout.bounds.y, w, layout.bounds.height);
1444 		}
1445 	}
1446 };
1447 
1448 ZmCalColView.prototype._getDayForDate =
1449 function(d) {
1450 	return this._dateToDayIndex[this._dayKey(d)];
1451 };
1452 
1453 ZmCalColView.prototype._getColForFolderId =
1454 function(folderId) {
1455 	return this._folderIdToColIndex[folderId];
1456 };
1457 
1458 ZmCalColView.prototype._getColFromX =
1459 function(x) {
1460 	var num = this._columns.length;
1461 	for (var i =0; i < num; i++) {
1462 		var col = this._columns[i];
1463 		if (x >= col.apptX && x <= col.apptX+col.apptWidth) return col;
1464 	}
1465 	return null;
1466 };
1467 
1468 ZmCalColView.prototype._getLocationForDate =
1469 function(d) {
1470 	var h = d.getHours();
1471 	var m = d.getMinutes();
1472 	var day = this._getDayForDate(d);
1473 	if (day == null) return null;
1474 	return new DwtPoint(day.apptX, Math.floor(((h+m/60) * ZmCalColView._HOUR_HEIGHT))+1);
1475 };
1476 
1477 ZmCalColView.prototype._getBoundsForAppt =
1478 function(appt) {
1479 	var sd = appt.startDate;
1480 	var endOfDay = new Date(sd);
1481 	endOfDay.setHours(23,59,59,999);
1482     var endDate = new Date(appt.endDate);
1483     endDate.setHours(0,0,0,0);
1484     var endTime = appt.getEndTime();
1485     if(appt.startDate.getTime()==endDate.getTime()){
1486         var diffOffset = appt.checkDSTChangeOnEndDate();
1487         endTime = endTime + (diffOffset*60*1000);
1488     }
1489 	var et = Math.min(endTime, endOfDay.getTime());
1490 
1491 	if (this._scheduleMode)
1492 		return this._getBoundsForCalendar(sd, et - sd.getTime(), appt.folderId);
1493 	else
1494 		return this._getBoundsForDate(sd, et - sd.getTime());
1495 };
1496 
1497 ZmCalColView.prototype._getBoundsForDate =
1498 function(d, duration, col) {
1499 	var durationMinutes = duration / 1000 / 60;
1500 	durationMinutes = Math.max(durationMinutes, 22);
1501 	var h = d.getHours();
1502 	var m = d.getMinutes();
1503 	if (col == null && !this._scheduleMode) {
1504 		var day = this._getDayForDate(d);
1505 		col = day ? this._columns[day.index] : null;
1506 	}
1507 	if (col == null) return null;
1508 	return new DwtRectangle(col.apptX, ((h+m/60) * ZmCalColView._HOUR_HEIGHT),
1509 					col.apptWidth, (ZmCalColView._HOUR_HEIGHT / 60) * durationMinutes);
1510 };
1511 
1512 ZmCalColView.prototype._getBoundsForCalendar =
1513 function(d, duration, folderId) {
1514 	var durationMinutes = duration / 1000 / 60;
1515 	durationMinutes = Math.max(durationMinutes, 22);
1516 	var h = d.getHours();
1517 	var m = d.getMinutes();
1518 	var col= this._getColForFolderId(folderId);
1519 	if (col == null) return null;
1520 	return new DwtRectangle(col.apptX, ((h+m/60) * ZmCalColView._HOUR_HEIGHT),
1521 					col.apptWidth, (ZmCalColView._HOUR_HEIGHT / 60) * durationMinutes);
1522 };
1523 
1524 ZmCalColView.prototype._getBoundsForAllDayDate =
1525 function(startSnap, endSnap, useYPadding) {
1526 	if (startSnap == null || endSnap == null) return null;
1527     var yOffset = useYPadding ? ZmCalColView._ALL_DAY_APPT_HEIGHT_PAD + 2 : 0;
1528 	return new DwtRectangle(startSnap.col.allDayX, yOffset,
1529 			(endSnap.col.allDayX + endSnap.col.allDayWidth) - startSnap.col.allDayX - this._daySepWidth-1,
1530 			ZmCalColView._ALL_DAY_APPT_HEIGHT);
1531 };
1532 
1533 // snapXY coord to specified minute boundary (15,30)
1534 // return x, y, col
1535 ZmCalColView.prototype._snapXY =
1536 function(x, y, snapMinutes, roundUp) {
1537 	// snap it to grid
1538 	var col = this._getColFromX(x);
1539 	if (col == null) return null;
1540 	x = col.apptX;
1541 	var height = (snapMinutes/60) * ZmCalColView._HOUR_HEIGHT;
1542 	y = Math.floor(y/height) * height;
1543 	if (roundUp) y += height;
1544 	return {x:x, y:y, col:col};
1545 };
1546 
1547 // snapXY coord to specified minute boundary (15,30)
1548 // return x, y, col
1549 ZmCalColView.prototype._snapAllDayXY =
1550 function(x, y) {
1551 	// snap it to grid
1552 	var col = this._getColFromX(x);
1553 	if (col == null) return null;
1554 	x = col.allDayX;
1555 	return {x:x, y:0, col:col};
1556 };
1557 
1558 ZmCalColView.prototype._snapAllDayOutsideGrid =
1559 function(x) {
1560     var colWidth = this._columns[0].allDayWidth + this._daySepWidth;
1561     var colIndex = Math.floor(x/colWidth);
1562     var colX = (colIndex * colWidth) + 2;
1563     return {x:colX, y:0, col:{index:colIndex}};
1564 }
1565 
1566 // Generate a date (time hour/min/sec == 0) from an arbitrary index
1567 // i.e. an index that may not have a col object
1568 ZmCalColView.prototype._createAllDayDateFromIndex =
1569 function(colIndex) {
1570     var dayIndex =  this._columns[0].dayIndex;
1571     var day = this._days[dayIndex];
1572     return new Date(day.date.getTime() + (AjxDateUtil.MSEC_PER_DAY * colIndex));
1573 }
1574 
1575 ZmCalColView.prototype._getDateFromXY =
1576 function(x, y, snapMinutes, roundUp) {
1577 	var col = this._getColFromX(x);
1578 	if (col == null) return null;
1579 	var minutes = Math.floor((y / ZmCalColView._HOUR_HEIGHT) * 60);
1580 	if (snapMinutes != null && snapMinutes > 1)	{
1581 		minutes = Math.floor(minutes/snapMinutes) * snapMinutes;
1582 		if (roundUp) minutes += snapMinutes;
1583 	}
1584 	var day = this._days[col.dayIndex];
1585 	if (day == null) return null;
1586 	return new Date(day.date.getTime() + (minutes * 60 * 1000));
1587 };
1588 
1589 ZmCalColView.prototype._getAllDayDateFromXY =
1590 function(x, y) {
1591 	var col = this._getColFromX(x);
1592 	if (col == null) return null;
1593 	var day = this._days[col.dayIndex];
1594 	if (day == null) return null;
1595 	return new Date(day.date.getTime());
1596 };
1597 
1598 // helper function to minimize code and catch errors
1599 ZmCalColView.prototype._setBounds =
1600 function(id, x, y, w, h) {
1601 	var el = typeof id === 'string' ? document.getElementById(id) : id;
1602 	if (el == null) {
1603 		DBG.println("ZmCalColView._setBounds null element for id: "+id);
1604 	} else {
1605 		Dwt.setBounds(el, x, y, w, h);
1606 	}
1607 };
1608 
1609 ZmCalColView.prototype._calcColWidth =
1610 function(bodyWidth, numCols, horzScroll) {
1611 //	var sbwfudge = (AjxEnv.isIE ? 1 : 0) + (horzScroll ? 0 : Dwt.SCROLLBAR_WIDTH);
1612 	var sbwfudge = 0;
1613 	return dayWidth = Math.floor((bodyWidth-sbwfudge)/numCols) - (this._daySepWidth == 1 ? 0 : 1);
1614 };
1615 
1616 ZmCalColView.prototype._calcMinBodyWidth =
1617 function(width, numCols) {
1618 	//return minWidth = (ZmCalColView.MIN_COLUMN_WIDTH * numCols) + (this._daySepWidth == 1 ? 0 : 1);
1619 	return minWidth = (ZmCalColView.MIN_COLUMN_WIDTH  + (this._daySepWidth == 1 ? 0 : 1)) * numCols;
1620 };
1621 
1622 ZmCalColView.prototype._layout =
1623 function(refreshApptLayout) {
1624 	DBG.println(AjxDebug.DBG2, "ZmCalColView in layout!");
1625 	this._updateDays();
1626 
1627 	var numCols = this._columns.length;
1628 
1629 	var sz = this.getSize(true); //get the size from the style - it's more accurate as it's exactly what it was set for
1630     if (!sz) {
1631         return;
1632     }
1633 
1634 	var width = sz.x + (this._isRight ? -2 : 0); // -2 is an adjustment due to some problem I can't figure out exactly. bug 75115
1635 	var height = sz.y;
1636 
1637 	if (width == 0 || height == 0) { return; }
1638 
1639 	this._needFirstLayout = false;
1640 
1641 	var hoursWidth = ZmCalColView._HOURS_DIV_WIDTH;
1642 
1643 	var bodyX = hoursWidth + this._daySepWidth;
1644 	var unionX = bodyX;
1645 	if (this._scheduleMode) {
1646 		bodyX += ZmCalColView._UNION_DIV_WIDTH + this._daySepWidth;
1647 	}
1648 
1649 	// compute height for hours/grid
1650 	this._bodyDivWidth = width - bodyX;
1651 
1652 	// size appts divs
1653 	this._apptBodyDivHeight = ZmCalColView._DAY_HEIGHT + 1; // extra for midnight to show up
1654 	this._apptBodyDivWidth = Math.max(this._bodyDivWidth, this._calcMinBodyWidth(this._bodyDivWidth, numCols));
1655 	var needHorzScroll = this._apptBodyDivWidth > this._bodyDivWidth;
1656 
1657 
1658 	this._horizontalScrollbar(needHorzScroll);
1659 	var sbwfudge = AjxEnv.isIE ? 1 : 0;
1660 	var dayWidth = this._calcColWidth(this._apptBodyDivWidth - Dwt.SCROLLBAR_WIDTH, numCols);
1661 
1662 	if (needHorzScroll) this._apptBodyDivWidth -= 18;
1663 	var scrollFudge = needHorzScroll ? 20 : 0; // need all day to be a little wider then grid
1664 
1665 	// year heading
1666 	this._setBounds(this._yearHeadingDivId, 0, 0, hoursWidth, Dwt.DEFAULT);
1667 
1668 	// column headings
1669 	var allDayHeadingDiv = document.getElementById(this._allDayHeadingDivId);
1670 	Dwt.setBounds(allDayHeadingDiv, 0, 0, this._apptBodyDivWidth + scrollFudge, Dwt.DEFAULT);
1671 	var allDayHeadingDivHeight = Dwt.getSize(allDayHeadingDiv).y;
1672 
1673 	// div for all day appts
1674 	var numRows = this._allDayApptsRowLayouts ? (this._allDayApptsRowLayouts.length) : 1;
1675 	if (this._allDayApptsList && this._allDayApptsList.length > 0) numRows++;
1676 	this._allDayFullDivHeight = (ZmCalColView._ALL_DAY_APPT_HEIGHT+ZmCalColView._ALL_DAY_APPT_HEIGHT_PAD) * numRows + ZmCalColView._ALL_DAY_APPT_HEIGHT_PAD;
1677 
1678 	var percentageHeight = (this._allDayFullDivHeight/height)*100;
1679 	this._allDayDivHeight = this._allDayFullDivHeight;
1680 	
1681 	// if height overflows more than 50% of full height set its height
1682 	// to nearest no of rows which occupies less than 50% of total height
1683 	if (percentageHeight > 50) {
1684 		var nearestNoOfRows = Math.floor((0.50*height-ZmCalColView._ALL_DAY_APPT_HEIGHT_PAD)/(ZmCalColView._ALL_DAY_APPT_HEIGHT+ZmCalColView._ALL_DAY_APPT_HEIGHT_PAD));
1685 		this._allDayDivHeight = (ZmCalColView._ALL_DAY_APPT_HEIGHT+ZmCalColView._ALL_DAY_APPT_HEIGHT_PAD) * nearestNoOfRows + ZmCalColView._ALL_DAY_APPT_HEIGHT_PAD;
1686 	}
1687 
1688 	this._setBounds(this._allDayApptScrollDivId, bodyX, allDayHeadingDivHeight, this._bodyDivWidth, this._allDayDivHeight);
1689 	this._setBounds(this._allDayDivId, 0, 0, this._apptBodyDivWidth + scrollFudge, this._allDayFullDivHeight);
1690 
1691 	this._allDayVerticalScrollbar(this._allDayDivHeight != this._allDayFullDivHeight);
1692 
1693 	// div under year
1694 	this._setBounds(this._yearAllDayDivId, 0, allDayHeadingDivHeight, hoursWidth, this._allDayDivHeight);
1695 
1696 	// all day scroll
1697 	var allDayScrollHeight = allDayHeadingDivHeight + this._allDayDivHeight;
1698 	this._setBounds(this._allDayScrollDivId, bodyX, 0, this._bodyDivWidth, allDayScrollHeight);
1699 
1700 	// vert sep between year and all day headings
1701 	this._setBounds(this._leftAllDaySepDivId, hoursWidth, 0, this._daySepWidth, allDayScrollHeight);
1702 
1703 	// horiz separator between all day appts and grid
1704 	this._setBounds(this._allDaySepDivId, 0, (this._hideAllDayAppt ? ZmCalColView._DAY_HEADING_HEIGHT : allDayScrollHeight), width, ZmCalColView._ALL_DAY_SEP_HEIGHT);
1705 
1706 	var bodyY =  (this._hideAllDayAppt ? ZmCalColView._DAY_HEADING_HEIGHT : allDayScrollHeight) + ZmCalColView._ALL_DAY_SEP_HEIGHT +  (AjxEnv.isIE ? 0 : 2);
1707 
1708 	this._bodyDivHeight = height - bodyY;
1709 
1710 	// hours
1711 	this._setBounds(this._hoursScrollDivId, 0, bodyY, hoursWidth, this._bodyDivHeight);
1712 
1713 	// vert sep between hours and grid
1714 	this._setBounds(this._leftApptSepDivId, hoursWidth, bodyY, this._daySepWidth, ZmCalColView._DAY_HEIGHT);
1715 
1716 	// div for scrolling grid
1717 	this._setBounds(this._bodyDivId, bodyX, bodyY, this._bodyDivWidth, this._bodyDivHeight);
1718 
1719 	this._setBounds(this._apptBodyDivId, 0, -1, this._apptBodyDivWidth, this._apptBodyDivHeight);
1720 
1721 	if (this._scheduleMode) {
1722 		//heading
1723 		this._setBounds(this._unionHeadingDivId, unionX, 0, ZmCalColView._UNION_DIV_WIDTH, Dwt.DEFAULT);
1724 
1725 		//div under heading
1726 		this._setBounds(this._unionAllDayDivId, unionX, allDayHeadingDivHeight, ZmCalColView._UNION_DIV_WIDTH, this._allDayDivHeight);
1727 
1728 		// sep in all day area
1729 		var unionSepX = unionX + ZmCalColView._UNION_DIV_WIDTH;
1730 		this._setBounds(this._unionHeadingSepDivId, unionSepX, 0, this._daySepWidth, allDayScrollHeight);
1731 
1732 		// div for scrolling union
1733 		this._setBounds(this._unionGridScrollDivId, unionX, bodyY, ZmCalColView._UNION_DIV_WIDTH, this._bodyDivHeight);
1734 		this._setBounds(this._unionGridDivId, 0, -1, ZmCalColView._UNION_DIV_WIDTH, this._apptBodyDivHeight+ZmCalColView._HOUR_HEIGHT);
1735 
1736 		// sep in grid area
1737 		this._setBounds(this._unionGridSepDivId, unionSepX, bodyY, this._daySepWidth, this._apptBodyDivHeight);
1738 	}
1739 
1740     this.layoutWorkingHours(this.workingHours);
1741 	this._layoutAllDayAppts();
1742 
1743     this._apptBodyDivOffset   = Dwt.toWindow(document.getElementById(this._apptBodyDivId), 0, 0, null, true);
1744     this._apptAllDayDivOffset = Dwt.toWindow(document.getElementById(this._allDayDivId), 0, 0, null, true);
1745 
1746 	if (this._scheduleMode || refreshApptLayout) {
1747 		this._layoutAppts();
1748 		this._checkForOffscreenAppt(document.getElementById(this._bodyDivId));
1749 		if (this._scheduleMode) {
1750 			this._layoutUnionData();
1751 		}
1752 	}
1753 };
1754 
1755 ZmCalColView.prototype.getPostionForWorkingHourDiv =
1756 function(dayIndex, workingHourIndex){
1757     dayIndex = dayIndex || 0;
1758     workingHourIndex = workingHourIndex || 0;
1759     var workingHrs = this.workingHours[dayIndex],
1760         startTime = workingHrs.startTime[workingHourIndex],
1761         endTime = workingHrs.endTime[workingHourIndex],
1762         startMin = (startTime%100)/15,
1763         endMin = (endTime%100)/15,
1764         startWorkingHour = 2 * Math.floor(startTime/100),
1765         endWorkingHour = 2 * Math.floor(endTime/100),
1766         fifteenMinHeight = ZmCalColView.HALF_HOUR_HEIGHT/2,
1767         topPosition = startWorkingHour*ZmCalColView.HALF_HOUR_HEIGHT,
1768         bottomPosition = endWorkingHour*ZmCalColView.HALF_HOUR_HEIGHT,
1769         workingDivHeight = bottomPosition - topPosition;//duration*halfHourHeight;
1770     return {
1771         topPosition : topPosition,
1772         workingDivHeight: workingDivHeight,
1773         startMinAdjust : startMin * fifteenMinHeight,
1774         endMinAdjust : endMin * fifteenMinHeight
1775     };
1776 };
1777 
1778 ZmCalColView.prototype.layoutWorkingHoursDiv =
1779 function(divId, pos, currentX, dayWidth){
1780     this._setBounds(divId, currentX, pos.topPosition+pos.startMinAdjust, dayWidth, pos.workingDivHeight+pos.endMinAdjust-pos.startMinAdjust);
1781     this._setBounds(document.getElementById(divId).firstChild, 0, -pos.startMinAdjust, dayWidth, pos.workingDivHeight+pos.endMinAdjust);
1782 };
1783 
1784 ZmCalColView.prototype.layoutWorkingHours =
1785 function(workingHours){
1786     if(!workingHours) {
1787         workingHours = ZmCalBaseView.parseWorkingHours(ZmCalBaseView.getWorkingHours());
1788         this.workingHours = workingHours;
1789     }
1790     var numCols = this._columns.length;
1791     var dayWidth = this._calcColWidth(this._apptBodyDivWidth - Dwt.SCROLLBAR_WIDTH, numCols);
1792 
1793     var allDayHeadingDiv = document.getElementById(this._allDayHeadingDivId);
1794 	var allDayHeadingDivHeight = Dwt.getSize(allDayHeadingDiv).y;
1795 
1796     var currentX = 0;
1797 
1798 	for (var i = 0; i < numCols; i++) {
1799 		var col = this._columns[i];
1800 
1801 		// position day heading
1802 		var day = this._days[col.dayIndex];
1803 		this._setBounds(col.titleId, currentX+1, Dwt.DEFAULT, dayWidth, ZmCalColView._DAY_HEADING_HEIGHT);
1804 		col.apptX = currentX + 2 ; //ZZZ
1805 		col.apptWidth = dayWidth - this._daySepWidth - 3;  //ZZZZ
1806 		col.allDayX = col.apptX;
1807 		col.allDayWidth = dayWidth; // doesn't include sep
1808 
1809         //split into half hrs sections
1810         var dayIndex = day.date.getDay(),
1811             workingHrs = this.workingHours[dayIndex],
1812             pos = this.getPostionForWorkingHourDiv(dayIndex, 0);
1813 
1814         if(day.isWorkingDay) {
1815             if(!this._scheduleMode) {
1816                 this.layoutWorkingHoursDiv(col.workingHrsFirstDivId, pos, currentX, dayWidth);
1817 
1818                 if( workingHrs.startTime.length >= 2 &&
1819                     workingHrs.endTime.length >= 2) {
1820 
1821                     pos = this.getPostionForWorkingHourDiv(dayIndex, 1);
1822                     this.layoutWorkingHoursDiv(col.workingHrsSecondDivId, pos, currentX, dayWidth);
1823                 }
1824             }
1825             if(this._scheduleMode) {
1826                 this.layoutWorkingHoursDiv(this._workingHrsFirstDivId, pos, 0, dayWidth);
1827 
1828                 if( workingHrs.startTime.length >= 2 &&
1829                     workingHrs.endTime.length >= 2) {
1830 
1831                     pos = this.getPostionForWorkingHourDiv(dayIndex, 1);
1832                     this.layoutWorkingHoursDiv(this._workingHrsSecondDivId, pos, 0, dayWidth);
1833 
1834                 }
1835             }
1836         }
1837         currentX += dayWidth;
1838 
1839 		this._setBounds(col.headingDaySepDivId, currentX, 0, this._daySepWidth, allDayHeadingDivHeight + this._allDayDivHeight);
1840 		this._setBounds(col.daySepDivId, currentX, 0, this._daySepWidth, this._apptBodyDivHeight);
1841 		currentX += this._daySepWidth;
1842 	}
1843 };
1844 
1845 // Must remain in sync with layoutWorkingHours
1846 ZmCalColView.prototype._calculateColumnApptLeft =
1847 function(index, dayWidth, numDays) {
1848     if (index < 0) {
1849         numDays = 0;
1850     }  else {
1851         numDays -= 1;
1852     }
1853     return (dayWidth * index) + (this._daySepWidth * numDays) + 2;
1854 }
1855 
1856 
1857 //Free Busy Bar
1858 
1859 ZmCalColView.prototype._layoutFBBar =
1860 function(){
1861     //Fetch FB Data from GetFreeBusyRequest
1862     var date = this._getDayForDate(this._date);
1863     var startDate = date ? date.date : this._date;
1864     var endDate = date ? date.endDate : null;
1865     this.getFreeBusyInfo(startDate, endDate, new AjxCallback(this, this._handleFBResponse));
1866 };
1867 
1868 ZmCalColView.prototype._handleFBResponse =
1869 function(result){
1870     var statusSlots = result.getResponse().GetFreeBusyResponse.usr;
1871     statusSlots = statusSlots[0]; // 1 User for Calendar View
1872 
1873 
1874     //Prepare UI
1875     var hoursDiv = document.getElementById(this._hoursScrollDivId);
1876     if(!this._fbBarSlots){
1877         var div = document.createElement("DIV");
1878         //div.style.backgroundColor = "#EFE7D4";
1879 		if (hoursDiv) {
1880 			hoursDiv.appendChild(div);
1881 			Dwt.setPosition(div, Dwt.ABSOLUTE_STYLE);
1882 			this._fbBarSlots = div;
1883 			this._fbBarSlotsId = div.id = Dwt.getNextId();
1884 		}
1885     }
1886 
1887     //Calculate X, Y
1888     if (hoursDiv) {
1889         var hourScrollDivLoc = Dwt.getLocation(hoursDiv);
1890         var x = hourScrollDivLoc.x;
1891         x = x + (ZmCalColView._HOURS_DIV_WIDTH - ZmCalColView._FBBAR_DIV_WIDTH + 1);
1892         Dwt.setLocation(this._fbBarSlots, x, 0);
1893 
1894         //Set Ht./ Width
1895         var calBodyHt = document.getElementById(this._bodyDivId).scrollHeight;
1896         Dwt.setSize(this._fbBarSlots, ZmCalColView._FBBAR_DIV_WIDTH - 2, calBodyHt);
1897 
1898         //Cleanup Existing Slots
1899         this._fbBarSlots.innerHTML = "";
1900 
1901         //Handle Slots
1902         if(statusSlots.t) this._drawSlots(ZmCalColView._STATUS_TENTATIVE, statusSlots.t);
1903         if(statusSlots.b) this._drawSlots(ZmCalColView._STATUS_BUSY, statusSlots.b);
1904         if(statusSlots.o) this._drawSlots(ZmCalColView._STATUS_OOO, statusSlots.o);
1905         if(statusSlots.u) this._drawSlots(ZmCalColView._STATUS_OOO, statusSlots.u);
1906         //non tentative/busy/ooo are all free, dont handle them
1907         //if(statusSlots.f) this._drawSlots(ZmCalColView._STATUS_FREE, statusSlots.f);
1908     }
1909 };
1910 
1911 ZmCalColView.prototype._drawSlots =
1912 function(status, slots){
1913 
1914     //Slots
1915     var currDate = this._timeRangeStart;
1916     var calBodyHt = document.getElementById(this._bodyDivId).scrollHeight;
1917     
1918     for(var i=0; i<slots.length; i++){
1919         var slot = slots[i];
1920         var start = slot.s;
1921         var end = slot.e;
1922         if(end > currDate + AjxDateUtil.MSEC_PER_DAY){
1923             end = currDate + AjxDateUtil.MSEC_PER_DAY;
1924         }
1925         if(start < currDate){
1926             start = currDate;
1927         }
1928 
1929         start = new Date(start);
1930         end = new Date(end);
1931 
1932         start = start.getHours()*60 + start.getMinutes();
1933         end   = end.getHours()*60 + end.getMinutes();
1934 
1935         var startPx = Math.floor(start * (calBodyHt / ( 24 * 60)));
1936         var endPx =  Math.floor(end * ( calBodyHt / (24 * 60)));
1937 
1938         var div = document.createElement("DIV");
1939         div.className = this._getFBBarSlotColor(status);
1940         Dwt.setPosition(div, Dwt.ABSOLUTE_STYLE);
1941         this._fbBarSlots.appendChild(div);
1942         div.style.top = ( startPx - 2 ) + "px";
1943         div.style.height = ( endPx - startPx) + "px";
1944         div.style.width = ZmCalColView._FBBAR_DIV_WIDTH - 2;
1945     }
1946     
1947 };
1948 
1949 ZmCalColView.prototype._getFBBarSlotColor =
1950 function(status){
1951     switch(status){
1952         case ZmCalColView._STATUS_FREE:         return "ZmFBBar-free";
1953         case ZmCalColView._STATUS_TENTATIVE:    return "ZmFBBar-tentative";
1954         case ZmCalColView._STATUS_BUSY:         return "ZmFBBar-busy";
1955         case ZmCalColView._STATUS_OOO:          return "ZmFBBar-ooo";
1956     }
1957     return "ZmFBBar-busy";
1958 };
1959 
1960 ZmCalColView.prototype.getFreeBusyInfo =
1961 function(startTime, endTime , callback, errorCallback) {
1962 
1963     if(startTime instanceof Date)
1964        startTime = startTime.getTime();
1965 
1966     if(endTime instanceof Date)
1967         endTime = endTime.getTime();
1968     
1969     endTime = endTime || (startTime + AjxDateUtil.MSEC_PER_DAY );
1970     var email = appCtxt.getActiveAccount().getEmail();
1971     
1972 	var soapDoc = AjxSoapDoc.create("GetFreeBusyRequest", "urn:zimbraMail");
1973 	soapDoc.setMethodAttribute("s", startTime);
1974 	soapDoc.setMethodAttribute("e", endTime);
1975 	soapDoc.setMethodAttribute("uid", email);
1976 
1977 	return appCtxt.getAppController().sendRequest({
1978 		soapDoc: soapDoc,
1979 		asyncMode: true,
1980 		callback: callback,
1981 		errorCallback: errorCallback,
1982 		noBusyOverlay: true
1983 	});
1984 };
1985 
1986 ZmCalColView.prototype._isFBBarDiv =
1987 function(ev){
1988     var target = DwtUiEvent.getTargetWithProp(ev, "id");
1989     if(target.id == this._fbBarSlotsId){
1990         return true;
1991     }   
1992     return false;
1993 };
1994 
1995 ZmCalColView.prototype._getFBBarToolTipContent =
1996 function(ev){
1997     var target = DwtUiEvent.getTarget(ev);
1998     var className = target.className;    
1999     if(/-busy$/.test(className))
2000         return ZmMsg.busy;
2001     if(/-tentative$/.test(className))
2002         return ZmMsg.tentative;
2003     if(/-ooo$/.test(className))
2004         return ZmMsg.outOfOffice;
2005     return ZmMsg.free;
2006 };
2007 
2008 ZmCalColView.prototype._getUnionToolTip =
2009 function(i) {
2010 	// cache it...
2011 	var tooltip = this._unionBusyDataToolTip[i];
2012 	if (tooltip) { return tooltip; }
2013 
2014 	var data = this._unionBusyData[i];
2015 	if (!data instanceof Object) return null;
2016 
2017 	var html = new AjxBuffer();
2018 	html.append("<table cellpadding=2 cellspacing=0 border=0>");
2019 	var checkedCals = this._controller.getCheckedCalendarFolderIds();
2020 	for (var i = 0; i < checkedCals.length; i++) {
2021 		var fid = checkedCals[i];
2022 		if (data[fid]) {
2023 			var cal = this._controller.getCalendar(fid);
2024 			if (cal) {
2025 				var color = ZmCalendarApp.COLORS[cal.color];
2026 				html.append("<tr valign='center' class='", color, "Bg'><td>", AjxImg.getImageHtml(cal.getIcon()), "</td>");
2027 				html.append("<td>", AjxStringUtil.htmlEncode(cal.getName()), "</td></tr>");
2028 			}
2029 		}
2030 	}
2031 	html.append("</table>");
2032 	tooltip = this._unionBusyDataToolTip[i] = html.toString();
2033 	return tooltip;
2034 };
2035 
2036 ZmCalColView.prototype._layoutUnionDataDiv =
2037 function(gridEl, allDayEl, i, data, numCols) {
2038 	var enable = data instanceof Object;
2039 	var id = this._unionBusyDivIds[i];
2040 	var divEl = null;
2041 
2042 	if (id == null) {
2043 		if (!enable) { return; }
2044 		id = this._unionBusyDivIds[i] = Dwt.getNextId();
2045 		var divEl = document.createElement("div");
2046 		divEl.style.position = 'absolute';
2047 		divEl.className = "calendar_sched_union_div";
2048 		this.associateItemWithElement(null, divEl, ZmCalBaseView.TYPE_SCHED_FREEBUSY, id, {index:i});
2049 
2050 		Dwt.setOpacity(divEl, 40);
2051 
2052 		if (i == 48) {
2053 			//have to resize every layout, since all day div height might change
2054 			allDayEl.appendChild(divEl);
2055 		} else {
2056 			// position/size once right here!
2057 			Dwt.setBounds(divEl, 2, ZmCalColView._HALF_HOUR_HEIGHT*i+1, ZmCalColView._UNION_DIV_WIDTH-4 , ZmCalColView._HALF_HOUR_HEIGHT-2);
2058 			gridEl.appendChild(divEl);
2059 		}
2060 
2061 	} else {
2062 		divEl =  document.getElementById(id);
2063 	}
2064 	// have to relayout each time
2065 	if (i == 48)	Dwt.setBounds(divEl, 1, 1, ZmCalColView._UNION_DIV_WIDTH-2, this._allDayDivHeight-2);
2066 
2067 	var num = 0;
2068 	for (var key in data) num++;
2069 
2070 	Dwt.setOpacity(divEl, 20 + (60 * (num/numCols)));
2071 	Dwt.setVisibility(divEl, enable);
2072 };
2073 
2074 ZmCalColView.prototype._layoutUnionData =
2075 function() {
2076 	if (!this._unionBusyData) { return; }
2077 
2078 	var gridEl = document.getElementById(this._unionGridDivId);
2079 	var allDayEl = document.getElementById(this._unionAllDayDivId);
2080 	var numCols = this._columns.length;
2081 	for (var i=0; i < 49; i++) {
2082 		this._layoutUnionDataDiv(gridEl, allDayEl, i, this._unionBusyData[i], numCols);
2083 	}
2084 };
2085 
2086 ZmCalColView.prototype._handleApptScrollRegion =
2087 function(docX, docY, incr, data) {
2088 	var offset = 0;
2089 	var upper = docY < this._apptBodyDivOffset.y;
2090     // Trigger scroll when scroll is within 8 px of the bottom
2091 	var lower = docY > this._apptBodyDivOffset.y+this._bodyDivHeight - 8;
2092 	if (upper || lower) {
2093 		var div = document.getElementById(this._bodyDivId);
2094 		var sTop = div.scrollTop;
2095 		if (upper && sTop > 0) {
2096 			offset = -(sTop > incr ? incr : sTop);
2097 		} else if (lower) {
2098 			var sVisibleTop = this._apptBodyDivHeight - this._bodyDivHeight;
2099 			if (sTop < sVisibleTop) {
2100 				var spaceLeft = sVisibleTop - sTop;
2101 				offset = spaceLeft  > incr ?incr : spaceLeft;
2102 			}
2103 		}
2104 		if (offset != 0) {
2105 			div.scrollTop += offset;
2106 			this._syncScroll();
2107 		}
2108         if (data) {
2109             data.docY -= offset;
2110         }
2111 	}
2112 	return offset;
2113 };
2114 
2115 ZmCalColView.prototype._controlListener =
2116 function(ev) {
2117 	if (ev.newWidth == Dwt.DEFAULT && ev.newHeight == Dwt.DEFAULT) return;
2118 	try {
2119 		if ((ev.oldWidth != ev.newWidth) || (ev.oldHeight != ev.newHeight)) {
2120 			this._layout(true);
2121 			this._updateTimeIndicator();
2122 	 	}
2123 	} catch(ex) {
2124 		DBG.dumpObj(ex);
2125 	}
2126 };
2127 
2128 ZmCalColView.prototype._apptSelected =
2129 function() {
2130 	//
2131 };
2132 
2133 ZmCalColView._ondblclickHandler =
2134 function (ev){
2135 	ev = DwtUiEvent.getEvent(ev);
2136 	ev._isDblClick = true;
2137 	ZmCalColView._onclickHandler(ev);
2138 };
2139 
2140 ZmCalColView.prototype._mouseOverAction =
2141 function(ev, div) {
2142 	var type = this._getItemData(div, "type");
2143 	if (type == ZmCalBaseView.TYPE_DAY_HEADER) {
2144 		div.style.textDecoration = "underline";
2145 	}
2146 };
2147 
2148 ZmCalColView.prototype.getToolTipContent =
2149 function(ev) {
2150     if(this._fbBarEnabled && this._isFBBarDiv(ev)){
2151         return this._getFBBarToolTipContent(ev);        
2152     }
2153 	var div = this.getTargetItemDiv(ev);
2154 	var type = this._getItemData(div, "type");
2155 	if (type == ZmCalBaseView.TYPE_SCHED_FREEBUSY) {
2156 		var index = this._getItemData(div, "index");
2157 		return this._getUnionToolTip(index);
2158 	}
2159 	return ZmCalBaseView.prototype.getToolTipContent.apply(this, arguments);
2160 };
2161 
2162 ZmCalColView.prototype._mouseOutAction =
2163 function(ev, div) {
2164 	ZmCalBaseView.prototype._mouseOutAction.call(this, ev, div);
2165 	var type = this._getItemData(div, "type");
2166 	if (type == ZmCalBaseView.TYPE_DAY_HEADER) {
2167 		div.style.textDecoration = "none";
2168 	} else if (type == ZmCalBaseView.TYPE_SCHED_FREEBUSY) {
2169 		this.setToolTipContent(null);
2170 	}
2171 };
2172 
2173 ZmCalColView.prototype._mouseUpAction =
2174 function(ev, div) {
2175 
2176 	if (Dwt.ffScrollbarCheck(ev)) { return false; }
2177 
2178 	var type = this._getItemData(div, "type");
2179 	if (type == ZmCalBaseView.TYPE_DAY_HEADER && !this._scheduleMode && ! this._isInviteMessage) {
2180 		var dayIndex = this._getItemData(div, "dayIndex");
2181 		var date = this._days[dayIndex].date;
2182 		var cc = appCtxt.getCurrentController();
2183 
2184 		if (this.numDays > 1) {
2185 			cc.setDate(date);
2186 			cc.show(ZmId.VIEW_CAL_DAY);
2187 		} else {
2188 			// TODO: use pref for work week
2189 			if (date.getDay() > 0 && date.getDay() < 6)
2190 				cc.show(ZmId.VIEW_CAL_WORK_WEEK);
2191 			else
2192 				cc.show(ZmId.VIEW_CAL_WEEK);
2193 		}
2194 	} else if (type == ZmCalBaseView.TYPE_DAY_SEP) {
2195 		this.toggleAllDayAppt(!this._hideAllDayAppt);
2196 	}
2197 };
2198 
2199 ZmCalColView.prototype._doubleClickAction =
2200 function(ev, div) {
2201 	ZmCalBaseView.prototype._doubleClickAction.call(this, ev, div);
2202 	var type = this._getItemData(div, "type");
2203 	if (type == ZmCalBaseView.TYPE_APPTS_DAYGRID ||
2204 		type == ZmCalBaseView.TYPE_ALL_DAY)
2205 	{
2206 		this._timeSelectionAction(ev, div, true);
2207 	}
2208 };
2209 
2210 ZmCalColView.prototype._timeSelectionAction =
2211 function(ev, div, dblclick) {
2212 	var date;
2213 	var duration = AjxDateUtil.MSEC_PER_HALF_HOUR;
2214 	var isAllDay = false;
2215 	var gridLoc;
2216 	var type = this._getItemData(div, "type");
2217 	switch (type) {
2218 		case ZmCalBaseView.TYPE_APPTS_DAYGRID:
2219 			gridLoc = Dwt.toWindow(ev.target, ev.elementX, ev.elementY, div, true);
2220 			date = this._getDateFromXY(gridLoc.x, gridLoc.y, 30);
2221 			break;
2222 		case ZmCalBaseView.TYPE_ALL_DAY:
2223 			gridLoc = Dwt.toWindow(ev.target, ev.elementX, ev.elementY, div, true);
2224 			date = this._getAllDayDateFromXY(gridLoc.x, gridLoc.y);
2225 			isAllDay = true;
2226 			break;
2227 		default:
2228 			return;
2229 	}
2230 
2231 	if (date == null) { return false; }
2232 	var col = this._getColFromX(gridLoc.x);
2233 	var folderId = col ? (col.cal ? col.cal.id : null) : null;
2234 
2235 	this._timeSelectionEvent(date, duration, dblclick, isAllDay, folderId, ev.shiftKey);
2236 };
2237 
2238 ZmCalColView.prototype._mouseDownAction =
2239 function(ev, div) {
2240 
2241 	//ZmCalBaseView.prototype._mouseDownAction.call(this, ev, div);
2242     //bug: 57755 - avoid scroll check hack for appt related mouse events
2243     //todo: disable ffScrollbarCheck for 3.6.4+ versions of firefox ( bug 55342 )
2244     var type = this._getItemData(div, "type");
2245 	if (type != ZmCalBaseView.TYPE_APPT && Dwt.ffScrollbarCheck(ev)) { return false; }
2246 
2247 	switch (type) {
2248 		case ZmCalBaseView.TYPE_APPT_BOTTOM_SASH:
2249 		case ZmCalBaseView.TYPE_APPT_TOP_SASH:
2250 			this.setToolTipContent(null);
2251 			return this._sashMouseDownAction(ev, div);
2252 			break;
2253 		case ZmCalBaseView.TYPE_APPT:
2254 			this.setToolTipContent(null);
2255 			return this._apptMouseDownAction(ev, div);
2256 			break;
2257 		case ZmCalBaseView.TYPE_HOURS_COL:
2258 			if (ev.button == DwtMouseEvent.LEFT) {
2259 				var gridLoc = AjxEnv.isIE ? Dwt.toWindow(ev.target, ev.elementX, ev.elementY, div, true) : {x: ev.elementX, y: ev.elementY};
2260 				var fakeLoc = this._getLocationForDate(this.getDate());
2261 				if (fakeLoc) {
2262 					gridLoc.x = fakeLoc.x;
2263 					var gridDiv = document.getElementById(this._apptBodyDivId);
2264 					return this._gridMouseDownAction(ev, gridDiv, gridLoc);
2265 				}
2266 			} else if (ev.button == DwtMouseEvent.RIGHT) {
2267 				DwtUiEvent.copy(this._actionEv, ev);
2268 				this._actionEv.item = this;
2269 				this._evtMgr.notifyListeners(ZmCalBaseView.VIEW_ACTION, this._actionEv);
2270 			}
2271 			break;
2272 		case ZmCalBaseView.TYPE_APPTS_DAYGRID:
2273             if (!appCtxt.isWebClientOffline()) {
2274                 this._timeSelectionAction(ev, div, false);
2275                 if (ev.button == DwtMouseEvent.LEFT) {
2276                     // save grid location here, since timeSelection might move the time selection div
2277                     var gridLoc = Dwt.toWindow(ev.target, ev.elementX, ev.elementY, div, true);
2278                     return this._gridMouseDownAction(ev, div, gridLoc);
2279                 } else if (ev.button == DwtMouseEvent.RIGHT) {
2280                     DwtUiEvent.copy(this._actionEv, ev);
2281                     this._actionEv.item = this;
2282                     this._evtMgr.notifyListeners(ZmCalBaseView.VIEW_ACTION, this._actionEv);
2283                 }
2284             }
2285 			break;
2286 		case ZmCalBaseView.TYPE_ALL_DAY:
2287 			this._timeSelectionAction(ev, div, false);
2288 			if (ev.button == DwtMouseEvent.LEFT) {
2289 				var gridLoc = Dwt.toWindow(ev.target, ev.elementX, ev.elementY, div, true);
2290 				return this._gridMouseDownAction(ev, div, gridLoc, true);
2291 			} else if (ev.button == DwtMouseEvent.RIGHT) {
2292 				DwtUiEvent.copy(this._actionEv, ev);
2293 				this._actionEv.item = this;
2294 				this._evtMgr.notifyListeners(ZmCalBaseView.VIEW_ACTION, this._actionEv);
2295 			}
2296 			break;
2297 	}
2298 	return false;
2299 };
2300 
2301 // BEGIN APPT ACTION HANDLERS
2302 
2303 
2304 // called when DND is confirmed after threshold
2305 ZmCalColView.prototype._apptDndBegin =
2306 function(data) {
2307 	var loc = Dwt.getLocation(data.apptEl);
2308 	data.dndObj = {};
2309 	data.apptX = loc.x;
2310 	data.apptY = loc.y;
2311 
2312 	data.apptsDiv    = document.getElementById(this._apptBodyDivId);
2313 	data.bodyDivEl   = document.getElementById(this._bodyDivId);
2314 	data.apptBodyEl  = document.getElementById(data.apptEl.id + "_body");
2315 
2316 	data.startDate   = new Date(data.appt.getStartTime());
2317 	data.startTimeEl = document.getElementById(data.apptEl.id +"_st");
2318 	data.endTimeEl   = document.getElementById(data.apptEl.id +"_et");
2319 
2320     if (data.appt.isAllDayEvent()) {
2321         data.saveHTML  = data.apptEl.innerHTML;
2322         data.saveLoc  = loc;
2323 
2324         // Adjust apptOffset.x to be the offset from the clicked on column.  Then create the
2325         // start snap using this offset (so that start column of a multi-day is tracked).
2326         var leftSnap = this._snapXY(data.apptX, data.apptY, 15);
2327         var colSnap  = this._snapXY(data.apptX + data.apptOffset.x, data.apptY, 15);
2328         data.apptOffset.x = data.apptOffset.x - colSnap.x + leftSnap.x;
2329 
2330         // Multi day appt may have its start off the grid.  It's will be truncated
2331         // by the layout code, so calculate the true start
2332         var dayOffset = this._calcOffsetFromZeroColumn(data.appt.getStartTime());
2333         // All columns should be the same width. Choose the 0th
2334         var colWidth = this._columns[0].allDayWidth + this._daySepWidth;
2335 
2336         var endTime = data.appt.getEndTime();
2337         var numDays = this._calcNumDays(data.startDate, endTime);
2338         data.apptX = this._calculateColumnApptLeft(dayOffset, colWidth, numDays);
2339         data.snap = this._snapAllDayOutsideGrid(data.apptX + data.apptOffset.x);
2340         data.apptWidth = (colWidth * numDays) - this._daySepWidth - 1;
2341 
2342         // Offset the y fudge that is applied in _layout, _positionAppt
2343         data.apptY -= ZmCalColView._APPT_Y_FUDGE;
2344         var bounds = new DwtRectangle(data.apptX, data.apptY,
2345             data.apptWidth, ZmCalColView._ALL_DAY_APPT_HEIGHT);
2346 
2347         this._layoutAppt(data.appt, data.apptEl, bounds.x, bounds.y, bounds.width, bounds.height);
2348 
2349         data.disableScroll = true;
2350      } else {
2351         data.snap = this._snapXY(data.apptX + data.apptOffset.x, data.apptY, 15); 	// get orig grid snap
2352     }
2353 
2354     if (data.snap == null) return false;
2355 
2356 	this.deselectAll();
2357 	this.setSelection(data.appt);
2358     Dwt.addClass(data.apptBodyEl, DwtCssStyle.DROPPABLE);
2359 	Dwt.setOpacity(data.apptEl, ZmCalColView._OPACITY_APPT_DND);
2360 	data.dndStarted = true;
2361 	return true;
2362 };
2363 
2364 
2365 ZmCalColView.prototype._restoreApptLoc =
2366 function(data) {
2367     if (data && data.appt) {
2368         //Appt move by drag cancelled
2369         var lo = data.appt._layout;
2370         data.view._layoutAppt(null, data.apptEl, lo.x, lo.y, lo.w, lo.h);
2371         if (data.startTimeEl) {
2372             data.startTimeEl.innerHTML = ZmCalBaseItem._getTTHour(data.appt.startDate);
2373         }
2374         if (data.endTimeEl) {
2375             data.endTimeEl.innerHTML = ZmCalBaseItem._getTTHour(data.appt.endDate);
2376         }
2377         ZmCalBaseView._setApptOpacity(data.appt, data.apptEl);
2378     }
2379     else if (data.newApptDivEl) {
2380         // ESC key is pressed while dragging the mouse
2381         // Undo the drag event and hide the new appt div
2382         data.gridEl.style.cursor = 'auto';
2383         var col = data.view._getColFromX(data.gridX);
2384 	    data.folderId = col ? (col.cal ? col.cal.id : null) : null;
2385 		Dwt.setVisible(data.newApptDivEl, false);
2386     }
2387 };
2388 
2389 
2390 ZmCalColView.prototype._restoreAppt =
2391 function(data) {
2392    if (data.appt.isAllDayEvent()) {
2393        Dwt.setLocation(data.apptEl, data.saveLoc.x, data.saveLoc.y);
2394        data.apptEl.innerHTML = data.saveHTML;
2395     }
2396 };
2397 
2398 
2399 ZmCalColView.prototype._createContainerRect =
2400 function(data) {
2401      this._containerRect = null;
2402     if (data.appt.isAllDayEvent()) {
2403         this._containerRect = new DwtRectangle(this._apptAllDayDivOffset.x,
2404                 this._apptAllDayDivOffset.y,
2405                 this._bodyDivWidth,
2406                 this._allDayDivHeight + this._bodyDivHeight + ZmCalColView._SCROLL_PRESSURE_FUDGE);
2407     } else {
2408         this._containerRect = new DwtRectangle(this._apptAllDayDivOffset.x,
2409                 this._apptBodyDivOffset.y - ZmCalColView._SCROLL_PRESSURE_FUDGE,
2410                 this._bodyDivWidth,
2411                 this._bodyDivHeight + ZmCalColView._SCROLL_PRESSURE_FUDGE);
2412     }
2413 }
2414 
2415 ZmCalColView.prototype._clearSnap =
2416 function(snap) {
2417     snap.x = null;
2418     snap.y = null;
2419 }
2420 
2421 
2422 ZmCalColView.prototype._restoreHighlight =
2423 function(data) {
2424     Dwt.setOpacity(data.apptEl, ZmCalColView._OPACITY_APPT_DND);
2425     Dwt.addClass(data.apptBodyEl, DwtCssStyle.DROPPABLE);
2426 }
2427 
2428 ZmCalColView.prototype._doApptMove =
2429 function(data, deltaX, deltaY) {
2430     // snap new location to grid
2431     var newDate = null;
2432     var snap = data.view._snapXY(data.apptX + data.apptOffset.x + deltaX, data.apptY + deltaY, 15);
2433     if (snap == null) {
2434         if (data.appt.isAllDayEvent()) {
2435             // For a multi day appt , the start snap may have started or be pushed off the grid.
2436             // Create a snap with a pseudo column.
2437             snap = data.view._snapAllDayOutsideGrid(data.apptX + data.apptOffset.x + deltaX);
2438             newDate = data.view._createAllDayDateFromIndex(snap.col.index);
2439         }
2440     } else {
2441         newDate = data.view._getDateFromXY(snap.x, snap.y, 15);
2442     }
2443 
2444     //DBG.println("mouseMove new snap: "+snap.x+","+snap.y+ " data snap: "+data.snap.x+","+data.snap.y);
2445     if (snap != null && ((snap.x != data.snap.x || snap.y != data.snap.y))) {
2446         if (newDate != null &&
2447             (!(data.view._scheduleMode && snap.col != data.snap.col)) && // don't allow col moves in sched view
2448             (newDate.getTime() != data.startDate.getTime()))
2449         {
2450             var bounds = null;
2451             if (data.appt.isAllDayEvent()) {
2452                 // Not using snapXY and GeBoundsForAllDayDate - snap requires that a date
2453                 // fall within one of its columns, which may not be so for a multi day appt.
2454                 var bounds = new DwtRectangle(snap.x, data.apptY,
2455                     data.apptWidth, ZmCalColView._ALL_DAY_APPT_HEIGHT);
2456             } else {
2457                 bounds = data.view._getBoundsForDate(newDate, data.appt._orig.getDuration(), snap.col);
2458             }
2459             data.view._layoutAppt(null, data.apptEl, bounds.x, bounds.y, bounds.width, bounds.height);
2460             data.startDate = newDate;
2461             data.snap = snap;
2462             if (data.startTimeEl) data.startTimeEl.innerHTML = ZmCalBaseItem._getTTHour(data.startDate);
2463             if (data.endTimeEl) data.endTimeEl.innerHTML = ZmCalBaseItem._getTTHour(new Date(data.startDate.getTime()+data.appt.getDuration()));
2464         }
2465     }
2466 }
2467 
2468 
2469 
2470 ZmCalColView.prototype._deselectDnDHighlight =
2471 function(data) {
2472     Dwt.delClass(data.apptBodyEl, DwtCssStyle.DROPPABLE);
2473     ZmCalBaseView._setApptOpacity(data.appt, data.apptEl);
2474 }
2475 
2476 // END APPT ACTION HANDLERS
2477 
2478 // BEGIN SASH ACTION HANDLERS
2479 
2480 ZmCalColView.prototype._sashMouseDownAction =
2481 function(ev, sash) {
2482 //	DBG.println("ZmCalColView._sashMouseDownHdlr");
2483 	if (ev.button != DwtMouseEvent.LEFT) {
2484 		return false;
2485 	}
2486 
2487 	var apptEl = sash.parentNode;
2488 	var apptBodyEl = document.getElementById(apptEl.id + "_body");
2489 
2490 	var appt = this.getItemFromElement(apptEl);
2491 	var origHeight = Dwt.getSize(apptBodyEl).y;
2492 	var origLoc = Dwt.getLocation(apptEl);
2493 	var parentOrigHeight = Dwt.getSize(apptEl).y;
2494 	var type = this._getItemData(sash, "type");
2495 	var isTop = (type == ZmCalBaseView.TYPE_APPT_TOP_SASH);
2496 	var data = {
2497 		sash: sash,
2498 		isTop: isTop,
2499 		appt:appt,
2500 		view:this,
2501 		apptEl: apptEl,
2502 		endTimeEl: document.getElementById(apptEl.id +"_et"),
2503 		startTimeEl: document.getElementById(apptEl.id +"_st"),
2504 		apptBodyEl: apptBodyEl,
2505 		origHeight: origHeight,
2506 		apptX: origLoc.x,
2507 		apptY: origLoc.y,
2508 		parentOrigHeight: parentOrigHeight,
2509 		startY: ev.docY
2510 	};
2511 
2512 	if (isTop) {
2513 		data.startDate = new Date(appt.getStartTime());
2514 	} else {
2515 		data.endDate = new Date(appt.getEndTime());
2516 	}
2517 
2518 	//TODO: only create one of these and change data each time...
2519 	var capture = new DwtMouseEventCapture({
2520 		targetObj:data,
2521 		mouseOverHdlr:ZmCalColView._emptyHdlr,
2522 		mouseDownHdlr:ZmCalColView._emptyHdlr, // mouse down (already handled by action)
2523 		mouseMoveHdlr:ZmCalColView._sashMouseMoveHdlr,
2524 		mouseUpHdlr:ZmCalColView._sashMouseUpHdlr,
2525 		mouseOutHdlr:ZmCalColView._emptyHdlr
2526 	});
2527 	capture.capture();
2528 	this.deselectAll();
2529 	this.setSelection(data.appt);
2530 	Dwt.setOpacity(apptEl, ZmCalColView._OPACITY_APPT_DND);
2531 	return false;
2532 };
2533 
2534 ZmCalColView._sashMouseMoveHdlr =
2535 function(ev) {
2536 //	DBG.println("ZmCalColView._sashMouseMoveHdlr");
2537 	var mouseEv = DwtShell.mouseEvent;
2538 	mouseEv.setFromDhtmlEvent(ev);
2539 	var delta = 0;
2540 	var data = DwtMouseEventCapture.getTargetObj();
2541 
2542 	if (mouseEv.docY > 0 && mouseEv.docY != data.startY) {
2543 		delta = mouseEv.docY - data.startY;
2544 	}
2545 
2546 	var draggedOut = data.view._apptDraggedOut(mouseEv.docX, mouseEv.docY);
2547 
2548 	if (draggedOut) {
2549 		if (!data._lastDraggedOut) {
2550 			data._lastDraggedOut = true;
2551 			data.view._restoreApptLoc(data);
2552 		}
2553 	} else {
2554 		if (data._lastDraggedOut) {
2555 			data._lastDraggedOut = false;
2556 			data.lastDelta = 0;
2557 			Dwt.setOpacity(data.apptEl, ZmCalColView._OPACITY_APPT_DND);
2558 		}
2559 		var scrollOffset = data.view._handleApptScrollRegion(mouseEv.docX, mouseEv.docY, ZmCalColView._HOUR_HEIGHT, null);
2560 		if (scrollOffset != 0) {
2561 			data.startY -= scrollOffset;
2562 		}
2563 
2564 		var delta15 = Math.floor(delta/ZmCalColView._15_MINUTE_HEIGHT);
2565 		delta = delta15 * ZmCalColView._15_MINUTE_HEIGHT;
2566 
2567 		if (delta != data.lastDelta) {
2568 			if (data.isTop) {
2569 				var newY = data.apptY + delta;
2570 				var newHeight = data.origHeight - delta;
2571 				if (newHeight >= ZmCalColView._15_MINUTE_HEIGHT) {
2572 					Dwt.setLocation(data.apptEl, Dwt.DEFAULT, newY);
2573 					Dwt.setSize(data.apptEl, Dwt.DEFAULT, data.parentOrigHeight - delta);
2574 					Dwt.setSize(data.apptBodyEl, Dwt.DEFAULT, Math.floor(newHeight));
2575 					data.lastDelta = delta;
2576 					data.startDate.setTime(data.appt.getStartTime() + (delta15 * AjxDateUtil.MSEC_PER_FIFTEEN_MINUTES)); // num msecs in 15 minutes
2577 					if (data.startTimeEl) data.startTimeEl.innerHTML = ZmCalBaseItem._getTTHour(data.startDate);
2578 				}
2579 			} else {
2580 				var newHeight = data.origHeight + delta;
2581 				if (newHeight >= ZmCalColView._15_MINUTE_HEIGHT) {
2582 					var parentNewHeight = data.parentOrigHeight + delta;
2583 					//DBG.println("delta = " + delta);
2584 					Dwt.setSize(data.apptEl, Dwt.DEFAULT, parentNewHeight);
2585 					Dwt.setSize(data.apptBodyEl, Dwt.DEFAULT, newHeight + ZmCalColView._APPT_HEIGHT_FUDGE);
2586 
2587 					data.lastDelta = delta;
2588 					data.endDate.setTime(data.appt.getEndTime() + (delta15 * AjxDateUtil.MSEC_PER_FIFTEEN_MINUTES)); // num msecs in 15 minutes
2589 					if (data.endTimeEl) data.endTimeEl.innerHTML = ZmCalBaseItem._getTTHour(data.endDate);
2590 				}
2591 			}
2592 		}
2593 	}
2594 
2595 	mouseEv._stopPropagation = true;
2596 	mouseEv._returnValue = false;
2597 	mouseEv.setToDhtmlEvent(ev);
2598 	return false;
2599 };
2600 
2601 ZmCalColView._sashMouseUpHdlr =
2602 function(ev) {
2603 //	DBG.println("ZmCalColView._sashMouseUpHdlr");
2604 	var data = DwtMouseEventCapture.getTargetObj();
2605 	ZmCalBaseView._setApptOpacity(data.appt, data.apptEl);
2606 	var mouseEv = DwtShell.mouseEvent;
2607 	mouseEv.setFromDhtmlEvent(ev);
2608 	if (mouseEv.button != DwtMouseEvent.LEFT) {
2609 		DwtUiEvent.setBehaviour(ev, true, false);
2610 		return false;
2611 	}
2612 
2613 	DwtMouseEventCapture.getCaptureObj().release();
2614 
2615 	mouseEv._stopPropagation = true;
2616 	mouseEv._returnValue = false;
2617 	mouseEv.setToDhtmlEvent(ev);
2618 
2619 	var draggedOut = data.view._apptDraggedOut(mouseEv.docX, mouseEv.docY);
2620 	if (draggedOut) {
2621 		data.view._restoreApptLoc(data);
2622 		return false;
2623 	}
2624 
2625 	var needUpdate = false;
2626 	var startDate = null, endDate = null;
2627 	if (data.isTop && data.startDate.getTime() != data.appt.getStartTime()) {
2628 		needUpdate = true;
2629 		startDate = data.startDate;
2630 	} else if (!data.isTop && data.endDate.getTime() != data.appt.getEndTime()) {
2631 		needUpdate = true;
2632 		endDate = data.endDate;
2633 	}
2634 	if (needUpdate) {
2635 		data.view._autoScrollDisabled = true;
2636 		var cc = data.view.getController();
2637 		var errorCallback = new AjxCallback(null, ZmCalColView._handleDnDError, data);
2638 		var sdOffset = startDate ? (startDate.getTime() - data.appt.getStartTime()) : null;
2639 		var edOffset = endDate ? (endDate.getTime() - data.appt.getEndTime()) : null;
2640 		cc.dndUpdateApptDate(data.appt._orig, sdOffset, edOffset, null, errorCallback, mouseEv);
2641 	}
2642 
2643 	return false;
2644 };
2645 
2646 // END SASH ACTION HANDLERS
2647 
2648 
2649 // BEGIN GRID ACTION HANDLERS
2650 
2651 ZmCalColView.prototype._gridMouseDownAction =
2652 function(ev, gridEl, gridLoc, isAllDay) {
2653 	if (ev.button != DwtMouseEvent.LEFT) { return false; }
2654 
2655     if(ZmCalViewController._contextMenuOpened){
2656         ZmCalViewController._contextMenuOpened = false;
2657         return false;
2658     }
2659 
2660 	var data = {
2661 		dndStarted: false,
2662 		view: this,
2663 		gridEl: gridEl,
2664 		gridX: gridLoc.x, // ev.elementX,
2665 		gridY: gridLoc.y,  //ev.elementY,
2666 		docX: ev.docX,
2667 		docY: ev.docY,
2668 		isAllDay: isAllDay
2669 	};
2670 
2671 	var capture = new DwtMouseEventCapture({
2672 		targetObj:data,
2673 		mouseOverHdlr:ZmCalColView._emptyHdlr,
2674 		mouseDownHdlr:ZmCalColView._emptyHdlr, // mouse down (already handled by action)
2675 		mouseMoveHdlr: isAllDay ? ZmCalColView._gridAllDayMouseMoveHdlr : ZmCalColView._gridMouseMoveHdlr,
2676 		mouseUpHdlr:ZmCalColView._gridMouseUpHdlr,
2677 		mouseOutHdlr:ZmCalColView._emptyHdlr
2678 	});
2679 	capture.capture();
2680 	return false;
2681 };
2682 
2683 // called when DND is confirmed after threshold
2684 ZmCalColView.prototype._gridDndBegin =
2685 function(data) {
2686     if(appCtxt.isExternalAccount()) { return false; }
2687 	var col = data.view._getColFromX(data.gridX);
2688 	data.folderId = col ? (col.cal ? col.cal.id : null) : null;
2689 	if (data.isAllDay) {
2690 		data.gridEl.style.cursor = 'e-resize';
2691 		data.newApptDivEl = document.getElementById(data.view._newAllDayApptDivId);
2692 		data.view._populateNewApptHtml(data.newApptDivEl, true, data.folderId);
2693 		data.apptBodyEl = document.getElementById(data.newApptDivEl.id + "_body");
2694 		data.view._allDayScrollToBottom();
2695 		//zzzzz
2696 	} else {
2697 		data.gridEl.style.cursor = 's-resize';
2698 		data.newApptDivEl = document.getElementById(data.view._newApptDivId);
2699 		data.view._populateNewApptHtml(data.newApptDivEl, false, data.folderId);
2700 		data.apptBodyEl = document.getElementById(data.newApptDivEl.id + "_body");
2701 		data.endTimeEl = document.getElementById(data.newApptDivEl.id +"_et");
2702 		data.startTimeEl = document.getElementById(data.newApptDivEl.id +"_st");
2703 	}
2704 	this.deselectAll();
2705 	return true;
2706 };
2707 
2708 /*
2709 *   Initializes the vertical scrollbar of the body element to 8AM.
2710  */
2711 ZmCalColView.prototype.initializeTimeScroll = function(){
2712     this._scrollToTime(8);
2713 }
2714 
2715 ZmCalColView._gridMouseMoveHdlr =
2716 function(ev) {
2717 
2718 	var mouseEv = DwtShell.mouseEvent;
2719 	mouseEv.setFromDhtmlEvent(ev);
2720 	var data = DwtMouseEventCapture.getTargetObj();
2721 
2722 	var deltaX = mouseEv.docX - data.docX;
2723 	var deltaY = mouseEv.docY - data.docY;
2724 
2725 	if (!data.dndStarted) {
2726 		var withinThreshold =  (Math.abs(deltaX) < ZmCalColView.DRAG_THRESHOLD && Math.abs(deltaY) < ZmCalColView.DRAG_THRESHOLD);
2727 		if (withinThreshold || !data.view._gridDndBegin(data)) {
2728 			mouseEv._stopPropagation = true;
2729 			mouseEv._returnValue = false;
2730 			mouseEv.setToDhtmlEvent(ev);
2731 			return false;
2732 		}
2733 	}
2734 
2735 	var scrollOffset = data.view._handleApptScrollRegion(mouseEv.docX, mouseEv.docY, ZmCalColView._HOUR_HEIGHT, null);
2736 	if (scrollOffset != 0) {
2737 		data.docY -= scrollOffset;
2738 		deltaY += scrollOffset;
2739 	}
2740 
2741 	// snap new location to grid
2742 	var snap = data.view._snapXY(data.gridX + deltaX, data.gridY + deltaY, 30);
2743 	if (snap == null) return false;
2744 
2745 	var newStart, newEnd;
2746 
2747 	if (deltaY >= 0) { // dragging down
2748 		newStart = data.view._snapXY(data.gridX, data.gridY, 30);
2749 		newEnd = data.view._snapXY(data.gridX, data.gridY + deltaY, 30, true);
2750 	} else { // dragging up
2751 		newEnd = data.view._snapXY(data.gridX, data.gridY, 30);
2752 		newStart = data.view._snapXY(data.gridX, data.gridY + deltaY, 30);
2753 	}
2754 
2755 	if (newStart == null || newEnd == null) return false;
2756 
2757 	if ((data.start == null) || (data.start.y != newStart.y) || (data.end.y != newEnd.y)) {
2758 
2759 		if (!data.dndStarted) data.dndStarted = true;
2760 
2761 		data.start = newStart;
2762 		data.end = newEnd;
2763 
2764 		data.startDate = data.view._getDateFromXY(data.start.x, data.start.y, 30, false);
2765 		data.endDate = data.view._getDateFromXY(data.end.x, data.end.y, 30, false);
2766 
2767 		var e = data.newApptDivEl;
2768 		if (!e) return;
2769 		var duration = (data.endDate.getTime() - data.startDate.getTime());
2770 		if (duration < AjxDateUtil.MSEC_PER_HALF_HOUR) duration = AjxDateUtil.MSEC_PER_HALF_HOUR;
2771 
2772 		var bounds = data.view._getBoundsForDate(data.startDate, duration, newStart.col);
2773 		if (bounds == null) return false;
2774 		data.view._layoutAppt(null, e, newStart.x, newStart.y, bounds.width, bounds.height);
2775 		Dwt.setVisible(e, true);
2776 		if (data.startTimeEl) data.startTimeEl.innerHTML = ZmCalBaseItem._getTTHour(data.startDate);
2777 		if (data.endTimeEl) data.endTimeEl.innerHTML = ZmCalBaseItem._getTTHour(data.endDate);
2778 	}
2779 	mouseEv._stopPropagation = true;
2780 	mouseEv._returnValue = false;
2781 	mouseEv.setToDhtmlEvent(ev);
2782 	return false;
2783 };
2784 
2785 ZmCalColView._gridMouseUpHdlr =
2786 function(ev) {
2787 	var data = DwtMouseEventCapture.getTargetObj();
2788 	var mouseEv = DwtShell.mouseEvent;
2789 	mouseEv.setFromDhtmlEvent(ev);
2790 
2791 	DwtMouseEventCapture.getCaptureObj().release();
2792 
2793     if (!data.dndStarted && appCtxt.get(ZmSetting.CAL_USE_QUICK_ADD)) {
2794         var newStart, newEnd;
2795         var deltaY = mouseEv.docY - data.docY;
2796 
2797         if (deltaY >= 0) { // dragging down
2798             newStart = data.view._snapXY(data.gridX, data.gridY, 30);
2799             newEnd = data.view._snapXY(data.gridX, data.gridY + deltaY, 30, true);
2800         } else { // dragging up
2801             newEnd = data.view._snapXY(data.gridX, data.gridY, 30);
2802             newStart = data.view._snapXY(data.gridX, data.gridY + deltaY, 30);
2803         }
2804 
2805         if (newStart == null || newEnd == null) return false;
2806 
2807         if ((data.start == null) || (data.start.y != newStart.y) || (data.end.y != newEnd.y)) {
2808 
2809             if (!data.dndStarted){
2810                 data.dndStarted = true;
2811             }
2812 
2813             data.start = newStart;
2814             data.end = newEnd;
2815 
2816             data.startDate = data.view._getDateFromXY(data.start.x, data.start.y, 30, false);
2817             data.endDate = data.view._getDateFromXY(data.end.x, data.end.y, 30, false);
2818         }
2819 
2820         if (data.isAllDay) {
2821 		    data.newApptDivEl = document.getElementById(data.view._newAllDayApptDivId);
2822         } else {
2823             data.newApptDivEl = document.getElementById(data.view._newApptDivId);
2824         }
2825     }
2826 
2827 	if (data.dndStarted) {
2828 		data.gridEl.style.cursor = 'auto';
2829         var col = data.view._getColFromX(data.gridX);
2830 	    data.folderId = col ? (col.cal ? col.cal.id : null) : null;
2831 		Dwt.setVisible(data.newApptDivEl, false);
2832 		if (data.isAllDay) {
2833 			appCtxt.getCurrentController().newAllDayAppointmentHelper(data.startDate, data.endDate, data.folderId, mouseEv.shiftKey);
2834 		} else {
2835 			var duration = (data.endDate.getTime() - data.startDate.getTime());
2836 			if (duration < AjxDateUtil.MSEC_PER_HALF_HOUR) duration = AjxDateUtil.MSEC_PER_HALF_HOUR;
2837 			appCtxt.getCurrentController().newAppointmentHelper(data.startDate, duration, data.folderId, mouseEv.shiftKey);
2838 		}
2839 	}
2840 
2841 	mouseEv._stopPropagation = true;
2842 	mouseEv._returnValue = false;
2843 	mouseEv.setToDhtmlEvent(ev);
2844 
2845 	return false;
2846 };
2847 
2848 // END GRID ACTION HANDLERS
2849 
2850 // BEGIN ALLDAY GRID ACTION HANDLERS
2851 
2852 ZmCalColView._gridAllDayMouseMoveHdlr =
2853 function(ev) {
2854 
2855 	var mouseEv = DwtShell.mouseEvent;
2856 	mouseEv.setFromDhtmlEvent(ev);
2857 	var data = DwtMouseEventCapture.getTargetObj();
2858 
2859 	var deltaX = mouseEv.docX - data.docX;
2860 	var deltaY = mouseEv.docY - data.docY;
2861 
2862 	if (!data.dndStarted) {
2863 		var withinThreshold =  (Math.abs(deltaX) < ZmCalColView.DRAG_THRESHOLD && Math.abs(deltaY) < ZmCalColView.DRAG_THRESHOLD);
2864 		if (withinThreshold || !data.view._gridDndBegin(data)) {
2865 			mouseEv._stopPropagation = true;
2866 			mouseEv._returnValue = false;
2867 			mouseEv.setToDhtmlEvent(ev);
2868 			return false;
2869 		}
2870 	}
2871 
2872 	// snap new location to grid
2873 	var snap = data.view._snapXY(data.gridX + deltaX, data.gridY + deltaY, 30);
2874 	if (snap == null) return false;
2875 
2876 	var newStart, newEnd;
2877 
2878 	if (deltaX >= 0) { // dragging right
2879 		newStart = data.view._snapAllDayXY(data.gridX, data.gridY);
2880 		newEnd = data.view._snapAllDayXY(data.gridX + deltaX, data.gridY);
2881 	} else { // dragging left
2882 		newEnd = data.view._snapAllDayXY(data.gridX, data.gridY);
2883 		newStart = data.view._snapAllDayXY(data.gridX + deltaX, data.gridY);
2884 	}
2885 
2886 	if (newStart == null || newEnd == null) return false;
2887 
2888 	if ((data.start == null) || (!data.view._scheduleMode && ((data.start.x != newStart.x) || (data.end.x != newEnd.x)))) {
2889 
2890 		if (!data.dndStarted) data.dndStarted = true;
2891 
2892 		data.start = newStart;
2893 		data.end = newEnd;
2894 
2895 		data.startDate = data.view._getAllDayDateFromXY(data.start.x, data.start.y);
2896 		data.endDate = data.view._getAllDayDateFromXY(data.end.x, data.end.y);
2897 
2898 		var e = data.newApptDivEl;
2899 		if (!e) return;
2900 
2901 		var bounds = data.view._getBoundsForAllDayDate(data.start, data.end);
2902 		if (bounds == null) return false;
2903 		// blank row at the bottom
2904 		var y = data.view._allDayFullDivHeight - (ZmCalColView._ALL_DAY_APPT_HEIGHT+ZmCalColView._ALL_DAY_APPT_HEIGHT_PAD);
2905 		Dwt.setLocation(e, newStart.x, y);
2906 		Dwt.setSize(e, bounds.width, bounds.height);
2907 		Dwt.setSize(data.apptBodyEl, bounds.width, bounds.height);
2908 		Dwt.setVisible(e, true);
2909 	}
2910 	mouseEv._stopPropagation = true;
2911 	mouseEv._returnValue = false;
2912 	mouseEv.setToDhtmlEvent(ev);
2913 	return false;
2914 };
2915 
2916 // END ALLDAY GRID ACTION HANDLERS
2917 
2918 ZmCalColView._emptyHdlr =
2919 function(ev) {
2920 	var mouseEv = DwtShell.mouseEvent;
2921 	mouseEv.setFromDhtmlEvent(ev);
2922 	mouseEv._stopPropagation = true;
2923 	mouseEv._returnValue = false;
2924 	mouseEv.setToDhtmlEvent(ev);
2925 	return false;
2926 };
2927 
2928 ZmCalColView._handleError =
2929 function(data) {
2930 	data.view.getController()._refreshAction(true);
2931 	return false;
2932 };
2933 
2934 ZmCalColView._handleDnDError =
2935 function(data) {
2936 	// Redraw the grid to reposition whatever DnD failed
2937 	data.view._layout(true);
2938 	return false;
2939 };
2940 
2941 
2942 ZmCalColView.prototype.toggleAllDayAppt =
2943 function(hide) {
2944 	var apptScroll = document.getElementById(this._allDayApptScrollDivId);
2945 	Dwt.setVisible(apptScroll, !hide);
2946     var sash = document.getElementById(this._allDaySepSashDivId);
2947     if(hide) {
2948         Dwt.addClass(sash, 'closed');
2949     }
2950     else {
2951         Dwt.delClass(sash, 'closed');
2952     }
2953 	if (this._scheduleMode) {
2954 		var unionAllDayDiv = document.getElementById(this._unionAllDayDivId);
2955 		Dwt.setVisible(unionAllDayDiv, !hide);
2956 	}
2957 
2958 	this._hideAllDayAppt = ! this._hideAllDayAppt;
2959 	this._layout();
2960 };
2961 
2962 ZmCalColView.prototype._postApptCreate =
2963 function(appt,div) {
2964 	var layout = this._layoutMap[this._getItemId(appt)];
2965 	if (layout){
2966 		layout.bounds = this._getBoundsForAppt(layout.appt);
2967 		if (!layout.bounds) { return; }
2968 
2969 		apptWidthPercent = ZmCalColView._getApptWidthPercent(layout.maxcol+1);
2970 		var w = Math.floor(layout.bounds.width*apptWidthPercent);
2971 		var xinc = layout.maxcol ? ((layout.bounds.width - w) / layout.maxcol) : 0; // n-1
2972 		var x = xinc * layout.col + (layout.bounds.x);
2973 		if (appt) appt._layout = {x: x, y: layout.bounds.y, w: w, h: layout.bounds.height};
2974 		var apptHeight = layout.bounds.height;
2975 		var apptY = layout.bounds.y;
2976 		var apptDiv = document.getElementById(this._getItemId(layout.appt));
2977 		this._layoutAppt(layout.appt, apptDiv, x, apptY, w, apptHeight);
2978 	}
2979 };
2980