text: 
  /* Methods for rendering SEGMENTS, pieces of content that live on the view
   ( this file is no longer just for events )
  ----------------------------------------------------------------------------------------------------------------------*/
  
  TimeGrid.mixin({
  
  	colContainerEls: null, // containers for each column
  
  	// inner-containers for each column where different types of segs live
  	fgContainerEls: null,
  	bgContainerEls: null,
  	helperContainerEls: null,
  	highlightContainerEls: null,
  	businessContainerEls: null,
  
  	// arrays of different types of displayed segments
  	fgSegs: null,
  	bgSegs: null,
  	helperSegs: null,
  	highlightSegs: null,
  	businessSegs: null,
  
  
  	// Renders the DOM that the view's content will live in
  	renderContentSkeleton: function() {
  		var cellHtml = '';
  		var i;
  		var skeletonEl;
  
  		for (i = 0; i < this.colCnt; i++) {
  			cellHtml +=
  				'<td>' +
  					'<div class="fc-content-col">' +
  						'<div class="fc-event-container fc-helper-container"></div>' +
  						'<div class="fc-event-container"></div>' +
  						'<div class="fc-highlight-container"></div>' +
  						'<div class="fc-bgevent-container"></div>' +
  						'<div class="fc-business-container"></div>' +
  					'</div>' +
  				'</td>';
  		}
  
  		skeletonEl = $(
  			'<div class="fc-content-skeleton">' +
  				'<table>' +
  					'<tr>' + cellHtml + '</tr>' +
  				'</table>' +
  			'</div>'
  		);
  
  		this.colContainerEls = skeletonEl.find('.fc-content-col');
  		this.helperContainerEls = skeletonEl.find('.fc-helper-container');
  		this.fgContainerEls = skeletonEl.find('.fc-event-container:not(.fc-helper-container)');
  		this.bgContainerEls = skeletonEl.find('.fc-bgevent-container');
  		this.highlightContainerEls = skeletonEl.find('.fc-highlight-container');
  		this.businessContainerEls = skeletonEl.find('.fc-business-container');
  
  		this.bookendCells(skeletonEl.find('tr')); // TODO: do this on string level
  		this.el.append(skeletonEl);
  	},
  
  
  	/* Foreground Events
  	------------------------------------------------------------------------------------------------------------------*/
  
  
  	renderFgSegs: function(segs) {
  		segs = this.renderFgSegsIntoContainers(segs, this.fgContainerEls);
  		this.fgSegs = segs;
  		return segs; // needed for Grid::renderEvents
  	},
  
  
  	unrenderFgSegs: function() {
  		this.unrenderNamedSegs('fgSegs');
  	},
  
  
  	/* Foreground Helper Events
  	------------------------------------------------------------------------------------------------------------------*/
  
  
  	renderHelperSegs: function(segs, sourceSeg) {
  		var helperEls = [];
  		var i, seg;
  		var sourceEl;
  
  		segs = this.renderFgSegsIntoContainers(segs, this.helperContainerEls);
  
  		// 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')
  				});
  			}
  			helperEls.push(seg.el[0]);
  		}
  
  		this.helperSegs = segs;
  
  		return $(helperEls); // must return rendered helpers
  	},
  
  
  	unrenderHelperSegs: function() {
  		this.unrenderNamedSegs('helperSegs');
  	},
  
  
  	/* Background Events
  	------------------------------------------------------------------------------------------------------------------*/
  
  
  	renderBgSegs: function(segs) {
  		segs = this.renderFillSegEls('bgEvent', segs); // TODO: old fill system
  		this.updateSegVerticals(segs);
  		this.attachSegsByCol(this.groupSegsByCol(segs), this.bgContainerEls);
  		this.bgSegs = segs;
  		return segs; // needed for Grid::renderEvents
  	},
  
  
  	unrenderBgSegs: function() {
  		this.unrenderNamedSegs('bgSegs');
  	},
  
  
  	/* Highlight
  	------------------------------------------------------------------------------------------------------------------*/
  
  
  	renderHighlightSegs: function(segs) {
  		segs = this.renderFillSegEls('highlight', segs); // TODO: instead of calling renderFill directly
  		this.updateSegVerticals(segs);
  		this.attachSegsByCol(this.groupSegsByCol(segs), this.highlightContainerEls);
  		this.highlightSegs = segs;
  	},
  
  
  	unrenderHighlightSegs: function() {
  		this.unrenderNamedSegs('highlightSegs');
  	},
  
  
  	/* Business Hours
  	------------------------------------------------------------------------------------------------------------------*/
  
  
  	renderBusinessSegs: function(segs) {
  		segs = this.renderFillSegEls('businessHours', segs); // TODO: instead of calling renderFill directly
  		this.updateSegVerticals(segs);
  		this.attachSegsByCol(this.groupSegsByCol(segs), this.businessContainerEls);
  		this.businessSegs = segs;
  	},
  
  
  	unrenderBusinessSegs: function() {
  		this.unrenderNamedSegs('businessSegs');
  	},
  
  
  	/* Seg Rendering Utils
  	------------------------------------------------------------------------------------------------------------------*/
  
  
  	// Given a flat array of segments, return an array of sub-arrays, grouped by each segment's col
  	groupSegsByCol: function(segs) {
  		var segsByCol = [];
  		var i;
  
  		for (i = 0; i < this.colCnt; i++) {
  			segsByCol.push([]);
  		}
  
  		for (i = 0; i < segs.length; i++) {
  			segsByCol[segs[i].col].push(segs[i]);
  		}
  
  		return segsByCol;
  	},
  
  
  	// Given segments grouped by column, insert the segments' elements into a parallel array of container
  	// elements, each living within a column.
  	attachSegsByCol: function(segsByCol, containerEls) {
  		var col;
  		var segs;
  		var i;
  
  		for (col = 0; col < this.colCnt; col++) { // iterate each column grouping
  			segs = segsByCol[col];
  
  			for (i = 0; i < segs.length; i++) {
  				containerEls.eq(col).append(segs[i].el);
  			}
  		}
  	},
  
  
  	// Given the name of a property of `this` object, assumed to be an array of segments,
  	// loops through each segment and removes from DOM. Will null-out the property afterwards.
  	unrenderNamedSegs: function(propName) {
  		var segs = this[propName];
  		var i;
  
  		if (segs) {
  			for (i = 0; i < segs.length; i++) {
  				segs[i].el.remove();
  			}
  			this[propName] = null;
  		}
  	},
  
  
  
  	/* Foreground Event Rendering Utils
  	------------------------------------------------------------------------------------------------------------------*/
  
  
  	// Given an array of foreground segments, render a DOM element for each, computes position,
  	// and attaches to the column inner-container elements.
  	renderFgSegsIntoContainers: function(segs, containerEls) {
  		var segsByCol;
  		var col;
  
  		segs = this.renderFgSegEls(segs); // will call fgSegHtml
  		segsByCol = this.groupSegsByCol(segs);
  
  		for (col = 0; col < this.colCnt; col++) {
  			this.updateFgSegCoords(segsByCol[col]);
  		}
  
  		this.attachSegsByCol(segsByCol, containerEls);
  
  		return segs;
  	},
  
  
  	// Renders the HTML for a single event segment's default rendering
  	fgSegHtml: function(seg, disableResizing) {
  		var view = this.view;
  		var calendar = view.calendar;
  		var componentFootprint = seg.footprint.componentFootprint;
  		var isAllDay = componentFootprint.isAllDay;
  		var eventDef = seg.footprint.eventDef;
  		var isDraggable = view.isEventDefDraggable(eventDef);
  		var isResizableFromStart = !disableResizing && seg.isStart && view.isEventDefResizableFromStart(eventDef);
  		var isResizableFromEnd = !disableResizing && seg.isEnd && view.isEventDefResizableFromEnd(eventDef);
  		var classes = this.getSegClasses(seg, isDraggable, isResizableFromStart || isResizableFromEnd);
  		var skinCss = cssToStr(this.getSegSkinCss(seg));
  		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', 'fc-v-event');
  
  		// if the event appears to span more than one day...
  		if (view.isMultiDayRange(componentFootprint.unzonedRange)) {
  			// 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) {
  				var zonedStart = calendar.msToMoment(seg.startMs);
  				var zonedEnd = calendar.msToMoment(seg.endMs);
  				timeText = this._getEventTimeText(zonedStart, zonedEnd, isAllDay);
  				fullTimeText = this._getEventTimeText(zonedStart, zonedEnd, isAllDay, 'LT');
  				startTimeText = this._getEventTimeText(zonedStart, zonedEnd, isAllDay, null, false); // displayEnd=false
  			}
  		}
  		else {
  			// Display the normal time text for the *event's* times
  			timeText = this.getEventTimeText(seg.footprint);
  			fullTimeText = this.getEventTimeText(seg.footprint, 'LT');
  			startTimeText = this.getEventTimeText(seg.footprint, null, false); // displayEnd=false
  		}
  
  		return '<a class="' + classes.join(' ') + '"' +
  			(eventDef.url ?
  				' href="' + htmlEscape(eventDef.url) + '"' :
  				''
  				) +
  			(skinCss ?
  				' style="' + skinCss + '"' :
  				''
  				) +
  			'>' +
  				'<div class="fc-content">' +
  					(timeText ?
  						'<div class="fc-time"' +
  						' data-start="' + htmlEscape(startTimeText) + '"' +
  						' data-full="' + htmlEscape(fullTimeText) + '"' +
  						'>' +
  							'<span>' + htmlEscape(timeText) + '</span>' +
  						'</div>' :
  						''
  						) +
  					(eventDef.title ?
  						'<div class="fc-title">' +
  							htmlEscape(eventDef.title) +
  						'</div>' :
  						''
  						) +
  				'</div>' +
  				'<div class="fc-bg"/>' +
  				/* TODO: write CSS for this
  				(isResizableFromStart ?
  					'<div class="fc-resizer fc-start-resizer" />' :
  					''
  					) +
  				*/
  				(isResizableFromEnd ?
  					'<div class="fc-resizer fc-end-resizer" />' :
  					''
  					) +
  			'</a>';
  	},
  
  
  	/* Seg Position Utils
  	------------------------------------------------------------------------------------------------------------------*/
  
  
  	// Refreshes the CSS top/bottom coordinates for each segment element.
  	// Works when called after initial render, after a window resize/zoom for example.
  	updateSegVerticals: function(segs) {
  		this.computeSegVerticals(segs);
  		this.assignSegVerticals(segs);
  	},
  
  
  	// For each segment in an array, computes and assigns its top and bottom properties
  	computeSegVerticals: function(segs) {
  		var i, seg;
  		var dayDate;
  
  		for (i = 0; i < segs.length; i++) {
  			seg = segs[i];
  			dayDate = this.dayDates[seg.dayIndex];
  
  			seg.top = this.computeDateTop(seg.startMs, dayDate);
  			seg.bottom = this.computeDateTop(seg.endMs, dayDate);
  		}
  	},
  
  
  	// Given segments that already have their top/bottom properties computed, applies those values to
  	// the segments' elements.
  	assignSegVerticals: function(segs) {
  		var i, seg;
  
  		for (i = 0; i < segs.length; i++) {
  			seg = segs[i];
  			seg.el.css(this.generateSegVerticalCss(seg));
  		}
  	},
  
  
  	// 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
  		};
  	},
  
  
  	/* Foreground Event Positioning Utils
  	------------------------------------------------------------------------------------------------------------------*/
  
  
  	// Given segments that are assumed to all live in the *same column*,
  	// compute their verical/horizontal coordinates and assign to their elements.
  	updateFgSegCoords: function(segs) {
  		this.computeSegVerticals(segs); // horizontals relies on this
  		this.computeFgSegHorizontals(segs); // compute horizontal coordinates, z-index's, and reorder the array
  		this.assignSegVerticals(segs);
  		this.assignFgSegHorizontals(segs);
  	},
  
  
  	// Given an array of segments that are all in the same column, sets the backwardCoord and forwardCoord on each.
  	// NOTE: Also reorders the given array by date!
  	computeFgSegHorizontals: function(segs) {
  		var levels;
  		var level0;
  		var i;
  
  		this.sortEventSegs(segs); // order by certain criteria
  		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++) {
  				this.computeFgSegForwardBack(level0[i], 0, 0);
  			}
  		}
  	},
  
  
  	// Calculate seg.forwardCoord and seg.backwardCoord for the segment, where both values range
  	// from 0 to 1. If the calendar is left-to-right, the seg.backwardCoord maps to "left" and
  	// seg.forwardCoord maps to "right" (via percentage). Vice-versa if the calendar is right-to-left.
  	//
  	// The segment might be part of a "series", which means consecutive segments with the same pressure
  	// who's width is unknown until an edge has been hit. `seriesBackwardPressure` is the number of
  	// segments behind this one in the current series, and `seriesBackwardCoord` is the starting
  	// coordinate of the first segment in the series.
  	computeFgSegForwardBack: function(seg, seriesBackwardPressure, seriesBackwardCoord) {
  		var forwardSegs = seg.forwardSegs;
  		var i;
  
  		if (seg.forwardCoord === undefined) { // not already computed
  
  			if (!forwardSegs.length) {
  
  				// if there are no forward segments, this segment should butt up against the edge
  				seg.forwardCoord = 1;
  			}
  			else {
  
  				// sort highest pressure first
  				this.sortForwardSegs(forwardSegs);
  
  				// this segment's forwardCoord will be calculated from the backwardCoord of the
  				// highest-pressure forward segment.
  				this.computeFgSegForwardBack(forwardSegs[0], seriesBackwardPressure + 1, seriesBackwardCoord);
  				seg.forwardCoord = forwardSegs[0].backwardCoord;
  			}
  
  			// calculate the backwardCoord from the forwardCoord. consider the series
  			seg.backwardCoord = seg.forwardCoord -
  				(seg.forwardCoord - seriesBackwardCoord) / // available width for series
  				(seriesBackwardPressure + 1); // # of segments in the series
  
  			// use this segment's coordinates to computed the coordinates of the less-pressurized
  			// forward segments
  			for (i=0; i<forwardSegs.length; i++) {
  				this.computeFgSegForwardBack(forwardSegs[i], 0, seg.forwardCoord);
  			}
  		}
  	},
  
  
  	sortForwardSegs: function(forwardSegs) {
  		forwardSegs.sort(proxy(this, 'compareForwardSegs'));
  	},
  
  
  	// A cmp function for determining which forward segment to rely on more when computing coordinates.
  	compareForwardSegs: function(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...
  			this.compareEventSegs(seg1, seg2);
  	},
  
  
  	// Given foreground event segments that have already had their position coordinates computed,
  	// assigns position-related CSS values to their elements.
  	assignFgSegHorizontals: function(segs) {
  		var i, seg;
  
  		for (i = 0; i < segs.length; i++) {
  			seg = segs[i];
  			seg.el.css(this.generateFgSegHorizontalCss(seg));
  
  			// if the height is short, add a className for alternate styling
  			if (seg.bottom - seg.top < 30) {
  				seg.el.addClass('fc-short');
  			}
  		}
  	},
  
  
  	// 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.
  	generateFgSegHorizontalCss: function(seg) {
  		var shouldOverlap = this.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 (this.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[this.isRTL ? 'marginLeft' : 'marginRight'] = 10 * 2; // 10 is a guesstimate of the icon's width
  		}
  
  		return props;
  	}
  
  });
  
  
  // 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<segs.length; i++) {
  		seg = segs[i];
  
  		// go through all the levels and stop on the first level where there are no collisions
  		for (j=0; j<levels.length; j++) {
  			if (!computeSlotSegCollisions(seg, levels[j]).length) {
  				break;
  			}
  		}
  
  		seg.level = j;
  
  		(levels[j] || (levels[j] = [])).push(seg);
  	}
  
  	return levels;
  }
  
  
  // For every segment, figure out the other segments that are in subsequent
  // levels that also occupy the same vertical space. Accumulate in seg.forwardSegs
  function computeForwardSlotSegs(levels) {
  	var i, level;
  	var j, seg;
  	var k;
  
  	for (i=0; i<levels.length; i++) {
  		level = levels[i];
  
  		for (j=0; j<level.length; j++) {
  			seg = level[j];
  
  			seg.forwardSegs = [];
  			for (k=i+1; k<levels.length; k++) {
  				computeSlotSegCollisions(seg, levels[k], seg.forwardSegs);
  			}
  		}
  	}
  }
  
  
  // Figure out which path forward (via seg.forwardSegs) results in the longest path until
  // the furthest edge is reached. The number of segments in this path will be seg.forwardPressure
  function computeSlotSegPressures(seg) {
  	var forwardSegs = seg.forwardSegs;
  	var forwardPressure = 0;
  	var i, forwardSeg;
  
  	if (seg.forwardPressure === undefined) { // not already computed
  
  		for (i=0; i<forwardSegs.length; i++) {
  			forwardSeg = forwardSegs[i];
  
  			// figure out the child's maximum forward path
  			computeSlotSegPressures(forwardSeg);
  
  			// either use the existing maximum, or use the child's forward pressure
  			// plus one (for the forwardSeg itself)
  			forwardPressure = Math.max(
  				forwardPressure,
  				1 + forwardSeg.forwardPressure
  			);
  		}
  
  		seg.forwardPressure = forwardPressure;
  	}
  }
  
  
  // Find all the segments in `otherSegs` that vertically collide with `seg`.
  // Append into an optionally-supplied `results` array and return.
  function computeSlotSegCollisions(seg, otherSegs, results) {
  	results = results || [];
  
  	for (var i=0; i<otherSegs.length; i++) {
  		if (isSlotSegCollision(seg, otherSegs[i])) {
  			results.push(otherSegs[i]);
  		}
  	}
  
  	return results;
  }
  
  
  // Do these segments occupy the same vertical space?
  function isSlotSegCollision(seg1, seg2) {
  	return seg1.bottom > seg2.top && seg1.top < seg2.bottom;
  }
  