1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 ZmCalMonthView = function(parent, posStyle, controller, dropTgt) {
 25 	ZmCalBaseView.call(this, parent, "calendar_view", posStyle, controller, ZmId.VIEW_CAL_MONTH, false);
 26 	var element = this.getHtmlElement();
 27 	// clear the onClick event handler.  Otherwise accessibility code will
 28 	// generate spurious mouse up/down events
 29 	this._setEventHdlrs([DwtEvent.ONCLICK], true, element);
 30 
 31 	this.setScrollStyle(DwtControl.CLIP);
 32 	this._needFirstLayout = true;
 33 	this.numDays = 42;
 34 };
 35 
 36 ZmCalMonthView.prototype = new ZmCalBaseView;
 37 ZmCalMonthView.prototype.constructor = ZmCalMonthView;
 38 
 39 ZmCalMonthView._DaySpacer = 1; 			// space between days
 40 ZmCalMonthView.FIRST_WORKWEEK_DAY = 1; 	// hard code to monday until we get real prefs
 41 ZmCalMonthView.NUM_DAYS_IN_WORKWEEK = 5;// hard code to 5 days until we get real prefs
 42 
 43 ZmCalMonthView.EXPANDED_HEIGHT_PERCENT = 70;
 44 ZmCalMonthView.EXPANDED_WIDTH_PERCENT = 50;
 45 ZmCalMonthView.ANIMATE_INTERVAL = 1000 / 30;
 46 ZmCalMonthView.ANIMATE_DURATION = 250;
 47 
 48 ZmCalMonthView.OUT_OF_BOUNDS_SNAP = -1000;
 49 
 50 ZmCalMonthView.ALL_DAY_DIV_BODY   = "_body";
 51 
 52 ZmCalMonthView.prototype.toString = 
 53 function() {
 54 	return "ZmCalMonthView";
 55 };
 56 
 57 ZmCalMonthView.prototype.getRollField =
 58 function() {
 59 	return AjxDateUtil.MONTH;
 60 };
 61 
 62 ZmCalMonthView.prototype._dateUpdate =
 63 function(rangeChanged) {
 64 	this._clearSelectedDay();
 65 	this._updateSelectedDay();
 66 };
 67 
 68 ZmCalMonthView.prototype._updateTitle =
 69 function()  {	
 70 	// updated in updateDays
 71 };
 72 
 73 ZmCalMonthView.prototype._clearSelectedDay =
 74 function() {
 75 	if (this._selectedData != null) {
 76 		var te = document.getElementById(this._selectedData.tdId);
 77 		te.className = 'calendar_month_cells_td';			
 78 		this._selectedData = null;
 79 	}
 80 };
 81 
 82 ZmCalMonthView.prototype._updateSelectedDay =
 83 function() {
 84 	var day = this._dateToDayIndex[this._dayKey(this._date)];
 85 	if (day) {
 86 		var te = document.getElementById( day.tdId);
 87 		te.className = 'calendar_month_cells_td-selected';
 88 		this._selectedData = day;
 89 	}
 90 };
 91 
 92 ZmCalMonthView.prototype._apptSelected =
 93 function() {
 94 	this._clearSelectedDay();
 95 };
 96 
 97 
 98 ZmCalMonthView.prototype._getDayForAppt =
 99 function(appt) {
100 	return this._getDayForDate(appt.startDate);
101 };
102 
103 ZmCalMonthView.prototype._getDayForDate =
104 function(date) {
105 	return this._dateToDayIndex[this._dayKey(date)];
106 };
107 
108 ZmCalMonthView.prototype._getDivForAppt =
109 function(appt) {
110 	var day = this._getDayForAppt(appt);
111 	return day ? document.getElementById( day.dayId) : null;
112 };
113 
114 ZmCalMonthView.prototype._getStartDate = 
115 function() {
116 	return new Date(this.getDate());
117 };
118 
119 ZmCalMonthView.prototype._dayTitle =
120 function(date) {
121 	if (this._shortMonInDay != date.getMonth()) {
122 		this._shortMonInDay = date.getMonth();
123 		var formatter = DwtCalendar.getDayFormatter();
124 		return formatter.format(date);
125 	}
126 	return date.getDate();
127 };
128 
129 ZmCalMonthView.prototype._getApptUniqueId =
130 function(appt) {
131     return (appt._orig) ? appt._orig.getUniqueId() : appt.getUniqueId();
132 }
133 
134 ZmCalMonthView.prototype._reserveRow = 
135 function(day, apptSet, appt) {
136 	var appts = day.allDayAppts;
137     var row = -1;
138 
139     if (apptSet.rows[day.week] === undefined) {
140         // New apptSet, or another week in the set.  Find a free slot or add to the end
141         row = this._allocateRow(day, apptSet);
142     } else {
143         // Use the existing row for the apptSet
144         row = apptSet.rows[day.week];
145     }
146     var apptToMove = appts[row];
147     appts[row] = appt;
148 
149     if (apptToMove) {
150         // The row was in use, need to move to free slot or end
151         var uniqId = this._getApptUniqueId(apptToMove);
152         var apptSetToMove = this._apptSets[uniqId];
153         row = this._allocateRow(day, apptSetToMove);
154         appts[row] = apptToMove;
155         apptSetToMove.rows[day.week] = row;
156     }
157 };
158 
159 ZmCalMonthView.prototype._allocateRow =
160 function(day, apptSet) {
161     var appts = day.allDayAppts;
162     apptSet.rows[day.week] = appts.length;
163     for (var i=0; i < appts.length; i++) {
164         if (appts[i] == null) {
165             apptSet.rows[day.week] = i;
166             break;
167         }
168     }
169     return apptSet.rows[day.week];
170 }
171 
172 ZmCalMonthView.prototype.addAppt = 
173 function(appt) {
174     var day = this._getDayForAppt(appt);
175 
176     if (appt._orig.isAllDayEvent() || appt._orig.isMultiDay()) {
177         var uniqueId = this._getApptUniqueId(appt);
178         var apptSet = this._apptSets[uniqueId];
179         if (apptSet == null) {
180             apptSet = this._createApptSet(appt, uniqueId);
181         }
182         apptSet.appts.push(appt);
183     }
184 
185     if (day) {
186         if (appt._orig.isAllDayEvent(appt)) {
187             // make sure multi-day all day appts line up
188             // Assuming sliced up appts passed in chronological order (first is start) - verify
189             if (!day.allDayAppts) {
190                 day.allDayAppts = [];
191             }  else {
192 
193             }
194             // Reserve a row if its onscreen
195             this._reserveRow(day, apptSet, appt);
196         } else {
197             if (!day.appts) {
198                 day.appts = [];
199             }
200             day.appts.push(appt);
201         }
202     }
203 
204 };
205 
206 
207 // Multi-day appts have been sliced into a set of single day appts.  Accumulate
208 // the appts into an apptSet, so that when DnD is performed we can update the
209 // position of each appt slice that comprises the full appt.
210 ZmCalMonthView.prototype._createApptSet =
211 function(appt, uniqueId, day) {
212     var dow;
213     var apptSet = null;
214     if (day) {
215         dow = day.dow;
216     } else {
217         // DayIndex should be < 0 (no corresponding day, and assuming sliced appts
218         // passed in from earliest to last)
219         var dayIndex = this._createDayIndexFromDate(appt.startDate);
220         var zeroDay = this._days[0]
221         dow = (zeroDay.dow + dayIndex + 7) % 7;
222     }
223     // The set tracks the starting dow of the full appt, whether or not it is
224     // an all-day event (note that there can be multi-day non-all-day appts),
225     // the appt slices (one per day) that comprise the full appt, and the row
226     // position of each slice  (in case the full appt spans multiple weeks).
227     apptSet = { appts: [], rows: {}, dow: dow, allDay: appt.isAllDayEvent()};
228     this._apptSets[uniqueId] = apptSet;
229     return apptSet;
230 }
231 
232 ZmCalMonthView.prototype._postSet = 
233 function() {
234 	// now go through each day and create appts in correct order to line things up
235 	var day;
236 	if(this._expandedDayInfo) {
237 		var row = this._expandedDayInfo.week;
238 		var col = this._expandedDayInfo.dow;
239 		var day = this._days[row*7+col];
240 		var d = new Date(this._date.getTime());
241 		d.setHours(0,0,0);
242 		if(d.getTime() == day.date.getTime()) {
243 			this.setDayView(day);
244 		}else {
245 			this.createApptItems();
246 		}
247 	}else {
248 		this.createApptItems();
249 	}
250 };
251 
252 
253 ZmCalMonthView.prototype.createApptItems =
254 function() {
255 	var allDayParent = document.getElementById( this._daysId);
256     var day;
257     // Create the all-day divs
258     this._apptAllDayDiv = {};
259     for (var uniqueId in this._apptSets) {
260         var apptSet = this._apptSets[uniqueId];
261         if (!apptSet.allDay) continue;
262 
263         var currentWeek = -1;
264         var first = true;
265         var last = false;
266         var startDayIndex = this._createDayIndexFromDate(apptSet.appts[0]._orig.startDate);
267         // Set first (header div) if offscreen and first div, or first on the grid,
268         //   starting a new week.
269         // Set last if in last day (41) or last in appt sequence
270         for (var iAppt = 0; iAppt < apptSet.appts.length; iAppt++) {
271             var appt = apptSet.appts[iAppt];
272             day = this._getDayForAppt(appt);
273             first = (iAppt == 0);
274             last = (iAppt == (apptSet.appts.length - 1));
275             if (day) {
276                 if (((startDayIndex + iAppt) == (this.numDays -1))) {
277                     last = true;
278                 }
279                 if (day.week != currentWeek) {
280                     // Catches 1st on-screen appt (0th or not)
281                     first = true;
282                     currentWeek = day.week;
283                 }
284             }
285             var allDayDiv = this._createAllDayApptDiv(allDayParent, appt, iAppt, first, last);
286             if (!day) {
287                 Dwt.setVisible(allDayDiv, false);
288             }
289             first = false;
290             last = false;
291 
292             if (day) {
293                 var date = DwtCalendar.getDateFormatter().format(day.date);
294                 allDayDiv.setAttribute('aria-label', date + ' ' + appt.getName());
295             }
296         }
297     }
298 
299 	for (var i=0; i < 6; i++)	 {
300 		for (var j=0; j < 7; j++)	 {
301             var dayIndex = i*7+j;
302 			day = this._days[dayIndex];
303 			if (day.allDayAppts) {
304 				for (var k=0; k < day.allDayAppts.length; k++) {
305 					var appt = day.allDayAppts[k];			
306 					var div = this._attachAllDayFillerHtml(appt, dayIndex);
307 					this._fillers.push(div);
308 				}
309 			}
310 			if (day.appts) {
311 				for (var k=0; k < day.appts.length; k++)
312 					var div = this._createItemHtml(day.appts[k], null);
313 			}
314 		}
315 	}
316 	
317 	if (!this._needFirstLayout)
318 		this._layout();
319 		
320 	if(this._dayView) {
321 		this._dayView.setVisible(false);
322 		this.clearExpandedDay();
323 	}
324 };
325 
326 ZmCalMonthView.prototype._preSet = 
327 function() {
328     // reset all layout data
329 	// cleanup any filler
330 	if (this._fillers.length > 0) {
331 		for (var i=0; i < this._fillers.length; i++) {
332 			var f = 	this._fillers[i];
333 			this._fillers[i] = null;
334 			f.parentNode.removeChild(f);
335 		}
336 		this._fillers = [];
337 	}
338     this._apptSets = new Object();;
339 	for (var i=0; i < 6; i++)	 {
340 		for (var j=0; j < 7; j++)	 {
341 			day = this._days[i*7+j];
342 			if (day.allDayAppts)	delete day.allDayAppts;
343 			if (day.appts) delete day.appts;
344 		}
345 	}
346 };
347 
348 
349 ZmCalMonthView.prototype._createAllDayApptDiv =
350 function(allDayParent, appt, iAppt, first, last) {
351     var allDayDiv = this._createAllDayItemHtml(appt, first, last);
352     allDayParent.appendChild(allDayDiv);
353     var divKey = appt.invId + "_" + iAppt.toString();
354     if (!this._apptAllDayDiv[divKey]) {
355         this._apptAllDayDiv[divKey] = allDayDiv.id;
356     }
357     return allDayDiv;
358 }
359 
360 
361 ZmCalMonthView.prototype._createAllDayItemHtml =
362 function(appt, first, last) {
363 	//DBG.println("---- createItem ---- "+appt);
364 	
365 	// set up DIV
366 	var div = document.createElement("div");	
367 
368 	div.style.position = 'absolute';
369 	Dwt.setSize(div, 10, 10);
370 	div.className = this._getStyle();
371 
372     div.style.overflow = "hidden";
373     div.style.paddingBottom = "4px"
374     div.head = first;
375     div.tail = last;
376 
377 	this.associateItemWithElement(appt, div, ZmCalBaseView.TYPE_APPT);
378     var id = this._getItemId(appt);
379 	div.innerHTML = ZmApptViewHelper._allDayItemHtml(appt, id, this._controller, first, last);
380     var apptBodyDiv = div.firstChild;
381 
382     if (!first) {
383         apptBodyDiv.style.cssText += "border-left: 0px none black !important;";
384     }
385     if (!last) {
386         apptBodyDiv.style.cssText += "border-right: 0px none black !important;";
387     }
388     // Set opacity on the table that is colored with the gradient - needed by IE
389     var tableEl = Dwt.getDescendant(apptBodyDiv, id + "_tableBody");
390     ZmCalBaseView._setApptOpacity(appt, tableEl);
391 
392 	return div;
393 };
394 
395 
396 ZmCalMonthView.prototype._attachAllDayFillerHtml =
397 function(appt, dayIndex) {
398     var day = this._days[dayIndex];
399     var dayTable = document.getElementById(day.dayId);
400     return this._createAllDayFillerHtml(appt, dayIndex, dayTable);
401 }
402 
403 ZmCalMonthView.prototype._createAllDayFillerHtml =
404 function(appt, dayIndex, dayTable) {
405     var targetTable = null;
406     var remove = false;
407     if (!dayTable) {
408         if (!this._fillerGenTableBody) {
409             var table = document.createElement("table");
410             this._fillerGenTableBody = document.createElement("tbody");
411             table.appendChild(this._fillerGenTableBody);
412         }
413         targetTable = this._fillerGenTableBody;
414         remove = true;
415     } else {
416         targetTable = dayTable;
417     }
418 	var	result = targetTable.insertRow(-1);
419     if (appt) {
420         result.id = appt.invId + ":" + dayIndex;
421     }
422 	result.className = "allday";
423     this._createAllDayFillerContent(result, true);
424     if (remove) {
425         result.parentNode.removeChild(result);
426     }
427 	return result;
428 };
429 
430 
431 ZmCalMonthView.prototype._createAllDayFillerContent =
432 function(tr, createCell) {
433     var cell;
434     if (createCell) {
435         cell = tr.insertCell(-1);
436     } else {
437         cell = tr.firstChild;
438     }
439     cell.innerHTML = "<table class=allday><tr><td><div class=allday_item_filler></div></td></tr></table>";
440     cell.className = "calendar_month_day_item";
441 }
442 
443 
444 
445 ZmCalMonthView.prototype._createItemHtml =	
446 function(appt) {
447 	var result = this._getDivForAppt(appt).insertRow(-1);
448 	result.className = this._getStyle(ZmCalBaseView.TYPE_APPT);
449     result.apptStartTimeOffset  = this._getTimeOffset(appt.getStartTime());
450 
451 	this.associateItemWithElement(appt, result, ZmCalBaseView.TYPE_APPT);
452 
453     this._createItemHtmlContents(appt, result);
454 
455 	return result;
456 };
457 
458 ZmCalMonthView.prototype._createItemHtmlContents =
459 function(appt, tr) {
460     var needsAction = appt.ptst == ZmCalBaseItem.PSTATUS_NEEDS_ACTION;
461     var calendar = appCtxt.getById(appt.folderId);
462     var fba = needsAction ? ZmCalBaseItem.PSTATUS_NEEDS_ACTION : appt.fba;
463 
464     var tagNames  = appt.getVisibleTags();
465     var tagIcon = appt.getTagImageFromNames(tagNames);
466 
467     var headerColors = ZmApptViewHelper.getApptColor(needsAction, calendar, tagNames, "header");
468     var headerStyle  = ZmCalBaseView._toColorsCss(headerColors.appt);
469     var bodyColors   = ZmApptViewHelper.getApptColor(needsAction, calendar, tagNames, "body");
470     var bodyStyle    = ZmCalBaseView._toColorsCss(bodyColors.appt);
471 
472 
473     var data = {
474         id: this._getItemId(appt),
475         appt: appt,
476         duration: appt.getShortStartHour(),
477         headerStyle: headerStyle,
478         bodyStyle: bodyStyle,
479         multiday: appt._fanoutFirst != null,
480         first: appt._fanoutFirst,
481         last: appt._fanoutLast,
482         showAsColor : ZmApptViewHelper._getShowAsColorFromId(fba),
483         tagIcon: tagIcon
484     };
485     ZmApptViewHelper.setupCalendarColor(true, bodyColors, tagNames, data, "headerStyle", null, 0, 0);
486 
487     var cell = tr.insertCell(-1);
488     cell.className = "calendar_month_day_item";
489     cell.innerHTML = AjxTemplate.expand("calendar.Calendar#month_appt", data);
490     // Hack for IE - it doesn't display the tag and peel unless you  alter a containing className.
491     // The month template div does not have any classNames, so this is safe.
492     cell.firstChild.className = "";
493 }
494 
495 ZmCalMonthView.prototype._createDay =
496 function(html, loc, week, dow) {
497 	var tdid = Dwt.getNextId();
498 	var did = Dwt.getNextId();
499 	var tid = Dwt.getNextId();	
500 
501 	html.append("<td class='calendar_month_cells_td' id='", tdid, "'>");
502 	html.append("<div style='width:100%;height:100%;'>");
503 	html.append("<table class='calendar_month_day_table'>");
504 	html.append("<tr><td colspan=2 id='", tid, "'></td></tr></table>");
505 	html.append("<table class='calendar_month_day_table'><tbody id='", did, "'>");
506 	html.append("</tbody></table>");
507 	html.append("</div>");
508 	html.append("</td>");
509 
510 	var data = { dayId: did, titleId: tid, tdId: tdid, week: week, dow: dow, view: this};
511 	this._days[loc] = data;
512 };
513 
514 ZmCalMonthView.prototype._createHtml =
515 function() {
516     this._showWeekNumber = appCtxt.get(ZmSetting.CAL_SHOW_CALENDAR_WEEK);    
517 	this._days = new Object();	
518 	this._rowIds = new Object();		
519     this._apptSets = new Object();
520 	this._dayInfo = new Object();
521 	this._fillers = [];
522 	this._headerId = Dwt.getNextId();
523 	this._titleId = Dwt.getNextId();	
524 	this._daysId = Dwt.getNextId();	
525 	this._bodyId = Dwt.getNextId();
526 	this._weekNumBodyId = Dwt.getNextId();
527     this._monthViewTable = Dwt.getNextId();
528 	this._headerColId = [];
529 	this._dayNameId = [];
530 	this._bodyColId = [];
531     this._weekNumberIds = {};
532 
533 	var html = new AjxBuffer();
534 			
535 	html.append("<table class=calendar_view_table cellpadding=0 cellspacing=0 id='",this._monthViewTable,"'>");
536 	html.append("<tr>");
537 
538 	html.append("<td>");
539 	html.append("<div id='", this._headerId, "' style='position:relative;'>");
540 	html.append("<table id=calendar_month_header_table class=calendar_month_header_table>");
541 	html.append("<colgroup>");
542 
543     // Add column group to adjust week title heading.
544     if (this._showWeekNumber) {
545         html.append("<col id='", Dwt.getNextId(), "'/>");
546     }
547 
548 	for (var i=0; i < 7; i++) {
549 		this._headerColId[i] = Dwt.getNextId();
550 		html.append("<col id='", this._headerColId[i], "'/>");
551 	}
552 	html.append("</colgroup>");
553 	html.append("<tr>");
554 	html.append("<td colspan=7 class=calendar_month_header_month id='", this._titleId, "'></td>");
555 	html.append("</tr>");
556 	html.append("<tr>");
557 
558     // Week title to heading.
559     if (this._showWeekNumber) {
560         html.append("<td width=10 valign='bottom' class='calendar_month_header_cells_text'>");
561         html.append(AjxMsg.calendarWeekTitle);
562         html.append("</td>");
563     }
564 	
565 	for (var day=0; day < 7; day++) {
566 		this._dayNameId[day] = Dwt.getNextId();
567 		html.append("<td class=calendar_month_header_cells_text id='",this._dayNameId[day],"'></td>");
568 	}
569 
570 	html.append("</tr>");
571 	html.append("</table>");
572 	html.append("</div>");
573 	html.append("</td></tr>");
574 	html.append("<tr>");
575 
576     html.append("<td class='calendar_month_body_container'>");
577 	html.append("<div id='", this._daysId, "' class=calendar_month_body>");
578 	
579 	html.append("<table id='", this._bodyId, "' class=calendar_month_table>");
580 	html.append("<colgroup>");
581 
582     // Add column group to adjust week number.
583     if (this._showWeekNumber) {
584         html.append("<col id='"+ this._weekNumBodyId + "'></col>");
585     }
586 
587 	for (var i=0; i < 7; i++) {
588 		this._bodyColId[i] = Dwt.getNextId();
589 		html.append("<col id='", this._bodyColId[i], "'/>");
590 	}
591 	html.append("</colgroup>");
592 								
593 	for (var i=0; i < 6; i++)	 {
594 		var weekId = Dwt.getNextId();
595 		html.append("<tr id='" +  weekId + "'>");
596 
597         // Holds week number per row.
598         if (this._showWeekNumber) {
599             var weekNumberId = Dwt.getNextId();
600             html.append("<td id='" + weekNumberId + "' class='calendar_month_weekno_td'></td>");
601             this._weekNumberIds[i] = weekNumberId;
602         }
603 
604 		for (var j=0; j < 7; j++)	 {
605 			this._createDay(html, i*7+j, i, j);
606 		}
607 		html.append("</tr>");	
608 		this._rowIds[i] = weekId;
609 	}
610 	
611 	html.append("</table>");
612 	html.append("</div>");
613 	html.append("</td></tr>");
614 	html.append("</table>");
615 	this.getHtmlElement().innerHTML = html.toString();
616     
617 };
618 
619 ZmCalMonthView.prototype._updateWeekNumber =
620 function(i) {
621     if(!this._showWeekNumber) return;
622 
623     var day = this._days[i*7 + 0];
624 	if(day && day.date) {
625         
626         //todo: need to use server setting to decide the weekno standard
627         var serverId = AjxTimezone.getServerId(AjxTimezone.DEFAULT);
628         var useISO8601WeekNo = (serverId && serverId.indexOf("Europe")==0 && serverId != "Europe/London");
629 
630         // AjxDateUtil alters the date.  Make a copy
631         var date = new Date(day.date.getTime());
632         var weekNumber = AjxDateUtil.getWeekNumber(date, this.firstDayOfWeek(), null, useISO8601WeekNo);
633 
634         var wkId = this._weekNumberIds[i];
635         var wkCell = wkId ? document.getElementById(wkId) : null;
636         if(wkCell) {
637             wkCell.innerHTML = weekNumber;   
638         }
639     }
640 };
641 
642 ZmCalMonthView.prototype._updateDays =
643 function() {
644 	var d = new Date(this._date.getTime());
645 	this._month = d.getMonth();
646 	
647 	d.setHours(0,0,0,0);
648 	d.setDate(1)	
649 	var dow = d.getDay();
650 	var fdow = this.firstDayOfWeek();
651 	if (dow != fdow) {
652 		d.setDate(d.getDate()-((dow+(7-fdow))%7));
653 	}
654 
655 	this._dateToDayIndex = new Object();
656 
657 	var today = new Date();
658 	today.setHours(0,0,0, 0);
659 	
660 	for (var i=0; i < 6; i++) {
661 		for (var j=0; j < 7; j++) {
662 			var loc = this._calcDayIndex(i, j);
663 			var day = this._days[loc];
664 			day.date = new Date(d.getTime());
665 			this._dateToDayIndex[this._dayKey(day.date)] = day;
666 			var thisMonth = day.date.getMonth() == this._month;
667 	 		var te = document.getElementById(day.titleId);
668 	 		var isToday = d.getTime() == today.getTime();
669 			//te.innerHTML = d.getTime() == today.getTime() ? ("<div class=calendar_month_day_today>" + this._dayTitle(d) + "</div>") : this._dayTitle(d);
670 			te.innerHTML = this._dayTitle(d);			
671 			te.className = (thisMonth ? 'calendar_month_day_label' : 'calendar_month_day_label_off_month') + (isToday ? "_today" : "");
672             day.dayClassName = te.className;
673 			var id = day.tdId;
674 	 		var de = document.getElementById(id);			
675 			de.className = 'calendar_month_cells_td';
676 			this.associateItemWithElement(null, de, ZmCalBaseView.TYPE_MONTH_DAY, id, {loc:loc});
677             //d.setTime(d.getTime() + AjxDateUtil.MSEC_PER_DAY);
678             var oldDate = d.getDate();
679             d.setDate(d.getDate() + 1);
680             if(oldDate == d.getDate()) {
681                 //daylight saving problem
682                 d.setHours(0,0,0,0);
683                 d.setTime(d.getTime() + AjxDateUtil.MSEC_PER_DAY);
684             }
685         }
686         this._updateWeekNumber(i);
687 	}
688 	
689 	var formatter = DwtCalendar.getMonthFormatter();
690 	this._title = formatter.format(this._date);
691 	var titleEl = document.getElementById(this._titleId);
692 	titleEl.innerHTML = this._title;
693 };
694 
695 ZmCalMonthView.prototype._calcDayIndex =
696 function(rowIndex, colIndex) {
697     return (rowIndex * 7) + colIndex;
698 }
699 
700 ZmCalMonthView.prototype.getShortCalTitle = function(){
701 	var formatter = DwtCalendar.getShortMonthFormatter();
702 	return formatter.format(this._date);
703 };
704 
705 ZmCalMonthView.prototype._setAllDayDivSize =
706 function(allDayDiv, width) {
707     Dwt.setSize(allDayDiv, width, 16 + 4); //Dwt.DEFAULT);
708     var apptBodyDiv = document.getElementById(allDayDiv.id + ZmCalMonthView.ALL_DAY_DIV_BODY);
709     Dwt.setSize(apptBodyDiv, width, 16); //Dwt.DEFAULT);
710 }
711 
712 ZmCalMonthView.prototype._layoutAllDay = 
713 function() {
714 	var dayY = [];
715 	var sum = 0;
716 	for (var i=0; i < 6; i++)  {
717 		dayY[i] = sum;
718 		var sz = Dwt.getSize(document.getElementById( this._days[7*i].tdId));
719 		if (i == 0)
720 			this.dayWidth = sz.x;
721 		sum += sz.y;
722 	}
723 
724     var apptWidth = this.dayWidth;
725     for (var uniqueId in this._apptSets) {
726         var apptSet = this._apptSets[uniqueId];
727         if (!apptSet.allDay) continue;
728 
729         for (var iAppt = 0; iAppt < apptSet.appts.length; iAppt++) {
730             var appt = apptSet.appts[iAppt];
731             var ae = document.getElementById( this._getItemId(appt));
732             if (ae) {
733                 var width = this._calculateAllDayWidth(apptWidth, ae.head, ae.tail);
734                 this._setAllDayDivSize(ae, width);
735 
736                 var day = this._getDayForAppt(appt);
737                 if (day) {
738 					var dow = (apptSet.dow + iAppt) % 7;
739                     var apptX = this._calculateAllDayX(dow, ae.head) + (this._showWeekNumber ? 15 : 0); // Add week number width
740                     var apptY = dayY[day.week] + (21*apptSet.rows[day.week]) + 18 + 3; //first 17, each appt + 1, second 17, day heading
741                     Dwt.setLocation(ae, apptX, apptY);
742                 }
743             }
744         }
745     }
746 
747 };
748 
749 // Week = week integer index, row = row index within cell, dow = day of week,
750 // iAppt = appt slice of a multi-day appt, 0 .. (numDays-1)
751 ZmCalMonthView.prototype._calculateAllDayX =
752 function(dow, head) {
753     var apptX = 0;
754     if (head) {
755         apptX = (this.dayWidth * dow) + 3;
756     } else {
757         apptX = this.dayWidth * dow;
758     }
759     return apptX;
760 }
761 
762 
763 ZmCalMonthView.prototype._calculateAllDayWidth =
764 function(baseWidth, head, tail) {
765     var apptWidth = baseWidth;
766     if (head) {
767         apptWidth -= 3;
768      }
769     //return (this.dayWidth * (dow + iAppt)) + 3;
770     // +1 for overlap to make box-shadow on the bottom be seamless
771     return apptWidth + (tail ? -3 : 1);
772 }
773 
774 
775 ZmCalMonthView.prototype._layout =
776 function() {
777 
778 	DBG.println("ZmCalMonthView _layout!");
779 
780 	var sz = this.getSize();
781 	var width = sz.x;
782 	var height = sz.y;
783 
784 	if (width == 0 || height == 0) {
785 		return;
786 	}
787 
788 	this._needFirstLayout = false;
789 		
790 	var he = document.getElementById(this._headerId);
791 	var headingHeight = Dwt.getSize(he).y;
792 
793 	var w = width - 5; // No need to subtract week number column width.
794 	var h = height - headingHeight - 10;
795 	
796 	var de = document.getElementById(this._daysId);
797 	Dwt.setSize(de, w, h);
798 
799 	var be = document.getElementById(this._bodyId);
800     if(h < Dwt.getSize(be).y){
801         w = w - 15; //Less Scroll bar width
802     }
803 	Dwt.setSize(be, w, h);
804 
805     // Set the width to 15px fixed.
806     if (this._showWeekNumber) {
807         var wk = document.getElementById(this._weekNumBodyId);
808         Dwt.setSize(wk, 15, Dwt.DEFAULT);
809     }
810 
811 	colWidth = Math.floor(w / (this._showWeekNumber ? 8 : 7)) - 1; // Divide by 8 columns.
812 
813 	var fdow = this.firstDayOfWeek();
814 	for (var i=0; i < 7; i++) {
815         var col = document.getElementById(this._headerColId[i]);
816         Dwt.setSize(col, colWidth, Dwt.DEFAULT);
817         col = document.getElementById(this._bodyColId[i]);
818         Dwt.setSize(col, colWidth, Dwt.DEFAULT);
819 
820 		var dayName = document.getElementById(this._dayNameId[i]);
821 		dayName.innerHTML = AjxDateUtil.WEEKDAY_LONG[(i+fdow)%7];
822 	}
823 
824 	for (var i=0; i < 6; i++) {
825 		var row = document.getElementById(this._rowIds[i]);
826 		Dwt.setSize(row, Dwt.DEFAULT, Math.floor(100/6) + '%');
827 	}
828 
829 	this._layoutAllDay(h);
830 	if(this._expandedDayInfo) {
831         this.resizeCalendarGrid();
832 	}
833     this.resizeAllWeekNumberCell();
834 };
835 
836 ZmCalMonthView.getDayToolTipText =
837 function(date, list, controller, noheader) {
838 	var html = [];
839 	var idx = 0;
840 
841 	html[idx++] = "<div><table cellpadding=0 cellspacing=0 border=0>";
842 	if (!noheader) {
843 		html[idx++] = "<tr><td><div class='calendar_tooltip_month_day_label'>";
844 		html[idx++] = DwtCalendar.getDateFullFormatter().format(date);
845 		html[idx++] = "</div></td></tr>";
846 	}
847 	html[idx++] = "<tr><td><table cellpadding=1 cellspacing=0 border=0 width=100%>";
848 
849 	var size = list ? list.size() : 0;
850 
851 	for (var i=0; i < size; i++) {
852 		var ao = list.get(i);
853 		if (ao.isAllDayEvent()) {
854 			var bs = "";
855 			//if (!ao._fanoutFirst) bs = "border-left:none;";
856 			//if (!ao._fanoutLast) bs += "border-right:none;";
857 			//var bodyStyle = bs != "" ? ("style='" + bs + "'") : "";
858 			html[idx++] = "<tr><td><div class='appt'>";
859 			html[idx++] = ZmApptViewHelper._allDayItemHtml(ao, this._getItemId(ao),
860                 controller, true, true);
861 			html[idx++] = "</div></td></tr>";
862 		}
863 	}
864 
865 	for (var i=0; i < size; i++) {
866 		var ao = list.get(i);
867 		if (!ao.isAllDayEvent()) {
868 			var isNew = ao.ptst == ZmCalBaseItem.PSTATUS_NEEDS_ACTION;
869 			var dur = ao.getDurationText(false, false);
870 
871 			html[idx++] = "<tr><td class='calendar_month_day_item'><div class='";
872 			html[idx++] = ZmCalendarApp.COLORS[controller.getCalendarColor(ao.folderId)];
873 			html[idx++] = isNew ? "DarkC" : "C";
874 			html[idx++] = "'>";
875 			if (isNew) html[idx++] = "<b>";
876 			html[idx++] = dur;
877 			if (dur != "") html[idx++] = " ";
878 			html[idx++] = AjxStringUtil.htmlEncode(ao.getName());
879 			if (isNew) html[idx++] = "</b>";
880 			html[idx++] = "</div></td></tr>";
881 		}
882 	}
883 	if ( size == 0) {
884 		html[idx++] = "<tr><td>";
885 		html[idx++] = ZmMsg.noAppts;
886 		html[idx++] = "</td></tr>";
887 	}
888 	html[idx++] = "</table></tr></td></table></div>";
889 
890 	return html.join("");
891 };
892 
893 ZmCalMonthView.prototype._mouseDownAction = 
894 function(ev, div) {
895 
896 	//if (Dwt.ffScrollbarCheck(ev)) { return false; }
897 
898 	var type = this._getItemData(div, "type");
899 	switch (type) {
900 		case ZmCalBaseView.TYPE_MONTH_DAY:
901             if (!appCtxt.isWebClientOffline()) {
902                 this._timeSelectionAction(ev, div, false);
903                 if (ev.button == DwtMouseEvent.RIGHT) {
904                     DwtUiEvent.copy(this._actionEv, ev);
905                     this._actionEv.item = this;
906                     this._evtMgr.notifyListeners(ZmCalBaseView.VIEW_ACTION, this._actionEv);
907                 }
908             }
909 			break;
910         case ZmCalBaseView.TYPE_APPT:
911             this.setToolTipContent(null);
912             this._apptMouseDownAction(ev, div);
913             break;
914         case ZmCalBaseView.TYPE_ALL_DAY:
915             this.setToolTipContent(null);
916             this._apptMouseDownAction(ev, div);
917             break;
918 	}
919 	return false;
920 };
921 
922 
923 ZmCalMonthView.prototype._doubleClickAction =
924 function(ev, div) {
925 	ZmCalBaseView.prototype._doubleClickAction.call(this, ev, div);
926 	var type = this._getItemData(div, "type");
927 	if (type == ZmCalBaseView.TYPE_MONTH_DAY) {
928 		this._timeSelectionAction(ev, div, true);
929 	}
930 };
931 
932 ZmCalMonthView.prototype._timeSelectionAction =
933 function(ev, div, dblclick) {
934 
935     var date;
936 
937     var type = this._getItemData(div, "type");
938     switch (type) {
939         case ZmCalBaseView.TYPE_MONTH_DAY:
940             var loc = this._getItemData(div, "loc");
941             date = new Date(this._days[loc].date.getTime());
942             var now = new Date();
943             date.setHours(now.getHours(), now.getMinutes());
944 			if(ev.button == DwtMouseEvent.LEFT) {
945                 if(ZmCalViewController._contextMenuOpened){
946                     ZmCalViewController._contextMenuOpened = false;
947                     break;
948                 }
949                 AjxTimedAction.scheduleAction(new AjxTimedAction(this, this.expandDay, [this._days[loc]]), 200);
950 			}
951             break;
952         default:
953             return;
954     }
955     this._timeSelectionEvent(date, AjxDateUtil.MSEC_PER_HOUR, dblclick);
956 };
957 
958 ZmCalMonthView.prototype.setDayView =
959 function(dayInfo) {
960     var tdCell = document.getElementById(dayInfo.tdId);
961     var size = Dwt.getSize(tdCell);
962     var view = this._dayView ;
963     var isDirty = !view || !view.getVisible() || !view.getDate() || view.getDate().getTime() !== dayInfo.date.getTime();
964 
965     if(!view) {
966         view = this._dayView = new ZmCalDayView(this, DwtControl.ABSOLUTE_STYLE, this._controller, this._dropTgt);
967         view.setCompactMode(true);
968         view.setCloseDayViewCallback(new AjxCallback(this, this._closeDayView));
969         //listener changes
970         view.addViewActionListener(new AjxListener(this._controller, this._controller._viewActionListener));
971         view.addTimeSelectionListener(new AjxListener(this._controller, this._controller._timeSelectionListener));
972         view.addSelectionListener(new AjxListener(this, this._dayListSelectionListener));
973         view.addActionListener(new AjxListener(this._controller, this._controller._listActionListener));
974 
975     }else {
976         view.setVisible(true);
977     }
978 
979     if (isDirty) {
980         view.setDate(dayInfo.date, 0, true);
981 
982         var subList = new AjxVector();
983         var appts = dayInfo.appts;
984 
985         if(appts) {
986             for(var i = 0; i < appts.length; i++) {
987                 subList.add(appts[i]);
988             }
989         }
990 
991         var allDayAppts = dayInfo.allDayAppts;
992 
993         if(allDayAppts) {
994             for(var i = 0; i < allDayAppts.length; i++) {
995                 subList.add(allDayAppts[i])
996             }
997         }
998 
999         view._preSet();
1000         view.set(subList, true);
1001     }
1002 
1003     view.setSize(size.x - 10, size.y - 12);
1004     view._syncScroll();
1005 
1006     var loc = Dwt.toWindow(tdCell, 0, 0, this.getHtmlElement(), true);
1007     view.setLocation(loc.x+5, loc.y+5);
1008 
1009     view._layout(true);
1010 };
1011 
1012 ZmCalMonthView.prototype.expandDay =
1013 function(dayInfo) {
1014     this.clearCalendarGrid(true);
1015     this.startExpand(dayInfo);
1016 };
1017 
1018 
1019 ZmCalMonthView.prototype.clearCalendarGrid =
1020 function(markApptDays) {
1021     for (var i=0; i < 6; i++) {
1022 
1023         //clear all day appts
1024         for (var uniqueId in this._apptSets) {
1025             var apptSet = this._apptSets[uniqueId];
1026             if (!apptSet.allDay) continue;
1027 
1028             for (var iAppt = 0; iAppt < apptSet.appts.length; iAppt++) {
1029                 var appt = apptSet.appts[iAppt];
1030                 var ae = document.getElementById( this._getItemId(appt));
1031                 if(ae) {
1032                     ae.parentNode.removeChild(ae);
1033                 }
1034             }
1035         }
1036 
1037         for (var j=0; j < 7; j++) {
1038             var loc = i*7+j;
1039             var day = this._days[loc];
1040             if(day && day.dayId) {
1041                 var node = document.getElementById(day.dayId);
1042                 while(node && node.firstChild) {
1043                     node.firstChild.parentNode.removeChild(node.firstChild);
1044                 }
1045             }
1046             if(day && day.titleId) {
1047                 var te = document.getElementById(day.titleId);
1048                 te.className = day.dayClassName?day.dayClassName : '';
1049                 Dwt.setOpacity(te, 100);
1050                 if(markApptDays) {
1051                     var apptAvailable = (day.appts && day.appts.length > 0) || (day.allDayAppts && day.allDayAppts.length > 0);
1052                     te.className = te.className + (apptAvailable ? ' calendar_month_day_label_bold' : '');
1053                 }
1054             }            
1055         }
1056     }
1057 
1058     //clear all day fillers
1059     if (this._fillers.length > 0) {
1060         for (var i=0; i < this._fillers.length; i++) {
1061             var f = 	this._fillers[i];
1062             this._fillers[i] = null;
1063             if(f.parentNode) {
1064                 f.parentNode.removeChild(f);
1065             }
1066         }
1067         this._fillers = [];
1068     }
1069 };
1070 
1071 ZmCalMonthView.prototype.resizeWeekNumberCell =
1072 function(row, height) {
1073 
1074     if(!this._showWeekNumber) return;
1075     
1076     var weekNumCell = document.getElementById(this._weekNumberIds[row]);
1077     if (weekNumCell) {
1078         Dwt.setSize(weekNumCell, 15, Dwt.DEFAULT); // No need to set fixed height to rows.
1079     }
1080 };
1081 
1082 ZmCalMonthView.prototype.resizeCalendarGrid =
1083 function() {
1084     var grid = document.getElementById(this._daysId)
1085     var size = Dwt.getSize(grid);
1086 
1087     var avgHeight = size.y/6;
1088     var avgWidth = size.x/7;
1089 
1090     for (var i=0; i < 6; i++) {
1091         var row = document.getElementById(this._rowIds[i]);
1092         if(i==5) {
1093             avgHeight = avgHeight-1;
1094         }
1095         Dwt.setSize(row, Dwt.DEFAULT, avgHeight);
1096 
1097         if(AjxEnv.isSafari) {
1098             Dwt.setSize(this.getCell(i, 0), Dwt.DEFAULT, avgHeight);            
1099         }
1100     }
1101 
1102     for (var j=0; j < 7; j++) {
1103         var hdrCol = document.getElementById(this._headerColId[j]);
1104         var bdyCol = document.getElementById(this._bodyColId[j]);
1105         if(j==6) {
1106             avgWidth = avgWidth-1;
1107         }
1108         Dwt.setSize(hdrCol, avgWidth, Dwt.DEFAULT);
1109         Dwt.setSize(bdyCol, avgWidth, Dwt.DEFAULT);
1110         if(AjxEnv.isSafari) {
1111             Dwt.setSize(this.getCell(0, j), avgWidth, Dwt.DEFAULT);            
1112         }
1113     }
1114 };
1115 
1116 ZmCalMonthView.prototype.resizeAllWeekNumberCell =
1117 function() {
1118     // Calculate the row heights and apply to the week number cells
1119     var previousY = 0;
1120     for (var iRow=0; iRow < 6; iRow++) {
1121         var row = document.getElementById(this._rowIds[iRow]);
1122         // Use location to calculate y size - getSize may get off by one
1123         // due to rounding errors.
1124         var location = Dwt.getLocation(row);
1125         if (iRow > 0) {
1126             var ySize = location.y - previousY;
1127             this.resizeWeekNumberCell(iRow-1, ySize);
1128         }
1129         previousY = location.y;
1130     }
1131 }
1132 
1133 ZmCalMonthView.prototype._closeDayView =
1134 function() {
1135     if(this._dayView) {
1136         this._dayView.setVisible(false);
1137         this._needFirstLayout = false;
1138         this.clearExpandedDay();
1139         this.clearCalendarGrid();
1140         var newList = new AjxVector();
1141         newList.addList(this._list || [])
1142         this.set(newList, true);
1143     }
1144 };
1145 
1146 ZmCalMonthView.prototype.resizeCol =
1147 function(colIdx, params) {
1148     var dayInfo = params.dayInfo;
1149     var hdrCol = document.getElementById(this._headerColId[colIdx]);
1150     var bdyCol = document.getElementById(this._bodyColId[colIdx]);
1151     var newWidth = params.avgWidth;
1152 
1153     if(dayInfo.dow == colIdx) {
1154         newWidth = params.expandedWidth;
1155     }else if( params.collapseColId == colIdx) {
1156         newWidth = params.collapsedWidth;
1157     }
1158 
1159     if(bdyCol && hdrCol) {
1160         newWidth = (colIdx==6) ? newWidth-1 : newWidth;
1161         Dwt.setSize(bdyCol, newWidth, Dwt.DEFAULT);
1162         Dwt.setSize(hdrCol, newWidth, Dwt.DEFAULT);
1163 
1164         if(AjxEnv.isSafari || AjxEnv.isChrome || (AjxEnv.isFirefox2_0up && !AjxEnv.isFirefox3up)) {
1165             //change first column cell
1166             Dwt.setSize(this.getCell(0, colIdx), newWidth, Dwt.DEFAULT);
1167         }
1168 
1169     }
1170 };
1171 
1172 ZmCalMonthView.prototype.resizeRow =
1173 function(rowIdx, params) {
1174 
1175     if(rowIdx==null) return;
1176 
1177     var dayInfo = params.dayInfo;
1178     var height = params.avgHeight;
1179 
1180     var colId = null;
1181 
1182     if(dayInfo.week == rowIdx) {
1183         height = params.expandedHeight;
1184         colId = dayInfo.dow;
1185     }else if( params.collapseRowId == rowIdx) {
1186         height = params.collapsedHeight;
1187     }
1188 
1189     height = (rowIdx==5) ? height-1 : height;
1190 
1191     var row = document.getElementById(this._rowIds[rowIdx]);
1192     Dwt.setSize(row, Dwt.DEFAULT, height);
1193 
1194     //change first row cell
1195     if(AjxEnv.isSafari || AjxEnv.isChrome) {
1196         Dwt.setSize(this.getCell(rowIdx,0), Dwt.DEFAULT, height);
1197     }
1198 
1199     //IE needs direct cell expansion
1200     if(AjxEnv.isIE && colId!=null && rowIdx!=null) {
1201         var day = this.getCell(rowIdx, colId);
1202         Dwt.setSize(day, Dwt.DEFAULT, height);
1203     }
1204 
1205     this.resizeWeekNumberCell(rowIdx, height);
1206 };
1207 
1208 ZmCalMonthView.prototype.resizeCell =
1209 function(dayInfo, height) {
1210     var day = this.getCell(dayInfo.week, dayInfo.dow);
1211     Dwt.setSize(day, Dwt.DEFAULT, height);
1212     this.resizeWeekNumberCell(dayInfo.week, height);
1213 };
1214 
1215 ZmCalMonthView.prototype.clearCellHeight =
1216 function(dayInfo) {
1217     if(!dayInfo) return;
1218     var day = this.getCell(dayInfo.week, dayInfo.dow);
1219     Dwt.setSize(day, Dwt.DEFAULT, Dwt.CLEAR);
1220     this.resizeWeekNumberCell(dayInfo.week, Dwt.CLEAR);
1221 };
1222 
1223 ZmCalMonthView.prototype.getCell =
1224 function(row, col) {
1225     var loc = row*7+col;
1226     var cellId = this._days[loc].tdId
1227     return document.getElementById(cellId);
1228 };
1229 
1230 ZmCalMonthView.prototype.startExpand =
1231 function(dayInfo) {
1232 
1233     var grid = document.getElementById(this._daysId)
1234     var size = Dwt.getSize(grid);
1235 
1236     var expandedHeight = size.y*ZmCalMonthView.EXPANDED_HEIGHT_PERCENT/100;
1237     var expandedWidth = size.x*ZmCalMonthView.EXPANDED_WIDTH_PERCENT/100;
1238     var avgHeight = (size.y-expandedHeight)/5;
1239     var avgWidth = (size.x-expandedWidth)/6;
1240 
1241     var param = {
1242         dayInfo: dayInfo,
1243         avgWidth: avgWidth,
1244         avgHeight: avgHeight,
1245         maxWidth: expandedWidth,
1246         maxHeight: expandedHeight,
1247         changeCol: true,
1248         changeRow: true,
1249         startTime: new Date().getTime()
1250     };
1251 
1252     //old expanded day needs to be collapsed
1253     if(this._expandedDayInfo) {
1254         var oldDayInfo = this._expandedDayInfo;
1255 
1256         param.collapseRowId = oldDayInfo.week;
1257         param.collapseColId = oldDayInfo.dow;
1258 
1259         if(oldDayInfo.week == dayInfo.week) {
1260             param.changeRow = false;
1261         }
1262         if(oldDayInfo.dow == dayInfo.dow) {
1263             param.changeCol = false;
1264         }
1265     }
1266 
1267     if(this._dayView) {
1268         this._dayView.setVisible(false);
1269     }
1270 
1271     clearInterval(this._animationInterval);
1272     this._animationFrames = 0;
1273     this._animationInterval =
1274         setInterval(this.animateExpansion.bind(this, param),
1275                     ZmCalMonthView.ANIMATE_INTERVAL);
1276 
1277     this.animateExpansion(param);
1278 };
1279 
1280 
1281 ZmCalMonthView.prototype.animateExpansion =
1282 function(param) {
1283     var diffWidth = param.maxWidth - param.avgWidth;
1284     var diffHeight = param.maxHeight - param.avgHeight;
1285 
1286     var passed = new Date().getTime() - param.startTime;
1287     var progressFraction =
1288         Math.min(passed / ZmCalMonthView.ANIMATE_DURATION, 1);
1289     var opacity = 100 * progressFraction;
1290 
1291     if(param.changeCol) {
1292         param.expandedWidth = param.avgWidth + diffWidth * progressFraction;
1293         param.collapsedWidth = param.maxWidth - diffWidth * progressFraction;
1294     }else {
1295         param.expandedWidth = param.collapsedWidth = param.maxWidth;
1296     }
1297 
1298     if(param.changeRow) {
1299         param.expandedHeight = param.avgHeight + diffHeight * progressFraction;
1300         param.collapsedHeight = param.maxHeight - diffHeight * progressFraction;
1301     }else {
1302         param.expandedHeight = param.collapsedHeight = param.maxHeight;
1303     }
1304 
1305     this._expandDayGrid(param);
1306     this.setDayView(param.dayInfo);
1307 
1308     this._dayView.setOpacity(opacity);
1309     Dwt.setOpacity(Dwt.byId(param.dayInfo.titleId), 100 - opacity);
1310 
1311     this._animationFrames += 1;
1312 
1313     // are we done?
1314     if (progressFraction === 1) {
1315         clearInterval(this._animationInterval);
1316 
1317         var dayInfo = param.dayInfo;
1318         this._expandedDayInfo = {
1319             week: dayInfo.week,
1320             dow: dayInfo.dow,
1321             date: dayInfo.date
1322         };
1323 
1324         var fps = Math.round(this._animationFrames / passed * 1000);
1325         DBG.println(AjxDebug.DBG1, "fisheye animation speed: " + fps + "FPS");
1326     }
1327 };
1328 
1329 ZmCalMonthView.prototype._expandDayGrid =
1330 function(params) {
1331 
1332     var dayInfo = params.dayInfo;
1333 
1334     if(!this._expandedDayInfo) {
1335         for (var i=0; i < 6; i++) {
1336             this.resizeRow(i, params);
1337         }
1338         for (var j=0; j < 7; j++) {
1339             this.resizeCol(j, params);
1340         }
1341     }else {
1342         if(params.changeRow) {
1343             this.resizeRow(params.collapseRowId, params);
1344             this.resizeRow(dayInfo.week, params);
1345         }
1346 
1347         if(params.changeCol) {
1348             this.resizeCol(params.collapseColId, params);
1349             this.resizeCol(dayInfo.dow, params);
1350         }
1351 
1352         if(AjxEnv.isIE){
1353             if(!params.changeRow) {
1354                 this.resizeCell(dayInfo, params.maxHeight);
1355             }
1356             this.clearCellHeight(this._expandedDayInfo);
1357         }
1358 
1359     }
1360 };
1361 
1362 ZmCalMonthView.prototype.clearExpandedDay =
1363 function() {
1364     if(!this._expandedDayInfo) return;
1365     this.clearCellHeight(this._expandedDayInfo);
1366     this.resizeCalendarGrid();
1367     this.resizeAllWeekNumberCell();
1368     this._expandedDayInfo = null;
1369 };
1370 
1371 ZmCalMonthView.prototype._controlListener =
1372 function(ev) {
1373     if(!this._expandedDayInfo) {
1374         ZmCalBaseView.prototype._controlListener.call(this, ev);
1375         var mvTable = document.getElementById(this._monthViewTable);
1376         var s = Dwt.getSize(mvTable);
1377         if(s.y != ev.newHeight || s.x != ev.newWidth){
1378             this._layout();
1379         }                
1380     }else {
1381         this._closeDayView();
1382     }
1383 };
1384 
1385 ZmCalMonthView.prototype._viewActionListener =
1386 function(ev) {
1387     this.notifyListeners(ZmCalBaseView.VIEW_ACTION, ev);
1388 };
1389 
1390 ZmCalMonthView.prototype._dayListSelectionListener =
1391 function(ev) {
1392     this._evtMgr.notifyListeners(DwtEvent.SELECTION, ev);
1393 };
1394 
1395 ZmCalMonthView.prototype.getSelection =
1396 function() {
1397     if(this._expandedDayInfo) {
1398         return this._dayView.getSelection();
1399     }else {
1400         return ZmCalBaseView.prototype.getSelection.call(this);
1401     }
1402 };
1403 
1404 ZmCalMonthView.prototype.resizeDayCell =
1405 function(rowId, colId) {
1406     var sz = this.getSize();
1407     var width = sz.x;
1408     var height = sz.y;
1409 
1410     var he = document.getElementById(this._headerId);
1411     var headingHeight = Dwt.getSize(he).y;
1412 
1413     var w = width - 5;
1414     var h = height - headingHeight - 10;
1415 
1416     var de = document.getElementById(this._daysId);
1417     Dwt.setSize(de, w, h);
1418 
1419     var be = document.getElementById(this._bodyId);
1420     Dwt.setSize(be, w, h);
1421 
1422     var grid = document.getElementById(this._daysId)
1423     var size = Dwt.getSize(grid);
1424     var height = size.y*ZmCalMonthView.EXPANDED_HEIGHT_PERCENT/100;
1425     var width = size.x*ZmCalMonthView.EXPANDED_WIDTH_PERCENT/100;
1426 
1427 
1428     var row = document.getElementById(this._rowIds[rowId]);
1429     var bodyCol = document.getElementById(this._bodyColId[colId]);
1430     var headerCol = document.getElementById(this._headerColId[colId]);
1431     if(row) {
1432         Dwt.setSize(row, Dwt.DEFAULT, height);
1433         Dwt.setSize(row.firstChild, Dwt.DEFAULT, height);
1434         Dwt.setSize(document.getElementById(this._days[rowId*7+colId].tdId), Dwt.DEFAULT, height);
1435     }
1436     if(bodyCol && headerCol) {
1437         Dwt.setSize(bodyCol, width, Dwt.DEFAULT);
1438         Dwt.setSize(headerCol, width, Dwt.DEFAULT);
1439     }
1440 
1441 };
1442 
1443 // --- Overrides of ZmCalBaseView Appt DnD, and custom DnD functions
1444 ZmCalMonthView.prototype._createContainerRect =
1445 function(data) {
1446     var calendarBody = document.getElementById(this._bodyId);
1447     var calPt = Dwt.getLocation(calendarBody);
1448     var calSize = Dwt.getSize(calendarBody);
1449     this._containerRect = new DwtRectangle(calPt.x, calPt.y, calSize.x, calSize.y);
1450     data.originX = calPt.x;
1451     data.originY = calPt.y;
1452     DBG.println(AjxDebug.DBG3,"_createContainerRect containerRect.y: " + calPt.y);
1453 }
1454 
1455 
1456 // called when DND is confirmed after threshold
1457 ZmCalMonthView.prototype._apptDndBegin =
1458 function(data) {
1459 	var loc = Dwt.getLocation(data.apptEl);
1460     data.dndObj = {};
1461     data.apptX = loc.x;
1462     data.apptY = loc.y;
1463     //DBG.println(AjxDebug.DBG3,"MouseMove Begin apptOffset.x,y: " + data.apptOffset.x + "," + data.apptOffset.y +
1464     //    ", originX, originY: " + data.originX + "," + data.originY);
1465 
1466     this._colWidth = this.dayWidth;
1467 
1468     data.snap = this._snapXYToDate(data.docX - data.originX, data.docY - data.originY);
1469     if (data.snap == null) return false;
1470 
1471     var originalAppt = data.appt._orig;
1472     data.startDate   = new Date(originalAppt.getStartTime());
1473     var date = new Date(data.startDate);
1474     date.setHours(0,0,0,0);
1475     data.startDayIndex = this._createDayIndexFromDate(date);
1476     data.offsetDayIndex = data.snap.dayIndex - data.startDayIndex;
1477     data.startDateOffset  = -(data.offsetDayIndex * AjxDateUtil.MSEC_PER_DAY);
1478     data.timeOffset  = [];
1479 
1480     if (data.appt.isAllDayEvent()) {
1481         // All day, possibly multi-day appt
1482         data.timeOffset.push(0);
1483         data.numDays = this._createDayIndexFromDate(originalAppt.endDate) - data.startDayIndex;
1484         data.offsetY  = [];
1485         var allDayDiv = null;
1486         var blankHtml = null;
1487 
1488         // Offscreen divs are already setup, merely not positioned and made visible.
1489         // Alter the display html of onscreen divs from 2nd to last-1 to be blank (!head and !tail)
1490         for (var i = 0; i < data.numDays; i++) {
1491             var iDay = data.startDayIndex + i;
1492             allDayDiv = data.apptEl || this._getAllDayDiv(data.appt, i);
1493             if ((iDay >= 0) && (iDay < this.numDays)) {
1494                 // Initially onscreen div
1495                 day = this._days[iDay];
1496                 this._calculateOffsetY(data, allDayDiv, day.week);
1497                 if (data.numDays > 1) {
1498                     if (i == 0) {
1499                         allDayDiv.saveHtml  = allDayDiv.innerHTML;
1500                         this._clearIcon(allDayDiv.id, "tag");
1501                         this._clearIcon(allDayDiv.id, "peel");
1502                     } else {
1503                         if (allDayDiv.head || (allDayDiv.tail  && (i < (data.numDays - 1)))) {
1504                             allDayDiv.saveHtml  = allDayDiv.innerHTML;
1505                             if (!blankHtml) {
1506                                 var itemId = this._getItemId(data.appt);
1507                                 blankHtml = ZmApptViewHelper._allDayItemHtml(data.appt, itemId, this._controller, false, false);
1508                             }
1509                             allDayDiv.innerHTML = blankHtml;
1510                             allDayDiv.firstChild.id = allDayDiv.id + ZmCalMonthView.ALL_DAY_DIV_BODY;
1511 
1512                             allDayDiv.firstChild.style.cssText += "border-left: 0px none black !important;";
1513                             allDayDiv.firstChild.style.cssText += "border-right: 0px none black !important;";
1514                             this._setAllDayDnDSize(allDayDiv, false, false);
1515                          }
1516                     }
1517                 }
1518 
1519             }
1520             //this._setAllDayDnDSize(data, i, allDayDiv);
1521             this._highlightAllDayDiv(allDayDiv, data.appt, true);
1522         }
1523 
1524     } else {
1525         // Non-all day appt - It could be a multi-day non-all-day appt
1526         var uniqueId = this._getApptUniqueId(data.appt);
1527         var apptSet = this._apptSets[uniqueId];
1528         if (!apptSet) {
1529              // Non-multiday, Non-all-day
1530             var apptDay = this._getDayForAppt(data.appt);
1531             apptSet = this._createApptSet(data.appt, uniqueId, apptDay);
1532             apptSet.appts.push(data.appt);
1533         }
1534         data.trEl = [];
1535         data.tableEl = [];
1536         for (var iAppt = 0; iAppt < apptSet.appts.length; iAppt++) {
1537             var appt = apptSet.appts[iAppt];
1538             var trId = this._getItemId(appt);
1539             var trEl = document.getElementById(trId);
1540             if (trEl == null) {
1541                 // Offscreen ,create a tr for DnD
1542                 trEl = document.createElement("tr");
1543                 trEl.className = "appt-selected";
1544                 this._createItemHtmlContents(appt, trEl);
1545                 this.associateItemWithElement(appt, trEl, ZmCalBaseView.TYPE_APPT);
1546             }
1547             data.tableEl[iAppt] = Dwt.getDescendant(trEl, this._getItemId(appt) + "_tableBody");
1548             data.trEl.push(trEl);
1549             data.timeOffset.push(this._getTimeOffset(appt.getStartTime()));
1550         }
1551         this._calculateWeekY(data);
1552         data.apptDiv = {};
1553     }
1554 
1555 	data.dndStarted = true;
1556 	return true;
1557 };
1558 
1559 ZmCalMonthView.prototype._clearIcon =
1560 function(allDayDivId, iconName) {
1561     var td = document.getElementById(allDayDivId + "_" + iconName);
1562     if (td) {
1563         td.innerHTML = "";
1564     }
1565 }
1566 
1567 
1568 ZmCalMonthView.prototype._highlightAllDayDiv =
1569 function(allDayDiv, appt, highlight) {
1570     var apptBodyDiv = document.getElementById(allDayDiv.id + ZmCalMonthView.ALL_DAY_DIV_BODY);
1571     var tableEl = document.getElementById(this._getItemId(appt) + "_tableBody");
1572     // Not altering opacity - it was setting it to 0.7 for DnD, but the base opacity for all day is 0.4
1573     if (highlight) {
1574         Dwt.addClass(apptBodyDiv, DwtCssStyle.DROPPABLE);
1575         Dwt.setZIndex(allDayDiv, "1000000000");
1576     } else {
1577         Dwt.delClass(apptBodyDiv, DwtCssStyle.DROPPABLE);
1578         Dwt.setZIndex(allDayDiv, "");
1579     }
1580 }
1581 
1582 ZmCalMonthView.prototype._setAllDayDnDSize =
1583 function(allDayDiv, first, last) {
1584     var width = this._calculateAllDayWidth(this.dayWidth, first, last);
1585     this._setAllDayDivSize(allDayDiv, width);
1586 }
1587 
1588 ZmCalMonthView.prototype._getAllDayDiv =
1589 function(appt, iSlice) {
1590     var divKey = appt.invId + "_" + iSlice.toString();
1591     var allDayDivId = this._apptAllDayDiv[divKey];
1592     return document.getElementById(allDayDivId);
1593 
1594 }
1595 
1596 
1597 // Generate a dayIndex that may be < 0 or > (number of days-1), using this._days[0] as
1598 // the 0 reference
1599 ZmCalMonthView.prototype._createDayIndexFromDate =
1600 function(dayDate) {
1601     // Bug 68507: all-day appointments don't appear correctly in month view
1602     // Round it.  If the dayDate is has a daylight savings time transition between itself
1603     // and the current day, the day index may be off by +/- 1/24.  The DayIndex needs
1604     // to be an integer value, otherwise we get incorrect dayOfWeek values (dayIndex % 7)
1605     return Math.round((dayDate.getTime() -
1606         this._days[0].date.getTime())/AjxDateUtil.MSEC_PER_DAY);
1607 }
1608 
1609 
1610 // Calculate the y position of each week
1611 ZmCalMonthView.prototype._calculateWeekY =
1612 function(data) {
1613 	data.weekY = [];
1614 	var y = 0;
1615 	for (var iWeek=0; iWeek < 6; iWeek++)  {
1616 		data.weekY[iWeek] = y;
1617 		var size = Dwt.getSize(document.getElementById( this._days[7*iWeek].tdId));
1618 		y += size.y;
1619 	}
1620     data.weekY[6] = y;
1621 }
1622 
1623 ZmCalMonthView.prototype._calculateOffsetY =
1624 function(data, allDayDiv, week) {
1625     // Record the y offset within the start cell for a particular week
1626     if (!data.weekY) {
1627         this._calculateWeekY(data);
1628     }
1629     if (data.offsetY[week] === undefined) {
1630         var allDayDivPt = Dwt.getLocation(allDayDiv);
1631         data.offsetY[week] = (allDayDivPt.y - data.weekY[week]);
1632     }
1633 }
1634 
1635 ZmCalMonthView.prototype._getTimeOffset =
1636 function(time) {
1637     var date = new Date(time);
1638     date.setHours(0,0,0,0);
1639     return time - date.getTime();
1640 }
1641 
1642 
1643 ZmCalMonthView.prototype._snapXYToDate =
1644 function(x, y) {
1645     var colIndex = Math.floor(x/this._colWidth);
1646     var rowIndex = 5;
1647 
1648     // Recheck the row heights each time - these can change as an DnD element
1649     // moves and out, potentially expanding or contracting a cell
1650     var height = 0;
1651     for (var iRow=0; iRow < 6; iRow++) {
1652         var row = document.getElementById(this._rowIds[iRow]);
1653         var rowSize = Dwt.getSize(row);
1654         height += rowSize.y;
1655         if (y < height) {
1656             rowIndex = iRow;
1657             break;
1658         }
1659     }
1660     // containerRect should aways have constrained this to be 0 <= index < numDays
1661     var dayIndex = this._calcDayIndex(rowIndex, colIndex);
1662     var dayOffset = 0;
1663     if (dayIndex < 0) {
1664         dayOffset = -dayIndex;
1665         dayIndex = 0;
1666     } else if (dayIndex >= this.numDays) {
1667         dayOffset = dayIndex - this.numDays + 1;
1668         dayIndex = this.numDays - 1;
1669     }
1670     var day = this._days[dayIndex];
1671     var dayDate = new Date(this._days[dayIndex].date.getTime());
1672     // Set to zero hours/min/sec/msec - the last day has a time set to 23:59:59:999
1673     dayDate.setHours(0,0,0,0);
1674 
1675     var snapDate = null;
1676     if(day && dayDate) {
1677          snapDate = new Date(dayDate.getTime() + (AjxDateUtil.MSEC_PER_DAY * dayOffset));
1678     }
1679     DBG.println(AjxDebug.DBG3,"mouseMove colIndex: " + colIndex + ", rowIndex: " + rowIndex + ", dayIndex: " + dayIndex + ", snapDate: " + snapDate);
1680 
1681     return {date:snapDate, dayIndex:dayIndex};
1682 }
1683 
1684 
1685 ZmCalMonthView.prototype._clearSnap =
1686 function(snap) {
1687     snap.dayIndex = ZmCalMonthView.OUT_OF_BOUNDS_SNAP;
1688 }
1689 
1690 
1691 ZmCalMonthView.prototype._doApptMove =
1692 function(data, deltaX, deltaY) {
1693     var x = data.docX - data.originX + deltaX;
1694     var y = data.docY - data.originY + deltaY;
1695     //DBG.println(AjxDebug.DBG3,"_doApptMove docY: " + data.docY + ",  originY: " + data.originY + ",  deltaY: " + deltaY + ",  y: " + y);
1696     var snap = this._snapXYToDate(x, y);
1697     if ((snap != null) && (snap.dayIndex != data.snap.dayIndex)) {
1698         DBG.println(AjxDebug.DBG3,"mouseMove new snap: " + snap.date + " (" + snap.dayIndex + ")   data snap: " +
1699                      data.snap.date+ " (" + data.snap.dayIndex + ")");
1700 
1701         if (data.appt.isAllDayEvent()) {
1702             // Map the dayIndex to the start of the (potentially) multi-day appt
1703             this._moveAllDayAppt(data, snap.dayIndex- data.offsetDayIndex);
1704         } else {
1705             this._moveApptRow(data, snap.dayIndex);
1706         }
1707         data.startDate = new Date(snap.date.getTime() + data.startDateOffset + data.timeOffset[0]);
1708         data.snap = snap;
1709     }
1710 
1711 }
1712 
1713 ZmCalMonthView.prototype._moveAllDayAppt =
1714 function(data, newDayIndex) {
1715     var currentWeek = -1;
1716     var firstDow = this.firstDayOfWeek();
1717     for (var i = 0; i < data.numDays; i++) {
1718         var iDay = newDayIndex + i;
1719         var allDayDiv = data.apptEl || this._getAllDayDiv(data.appt, i);
1720         if ((iDay < 0) || (iDay >= this.numDays)) {
1721             Dwt.setVisible(allDayDiv, false);
1722         } else {
1723             var dow = (newDayIndex + i) % 7;
1724             var first = (i== 0) || (firstDow == dow);
1725             var last  = ((i == (data.numDays-1) || (iDay == (this.numDays-1)) || (dow == (firstDow + 6))));
1726             var apptX = this._calculateAllDayX(dow, first);
1727             var day = this._days[iDay];
1728             var apptY = 0;
1729             Dwt.setVisible(allDayDiv, true);
1730             this._setAllDayDnDSize(allDayDiv, first, last);
1731             var size = Dwt.getSize(allDayDiv);
1732             var halfHeight = size.y/2;
1733             if (data.offsetY[day.week] !== undefined) {
1734                 apptY = data.weekY[day.week] + data.offsetY[day.week];
1735             } else {
1736                 apptY = (data.weekY[day.week] + data.weekY[day.week + 1])/2 - halfHeight;
1737             }
1738             Dwt.setLocation(allDayDiv, apptX, apptY);
1739         }
1740     }
1741 }
1742 
1743 
1744 // Move a non-all-day appt
1745 ZmCalMonthView.prototype._moveApptRow =
1746 function(data, newDayIndex) {
1747     newDayIndex = newDayIndex - data.offsetDayIndex;
1748     var allDayParent = null;
1749     for (var i = 0; i < data.trEl.length; i++) {
1750         var day = this._days[newDayIndex + i];
1751         if (day) {
1752             if (!data.apptDiv[i]) {
1753                 // TR -> TD -> TemplateApptDiv.
1754                 var td =  data.trEl[i].firstChild;
1755                 var templateApptDiv = td.firstChild;
1756                 td.saveHTML = td.innerHTML;
1757                 // Replace the templateApptDiv with filler content
1758                 td.removeChild(templateApptDiv);
1759                 // Create a spacer row - changes in height invalidates the all day div positioning
1760                 this._createAllDayFillerContent(data.trEl[i], false);
1761                 if (!allDayParent) {
1762                     allDayParent = document.getElementById( this._daysId);
1763                 }
1764                 data.apptDiv[i] = this._createDnDApptDiv(data, i, allDayParent, templateApptDiv);
1765             }
1766             Dwt.setVisible(data.apptDiv[i], true);
1767             var apptTable = data.apptDiv[i].firstChild;
1768             var apptSize = Dwt.getSize(apptTable);
1769             var apptX = (this.dayWidth * day.dow) + this.dayWidth/2 - apptSize.x/2 + (this._showWeekNumber ? 15 : 0); // Adjust week number width while dragging
1770             var apptY = (data.weekY[day.week] + data.weekY[day.week + 1])/2 - apptSize.y/2;
1771             Dwt.setLocation(data.apptDiv[i], apptX, apptY);
1772 
1773         }  else if (data.apptDiv[i]) {
1774             Dwt.setVisible(data.apptDiv[i], false);
1775         }
1776     }
1777 }
1778 
1779 ZmCalMonthView.prototype._createDnDApptDiv =
1780 function(data, iAppt, allDayParent, templateApptDiv) {
1781     var div = document.createElement("div");
1782     var subs = { apptSlice:iAppt};
1783     div.style.position = "absolute";
1784     // Attach month appt to DnD proxy div
1785     div.appendChild(templateApptDiv);
1786 
1787     var trSize  = Dwt.getSize(data.trEl[iAppt]);
1788     Dwt.setSize(div, trSize.x, Dwt.CLEAR);
1789     Dwt.setZIndex(div, '100000000');
1790     allDayParent.appendChild(div);
1791 
1792     // Set the opacity on the table that has the gradient coloring; Needed for IE
1793     Dwt.setOpacity(data.tableEl[iAppt], ZmCalColView._OPACITY_APPT_DND);
1794     Dwt.addClass(div, DwtCssStyle.DROPPABLE);
1795 
1796     return div;
1797 }
1798 
1799 
1800 ZmCalMonthView.prototype._reattachApptDnDHtml =
1801 function(data, startIndex, deselect) {
1802      for (var i = 0; i < data.trEl.length; i++) {
1803          var day = this._days[startIndex + i];
1804          if (data.apptDiv[i]) {
1805              // Detach the Appt DnD Proxy Div from the allDayParent
1806              data.apptDiv[i].parentNode.removeChild(data.apptDiv[i]);
1807          }
1808 
1809          if (day && data.trEl[i]) {
1810              // TD that originally contained the appt
1811              var td = data.trEl[i].firstChild;
1812              if (td.saveHTML) {
1813                  if (startIndex == data.startDayIndex) {
1814                      // Remove the filler
1815                      td.removeChild(td.firstChild);
1816                  } else {
1817                      // Dropped in a new cell - find the correct position within the appts of the current day
1818                      var tBody = document.getElementById(day.dayId);
1819                      var insertIndex = 0;
1820                      for (insertIndex = 0; insertIndex < tBody.childNodes.length; insertIndex++) {
1821                          var targetTR = tBody.childNodes[insertIndex];
1822                          if ((targetTR.apptStartTimeOffset !== undefined) && (targetTR.apptStartTimeOffset > data.timeOffset[i])) {
1823                               break;
1824                         }
1825                      }
1826                      // Remove the original TR from its day div
1827                      if (data.trEl[i].parentNode) {
1828                          data.trEl[i].parentNode.removeChild(data.trEl[i]);
1829                      }
1830                      data.trEl[i].removeChild(td);
1831 
1832                      // Create a new TR in the new day div
1833                      var tr = tBody.insertRow(insertIndex);
1834                      tr.appendChild(td);
1835                      tr.id = data.trEl[i].id;
1836                      tr.className = data.trEl[i].className;
1837                  }
1838                  // Set the td with the original appt content.  Do via innerHTML since IE
1839                  // does not handle the gradient coloring properly if the appt's div is simply moved
1840                  td.innerHTML = td.saveHTML;
1841                  // Hack for IE - it doesn't display the tag and peel unless you  alter a containing className.
1842                  // The month template div does not have any classNames, so this is safe.
1843                  td.firstChild.className = "";
1844              }
1845          }
1846      }
1847      data.apptDiv = {};
1848 }
1849 
1850 ZmCalMonthView.prototype._removeDnDApptDiv =
1851 function(data) {
1852     if (data.apptDiv) {
1853         for (var iAppt in data.apptDiv) {
1854             data.apptDiv[iAppt].parentNode.removeChild(data.apptDiv[iAppt]);
1855         }
1856         data.apptDiv = null;
1857     }
1858 }
1859 
1860 ZmCalMonthView.prototype._restoreApptLoc =
1861 function(data) {
1862     if(data && data.appt) {
1863         if (data.appt.isAllDayEvent()) {
1864             this._moveAllDayAppt(data, data.startDayIndex);
1865         } else {
1866             this._reattachApptDnDHtml(data, data.startDayIndex, false);
1867         }
1868         data.snap.dayIndex = data.startDayIndex;
1869     }
1870 };
1871 
1872 ZmCalMonthView.prototype._deselectDnDHighlight =
1873 function(data) {
1874     if (data.appt.isAllDayEvent()) {
1875         for (var i = 0; i < data.numDays; i++) {
1876             var allDayDiv = data.apptEl || this._getAllDayDiv(data.appt, i);
1877             if (allDayDiv) {
1878                 this._highlightAllDayDiv(allDayDiv, data.appt, false);
1879             }
1880         }
1881     } else {
1882         if (data.snap.dayIndex == ZmCalMonthView.OUT_OF_BOUNDS_SNAP) {
1883             for (var i = 0; i < data.trEl.length; i++) {
1884                 var day = this._days[data.startDayIndex + i];
1885                 if (day) {
1886                     var td = data.trEl[i].firstChild;
1887                     // Set the opacity on the table containing the gradient coloring; needed for IE
1888                     ZmCalBaseView._setApptOpacity(data.appt, data.tableEl[i]);
1889                 }
1890             }
1891         } else {
1892             this._reattachApptDnDHtml(data, data.snap.dayIndex - data.offsetDayIndex, true);
1893         }
1894     }
1895 };
1896 
1897 ZmCalMonthView.prototype._restoreAppt =
1898 function(data) {
1899    if (data.appt.isAllDayEvent()) {
1900         for (var i = 0; i < data.numDays; i++) {
1901             var allDayDiv = data.apptEl || this._getAllDayDiv(data.appt, i);
1902             if (allDayDiv.saveHtml !== undefined) {
1903                 allDayDiv.innerHTML = allDayDiv.saveHtml;
1904                 allDayDiv.saveHtml = undefined;
1905             }
1906         }
1907     }
1908 };
1909 
1910 ZmCalMonthView.prototype._handleApptScrollRegion =
1911 function(docX, docY, incr, data) {
1912 	var offset = 0;
1913     var div = document.getElementById(this._daysId);
1914     var fullDiv = document.getElementById(this._bodyId);
1915     var originPt = Dwt.getLocation(div);
1916     var size = Dwt.getSize(div);
1917     var he = document.getElementById(this._headerId);
1918     var headingHeight = Dwt.getSize(he).y;
1919     var headingBaseY  = Dwt.getLocation(he).y;
1920 
1921     DBG.println(AjxDebug.DBG3,"_handleApptScrollRegion mouseY: " + docY + "    headingHeight:" + headingHeight +
1922         "    headingBaseY: " + headingBaseY +  "    sizeY:" + size.y);
1923 
1924 	var upper = docY < headingBaseY + headingHeight + 4;;
1925 	var lower = docY > originPt.y + size.y - 8; // - 8;
1926 
1927 	if (upper || lower) {
1928 		var sTop = div.scrollTop;
1929 		if (upper && sTop > 0) {
1930             DBG.println(AjxDebug.DBG3,"_handleApptScrollRegion sTop: " + sTop);
1931 			offset = -(sTop > incr ? incr : sTop);
1932 		} else if (lower) {
1933             var fullSize = Dwt.getSize(fullDiv);
1934             var sVisibleTop = fullSize.y - size.y;
1935             DBG.println(AjxDebug.DBG3,"_handleApptScrollRegion sTop: " + sTop + ", sVisibleTop: " + sVisibleTop);
1936 			if (sTop < sVisibleTop) {
1937 				var spaceLeft = sVisibleTop - sTop;
1938 				offset = spaceLeft  > incr ?incr : spaceLeft;
1939                 DBG.println(AjxDebug.DBG3,"_handleApptScrollRegion spaceLeft: " + spaceLeft);
1940 			}
1941 		}
1942 		if (offset != 0) {
1943 			div.scrollTop += offset;
1944             DBG.println(AjxDebug.DBG3,"_handleApptScrollRegion offset: " + offset);
1945             this._containerRect.set(this._containerRect.x, this._containerRect.y - offset);
1946             data.originY -= offset;
1947             //DBG.println(AjxDebug.DBG3,"_handleApptScrollRegion new containerRect.y = " + this._containerRect.y + ",   originY = " + data.originY);
1948 		}
1949 
1950 	}
1951 	return offset;
1952 };
1953 
1954 ZmCalMonthView.prototype.startIndicatorTimer=function(){
1955    if(!this._indicatorTimer){
1956     this._indicatorTimer = this.updateTimeIndicator();
1957    }
1958 };
1959 
1960 ZmCalMonthView.prototype.updateTimeIndicator=function(){
1961     // For the monthView, the indicator is the highlighting for the current day
1962     if (this._selectedData) {
1963         var today = new Date();
1964         today.setHours(0,0,0, 0);
1965         if (this._selectedData.date.getTime() != today.getTime()) {
1966             // Current date has changed
1967             this._date = today;
1968             this._dateUpdate();
1969         }
1970     }
1971     return this.setTimer(1);
1972 };
1973