' + // hcLee 2015 07 15
					(isRTL ?
						titleHtml + ' ' + timeHtml : // put a natural space in between
						timeHtml + ' ' + titleHtml   //
						) +
				'
' :
					''
					) +
			'';
	},
	// Given a row # and an array of segments all in the same row, render a 
');
		var segMatrix = []; // lookup for which segments are rendered into which level+col cells
		var cellMatrix = []; // lookup for all 
');
					tr.append(td);
				}
				cellMatrix[i][col] = td;
				loneCellMatrix[i][col] = td;
				col++;
			}
		}
		for (i = 0; i < levelCnt; i++) { // iterate through all levels
			levelSegs = segLevels[i];
			col = 0;
			tr = $('
');
			segMatrix.push([]);
			cellMatrix.push([]);
			loneCellMatrix.push([]);
			// levelCnt might be 1 even though there are no actual levels. protect against this.
			// this single empty row is useful for styling.
			if (levelSegs) {
				for (j = 0; j < levelSegs.length; j++) { // iterate through segments in level
					seg = levelSegs[j];
					emptyCellsUntil(seg.leftCol);
					// create a container that occupies or more columns. append the event element.
					td = $('
').append(seg.el);
					if (seg.leftCol != seg.rightCol) {
						td.attr('colspan', seg.rightCol - seg.leftCol + 1);
					}
					else { // a single-column segment
						loneCellMatrix[i][col] = td;
					}
					while (col <= seg.rightCol) {
						cellMatrix[i][col] = td;
						segMatrix[i][col] = seg;
						col++;
					}
					tr.append(td);
				}
			}
			emptyCellsUntil(colCnt); // finish off the row
			this.bookendCells(tr, 'eventSkeleton');
			tbody.append(tr);
		}
		return { // a "rowStruct"
			row: row, // the row number
			tbodyEl: tbody,
			cellMatrix: cellMatrix,
			segMatrix: segMatrix,
			segLevels: segLevels,
			segs: rowSegs
		};
	},
	// Stacks a flat array of segments, which are all assumed to be in the same row, into subarrays of vertical levels.
	buildSegLevels: function(segs) {
		var levels = [];
		var i, seg;
		var j;
		// Give preference to elements with certain criteria, so they have
		// a chance to be closer to the top.
		segs.sort(compareSegs);
		
		for (i = 0; i < segs.length; i++) {
			seg = segs[i];
			// loop through levels, starting with the topmost, until the segment doesn't collide with other segments
			for (j = 0; j < levels.length; j++) {
				if (!isDaySegCollision(seg, levels[j])) {
					break;
				}
			}
			// `j` now holds the desired subrow index
			seg.level = j;
			// create new level array if needed and append segment
			(levels[j] || (levels[j] = [])).push(seg);
		}
		// order segments left-to-right. very important if calendar is RTL
		for (j = 0; j < levels.length; j++) {
			levels[j].sort(compareDaySegCols);
		}
		return levels;
	},
	// Given a flat array of segments, return an array of sub-arrays, grouped by each segment's row
	groupSegRows: function(segs) {
		var view = this.view;
		var segRows = [];
		var i;
		for (i = 0; i < view.rowCnt; i++) {
			segRows.push([]);
		}
		for (i = 0; i < segs.length; i++) {
			segRows[segs[i].row].push(segs[i]);
		}
		return segRows;
	}
});
// Computes whether two segments' columns collide. They are assumed to be in the same row.
function isDaySegCollision(seg, otherSegs) {
	var i, otherSeg;
	for (i = 0; i < otherSegs.length; i++) {
		otherSeg = otherSegs[i];
		if (
			otherSeg.leftCol <= seg.rightCol &&
			otherSeg.rightCol >= seg.leftCol
		) {
			return true;
		}
	}
	return false;
}
// A cmp function for determining the leftmost event
function compareDaySegCols(a, b) {
	return a.leftCol - b.leftCol;
}
;;
/* Methods relate to limiting the number events for a given day on a DayGrid
----------------------------------------------------------------------------------------------------------------------*/
$.extend(DayGrid.prototype, {
	segPopover: null, // the Popover that holds events that can't fit in a cell. null when not visible
	popoverSegs: null, // an array of segment objects that the segPopover holds. null when not visible
	destroySegPopover: function() {
		if (this.segPopover) {
			this.segPopover.hide(); // will trigger destruction of `segPopover` and `popoverSegs`
		}
	},
	// Limits the number of "levels" (vertically stacking layers of events) for each row of the grid.
	// `levelLimit` can be false (don't limit), a number, or true (should be computed).
	limitRows: function(levelLimit) {
		var rowStructs = this.rowStructs || [];
		var row; // row #
		var rowLevelLimit;
		for (row = 0; row < rowStructs.length; row++) {
			this.unlimitRow(row);
			if (!levelLimit) {
				rowLevelLimit = false;
			}
			else if (typeof levelLimit === 'number') {
				rowLevelLimit = levelLimit;
			}
			else {
				rowLevelLimit = this.computeRowLevelLimit(row);
			}
			if (rowLevelLimit !== false) {
				this.limitRow(row, rowLevelLimit);
			}
		}
	},
	// Computes the number of levels a row will accomodate without going outside its bounds.
	// Assumes the row is "rigid" (maintains a constant height regardless of what is inside).
	// `row` is the row number.
	computeRowLevelLimit: function(row) {
		var rowEl = this.rowEls.eq(row); // the containing "fake" row div
		var rowHeight = rowEl.height(); // TODO: cache somehow?
		var trEls = this.rowStructs[row].tbodyEl.children();
		var i, trEl;
		// Reveal one level 
 elements past the limit
				.addClass('fc-limited').get(); // hide elements and get a simple DOM-nodes array
			// iterate though segments in the last allowable level
			for (i = 0; i < levelSegs.length; i++) {
				seg = levelSegs[i];
				emptyCellsUntil(seg.leftCol); // process empty cells before the segment
				// determine *all* segments below `seg` that occupy the same columns
				colSegsBelow = [];
				totalSegsBelow = 0;
				while (col <= seg.rightCol) {
					cell = { row: row, col: col };
					segsBelow = this.getCellSegs(cell, levelLimit);
					colSegsBelow.push(segsBelow);
					totalSegsBelow += segsBelow.length;
					col++;
				}
				if (totalSegsBelow) { // do we need to replace this segment with one or many "more" links?
					td = cellMatrix[levelLimit - 1][seg.leftCol]; // the segment's parent cell
					rowspan = td.attr('rowspan') || 1;
					segMoreNodes = [];
					// make a replacement | for each column the segment occupies. will be one for each colspan
					for (j = 0; j < colSegsBelow.length; j++) {
						moreTd = $(' | ').attr('rowspan', rowspan);
						segsBelow = colSegsBelow[j];
						cell = { row: row, col: seg.leftCol + j };
						moreLink = this.renderMoreLink(cell, [ seg ].concat(segsBelow)); // count seg as hidden too
						moreWrap = $('').append(moreLink);
						moreTd.append(moreWrap);
						segMoreNodes.push(moreTd[0]);
						moreNodes.push(moreTd[0]);
					}
					td.addClass('fc-limited').after($(segMoreNodes)); // hide original | and inject replacements
					limitedNodes.push(td[0]);
				}
			}
			emptyCellsUntil(view.colCnt); // finish off the level
			rowStruct.moreEls = $(moreNodes); // for easy undoing later
			rowStruct.limitedEls = $(limitedNodes); // for easy undoing later
		}
	},
	// Reveals all levels and removes all "more"-related elements for a grid's row.
	// `row` is a row number.
	unlimitRow: function(row) {
		var rowStruct = this.rowStructs[row];
		if (rowStruct.moreEls) {
			rowStruct.moreEls.remove();
			rowStruct.moreEls = null;
		}
		if (rowStruct.limitedEls) {
			rowStruct.limitedEls.removeClass('fc-limited');
			rowStruct.limitedEls = null;
		}
	},
	// Renders an  element that represents hidden event element for a cell.
	// Responsible for attaching click handler as well.
	renderMoreLink: function(cell, hiddenSegs) {
		var _this = this;
		var view = this.view;
		return $('')
			.text(
				this.getMoreLinkText(hiddenSegs.length)
			)
			.on('click', function(ev) {
				var clickOption = view.opt('eventLimitClick');
				var date = view.cellToDate(cell);
				var moreEl = $(this);
				var dayEl = _this.getCellDayEl(cell);
				var allSegs = _this.getCellSegs(cell);
				// rescope the segments to be within the cell's date
				var reslicedAllSegs = _this.resliceDaySegs(allSegs, date);
				var reslicedHiddenSegs = _this.resliceDaySegs(hiddenSegs, date);
				if (typeof clickOption === 'function') {
					// the returned value can be an atomic option
					clickOption = view.trigger('eventLimitClick', null, {
						date: date,
						dayEl: dayEl,
						moreEl: moreEl,
						segs: reslicedAllSegs,
						hiddenSegs: reslicedHiddenSegs
					}, ev);
				}
				if (clickOption === 'popover') {
					_this.showSegPopover(date, cell, moreEl, reslicedAllSegs);
				}
				else if (typeof clickOption === 'string') { // a view name
					view.calendar.zoomTo(date, clickOption);
				}
			});
	},
	// Reveals the popover that displays all events within a cell
	showSegPopover: function(date, cell, moreLink, segs) {
		var _this = this;
		var view = this.view;
		var moreWrap = moreLink.parent(); // the  wrapper around the var topEl; // the element we want to match the top coordinate of
		var options;
		if (view.rowCnt == 1) {
			topEl = this.view.el; // will cause the popover to cover any sort of header
		}
		else {
			topEl = this.rowEls.eq(cell.row); // will align with top of row
		}
		options = {
			className: 'fc-more-popover',
			content: this.renderSegPopoverContent(date, segs),
			parentEl: this.el,
			top: topEl.offset().top,
			autoHide: true, // when the user clicks elsewhere, hide the popover
			viewportConstrain: view.opt('popoverViewportConstrain'),
			hide: function() {
				// destroy everything when the popover is hidden
				_this.segPopover.destroy();
				_this.segPopover = null;
				_this.popoverSegs = null;
			}
		};
		// Determine horizontal coordinate.
		// We use the moreWrap instead of the to avoid border confusion.
		if (view.opt('isRTL')) {
			options.right = moreWrap.offset().left + moreWrap.outerWidth() + 1; // +1 to be over cell border
		}
		else {
			options.left = moreWrap.offset().left - 1; // -1 to be over cell border
		}
		this.segPopover = new Popover(options);
		this.segPopover.show();
	},
	// Builds the inner DOM contents of the segment popover
	renderSegPopoverContent: function(date, segs) {
		var view = this.view;
		var isTheme = view.opt('theme');
		var title = date.format(view.opt('dayPopoverFormat'));
		var content = $(
			'' +
			''
		);
		var segContainer = content.find('.fc-event-container');
		var i;
		// render each seg's `el` and only return the visible segs
		segs = this.renderSegs(segs, true); // disableResizing=true
		this.popoverSegs = segs;
		for (i = 0; i < segs.length; i++) {
			// because segments in the popover are not part of a grid coordinate system, provide a hint to any
			// grids that want to do drag-n-drop about which cell it came from
			segs[i].cellDate = date;
			segContainer.append(segs[i].el);
		}
		return content;
	},
	// Given the events within an array of segment objects, reslice them to be in a single day
	resliceDaySegs: function(segs, dayDate) {
		var events = $.map(segs, function(seg) {
			return seg.event;
		});
		var dayStart = dayDate.clone().stripTime();
		var dayEnd = dayStart.clone().add(1, 'days');
		return this.eventsToSegs(events, dayStart, dayEnd);
	},
	// Generates the text that should be inside a "more" link, given the number of events it represents
	getMoreLinkText: function(num) {
		var view = this.view;
		var opt = view.opt('eventLimitText');
		if (typeof opt === 'function') {
			return opt(num);
		}
		else {
			return '+' + num + ' ' + opt;
		}
	},
	// Returns segments within a given cell.
	// If `startLevel` is specified, returns only events including and below that level. Otherwise returns all segs.
	getCellSegs: function(cell, startLevel) {
		var segMatrix = this.rowStructs[cell.row].segMatrix;
		var level = startLevel || 0;
		var segs = [];
		var seg;
		while (level < segMatrix.length) {
			seg = segMatrix[level][cell.col];
			if (seg) {
				segs.push(seg);
			}
			level++;
		}
		return segs;
	}
});
;;
/* A component that renders one or more columns of vertical time slots
----------------------------------------------------------------------------------------------------------------------*/
function TimeGrid(view) {
	Grid.call(this, view); // call the super-constructor
}
TimeGrid.prototype = createObject(Grid.prototype); // define the super-class
$.extend(TimeGrid.prototype, {
	slotDuration: null, // duration of a "slot", a distinct time segment on given day, visualized by lines
	snapDuration: null, // granularity of time for dragging and selecting
	minTime: null, // Duration object that denotes the first visible time of any given day
	maxTime: null, // Duration object that denotes the exclusive visible end time of any given day
	dayEls: null, // cells elements in the day-row background
	slatEls: null, // elements running horizontally across all columns
	slatTops: null, // an array of top positions, relative to the container. last item holds bottom of last slot
	highlightEl: null, // cell skeleton element for rendering the highlight
	helperEl: null, // cell skeleton element for rendering the mock event "helper"
	// Renders the time grid into `this.el`, which should already be assigned.
	// Relies on the view's colCnt. In the future, this component should probably be self-sufficient.
	render: function() {
		this.processOptions();
		this.el.html(this.renderHtml());
		this.dayEls = this.el.find('.fc-day');
		this.slatEls = this.el.find('.fc-slats tr');
		this.computeSlatTops();
		Grid.prototype.render.call(this); // call the super-method
	},
	// Renders the basic HTML skeleton for the grid
	renderHtml: function() {
		return '' +
			' ' +
			'' +
				' ' +
					this.rowHtml('slotBg') + // leverages RowRenderer, which will call slotBgCellHtml
				' ' +
			'';
	},
	// Renders the HTML for a vertical background cell behind the slots.
	// This method is distinct from 'bg' because we wanted a new `rowType` so the View could customize the rendering.
	slotBgCellHtml: function(row, col, date) {
		return this.bgCellHtml(row, col, date);
	},
	// Generates the HTML for the horizontal "slats" that run width-wise. Has a time axis on a side. Depends on RTL.
	slatRowHtml: function() {
		var view = this.view;
		var calendar = view.calendar;
		var isRTL = view.opt('isRTL');
		var html = '';
		var slotNormal = this.slotDuration.asMinutes() % 15 === 0;
		var slotTime = moment.duration(+this.minTime); // wish there was .clone() for durations
		var slotDate; // will be on the view's first day, but we only care about its time
		var minutes;
		var axisHtml;
		// Calculate the time for each slot
		while (slotTime < this.maxTime) {
			slotDate = view.start.clone().time(slotTime); // will be in UTC but that's good. to avoid DST issues
			minutes = slotDate.minutes();
			axisHtml =
				'' +
				' ' +
					this.slatRowHtml() +
				' ' +
			' | ' +
					((!slotNormal || !minutes) ? // if irregular slot duration, or on the hour, then display the time
						'' + // for matchCellWidths
							htmlEscape(calendar.formatDate(slotDate, view.opt('axisFormat'))) +
						'' :
						''
						) +
				' | ';
			html +=
				' ' +
					(!isRTL ? axisHtml : '') +
					'| ' +
					(isRTL ? axisHtml : '') +
				" |  ";
			slotTime.add(this.slotDuration);
		}
		return html;
	},
	// Parses various options into properties of this object
	processOptions: function() {
		var view = this.view;
		var slotDuration = view.opt('slotDuration');
		var snapDuration = view.opt('snapDuration');
		slotDuration = moment.duration(slotDuration);
		snapDuration = snapDuration ? moment.duration(snapDuration) : slotDuration;
		this.slotDuration = slotDuration;
		this.snapDuration = snapDuration;
		this.cellDuration = snapDuration; // important to assign this for Grid.events.js
		this.minTime = moment.duration(view.opt('minTime'));
		this.maxTime = moment.duration(view.opt('maxTime'));
	},
	// Slices up a date range into a segment for each column
	rangeToSegs: function(rangeStart, rangeEnd) {
		var view = this.view;
		var segs = [];
		var seg;
		var col;
		var cellDate;
		var colStart, colEnd;
		// normalize
		rangeStart = rangeStart.clone().stripZone();
		rangeEnd = rangeEnd.clone().stripZone();
		for (col = 0; col < view.colCnt; col++) {
			cellDate = view.cellToDate(0, col); // use the View's cell system for this
			colStart = cellDate.clone().time(this.minTime);
			colEnd = cellDate.clone().time(this.maxTime);
			seg = intersectionToSeg(rangeStart, rangeEnd, colStart, colEnd);
			if (seg) {
				seg.col = col;
				segs.push(seg);
			}
		}
		return segs;
	},
	/* Coordinates
	------------------------------------------------------------------------------------------------------------------*/
	// Called when there is a window resize/zoom and we need to recalculate coordinates for the grid
	resize: function() {
		this.computeSlatTops();
		this.updateSegVerticals();
	},
	// Populates the given empty `rows` and `cols` arrays with offset positions of the "snap" cells.
	// "Snap" cells are different the slots because they might have finer granularity.
	buildCoords: function(rows, cols) {
		var colCnt = this.view.colCnt;
		var originTop = this.el.offset().top;
		var snapTime = moment.duration(+this.minTime);
		var p = null;
		var e, n;
		this.dayEls.slice(0, colCnt).each(function(i, _e) {
			e = $(_e);
			n = e.offset().left;
			if (p) {
				p[1] = n;
			}
			p = [ n ];
			cols[i] = p;
		});
		p[1] = n + e.outerWidth();
		p = null;
		while (snapTime < this.maxTime) {
			n = originTop + this.computeTimeTop(snapTime);
			if (p) {
				p[1] = n;
			}
			p = [ n ];
			rows.push(p);
			snapTime.add(this.snapDuration);
		}
		p[1] = originTop + this.computeTimeTop(snapTime); // the position of the exclusive end
	},
	// Gets the datetime for the given slot cell
	getCellDate: function(cell) {
		var view = this.view;
		var calendar = view.calendar;
		return calendar.rezoneDate( // since we are adding a time, it needs to be in the calendar's timezone
			view.cellToDate(0, cell.col) // View's coord system only accounts for start-of-day for column
				.time(this.minTime + this.snapDuration * cell.row)
		);
	},
	// Gets the element that represents the whole-day the cell resides on
	getCellDayEl: function(cell) {
		return this.dayEls.eq(cell.col);
	},
	// Computes the top coordinate, relative to the bounds of the grid, of the given date.
	// A `startOfDayDate` must be given for avoiding ambiguity over how to treat midnight.
	computeDateTop: function(date, startOfDayDate) {
		return this.computeTimeTop(
			moment.duration(
				date.clone().stripZone() - startOfDayDate.clone().stripTime()
			)
		);
	},
	// Computes the top coordinate, relative to the bounds of the grid, of the given time (a Duration).
	computeTimeTop: function(time) {
		var slatCoverage = (time - this.minTime) / this.slotDuration; // floating-point value of # of slots covered
		var slatIndex;
		var slatRemainder;
		var slatTop;
		var slatBottom;
		// constrain. because minTime/maxTime might be customized
		slatCoverage = Math.max(0, slatCoverage);
		slatCoverage = Math.min(this.slatEls.length, slatCoverage);
		slatIndex = Math.floor(slatCoverage); // an integer index of the furthest whole slot
		slatRemainder = slatCoverage - slatIndex;
		slatTop = this.slatTops[slatIndex]; // the top position of the furthest whole slot
		if (slatRemainder) { // time spans part-way into the slot
			slatBottom = this.slatTops[slatIndex + 1];
			return slatTop + (slatBottom - slatTop) * slatRemainder; // part-way between slots
		}
		else {
			return slatTop;
		}
	},
	// Queries each `slatEl` for its position relative to the grid's container and stores it in `slatTops`.
	// Includes the the bottom of the last slat as the last item in the array.
	computeSlatTops: function() {
		var tops = [];
		var top;
		this.slatEls.each(function(i, node) {
			top = $(node).position().top;
			tops.push(top);
		});
		tops.push(top + this.slatEls.last().outerHeight()); // bottom of the last slat
		this.slatTops = tops;
	},
	/* Event Drag Visualization
	------------------------------------------------------------------------------------------------------------------*/
	// Renders a visual indication of an event being dragged over the specified date(s).
	// `end` and `seg` can be null. See View's documentation on renderDrag for more info.
	renderDrag: function(start, end, seg) {
		var opacity;
		if (seg) { // if there is event information for this drag, render a helper event
			this.renderRangeHelper(start, end, seg);
			opacity = this.view.opt('dragOpacity');
			if (opacity !== undefined) {
				this.helperEl.css('opacity', opacity);
			}
			return true; // signal that a helper has been rendered
		}
		else {
			// otherwise, just render a highlight
			this.renderHighlight(
				start,
				end || this.view.calendar.getDefaultEventEnd(false, start)
			);
		}
	},
	// Unrenders any visual indication of an event being dragged
	destroyDrag: function() {
		this.destroyHelper();
		this.destroyHighlight();
	},
	/* Event Resize Visualization
	------------------------------------------------------------------------------------------------------------------*/
	// Renders a visual indication of an event being resized
	renderResize: function(start, end, seg) {
		this.renderRangeHelper(start, end, seg);
	},
	// Unrenders any visual indication of an event being resized
	destroyResize: function() {
		this.destroyHelper();
	},
	/* Event Helper
	------------------------------------------------------------------------------------------------------------------*/
	// Renders a mock "helper" event. `sourceSeg` is the original segment object and might be null (an external drag)
	renderHelper: function(event, sourceSeg) {
		var res = this.renderEventTable([ event ]);
		var tableEl = res.tableEl;
		var segs = res.segs;
		var i, seg;
		var sourceEl;
		// Try to make the segment that is in the same row as sourceSeg look the same
		for (i = 0; i < segs.length; i++) {
			seg = segs[i];
			if (sourceSeg && sourceSeg.col === seg.col) {
				sourceEl = sourceSeg.el;
				seg.el.css({
					left: sourceEl.css('left'),
					right: sourceEl.css('right'),
					'margin-left': sourceEl.css('margin-left'),
					'margin-right': sourceEl.css('margin-right')
				});
			}
		}
		this.helperEl = $(' ')
			.append(tableEl)
				.appendTo(this.el);
	},
	// Unrenders any mock helper event
	destroyHelper: function() {
		if (this.helperEl) {
			this.helperEl.remove();
			this.helperEl = null;
		}
	},
	/* Selection
	------------------------------------------------------------------------------------------------------------------*/
	// Renders a visual indication of a selection. Overrides the default, which was to simply render a highlight.
	renderSelection: function(start, end) {
		if (this.view.opt('selectHelper')) { // this setting signals that a mock helper event should be rendered
			this.renderRangeHelper(start, end);
		}
		else {
			this.renderHighlight(start, end);
		}
	},
	// Unrenders any visual indication of a selection
	destroySelection: function() {
		this.destroyHelper();
		this.destroyHighlight();
	},
	/* Highlight
	------------------------------------------------------------------------------------------------------------------*/
	// Renders an emphasis on the given date range. `start` is inclusive. `end` is exclusive.
	renderHighlight: function(start, end) {
		this.highlightEl = $(
			this.highlightSkeletonHtml(start, end)
		).appendTo(this.el);
	},
	// Unrenders the emphasis on a date range
	destroyHighlight: function() {
		if (this.highlightEl) {
			this.highlightEl.remove();
			this.highlightEl = null;
		}
	},
	// Generates HTML for a table element with containers in each column, responsible for absolutely positioning the
	// highlight elements to cover the highlighted slots.
	highlightSkeletonHtml: function(start, end) {
		var view = this.view;
		var segs = this.rangeToSegs(start, end);
		var cellHtml = '';
		var col = 0;
		var i, seg;
		var dayDate;
		var top, bottom;
		for (i = 0; i < segs.length; i++) { // loop through the segments. one per column
			seg = segs[i];
			// need empty cells beforehand?
			if (col < seg.col) {
				cellHtml += '  | ';
				col = seg.col;
			}
			// compute vertical position
			dayDate = view.cellToDate(0, col);
			top = this.computeDateTop(seg.start, dayDate);
			bottom = this.computeDateTop(seg.end, dayDate); // the y position of the bottom edge
			// generate the cell HTML. bottom becomes negative because it needs to be a CSS value relative to the
			// bottom edge of the zero-height container.
			cellHtml +=
				' ' +
					'' +
				' | ';
			col++;
		}
		// need empty cells after the last segment?
		if (col < view.colCnt) {
			cellHtml += '  | ';
		}
		cellHtml = this.bookendCells(cellHtml, 'highlight');
		return '' +
			' ' +
				' ' +
					'' +
						cellHtml +
					'' +
				' ' +
			'';
	}
});
;;
/* Event-rendering methods for the TimeGrid class
----------------------------------------------------------------------------------------------------------------------*/
$.extend(TimeGrid.prototype, {
	segs: null, // segment objects rendered in the component. null of events haven't been rendered yet
	eventSkeletonEl: null, // has cells with event-containers, which contain absolutely positioned event elements
	// Renders the events onto the grid and returns an array of segments that have been rendered
	renderEvents: function(events) {
		var res = this.renderEventTable(events);
		this.eventSkeletonEl = $(' ').append(res.tableEl);
		this.el.append(this.eventSkeletonEl);
		this.segs = res.segs;
	},
	// Retrieves rendered segment objects
	getSegs: function() {
		return this.segs || [];
	},
	// Removes all event segment elements from the view
	destroyEvents: function() {
		Grid.prototype.destroyEvents.call(this); // call the super-method
		if (this.eventSkeletonEl) {
			this.eventSkeletonEl.remove();
			this.eventSkeletonEl = null;
		}
		this.segs = null;
	},
	// Renders and returns the   portion of the event-skeleton.
	// Returns an object with properties 'tbodyEl' and 'segs'.
	renderEventTable: function(events) {
		var tableEl = $('');
		var trEl = tableEl.find('tr');
		var segs = this.eventsToSegs(events);
		var segCols;
		var i, seg;
		var col, colSegs;
		var containerEl;
		segs = this.renderSegs(segs); // returns only the visible segs
		segCols = this.groupSegCols(segs); // group into sub-arrays, and assigns 'col' to each seg
		this.computeSegVerticals(segs); // compute and assign top/bottom
		for (col = 0; col < segCols.length; col++) { // iterate each column grouping
			colSegs = segCols[col];
			placeSlotSegs(colSegs); // compute horizontal coordinates, z-index's, and reorder the array
			containerEl = $('');
			// assign positioning CSS and insert into container
			for (i = 0; i < colSegs.length; i++) {
				seg = colSegs[i];
				seg.el.css(this.generateSegPositionCss(seg));
				// if the height is short, add a className for alternate styling
				if (seg.bottom - seg.top < 30) {
					seg.el.addClass('fc-short');
				}
				containerEl.append(seg.el);
			}
			trEl.append($('| ').append(containerEl));
		}
		this.bookendCells(trEl, 'eventSkeleton');
		return  {
			tableEl: tableEl,
			segs: segs
		};
	},
	// Refreshes the CSS top/bottom coordinates for each segment element. Probably after a window resize/zoom.
	updateSegVerticals: function() {
		var segs = this.segs;
		var i;
		if (segs) {
			this.computeSegVerticals(segs);
			for (i = 0; i < segs.length; i++) {
				segs[i].el.css(
					this.generateSegVerticalCss(segs[i])
				);
			}
		}
	},
	// For each segment in an array, computes and assigns its top and bottom properties
	computeSegVerticals: function(segs) {
		var i, seg;
		for (i = 0; i < segs.length; i++) {
			seg = segs[i];
			seg.top = this.computeDateTop(seg.start, seg.start);
			seg.bottom = this.computeDateTop(seg.end, seg.start);
		}
	},
	// Renders the HTML for a single event segment's default rendering
	renderSegHtml: function(seg, disableResizing) {
		var view = this.view;
		var event = seg.event;
		var isDraggable = view.isEventDraggable(event);
		var isResizable = !disableResizing && seg.isEnd && view.isEventResizable(event);
		var classes = this.getSegClasses(seg, isDraggable, isResizable);
		var skinCss = this.getEventSkinCss(event);
		var timeText;
		var fullTimeText; // more verbose time text. for the print stylesheet
		var startTimeText; // just the start time text
		classes.unshift('fc-time-grid-event');
		if (view.isMultiDayEvent(event)) { // if the event appears to span more than one day...
			// Don't display time text on segments that run entirely through a day.
			// That would appear as midnight-midnight and would look dumb.
			// Otherwise, display the time text for the *segment's* times (like 6pm-midnight or midnight-10am)
			if (seg.isStart || seg.isEnd) {
				timeText = view.getEventTimeText(seg.start, seg.end);
				fullTimeText = view.getEventTimeText(seg.start, seg.end, 'LT');
				startTimeText = view.getEventTimeText(seg.start, null);
			}
		} else {
			// Display the normal time text for the *event's* times
			timeText = view.getEventTimeText(event);
			fullTimeText = view.getEventTimeText(event, 'LT');
			startTimeText = view.getEventTimeText(event.start, null);
		}
		return '' +
				' | ' +
				'' +
				(isResizable ?
					'' :
					''
					) +
			'';
	},
	// Generates an object with CSS properties/values that should be applied to an event segment element.
	// Contains important positioning-related properties that should be applied to any event element, customized or not.
	generateSegPositionCss: function(seg) {
		var view = this.view;
		var isRTL = view.opt('isRTL');
		var shouldOverlap = view.opt('slotEventOverlap');
		var backwardCoord = seg.backwardCoord; // the left side if LTR. the right side if RTL. floating-point
		var forwardCoord = seg.forwardCoord; // the right side if LTR. the left side if RTL. floating-point
		var props = this.generateSegVerticalCss(seg); // get top/bottom first
		var left; // amount of space from left edge, a fraction of the total width
		var right; // amount of space from right edge, a fraction of the total width
		if (shouldOverlap) {
			// double the width, but don't go beyond the maximum forward coordinate (1.0)
			forwardCoord = Math.min(1, backwardCoord + (forwardCoord - backwardCoord) * 2);
		}
		if (isRTL) {
			left = 1 - forwardCoord;
			right = backwardCoord;
		}
		else {
			left = backwardCoord;
			right = 1 - forwardCoord;
		}
		props.zIndex = seg.level + 1; // convert from 0-base to 1-based
		props.left = left * 100 + '%';
		props.right = right * 100 + '%';
		if (shouldOverlap && seg.forwardPressure) {
			// add padding to the edge so that forward stacked events don't cover the resizer's icon
			props[isRTL ? 'marginLeft' : 'marginRight'] = 10 * 2; // 10 is a guesstimate of the icon's width 
		}
		return props;
	},
	// Generates an object with CSS properties for the top/bottom coordinates of a segment element
	generateSegVerticalCss: function(seg) {
		return {
			top: seg.top,
			bottom: -seg.bottom // flipped because needs to be space beyond bottom edge of event container
		};
	},
	// Given a flat array of segments, return an array of sub-arrays, grouped by each segment's col
	groupSegCols: function(segs) {
		var view = this.view;
		var segCols = [];
		var i;
		for (i = 0; i < view.colCnt; i++) {
			segCols.push([]);
		}
		for (i = 0; i < segs.length; i++) {
			segCols[segs[i].col].push(segs[i]);
		}
		return segCols;
	}
});
// Given an array of segments that are all in the same column, sets the backwardCoord and forwardCoord on each.
// Also reorders the given array by date!
function placeSlotSegs(segs) {
	var levels;
	var level0;
	var i;
	segs.sort(compareSegs); // order by date
	levels = buildSlotSegLevels(segs);
	computeForwardSlotSegs(levels);
	if ((level0 = levels[0])) {
		for (i = 0; i < level0.length; i++) {
			computeSlotSegPressures(level0[i]);
		}
		for (i = 0; i < level0.length; i++) {
			computeSlotSegCoords(level0[i], 0, 0);
		}
	}
}
// Builds an array of segments "levels". The first level will be the leftmost tier of segments if the calendar is
// left-to-right, or the rightmost if the calendar is right-to-left. Assumes the segments are already ordered by date.
function buildSlotSegLevels(segs) {
	var levels = [];
	var i, seg;
	var j;
	for (i=0; i seg2.top && seg1.top < seg2.bottom;
}
// A cmp function for determining which forward segment to rely on more when computing coordinates.
function compareForwardSlotSegs(seg1, seg2) {
	// put higher-pressure first
	return seg2.forwardPressure - seg1.forwardPressure ||
		// put segments that are closer to initial edge first (and favor ones with no coords yet)
		(seg1.backwardCoord || 0) - (seg2.backwardCoord || 0) ||
		// do normal sorting...
		compareSegs(seg1, seg2);
}
;;
/* An abstract class from which other views inherit from
----------------------------------------------------------------------------------------------------------------------*/
// Newer methods should be written as prototype methods, not in the monster `View` function at the bottom.
View.prototype = {
	calendar: null, // owner Calendar object
	coordMap: null, // a CoordMap object for converting pixel regions to dates
	el: null, // the view's containing element. set by Calendar
	// important Moments
	start: null, // the date of the very first cell
	end: null, // the date after the very last cell
	intervalStart: null, // the start of the interval of time the view represents (1st of month for month view)
	intervalEnd: null, // the exclusive end of the interval of time the view represents
	// used for cell-to-date and date-to-cell calculations
	rowCnt: null, // # of weeks
	colCnt: null, // # of days displayed in a week
	isSelected: false, // boolean whether cells are user-selected or not
	// subclasses can optionally use a scroll container
	scrollerEl: null, // the element that will most likely scroll when content is too tall
	scrollTop: null, // cached vertical scroll value
	// classNames styled by jqui themes
	widgetHeaderClass: null,
	widgetContentClass: null,
	highlightStateClass: null,
	// document handlers, bound to `this` object
	documentMousedownProxy: null,
	documentDragStartProxy: null,
	// Serves as a "constructor" to suppliment the monster `View` constructor below
	init: function() {
		var tm = this.opt('theme') ? 'ui' : 'fc';
		this.widgetHeaderClass = tm + '-widget-header';
		this.widgetContentClass = tm + '-widget-content';
		this.highlightStateClass = tm + '-state-highlight';
		// save references to `this`-bound handlers
		this.documentMousedownProxy = $.proxy(this, 'documentMousedown');
		this.documentDragStartProxy = $.proxy(this, 'documentDragStart');
	},
	// Renders the view inside an already-defined `this.el`.
	// Subclasses should override this and then call the super method afterwards.
	render: function() {
		this.updateSize();
		this.trigger('viewRender', this, this, this.el);
		// attach handlers to document. do it here to allow for destroy/rerender
		$(document)
			.on('mousedown', this.documentMousedownProxy)
			.on('dragstart', this.documentDragStartProxy); // jqui drag
	},
	// Clears all view rendering, event elements, and unregisters handlers
	destroy: function() {
		this.unselect();
		this.trigger('viewDestroy', this, this, this.el);
		this.destroyEvents();
		this.el.empty(); // removes inner contents but leaves the element intact
		$(document)
			.off('mousedown', this.documentMousedownProxy)
			.off('dragstart', this.documentDragStartProxy);
	},
	// Used to determine what happens when the users clicks next/prev. Given -1 for prev, 1 for next.
	// Should apply the delta to `date` (a Moment) and return it.
	incrementDate: function(date, delta) {
		// subclasses should implement
	},
	/* Dimensions
	------------------------------------------------------------------------------------------------------------------*/
	// Refreshes anything dependant upon sizing of the container element of the grid
	updateSize: function(isResize) {
		if (isResize) {
			this.recordScroll();
		}
		this.updateHeight();
		this.updateWidth();
	},
	// Refreshes the horizontal dimensions of the calendar
	updateWidth: function() {
		// subclasses should implement
	},
	// Refreshes the vertical dimensions of the calendar
	updateHeight: function() {
		var calendar = this.calendar; // we poll the calendar for height information
		this.setHeight(
			calendar.getSuggestedViewHeight(),
			calendar.isHeightAuto()
		);
	},
	// Updates the vertical dimensions of the calendar to the specified height.
	// if `isAuto` is set to true, height becomes merely a suggestion and the view should use its "natural" height.
	setHeight: function(height, isAuto) {
		// subclasses should implement
	},
	// Given the total height of the view, return the number of pixels that should be used for the scroller.
	// Utility for subclasses.
	computeScrollerHeight: function(totalHeight) {
		var both = this.el.add(this.scrollerEl);
		var otherHeight; // cumulative height of everything that is not the scrollerEl in the view (header+borders)
		// fuckin IE8/9/10/11 sometimes returns 0 for dimensions. this weird hack was the only thing that worked
		both.css({
			position: 'relative', // cause a reflow, which will force fresh dimension recalculation
			left: -1 // ensure reflow in case the el was already relative. negative is less likely to cause new scroll
		});
		otherHeight = this.el.outerHeight() - this.scrollerEl.height(); // grab the dimensions
		both.css({ position: '', left: '' }); // undo hack
		return totalHeight - otherHeight;
	},
	// Called for remembering the current scroll value of the scroller.
	// Should be called before there is a destructive operation (like removing DOM elements) that might inadvertently
	// change the scroll of the container.
	recordScroll: function() {
		if (this.scrollerEl) {
			this.scrollTop = this.scrollerEl.scrollTop();
		}
	},
	// Set the scroll value of the scroller to the previously recorded value.
	// Should be called after we know the view's dimensions have been restored following some type of destructive
	// operation (like temporarily removing DOM elements).
	restoreScroll: function() {
		if (this.scrollTop !== null) {
			this.scrollerEl.scrollTop(this.scrollTop);
		}
	},
	/* Events
	------------------------------------------------------------------------------------------------------------------*/
	// Renders the events onto the view.
	// Should be overriden by subclasses. Subclasses should call the super-method afterwards.
	renderEvents: function(events) {
		this.segEach(function(seg) {
			this.trigger('eventAfterRender', seg.event, seg.event, seg.el);
		});
		this.trigger('eventAfterAllRender');
	},
	// Removes event elements from the view.
	// Should be overridden by subclasses. Should call this super-method FIRST, then subclass DOM destruction.
	destroyEvents: function() {
		this.segEach(function(seg) {
			this.trigger('eventDestroy', seg.event, seg.event, seg.el);
		});
	},
	// Given an event and the default element used for rendering, returns the element that should actually be used.
	// Basically runs events and elements through the eventRender hook.
	resolveEventEl: function(event, el) {
		var custom = this.trigger('eventRender', event, event, el);
		if (custom === false) { // means don't render at all
			el = null;
		}
		else if (custom && custom !== true) {
			el = $(custom);
		}
		return el;
	},
	// Hides all rendered event segments linked to the given event
	showEvent: function(event) {
		this.segEach(function(seg) {
			seg.el.css('visibility', '');
		}, event);
	},
	// Shows all rendered event segments linked to the given event
	hideEvent: function(event) {
		this.segEach(function(seg) {
			seg.el.css('visibility', 'hidden');
		}, event);
	},
	// Iterates through event segments. Goes through all by default.
	// If the optional `event` argument is specified, only iterates through segments linked to that event.
	// The `this` value of the callback function will be the view.
	segEach: function(func, event) {
		var segs = this.getSegs();
		var i;
		for (i = 0; i < segs.length; i++) {
			if (!event || segs[i].event._id === event._id) {
				func.call(this, segs[i]);
			}
		}
	},
	// Retrieves all the rendered segment objects for the view
	getSegs: function() {
		// subclasses must implement
	},
	/* Event Drag Visualization
	------------------------------------------------------------------------------------------------------------------*/
	// Renders a visual indication of an event hovering over the specified date.
	// `end` is a Moment and might be null.
	// `seg` might be null. if specified, it is the segment object of the event being dragged.
	//       otherwise, an external event from outside the calendar is being dragged.
	renderDrag: function(start, end, seg) {
		// subclasses should implement
	},
	// Unrenders a visual indication of event hovering
	destroyDrag: function() {
		// subclasses should implement
	},
	// Handler for accepting externally dragged events being dropped in the view.
	// Gets called when jqui's 'dragstart' is fired.
	documentDragStart: function(ev, ui) {
		var _this = this;
		var dropDate = null;
		var dragListener;
		if (this.opt('droppable')) { // only listen if this setting is on
			// listener that tracks mouse movement over date-associated pixel regions
			dragListener = new DragListener(this.coordMap, {
				cellOver: function(cell, date) {
					dropDate = date;
					_this.renderDrag(date);
				},
				cellOut: function() {
					dropDate = null;
					_this.destroyDrag();
				}
			});
			// gets called, only once, when jqui drag is finished
			$(document).one('dragstop', function(ev, ui) {
				_this.destroyDrag();
				if (dropDate) {
					_this.trigger('drop', ev.target, dropDate, ev, ui);
				}
			});
			dragListener.startDrag(ev); // start listening immediately
		}
	},
	/* Selection
	------------------------------------------------------------------------------------------------------------------*/
	// Selects a date range on the view. `start` and `end` are both Moments.
	// `ev` is the native mouse event that begin the interaction.
	select: function(start, end, ev) {
		this.unselect(ev);
		this.renderSelection(start, end);
		this.reportSelection(start, end, ev);
	},
	// Renders a visual indication of the selection
	renderSelection: function(start, end) {
		// subclasses should implement
	},
	// Called when a new selection is made. Updates internal state and triggers handlers.
	reportSelection: function(start, end, ev) {
		this.isSelected = true;
		this.trigger('select', null, start, end, ev);
	},
	// Undoes a selection. updates in the internal state and triggers handlers.
	// `ev` is the native mouse event that began the interaction.
	unselect: function(ev) {
		if (this.isSelected) {
			this.isSelected = false;
			this.destroySelection();
			this.trigger('unselect', null, ev);
		}
	},
	// Unrenders a visual indication of selection
	destroySelection: function() {
		// subclasses should implement
	},
	// Handler for unselecting when the user clicks something and the 'unselectAuto' setting is on
	documentMousedown: function(ev) {
		var ignore;
		// is there a selection, and has the user made a proper left click?
		if (this.isSelected && this.opt('unselectAuto') && isPrimaryMouseButton(ev)) {
			// only unselect if the clicked element is not identical to or inside of an 'unselectCancel' element
			ignore = this.opt('unselectCancel');
			if (!ignore || !$(ev.target).closest(ignore).length) {
				this.unselect(ev);
			}
		}
	}
};
// We are mixing JavaScript OOP design patterns here by putting methods and member variables in the closed scope of the
// constructor. Going forward, methods should be part of the prototype.
function View(calendar) {
	var t = this;
	
	// exports
	t.calendar = calendar;
	t.opt = opt;
	t.trigger = trigger;
	t.isEventDraggable = isEventDraggable;
	t.isEventResizable = isEventResizable;
	t.eventDrop = eventDrop;
	t.eventResize = eventResize;
	
	// imports
	var reportEventChange = calendar.reportEventChange;
	
	// locals
	var options = calendar.options;
	var nextDayThreshold = moment.duration(options.nextDayThreshold);
	t.init(); // the "constructor" that concerns the prototype methods
	
	
	function opt(name) {
		var v = options[name];
		if ($.isPlainObject(v) && !isForcedAtomicOption(name)) {
			return smartProperty(v, t.name);
		}
		return v;
	}
	
	function trigger(name, thisObj) {
		return calendar.trigger.apply(
			calendar,
			[name, thisObj || t].concat(Array.prototype.slice.call(arguments, 2), [t])
		);
	}
	
	/* Event Editable Boolean Calculations
	------------------------------------------------------------------------------*/
	
	function isEventDraggable(event) {
		var source = event.source || {};
		return firstDefined(
			event.startEditable,
			source.startEditable,
			opt('eventStartEditable'),
			event.editable,
			source.editable,
			opt('editable')
		);
	}
	
	
	function isEventResizable(event) {
		var source = event.source || {};
		return firstDefined(
			event.durationEditable,
			source.durationEditable,
			opt('eventDurationEditable'),
			event.editable,
			source.editable,
			opt('editable')
		);
	}
	
	
	
	/* Event Elements
	------------------------------------------------------------------------------*/
	// Compute the text that should be displayed on an event's element.
	// Based off the settings of the view. Possible signatures:
	//   .getEventTimeText(event, formatStr)
	//   .getEventTimeText(startMoment, endMoment, formatStr)
	//   .getEventTimeText(startMoment, null, formatStr)
	// `timeFormat` is used but the `formatStr` argument can be used to override.
	t.getEventTimeText = function(event, formatStr) {
		var start;
		var end;
		if (typeof event === 'object' && typeof formatStr === 'object') {
			// first two arguments are actually moments (or null). shift arguments.
			start = event;
			end = formatStr;
			formatStr = arguments[2];
		}
		else {
			// otherwise, an event object was the first argument
			start = event.start;
			end = event.end;
		}
		formatStr = formatStr || opt('timeFormat');
		if (end && opt('displayEventEnd')) {
			return calendar.formatRange(start, end, formatStr);
		}
		else {
			return calendar.formatDate(start, formatStr);
		}
	};
	
	
	/* Event Modification Reporting
	---------------------------------------------------------------------------------*/
	
	function eventDrop(el, event, newStart, ev) {
		var mutateResult = calendar.mutateEvent(event, newStart, null);
		trigger(
			'eventDrop',
			el,
			event,
			mutateResult.dateDelta,
			function() {
				mutateResult.undo();
				reportEventChange();
			},
			ev,
			{} // jqui dummy
		);
		reportEventChange();
	}
	function eventResize(el, event, newEnd, ev) {
		var mutateResult = calendar.mutateEvent(event, null, newEnd);
		trigger(
			'eventResize',
			el,
			event,
			mutateResult.durationDelta,
			function() {
				mutateResult.undo();
				reportEventChange();
			},
			ev,
			{} // jqui dummy
		);
		reportEventChange();
	}
	// ====================================================================================================
	// Utilities for day "cells"
	// ====================================================================================================
	// The "basic" views are completely made up of day cells.
	// The "agenda" views have day cells at the top "all day" slot.
	// This was the obvious common place to put these utilities, but they should be abstracted out into
	// a more meaningful class (like DayEventRenderer).
	// ====================================================================================================
	// For determining how a given "cell" translates into a "date":
	//
	// 1. Convert the "cell" (row and column) into a "cell offset" (the # of the cell, cronologically from the first).
	//    Keep in mind that column indices are inverted with isRTL. This is taken into account.
	//
	// 2. Convert the "cell offset" to a "day offset" (the # of days since the first visible day in the view).
	//
	// 3. Convert the "day offset" into a "date" (a Moment).
	//
	// The reverse transformation happens when transforming a date into a cell.
	// exports
	t.isHiddenDay = isHiddenDay;
	t.skipHiddenDays = skipHiddenDays;
	t.getCellsPerWeek = getCellsPerWeek;
	t.dateToCell = dateToCell;
	t.dateToDayOffset = dateToDayOffset;
	t.dayOffsetToCellOffset = dayOffsetToCellOffset;
	t.cellOffsetToCell = cellOffsetToCell;
	t.cellToDate = cellToDate;
	t.cellToCellOffset = cellToCellOffset;
	t.cellOffsetToDayOffset = cellOffsetToDayOffset;
	t.dayOffsetToDate = dayOffsetToDate;
	t.rangeToSegments = rangeToSegments;
	t.isMultiDayEvent = isMultiDayEvent;
	// internals
	var hiddenDays = opt('hiddenDays') || []; // array of day-of-week indices that are hidden
	var isHiddenDayHash = []; // is the day-of-week hidden? (hash with day-of-week-index -> bool)
	var cellsPerWeek;
	var dayToCellMap = []; // hash from dayIndex -> cellIndex, for one week
	var cellToDayMap = []; // hash from cellIndex -> dayIndex, for one week
	var isRTL = opt('isRTL');
	// initialize important internal variables
	(function() {
		if (opt('weekends') === false) {
			hiddenDays.push(0, 6); // 0=sunday, 6=saturday
		}
		// Loop through a hypothetical week and determine which
		// days-of-week are hidden. Record in both hashes (one is the reverse of the other).
		for (var dayIndex=0, cellIndex=0; dayIndex<7; dayIndex++) {
			dayToCellMap[dayIndex] = cellIndex;
			isHiddenDayHash[dayIndex] = $.inArray(dayIndex, hiddenDays) != -1;
			if (!isHiddenDayHash[dayIndex]) {
				cellToDayMap[cellIndex] = dayIndex;
				cellIndex++;
			}
		}
		cellsPerWeek = cellIndex;
		if (!cellsPerWeek) {
			throw 'invalid hiddenDays'; // all days were hidden? bad.
		}
	})();
	// Is the current day hidden?
	// `day` is a day-of-week index (0-6), or a Moment
	function isHiddenDay(day) {
		if (moment.isMoment(day)) {
			day = day.day();
		}
		return isHiddenDayHash[day];
	}
	function getCellsPerWeek() {
		return cellsPerWeek;
	}
	// Incrementing the current day until it is no longer a hidden day, returning a copy.
	// If the initial value of `date` is not a hidden day, don't do anything.
	// Pass `isExclusive` as `true` if you are dealing with an end date.
	// `inc` defaults to `1` (increment one day forward each time)
	function skipHiddenDays(date, inc, isExclusive) {
		var out = date.clone();
		inc = inc || 1;
		while (
			isHiddenDayHash[(out.day() + (isExclusive ? inc : 0) + 7) % 7]
		) {
			out.add(inc, 'days');
		}
		return out;
	}
	//
	// TRANSFORMATIONS: cell -> cell offset -> day offset -> date
	//
	// cell -> date (combines all transformations)
	// Possible arguments:
	// - row, col
	// - { row:#, col: # }
	function cellToDate() {
		var cellOffset = cellToCellOffset.apply(null, arguments);
		var dayOffset = cellOffsetToDayOffset(cellOffset);
		var date = dayOffsetToDate(dayOffset);
		return date;
	}
	// cell -> cell offset
	// Possible arguments:
	// - row, col
	// - { row:#, col:# }
	function cellToCellOffset(row, col) {
		var colCnt = t.colCnt;
		// rtl variables. wish we could pre-populate these. but where?
		var dis = isRTL ? -1 : 1;
		var dit = isRTL ? colCnt - 1 : 0;
		if (typeof row == 'object') {
			col = row.col;
			row = row.row;
		}
		var cellOffset = row * colCnt + (col * dis + dit); // column, adjusted for RTL (dis & dit)
		return cellOffset;
	}
	// cell offset -> day offset
	function cellOffsetToDayOffset(cellOffset) {
		var day0 = t.start.day(); // first date's day of week
		cellOffset += dayToCellMap[day0]; // normlize cellOffset to beginning-of-week
		return Math.floor(cellOffset / cellsPerWeek) * 7 + // # of days from full weeks
			cellToDayMap[ // # of days from partial last week
				(cellOffset % cellsPerWeek + cellsPerWeek) % cellsPerWeek // crazy math to handle negative cellOffsets
			] -
			day0; // adjustment for beginning-of-week normalization
	}
	// day offset -> date
	function dayOffsetToDate(dayOffset) {
		return t.start.clone().add(dayOffset, 'days');
	}
	//
	// TRANSFORMATIONS: date -> day offset -> cell offset -> cell
	//
	// date -> cell (combines all transformations)
	function dateToCell(date) {
		var dayOffset = dateToDayOffset(date);
		var cellOffset = dayOffsetToCellOffset(dayOffset);
		var cell = cellOffsetToCell(cellOffset);
		return cell;
	}
	// date -> day offset
	function dateToDayOffset(date) {
		return date.clone().stripTime().diff(t.start, 'days');
	}
	// day offset -> cell offset
	function dayOffsetToCellOffset(dayOffset) {
		var day0 = t.start.day(); // first date's day of week
		dayOffset += day0; // normalize dayOffset to beginning-of-week
		return Math.floor(dayOffset / 7) * cellsPerWeek + // # of cells from full weeks
			dayToCellMap[ // # of cells from partial last week
				(dayOffset % 7 + 7) % 7 // crazy math to handle negative dayOffsets
			] -
			dayToCellMap[day0]; // adjustment for beginning-of-week normalization
	}
	// cell offset -> cell (object with row & col keys)
	function cellOffsetToCell(cellOffset) {
		var colCnt = t.colCnt;
		// rtl variables. wish we could pre-populate these. but where?
		var dis = isRTL ? -1 : 1;
		var dit = isRTL ? colCnt - 1 : 0;
		var row = Math.floor(cellOffset / colCnt);
		var col = ((cellOffset % colCnt + colCnt) % colCnt) * dis + dit; // column, adjusted for RTL (dis & dit)
		return {
			row: row,
			col: col
		};
	}
	//
	// Converts a date range into an array of segment objects.
	// "Segments" are horizontal stretches of time, sliced up by row.
	// A segment object has the following properties:
	// - row
	// - cols
	// - isStart
	// - isEnd
	//
	function rangeToSegments(start, end) {
		var rowCnt = t.rowCnt;
		var colCnt = t.colCnt;
		var segments = []; // array of segments to return
		// day offset for given date range
		var dayRange = computeDayRange(start, end); // convert to a whole-day range
		var rangeDayOffsetStart = dateToDayOffset(dayRange.start);
		var rangeDayOffsetEnd = dateToDayOffset(dayRange.end); // an exclusive value
		// first and last cell offset for the given date range
		// "last" implies inclusivity
		var rangeCellOffsetFirst = dayOffsetToCellOffset(rangeDayOffsetStart);
		var rangeCellOffsetLast = dayOffsetToCellOffset(rangeDayOffsetEnd) - 1;
		// loop through all the rows in the view
		for (var row=0; row= nextDayThreshold) {
				endDay.add(1, 'days');
			}
		}
		// If no end was specified, or if it is within `startDay` but not past nextDayThreshold,
		// assign the default duration of one day.
		if (!end || endDay <= startDay) {
			endDay = startDay.clone().add(1, 'days');
		}
		return { start: startDay, end: endDay };
	}
	// Does the given event visually appear to occupy more than one day?
	function isMultiDayEvent(event) {
		var range = computeDayRange(event.start, event.end);
		return range.end.diff(range.start, 'days') > 1;
	}
}
;;
/* An abstract class for the "basic" views, as well as month view. Renders one or more rows of day cells.
----------------------------------------------------------------------------------------------------------------------*/
// It is a manager for a DayGrid subcomponent, which does most of the heavy lifting.
// It is responsible for managing width/height.
function BasicView(calendar) {
	View.call(this, calendar); // call the super-constructor
	this.dayGrid = new DayGrid(this);
	this.coordMap = this.dayGrid.coordMap; // the view's date-to-cell mapping is identical to the subcomponent's
}
BasicView.prototype = createObject(View.prototype); // define the super-class
$.extend(BasicView.prototype, {
	dayGrid: null, // the main subcomponent that does most of the heavy lifting
	dayNumbersVisible: false, // display day numbers on each day cell?
	weekNumbersVisible: false, // display week numbers along the side?
	weekNumberWidth: null, // width of all the week-number cells running down the side
	headRowEl: null, // the fake row element of the day-of-week header
	// Renders the view into `this.el`, which should already be assigned.
	// rowCnt, colCnt, and dayNumbersVisible have been calculated by a subclass and passed here.
	render: function(rowCnt, colCnt, dayNumbersVisible) {
		// needed for cell-to-date and date-to-cell calculations in View
		this.rowCnt = rowCnt;
		this.colCnt = colCnt;
		this.dayNumbersVisible = dayNumbersVisible;
		this.weekNumbersVisible = this.opt('weekNumbers');
		this.dayGrid.numbersVisible = this.dayNumbersVisible || this.weekNumbersVisible;
		this.el.addClass('fc-basic-view').html(this.renderHtml());
		this.headRowEl = this.el.find('thead .fc-row');
		this.scrollerEl = this.el.find('.fc-day-grid-container');
		this.dayGrid.coordMap.containerEl = this.scrollerEl; // constrain clicks/etc to the dimensions of the scroller
		this.dayGrid.el = this.el.find('.fc-day-grid');
		this.dayGrid.render(this.hasRigidRows());
		View.prototype.render.call(this); // call the super-method
	},
	// Make subcomponents ready for cleanup
	destroy: function() {
		this.dayGrid.destroy();
		View.prototype.destroy.call(this); // call the super-method
	},
	// Builds the HTML skeleton for the view.
	// The day-grid component will render inside of a container defined by this HTML.
	renderHtml: function() {
		return '' +
			'' +
					(timeText ?
						' ' +
							'' + htmlEscape(timeText) + '' +
						' ' :
						''
						) +
					(event.title ?
						' ' +
							htmlEscape(event.title) +
						' ' :
						''
						) +
				'' +
				'' +
					'';
	},
	// Generates the HTML that will go before the day-of week header cells.
	// Queried by the DayGrid subcomponent when generating rows. Ordering depends on isRTL.
	headIntroHtml: function() {
		if (this.weekNumbersVisible) {
			return '' +
				'';
		}
	},
	// Generates the HTML that will go before content-skeleton cells that display the day/week numbers.
	// Queried by the DayGrid subcomponent. Ordering depends on isRTL.
	numberIntroHtml: function(row) {
		if (this.weekNumbersVisible) {
			return '' +
				'' +
						'' +
					'' +
				'' +
				'' +
					' ' +
						'' +
				'' +
			'| ' +
							'' +
						'' +
					' |  ' +
					'' + // needed for matchCellWidths
						this.calendar.calculateWeekNumber(this.cellToDate(row, 0)) +
					'' +
				'';
		}
	},
	// Generates the HTML that goes before the day bg cells for each day-row.
	// Queried by the DayGrid subcomponent. Ordering depends on isRTL.
	dayIntroHtml: function() {
		if (this.weekNumbersVisible) {
			return ' | ';
		}
	},
	// Generates the HTML that goes before every other type of row generated by DayGrid. Ordering depends on isRTL.
	// Affects helper-skeleton and highlight-skeleton rows.
	introHtml: function() {
		if (this.weekNumbersVisible) {
			return ' | ';
		}
	},
	// Generates the HTML for the | s of the "number" row in the DayGrid's content skeleton.
	// The number row will only exist if either day numbers or week numbers are turned on.
	numberCellHtml: function(row, col, date) {
		var classes;
		if (!this.dayNumbersVisible) { // if there are week numbers but not day numbers
			return ' | '; //  will create an empty space above events :(
		}
		classes = this.dayGrid.getDayClasses(date);
		classes.unshift('fc-day-number');
		return '' +
			' | ' +
				date.date() +
			'';
	},
	// Generates an HTML attribute string for setting the width of the week number column, if it is known
	weekNumberStyleAttr: function() {
		if (this.weekNumberWidth !== null) {
			return 'style="width:' + this.weekNumberWidth + 'px"';
		}
		return '';
	},
	// Determines whether each row should have a constant height
	hasRigidRows: function() {
		var eventLimit = this.opt('eventLimit');
		return eventLimit && typeof eventLimit !== 'number';
	},
	/* Dimensions
	------------------------------------------------------------------------------------------------------------------*/
	// Refreshes the horizontal dimensions of the view
	updateWidth: function() {
		if (this.weekNumbersVisible) {
			// Make sure all week number cells running down the side have the same width.
			// Record the width for cells created later.
			this.weekNumberWidth = matchCellWidths(
				this.el.find('.fc-week-number')
			);
		}
	},
	// Adjusts the vertical dimensions of the view to the specified values
	setHeight: function(totalHeight, isAuto) {
		var eventLimit = this.opt('eventLimit');
		var scrollerHeight;
		// reset all heights to be natural
		unsetScroller(this.scrollerEl);
		uncompensateScroll(this.headRowEl);
		this.dayGrid.destroySegPopover(); // kill the "more" popover if displayed
		// is the event limit a constant level number?
		if (eventLimit && typeof eventLimit === 'number') {
			this.dayGrid.limitRows(eventLimit); // limit the levels first so the height can redistribute after
		}
		scrollerHeight = this.computeScrollerHeight(totalHeight);
		this.setGridHeight(scrollerHeight, isAuto);
		// is the event limit dynamically calculated?
		if (eventLimit && typeof eventLimit !== 'number') {
			this.dayGrid.limitRows(eventLimit); // limit the levels after the grid's row heights have been set
		}
		if (!isAuto && setPotentialScroller(this.scrollerEl, scrollerHeight)) { // using scrollbars?
			compensateScroll(this.headRowEl, getScrollbarWidths(this.scrollerEl));
			// doing the scrollbar compensation might have created text overflow which created more height. redo
			scrollerHeight = this.computeScrollerHeight(totalHeight);
			this.scrollerEl.height(scrollerHeight);
			this.restoreScroll();
		}
	},
	// Sets the height of just the DayGrid component in this view
	setGridHeight: function(height, isAuto) {
		if (isAuto) {
			undistributeHeight(this.dayGrid.rowEls); // let the rows be their natural height with no expanding
		}
		else {
			distributeHeight(this.dayGrid.rowEls, height, true); // true = compensate for height-hogging rows
		}
	},
	/* Events
	------------------------------------------------------------------------------------------------------------------*/
	// Renders the given events onto the view and populates the segments array
	renderEvents: function(events) {
		this.dayGrid.renderEvents(events);
		this.updateHeight(); // must compensate for events that overflow the row
		View.prototype.renderEvents.call(this, events); // call the super-method
	},
	// Retrieves all segment objects that are rendered in the view
	getSegs: function() {
		return this.dayGrid.getSegs();
	},
	// Unrenders all event elements and clears internal segment data
	destroyEvents: function() {
		View.prototype.destroyEvents.call(this); // do this before dayGrid's segs have been cleared
		this.recordScroll(); // removing events will reduce height and mess with the scroll, so record beforehand
		this.dayGrid.destroyEvents();
		// we DON'T need to call updateHeight() because:
		// A) a renderEvents() call always happens after this, which will eventually call updateHeight()
		// B) in IE8, this causes a flash whenever events are rerendered
	},
	/* Event Dragging
	------------------------------------------------------------------------------------------------------------------*/
	// Renders a visual indication of an event being dragged over the view.
	// A returned value of `true` signals that a mock "helper" event has been rendered.
	renderDrag: function(start, end, seg) {
		return this.dayGrid.renderDrag(start, end, seg);
	},
	// Unrenders the visual indication of an event being dragged over the view
	destroyDrag: function() {
		this.dayGrid.destroyDrag();
	},
	/* Selection
	------------------------------------------------------------------------------------------------------------------*/
	// Renders a visual indication of a selection
	renderSelection: function(start, end) {
		this.dayGrid.renderSelection(start, end);
	},
	// Unrenders a visual indications of a selection
	destroySelection: function() {
		this.dayGrid.destroySelection();
	}
});
;;
/* A month view with day cells running in rows (one-per-week) and columns
----------------------------------------------------------------------------------------------------------------------*/
setDefaults({
	fixedWeekCount: true
});
fcViews.month = MonthView; // register the view
function MonthView(calendar) {
	BasicView.call(this, calendar); // call the super-constructor
}
MonthView.prototype = createObject(BasicView.prototype); // define the super-class
$.extend(MonthView.prototype, {
	name: 'month',
	incrementDate: function(date, delta) {
		return date.clone().stripTime().add(delta, 'months').startOf('month');
	},
	render: function(date) {
		var rowCnt;
		this.intervalStart = date.clone().stripTime().startOf('month');
		this.intervalEnd = this.intervalStart.clone().add(1, 'months');
		this.start = this.intervalStart.clone();
		this.start = this.skipHiddenDays(this.start); // move past the first week if no visible days
		this.start.startOf('week');
		this.start = this.skipHiddenDays(this.start); // move past the first invisible days of the week
		this.end = this.intervalEnd.clone();
		this.end = this.skipHiddenDays(this.end, -1, true); // move in from the last week if no visible days
		this.end.add((7 - this.end.weekday()) % 7, 'days'); // move to end of week if not already
		this.end = this.skipHiddenDays(this.end, -1, true); // move in from the last invisible days of the week
		rowCnt = Math.ceil( // need to ceil in case there are hidden days
			this.end.diff(this.start, 'weeks', true) // returnfloat=true
		);
		if (this.isFixedWeeks()) {
			this.end.add(6 - rowCnt, 'weeks');
			rowCnt = 6;
		}
		var oldTitle = this.title; // yo2dh 2014-09-17
		this.title = this.calendar.formatDate(this.intervalStart, this.opt('titleFormat'));
        if( oldTitle !== this.title ) {
            this.trigger( 'changedMonth', this.calendar.getDate() );                                
        }
		BasicView.prototype.render.call(this, rowCnt, this.getCellsPerWeek(), true); // call the super-method
	},
	// Overrides the default BasicView behavior to have special multi-week auto-height logic
	setGridHeight: function(height, isAuto) {
		isAuto = isAuto || this.opt('weekMode') === 'variable'; // LEGACY: weekMode is deprecated
		// if auto, make the height of each row the height that it would be if there were 6 weeks
		if (isAuto) {
			height *= this.rowCnt / 6;
		}
		distributeHeight(this.dayGrid.rowEls, height, !isAuto); // if auto, don't compensate for height-hogging rows
	},
	isFixedWeeks: function() {
		var weekMode = this.opt('weekMode'); // LEGACY: weekMode is deprecated
		if (weekMode) {
			return weekMode === 'fixed'; // if any other type of weekMode, assume NOT fixed
		}
		return this.opt('fixedWeekCount');
	}
});
;;
/* A week view with simple day cells running horizontally
----------------------------------------------------------------------------------------------------------------------*/
// TODO: a WeekView mixin for calculating dates and titles
fcViews.basicWeek = BasicWeekView; // register this view
function BasicWeekView(calendar) {
	BasicView.call(this, calendar); // call the super-constructor
}
BasicWeekView.prototype = createObject(BasicView.prototype); // define the super-class
$.extend(BasicWeekView.prototype, {
	name: 'basicWeek',
	incrementDate: function(date, delta) {
		return date.clone().stripTime().add(delta, 'weeks').startOf('week');
	},
	render: function(date) {
		this.intervalStart = date.clone().stripTime().startOf('week');
		this.intervalEnd = this.intervalStart.clone().add(1, 'weeks');
		this.start = this.skipHiddenDays(this.intervalStart);
		this.end = this.skipHiddenDays(this.intervalEnd, -1, true);
		this.title = this.calendar.formatRange(
			this.start,
			this.end.clone().subtract(1), // make inclusive by subtracting 1 ms
			this.opt('titleFormat'),
			' \u2014 ' // emphasized dash
		);
		BasicView.prototype.render.call(this, 1, this.getCellsPerWeek(), false); // call the super-method
	}
	
});
;;
/* A view with a single simple day cell
----------------------------------------------------------------------------------------------------------------------*/
fcViews.basicDay = BasicDayView; // register this view
function BasicDayView(calendar) {
	BasicView.call(this, calendar); // call the super-constructor
}
BasicDayView.prototype = createObject(BasicView.prototype); // define the super-class
$.extend(BasicDayView.prototype, {
	name: 'basicDay',
	incrementDate: function(date, delta) {
		var out = date.clone().stripTime().add(delta, 'days');
		out = this.skipHiddenDays(out, delta < 0 ? -1 : 1);
		return out;
	},
	render: function(date) {
		this.start = this.intervalStart = date.clone().stripTime();
		this.end = this.intervalEnd = this.start.clone().add(1, 'days');
		this.title = this.calendar.formatDate(this.start, this.opt('titleFormat'));
		BasicView.prototype.render.call(this, 1, 1, false); // call the super-method
	}
});
;;
/* An abstract class for all agenda-related views. Displays one more columns with time slots running vertically.
----------------------------------------------------------------------------------------------------------------------*/
// Is a manager for the TimeGrid subcomponent and possibly the DayGrid subcomponent (if allDaySlot is on).
// Responsible for managing width/height.
setDefaults({
	allDaySlot: true,
	allDayText: 'all-day',
	scrollTime: '06:00:00',
	slotDuration: '00:30:00',
	axisFormat: generateAgendaAxisFormat,
	timeFormat: {
		agenda: generateAgendaTimeFormat
	},
	minTime: '00:00:00',
	maxTime: '24:00:00',
	slotEventOverlap: true
});
var AGENDA_ALL_DAY_EVENT_LIMIT = 5;
function generateAgendaAxisFormat(options, langData) {
	return langData.longDateFormat('LT')
		.replace(':mm', '(:mm)')
		.replace(/(\Wmm)$/, '($1)') // like above, but for foreign langs
		.replace(/\s*a$/i, 'a'); // convert AM/PM/am/pm to lowercase. remove any spaces beforehand
}
function generateAgendaTimeFormat(options, langData) {
	return langData.longDateFormat('LT')
		.replace(/\s*a$/i, ''); // remove trailing AM/PM
}
function AgendaView(calendar) {
	View.call(this, calendar); // call the super-constructor
	this.timeGrid = new TimeGrid(this);
	if (this.opt('allDaySlot')) { // should we display the "all-day" area?
		this.dayGrid = new DayGrid(this); // the all-day subcomponent of this view
		// the coordinate grid will be a combination of both subcomponents' grids
		this.coordMap = new ComboCoordMap([
			this.dayGrid.coordMap,
			this.timeGrid.coordMap
		]);
	}
	else {
		this.coordMap = this.timeGrid.coordMap;
	}
}
AgendaView.prototype = createObject(View.prototype); // define the super-class
$.extend(AgendaView.prototype, {
	timeGrid: null, // the main time-grid subcomponent of this view
	dayGrid: null, // the "all-day" subcomponent. if all-day is turned off, this will be null
	axisWidth: null, // the width of the time axis running down the side
	noScrollRowEls: null, // set of fake row elements that must compensate when scrollerEl has scrollbars
	// when the time-grid isn't tall enough to occupy the given height, we render an | underneath
	bottomRuleEl: null,
	bottomRuleHeight: null,
	/* Rendering
	------------------------------------------------------------------------------------------------------------------*/
	// Renders the view into `this.el`, which has already been assigned.
	// `colCnt` has been calculated by a subclass and passed here.
	render: function(colCnt) {
		// needed for cell-to-date and date-to-cell calculations in View
		this.rowCnt = 1;
		this.colCnt = colCnt;
		this.el.addClass('fc-agenda-view').html(this.renderHtml());
		// the element that wraps the time-grid that will probably scroll
		this.scrollerEl = this.el.find('.fc-time-grid-container');
		this.timeGrid.coordMap.containerEl = this.scrollerEl; // don't accept clicks/etc outside of this
		this.timeGrid.el = this.el.find('.fc-time-grid');
		this.timeGrid.render();
		// the
 that sometimes displays under the time-grid
		this.bottomRuleEl = $('')
			.appendTo(this.timeGrid.el); // inject it into the time-grid
		if (this.dayGrid) {
			this.dayGrid.el = this.el.find('.fc-day-grid');
			this.dayGrid.render();
			// have the day-grid extend it's coordinate area over the
 dividing the two grids
			this.dayGrid.bottomCoordPadding = this.dayGrid.el.next('hr').outerHeight();
		}
		this.noScrollRowEls = this.el.find('.fc-row:not(.fc-scroller *)'); // fake rows not within the scroller
		View.prototype.render.call(this); // call the super-method
		this.resetScroll(); // do this after sizes have been set
	},
	// Make subcomponents ready for cleanup
	destroy: function() {
		this.timeGrid.destroy();
		if (this.dayGrid) {
			this.dayGrid.destroy();
		}
		View.prototype.destroy.call(this); // call the super-method
	},
	// Builds the HTML skeleton for the view.
	// The day-grid and time-grid components will render inside containers defined by this HTML.
	renderHtml: function() {
		return '' +
			'
 ' +
				'' +
					'';
	},
	// Generates the HTML that will go before the day-of week header cells.
	// Queried by the TimeGrid subcomponent when generating rows. Ordering depends on isRTL.
	headIntroHtml: function() {
		var date;
		var weekNumber;
		var weekTitle;
		var weekText;
		if (this.opt('weekNumbers')) {
			date = this.cellToDate(0, 0);
			weekNumber = this.calendar.calculateWeekNumber(date);
			weekTitle = this.opt('weekNumberTitle');
			if (this.opt('isRTL')) {
				weekText = weekNumber + weekTitle;
			}
			else {
				weekText = weekTitle + weekNumber;
			}
			return '' +
				'';
		}
		else {
			return '';
		}
	},
	// Generates the HTML that goes before the all-day cells.
	// Queried by the DayGrid subcomponent when generating rows. Ordering depends on isRTL.
	dayIntroHtml: function() {
		return '' +
			'' +
						'' +
					'' +
				'' +
				'' +
					' ' +
						'' +
				'' +
			'| ' +
							(this.dayGrid ?
								'' +
								'' :
								''
								) +
							'' +
						'' +
					' |  ' +
				'' + // needed for matchCellWidths
					(this.opt('allDayHtml') || htmlEscape(this.opt('allDayText'))) +
				'' +
			'';
	},
	// Generates the HTML that goes before the bg of the TimeGrid slot area. Long vertical column.
	slotBgIntroHtml: function() {
		return ' | ';
	},
	// Generates the HTML that goes before all other types of cells.
	// Affects content-skeleton, helper-skeleton, highlight-skeleton for both the time-grid and day-grid.
	// Queried by the TimeGrid and DayGrid subcomponents when generating rows. Ordering depends on isRTL.
	introHtml: function() {
		return ' | ';
	},
	// Generates an HTML attribute string for setting the width of the axis, if it is known
	axisStyleAttr: function() {
		if (this.axisWidth !== null) {
			 return 'style="width:' + this.axisWidth + 'px"';
		}
		return '';
	},
	/* Dimensions
	------------------------------------------------------------------------------------------------------------------*/
	updateSize: function(isResize) {
		if (isResize) {
			this.timeGrid.resize();
		}
		View.prototype.updateSize.call(this, isResize);
	},
	// Refreshes the horizontal dimensions of the view
	updateWidth: function() {
		// make all axis cells line up, and record the width so newly created axis cells will have it
		this.axisWidth = matchCellWidths(this.el.find('.fc-axis'));
	},
	// Adjusts the vertical dimensions of the view to the specified values
	setHeight: function(totalHeight, isAuto) {
		var eventLimit;
		var scrollerHeight;
		if (this.bottomRuleHeight === null) {
			// calculate the height of the rule the very first time
			this.bottomRuleHeight = this.bottomRuleEl.outerHeight();
		}
		this.bottomRuleEl.hide(); // .show() will be called later if this | is necessary
		// reset all dimensions back to the original state
		this.scrollerEl.css('overflow', '');
		unsetScroller(this.scrollerEl);
		uncompensateScroll(this.noScrollRowEls);
		// limit number of events in the all-day area
		if (this.dayGrid) {
			this.dayGrid.destroySegPopover(); // kill the "more" popover if displayed
			eventLimit = this.opt('eventLimit');
			if (eventLimit && typeof eventLimit !== 'number') {
				eventLimit = AGENDA_ALL_DAY_EVENT_LIMIT; // make sure "auto" goes to a real number
			}
			if (eventLimit) {
				this.dayGrid.limitRows(eventLimit);
			}
		}
		if (!isAuto) { // should we force dimensions of the scroll container, or let the contents be natural height?
			scrollerHeight = this.computeScrollerHeight(totalHeight);
			if (setPotentialScroller(this.scrollerEl, scrollerHeight)) { // using scrollbars?
				// make the all-day and header rows lines up
				compensateScroll(this.noScrollRowEls, getScrollbarWidths(this.scrollerEl));
				// the scrollbar compensation might have changed text flow, which might affect height, so recalculate
				// and reapply the desired height to the scroller.
				scrollerHeight = this.computeScrollerHeight(totalHeight);
				this.scrollerEl.height(scrollerHeight);
				this.restoreScroll();
			}
			else { // no scrollbars
				// still, force a height and display the bottom rule (marks the end of day)
				this.scrollerEl.height(scrollerHeight).css('overflow', 'hidden'); // in case
 goes outside
				this.bottomRuleEl.show();
			}
		}
	},
	// Sets the scroll value of the scroller to the intial pre-configured state prior to allowing the user to change it.
	resetScroll: function() {
		var _this = this;
		var scrollTime = moment.duration(this.opt('scrollTime'));
		var top = this.timeGrid.computeTimeTop(scrollTime);
		// zoom can give weird floating-point values. rather scroll a little bit further
		top = Math.ceil(top);
		if (top) {
			top++; // to overcome top border that slots beyond the first have. looks better
		}
		function scroll() {
			_this.scrollerEl.scrollTop(top);
		}
		scroll();
		setTimeout(scroll, 0); // overrides any previous scroll state made by the browser
	},
	/* Events
	------------------------------------------------------------------------------------------------------------------*/
	// Renders events onto the view and populates the View's segment array
	renderEvents: function(events) {
		var dayEvents = [];
		var timedEvents = [];
		var daySegs = [];
		var timedSegs;
		var i;
		// separate the events into all-day and timed
		for (i = 0; i < events.length; i++) {
			if (events[i].allDay) {
				dayEvents.push(events[i]);
			}
			else {
				timedEvents.push(events[i]);
			}
		}
		// render the events in the subcomponents
		timedSegs = this.timeGrid.renderEvents(timedEvents);
		if (this.dayGrid) {
			daySegs = this.dayGrid.renderEvents(dayEvents);
		}
		// the all-day area is flexible and might have a lot of events, so shift the height
		this.updateHeight();
		View.prototype.renderEvents.call(this, events); // call the super-method
	},
	// Retrieves all segment objects that are rendered in the view
	getSegs: function() {
		return this.timeGrid.getSegs().concat(
			this.dayGrid ? this.dayGrid.getSegs() : []
		);
	},
	// Unrenders all event elements and clears internal segment data
	destroyEvents: function() {
		View.prototype.destroyEvents.call(this); // do this before the grids' segs have been cleared
		// if destroyEvents is being called as part of an event rerender, renderEvents will be called shortly
		// after, so remember what the scroll value was so we can restore it.
		this.recordScroll();
		// destroy the events in the subcomponents
		this.timeGrid.destroyEvents();
		if (this.dayGrid) {
			this.dayGrid.destroyEvents();
		}
		// we DON'T need to call updateHeight() because:
		// A) a renderEvents() call always happens after this, which will eventually call updateHeight()
		// B) in IE8, this causes a flash whenever events are rerendered
	},
	/* Event Dragging
	------------------------------------------------------------------------------------------------------------------*/
	// Renders a visual indication of an event being dragged over the view.
	// A returned value of `true` signals that a mock "helper" event has been rendered.
	renderDrag: function(start, end, seg) {
		if (start.hasTime()) {
			return this.timeGrid.renderDrag(start, end, seg);
		}
		else if (this.dayGrid) {
			return this.dayGrid.renderDrag(start, end, seg);
		}
	},
	// Unrenders a visual indications of an event being dragged over the view
	destroyDrag: function() {
		this.timeGrid.destroyDrag();
		if (this.dayGrid) {
			this.dayGrid.destroyDrag();
		}
	},
	/* Selection
	------------------------------------------------------------------------------------------------------------------*/
	// Renders a visual indication of a selection
	renderSelection: function(start, end) {
		if (start.hasTime() || end.hasTime()) {
			this.timeGrid.renderSelection(start, end);
		}
		else if (this.dayGrid) {
			this.dayGrid.renderSelection(start, end);
		}
	},
	// Unrenders a visual indications of a selection
	destroySelection: function() {
		this.timeGrid.destroySelection();
		if (this.dayGrid) {
			this.dayGrid.destroySelection();
		}
	}
});
;;
/* A week view with an all-day cell area at the top, and a time grid below
----------------------------------------------------------------------------------------------------------------------*/
// TODO: a WeekView mixin for calculating dates and titles
fcViews.agendaWeek = AgendaWeekView; // register the view
function AgendaWeekView(calendar) {
	AgendaView.call(this, calendar); // call the super-constructor
}
AgendaWeekView.prototype = createObject(AgendaView.prototype); // define the super-class
$.extend(AgendaWeekView.prototype, {
	name: 'agendaWeek',
	incrementDate: function(date, delta) {
		return date.clone().stripTime().add(delta, 'weeks').startOf('week');
	},
	render: function(date) {
		this.intervalStart = date.clone().stripTime().startOf('week');
		this.intervalEnd = this.intervalStart.clone().add(1, 'weeks');
		this.start = this.skipHiddenDays(this.intervalStart);
		this.end = this.skipHiddenDays(this.intervalEnd, -1, true);
		this.title = this.calendar.formatRange(
			this.start,
			this.end.clone().subtract(1), // make inclusive by subtracting 1 ms
			this.opt('titleFormat'),
			' \u2014 ' // emphasized dash
		);
		AgendaView.prototype.render.call(this, this.getCellsPerWeek()); // call the super-method
	}
});
;;
/* A day view with an all-day cell area at the top, and a time grid below
----------------------------------------------------------------------------------------------------------------------*/
fcViews.agendaDay = AgendaDayView; // register the view
function AgendaDayView(calendar) {
	AgendaView.call(this, calendar); // call the super-constructor
}
AgendaDayView.prototype = createObject(AgendaView.prototype); // define the super-class
$.extend(AgendaDayView.prototype, {
	name: 'agendaDay',
	incrementDate: function(date, delta) {
		var out = date.clone().stripTime().add(delta, 'days');
		out = this.skipHiddenDays(out, delta < 0 ? -1 : 1);
		return out;
	},
	render: function(date) {
		this.start = this.intervalStart = date.clone().stripTime();
		this.end = this.intervalEnd = this.start.clone().add(1, 'days');
		this.title = this.calendar.formatDate(this.start, this.opt('titleFormat'));
		AgendaView.prototype.render.call(this, 1); // call the super-method
	}
});
;;
});
 |