Parcourir la source

rearrange a bunch of stuff

Adam Shaw il y a 8 ans
Parent
commit
91127f6f44
2 fichiers modifiés avec 354 ajouts et 352 suppressions
  1. 51 48
      src/common/DayGrid.js
  2. 303 304
      src/common/TimeGrid.js

+ 51 - 48
src/common/DayGrid.js

@@ -32,11 +32,46 @@ var DayGrid = FC.DayGrid = ChronoComponent.extend(CoordChronoComponentMixin, Seg
 	},
 
 
+	// Slices up the given span (unzoned start/end with other misc data) into an array of segments
+	componentFootprintToSegs: function(componentFootprint) {
+		var segs = this.sliceRangeByRow(componentFootprint.unzonedRange);
+		var i, seg;
+
+		for (i = 0; i < segs.length; i++) {
+			seg = segs[i];
+
+			if (this.isRTL) {
+				seg.leftCol = this.daysPerRow - 1 - seg.lastRowDayIndex;
+				seg.rightCol = this.daysPerRow - 1 - seg.firstRowDayIndex;
+			}
+			else {
+				seg.leftCol = seg.firstRowDayIndex;
+				seg.rightCol = seg.lastRowDayIndex;
+			}
+		}
+
+		return segs;
+	},
+
+
+	rangeUpdated: function() {
+		this.updateDayTable();
+
+		// needs to go after updateDayTable because computeEventTimeFormat/computeDisplayEventEnd depends on colCnt.
+		// TODO: easy to forget. use listener.
+		this.eventRenderUtils.rangeUpdated();
+	},
+
+
 	opt: function(name) {
 		return this.view.opt(name);
 	},
 
 
+	/* Date Rendering
+	------------------------------------------------------------------------------------------------------------------*/
+
+
 	// Renders the rows and columns into the component's `this.el`, which should already be assigned.
 	// isRigid determins whether the individual rows should ignore the contents and be a constant height.
 	// Relies on the view's colCnt and rowCnt. In the future, this component should probably be self-sufficient.
@@ -86,18 +121,6 @@ var DayGrid = FC.DayGrid = ChronoComponent.extend(CoordChronoComponentMixin, Seg
 	},
 
 
-	renderBusinessHours: function() {
-		var segs = this.buildBusinessHourSegs(true); // wholeDay=true
-
-		this.fillSystem.render('businessHours', segs, 'bgevent');
-	},
-
-
-	unrenderBusinessHours: function() {
-		this.fillSystem.unrender('businessHours');
-	},
-
-
 	// Generates the HTML for a single row, which is a div that wraps a table.
 	// `row` is the row number.
 	renderDayRowHtml: function(row, isRigid) {
@@ -222,41 +245,6 @@ var DayGrid = FC.DayGrid = ChronoComponent.extend(CoordChronoComponentMixin, Seg
 	},
 
 
-	/* Dates
-	------------------------------------------------------------------------------------------------------------------*/
-
-
-	rangeUpdated: function() {
-		this.updateDayTable();
-
-		// needs to go after updateDayTable because computeEventTimeFormat/computeDisplayEventEnd depends on colCnt.
-		// TODO: easy to forget. use listener.
-		this.eventRenderUtils.rangeUpdated();
-	},
-
-
-	// Slices up the given span (unzoned start/end with other misc data) into an array of segments
-	componentFootprintToSegs: function(componentFootprint) {
-		var segs = this.sliceRangeByRow(componentFootprint.unzonedRange);
-		var i, seg;
-
-		for (i = 0; i < segs.length; i++) {
-			seg = segs[i];
-
-			if (this.isRTL) {
-				seg.leftCol = this.daysPerRow - 1 - seg.lastRowDayIndex;
-				seg.rightCol = this.daysPerRow - 1 - seg.firstRowDayIndex;
-			}
-			else {
-				seg.leftCol = seg.firstRowDayIndex;
-				seg.rightCol = seg.lastRowDayIndex;
-			}
-		}
-
-		return segs;
-	},
-
-
 	/* Hit System
 	------------------------------------------------------------------------------------------------------------------*/
 
@@ -357,7 +345,6 @@ var DayGrid = FC.DayGrid = ChronoComponent.extend(CoordChronoComponentMixin, Seg
 
 	/* Event Drag Visualization
 	------------------------------------------------------------------------------------------------------------------*/
-	// TODO: move to DayGrid.event, similar to what we did with Grid's drag methods
 
 
 	// Renders a visual indication of an event or external element being dragged.
@@ -456,6 +443,22 @@ var DayGrid = FC.DayGrid = ChronoComponent.extend(CoordChronoComponentMixin, Seg
 	},
 
 
+	/* Business Hours
+	------------------------------------------------------------------------------------------------------------------*/
+
+
+	renderBusinessHours: function() {
+		var segs = this.buildBusinessHourSegs(true); // wholeDay=true
+
+		this.fillSystem.render('businessHours', segs, 'bgevent');
+	},
+
+
+	unrenderBusinessHours: function() {
+		this.fillSystem.unrender('businessHours');
+	},
+
+
 	/* Fill System (highlight, background events, business hours)
 	------------------------------------------------------------------------------------------------------------------*/
 

+ 303 - 304
src/common/TimeGrid.js

@@ -56,11 +56,132 @@ var TimeGrid = FC.TimeGrid = ChronoComponent.extend(CoordChronoComponentMixin, S
 	},
 
 
+	// Slices up the given span (unzoned start/end with other misc data) into an array of segments
+	componentFootprintToSegs: function(componentFootprint) {
+		var segs = this.sliceRangeByTimes(componentFootprint.unzonedRange);
+		var i;
+
+		for (i = 0; i < segs.length; i++) {
+			if (this.isRTL) {
+				segs[i].col = this.daysPerRow - 1 - segs[i].dayIndex;
+			}
+			else {
+				segs[i].col = segs[i].dayIndex;
+			}
+		}
+
+		return segs;
+	},
+
+
+	/* Date Handling
+	------------------------------------------------------------------------------------------------------------------*/
+
+
+	rangeUpdated: function() {
+		var view = this.view;
+
+		this.updateDayTable();
+
+		// needs to go after updateDayTable because computeEventTimeFormat/computeDisplayEventEnd depends on colCnt.
+		// TODO: easy to forget. use listener.
+		this.eventRenderUtils.rangeUpdated();
+
+		this.dayRanges = this.dayDates.map(function(dayDate) {
+			return new UnzonedRange(
+				dayDate.clone().add(view.minTime),
+				dayDate.clone().add(view.maxTime)
+			);
+		});
+	},
+
+
+	sliceRangeByTimes: function(unzonedRange) {
+		var segs = [];
+		var segRange;
+		var dayIndex;
+
+		for (dayIndex = 0; dayIndex < this.daysPerRow; dayIndex++) {
+
+			segRange = unzonedRange.intersect(this.dayRanges[dayIndex]);
+
+			if (segRange) {
+				segs.push({
+					startMs: segRange.startMs,
+					endMs: segRange.endMs,
+					isStart: segRange.isStart,
+					isEnd: segRange.isEnd,
+					dayIndex: dayIndex
+				});
+			}
+		}
+
+		return segs;
+	},
+
+
+	/* Options
+	------------------------------------------------------------------------------------------------------------------*/
+
+
 	opt: function(name) {
 		return this.view.opt(name);
 	},
 
 
+	// Parses various options into properties of this object
+	processOptions: function() {
+		var slotDuration = this.opt('slotDuration');
+		var snapDuration = this.opt('snapDuration');
+		var input;
+
+		slotDuration = moment.duration(slotDuration);
+		snapDuration = snapDuration ? moment.duration(snapDuration) : slotDuration;
+
+		this.slotDuration = slotDuration;
+		this.snapDuration = snapDuration;
+		this.snapsPerSlot = slotDuration / snapDuration; // TODO: ensure an integer multiple?
+
+		// might be an array value (for TimelineView).
+		// if so, getting the most granular entry (the last one probably).
+		input = this.opt('slotLabelFormat');
+		if ($.isArray(input)) {
+			input = input[input.length - 1];
+		}
+
+		this.labelFormat = input ||
+			this.opt('smallTimeFormat'); // the computed default
+
+		input = this.opt('slotLabelInterval');
+		this.labelInterval = input ?
+			moment.duration(input) :
+			this.computeLabelInterval(slotDuration);
+	},
+
+
+	// Computes an automatic value for slotLabelInterval
+	computeLabelInterval: function(slotDuration) {
+		var i;
+		var labelInterval;
+		var slotsPerLabel;
+
+		// find the smallest stock label interval that results in more than one slots-per-label
+		for (i = AGENDA_STOCK_SUB_DURATIONS.length - 1; i >= 0; i--) {
+			labelInterval = moment.duration(AGENDA_STOCK_SUB_DURATIONS[i]);
+			slotsPerLabel = divideDurationByDuration(labelInterval, slotDuration);
+			if (isInt(slotsPerLabel) && slotsPerLabel > 1) {
+				return labelInterval;
+			}
+		}
+
+		return moment.duration(slotDuration); // fall back. clone
+	},
+
+
+	/* Date Rendering
+	------------------------------------------------------------------------------------------------------------------*/
+
+
 	// 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.
 	renderDates: function() {
@@ -145,194 +266,143 @@ var TimeGrid = FC.TimeGrid = ChronoComponent.extend(CoordChronoComponentMixin, S
 	},
 
 
-	/* Options
+	/* Content Skeleton
 	------------------------------------------------------------------------------------------------------------------*/
 
 
-	// Parses various options into properties of this object
-	processOptions: function() {
-		var slotDuration = this.opt('slotDuration');
-		var snapDuration = this.opt('snapDuration');
-		var input;
-
-		slotDuration = moment.duration(slotDuration);
-		snapDuration = snapDuration ? moment.duration(snapDuration) : slotDuration;
-
-		this.slotDuration = slotDuration;
-		this.snapDuration = snapDuration;
-		this.snapsPerSlot = slotDuration / snapDuration; // TODO: ensure an integer multiple?
+	// Renders the DOM that the view's content will live in
+	renderContentSkeleton: function() {
+		var cellHtml = '';
+		var i;
+		var skeletonEl;
 
-		// might be an array value (for TimelineView).
-		// if so, getting the most granular entry (the last one probably).
-		input = this.opt('slotLabelFormat');
-		if ($.isArray(input)) {
-			input = input[input.length - 1];
+		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>';
 		}
 
-		this.labelFormat = input ||
-			this.opt('smallTimeFormat'); // the computed default
+		skeletonEl = $(
+			'<div class="fc-content-skeleton">' +
+				'<table>' +
+					'<tr>' + cellHtml + '</tr>' +
+				'</table>' +
+			'</div>'
+		);
 
-		input = this.opt('slotLabelInterval');
-		this.labelInterval = input ?
-			moment.duration(input) :
-			this.computeLabelInterval(slotDuration);
+		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);
 	},
 
 
-	// Computes an automatic value for slotLabelInterval
-	computeLabelInterval: function(slotDuration) {
+	// 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;
-		var labelInterval;
-		var slotsPerLabel;
 
-		// find the smallest stock label interval that results in more than one slots-per-label
-		for (i = AGENDA_STOCK_SUB_DURATIONS.length - 1; i >= 0; i--) {
-			labelInterval = moment.duration(AGENDA_STOCK_SUB_DURATIONS[i]);
-			slotsPerLabel = divideDurationByDuration(labelInterval, slotDuration);
-			if (isInt(slotsPerLabel) && slotsPerLabel > 1) {
-				return labelInterval;
-			}
+		for (i = 0; i < this.colCnt; i++) {
+			segsByCol.push([]);
 		}
 
-		return moment.duration(slotDuration); // fall back. clone
-	},
-
-
-	/* Hit System
-	------------------------------------------------------------------------------------------------------------------*/
-
-
-	prepareHits: function() {
-		this.colCoordCache.build();
-		this.slatCoordCache.build();
-	},
-
+		for (i = 0; i < segs.length; i++) {
+			segsByCol[segs[i].col].push(segs[i]);
+		}
 
-	releaseHits: function() {
-		this.colCoordCache.clear();
-		// NOTE: don't clear slatCoordCache because we rely on it for computeTimeTop
+		return segsByCol;
 	},
 
 
-	queryHit: function(leftOffset, topOffset) {
-		var snapsPerSlot = this.snapsPerSlot;
-		var colCoordCache = this.colCoordCache;
-		var slatCoordCache = this.slatCoordCache;
-
-		if (colCoordCache.isLeftInBounds(leftOffset) && slatCoordCache.isTopInBounds(topOffset)) {
-			var colIndex = colCoordCache.getHorizontalIndex(leftOffset);
-			var slatIndex = slatCoordCache.getVerticalIndex(topOffset);
+	// 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;
 
-			if (colIndex != null && slatIndex != null) {
-				var slatTop = slatCoordCache.getTopOffset(slatIndex);
-				var slatHeight = slatCoordCache.getHeight(slatIndex);
-				var partial = (topOffset - slatTop) / slatHeight; // floating point number between 0 and 1
-				var localSnapIndex = Math.floor(partial * snapsPerSlot); // the snap # relative to start of slat
-				var snapIndex = slatIndex * snapsPerSlot + localSnapIndex;
-				var snapTop = slatTop + (localSnapIndex / snapsPerSlot) * slatHeight;
-				var snapBottom = slatTop + ((localSnapIndex + 1) / snapsPerSlot) * slatHeight;
+		for (col = 0; col < this.colCnt; col++) { // iterate each column grouping
+			segs = segsByCol[col];
 
-				return {
-					col: colIndex,
-					snap: snapIndex,
-					component: this, // needed unfortunately :(
-					left: colCoordCache.getLeftOffset(colIndex),
-					right: colCoordCache.getRightOffset(colIndex),
-					top: snapTop,
-					bottom: snapBottom
-				};
+			for (i = 0; i < segs.length; i++) {
+				containerEls.eq(col).append(segs[i].el);
 			}
 		}
 	},
 
 
-	getHitFootprint: function(hit) {
-		var start = this.getCellDate(0, hit.col); // row=0
-		var time = this.computeSnapTime(hit.snap); // pass in the snap-index
-		var end;
-
-		start.time(time);
-		end = start.clone().add(this.snapDuration);
-
-		return new ComponentFootprint(
-			new UnzonedRange(start, end),
-			false // all-day?
-		);
-	},
-
+	// 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;
 
-	getHitEl: function(hit) {
-		return this.colEls.eq(hit.col);
+		if (segs) {
+			for (i = 0; i < segs.length; i++) {
+				segs[i].el.remove();
+			}
+			this[propName] = null;
+		}
 	},
 
 
-	/* Dates
+	/* Now Indicator
 	------------------------------------------------------------------------------------------------------------------*/
 
 
-	rangeUpdated: function() {
-		var view = this.view;
-
-		this.updateDayTable();
-
-		// needs to go after updateDayTable because computeEventTimeFormat/computeDisplayEventEnd depends on colCnt.
-		// TODO: easy to forget. use listener.
-		this.eventRenderUtils.rangeUpdated();
-
-		this.dayRanges = this.dayDates.map(function(dayDate) {
-			return new UnzonedRange(
-				dayDate.clone().add(view.minTime),
-				dayDate.clone().add(view.maxTime)
-			);
-		});
-	},
-
-
-	// Given a row number of the grid, representing a "snap", returns a time (Duration) from its start-of-day
-	computeSnapTime: function(snapIndex) {
-		return moment.duration(this.view.minTime + this.snapDuration * snapIndex);
+	getNowIndicatorUnit: function() {
+		return 'minute'; // will refresh on the minute
 	},
 
 
-	// Slices up the given span (unzoned start/end with other misc data) into an array of segments
-	componentFootprintToSegs: function(componentFootprint) {
-		var segs = this.sliceRangeByTimes(componentFootprint.unzonedRange);
+	renderNowIndicator: function(date) {
+		// seg system might be overkill, but it handles scenario where line needs to be rendered
+		//  more than once because of columns with the same date (resources columns for example)
+		var segs = this.componentFootprintToSegs(
+			new ComponentFootprint(
+				new UnzonedRange(date, date.valueOf() + 1), // protect against null range
+				false // all-day
+			)
+		);
+		var top = this.computeDateTop(date, date);
+		var nodes = [];
 		var i;
 
+		// render lines within the columns
 		for (i = 0; i < segs.length; i++) {
-			if (this.isRTL) {
-				segs[i].col = this.daysPerRow - 1 - segs[i].dayIndex;
-			}
-			else {
-				segs[i].col = segs[i].dayIndex;
-			}
+			nodes.push($('<div class="fc-now-indicator fc-now-indicator-line"></div>')
+				.css('top', top)
+				.appendTo(this.colContainerEls.eq(segs[i].col))[0]);
 		}
 
-		return segs;
-	},
-
-
-	sliceRangeByTimes: function(unzonedRange) {
-		var segs = [];
-		var segRange;
-		var dayIndex;
-
-		for (dayIndex = 0; dayIndex < this.daysPerRow; dayIndex++) {
-
-			segRange = unzonedRange.intersect(this.dayRanges[dayIndex]);
-
-			if (segRange) {
-				segs.push({
-					startMs: segRange.startMs,
-					endMs: segRange.endMs,
-					isStart: segRange.isStart,
-					isEnd: segRange.isEnd,
-					dayIndex: dayIndex
-				});
-			}
+		// render an arrow over the axis
+		if (segs.length > 0) { // is the current time in view?
+			nodes.push($('<div class="fc-now-indicator fc-now-indicator-arrow"></div>')
+				.css('top', top)
+				.appendTo(this.el.find('.fc-content-skeleton'))[0]);
 		}
 
-		return segs;
+		this.nowIndicatorEls = $(nodes);
+	},
+
+
+	unrenderNowIndicator: function() {
+		if (this.nowIndicatorEls) {
+			this.nowIndicatorEls.remove();
+			this.nowIndicatorEls = null;
+		}
 	},
 
 
@@ -439,6 +509,80 @@ var TimeGrid = FC.TimeGrid = ChronoComponent.extend(CoordChronoComponentMixin, S
 	},
 
 
+	/* Hit System
+	------------------------------------------------------------------------------------------------------------------*/
+
+
+	prepareHits: function() {
+		this.colCoordCache.build();
+		this.slatCoordCache.build();
+	},
+
+
+	releaseHits: function() {
+		this.colCoordCache.clear();
+		// NOTE: don't clear slatCoordCache because we rely on it for computeTimeTop
+	},
+
+
+	queryHit: function(leftOffset, topOffset) {
+		var snapsPerSlot = this.snapsPerSlot;
+		var colCoordCache = this.colCoordCache;
+		var slatCoordCache = this.slatCoordCache;
+
+		if (colCoordCache.isLeftInBounds(leftOffset) && slatCoordCache.isTopInBounds(topOffset)) {
+			var colIndex = colCoordCache.getHorizontalIndex(leftOffset);
+			var slatIndex = slatCoordCache.getVerticalIndex(topOffset);
+
+			if (colIndex != null && slatIndex != null) {
+				var slatTop = slatCoordCache.getTopOffset(slatIndex);
+				var slatHeight = slatCoordCache.getHeight(slatIndex);
+				var partial = (topOffset - slatTop) / slatHeight; // floating point number between 0 and 1
+				var localSnapIndex = Math.floor(partial * snapsPerSlot); // the snap # relative to start of slat
+				var snapIndex = slatIndex * snapsPerSlot + localSnapIndex;
+				var snapTop = slatTop + (localSnapIndex / snapsPerSlot) * slatHeight;
+				var snapBottom = slatTop + ((localSnapIndex + 1) / snapsPerSlot) * slatHeight;
+
+				return {
+					col: colIndex,
+					snap: snapIndex,
+					component: this, // needed unfortunately :(
+					left: colCoordCache.getLeftOffset(colIndex),
+					right: colCoordCache.getRightOffset(colIndex),
+					top: snapTop,
+					bottom: snapBottom
+				};
+			}
+		}
+	},
+
+
+	getHitFootprint: function(hit) {
+		var start = this.getCellDate(0, hit.col); // row=0
+		var time = this.computeSnapTime(hit.snap); // pass in the snap-index
+		var end;
+
+		start.time(time);
+		end = start.clone().add(this.snapDuration);
+
+		return new ComponentFootprint(
+			new UnzonedRange(start, end),
+			false // all-day?
+		);
+	},
+
+
+	// Given a row number of the grid, representing a "snap", returns a time (Duration) from its start-of-day
+	computeSnapTime: function(snapIndex) {
+		return moment.duration(this.view.minTime + this.snapDuration * snapIndex);
+	},
+
+
+	getHitEl: function(hit) {
+		return this.colEls.eq(hit.col);
+	},
+
+
 	/* Event Rendering
 	------------------------------------------------------------------------------------------------------------------*/
 
@@ -468,7 +612,6 @@ var TimeGrid = FC.TimeGrid = ChronoComponent.extend(CoordChronoComponentMixin, S
 	},
 
 
-
 	/* Event Drag Visualization
 	------------------------------------------------------------------------------------------------------------------*/
 
@@ -599,163 +742,6 @@ var TimeGrid = FC.TimeGrid = ChronoComponent.extend(CoordChronoComponentMixin, S
 	},
 
 
-	/* Highlight
-	------------------------------------------------------------------------------------------------------------------*/
-
-
-	renderHighlightSegs: function(segs) {
-		segs = this.fillSystem.buildSegEls('highlight', segs);
-		this.updateSegVerticals(segs);
-		this.attachSegsByCol(this.groupSegsByCol(segs), this.highlightContainerEls);
-		this.highlightSegs = segs;
-	},
-
-
-	unrenderHighlightSegs: function() {
-		this.unrenderNamedSegs('highlightSegs');
-	},
-
-
-	/* Seg Rendering Utils
-	------------------------------------------------------------------------------------------------------------------*/
-
-
-	// 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);
-	},
-
-
-	// 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;
-		}
-	},
-
-
-	/* Now Indicator
-	------------------------------------------------------------------------------------------------------------------*/
-
-
-	getNowIndicatorUnit: function() {
-		return 'minute'; // will refresh on the minute
-	},
-
-
-	renderNowIndicator: function(date) {
-		// seg system might be overkill, but it handles scenario where line needs to be rendered
-		//  more than once because of columns with the same date (resources columns for example)
-		var segs = this.componentFootprintToSegs(
-			new ComponentFootprint(
-				new UnzonedRange(date, date.valueOf() + 1), // protect against null range
-				false // all-day
-			)
-		);
-		var top = this.computeDateTop(date, date);
-		var nodes = [];
-		var i;
-
-		// render lines within the columns
-		for (i = 0; i < segs.length; i++) {
-			nodes.push($('<div class="fc-now-indicator fc-now-indicator-line"></div>')
-				.css('top', top)
-				.appendTo(this.colContainerEls.eq(segs[i].col))[0]);
-		}
-
-		// render an arrow over the axis
-		if (segs.length > 0) { // is the current time in view?
-			nodes.push($('<div class="fc-now-indicator fc-now-indicator-arrow"></div>')
-				.css('top', top)
-				.appendTo(this.el.find('.fc-content-skeleton'))[0]);
-		}
-
-		this.nowIndicatorEls = $(nodes);
-	},
-
-
-	unrenderNowIndicator: function() {
-		if (this.nowIndicatorEls) {
-			this.nowIndicatorEls.remove();
-			this.nowIndicatorEls = null;
-		}
-	},
-
-
 	/* Selection
 	------------------------------------------------------------------------------------------------------------------*/
 
@@ -793,6 +779,19 @@ var TimeGrid = FC.TimeGrid = ChronoComponent.extend(CoordChronoComponentMixin, S
 
 	unrenderHighlight: function() {
 		this.unrenderHighlightSegs();
+	},
+
+
+	renderHighlightSegs: function(segs) {
+		segs = this.fillSystem.buildSegEls('highlight', segs);
+		this.updateSegVerticals(segs);
+		this.attachSegsByCol(this.groupSegsByCol(segs), this.highlightContainerEls);
+		this.highlightSegs = segs;
+	},
+
+
+	unrenderHighlightSegs: function() {
+		this.unrenderNamedSegs('highlightSegs');
 	}
 
 });