ソースを参照

move more rendering for DayGrid/TimeGrid into EventRenderUtils sublcass

Adam Shaw 8 年 前
コミット
92c9b33a25

+ 199 - 201
src/common/DayGrid.events.js

@@ -4,12 +4,10 @@
 
 DayGrid.mixin({
 
-	rowStructs: null, // an array of objects, each holding information about a row's foreground event-rendering
-
-
 	eventRenderUtilsClass: SegChronoComponentMixin.eventRenderUtilsClass.extend({
 
 		dayGrid: null,
+		rowStructs: null, // an array of objects, each holding information about a row's foreground event-rendering
 
 
 		constructor: function(dayGrid) {
@@ -18,6 +16,204 @@ DayGrid.mixin({
 		},
 
 
+		// Renders the given foreground event segments onto the grid
+		renderFgSegs: function(segs) {
+			var rowStructs;
+
+			// render an `.el` on each seg
+			// returns a subset of the segs. segs that were actually rendered
+			segs = this.renderFgSegEls(segs);
+
+			rowStructs = this.rowStructs = this.renderSegRows(segs);
+
+			// append to each row's content skeleton
+			this.dayGrid.rowEls.each(function(i, rowNode) {
+				$(rowNode).find('.fc-content-skeleton > table').append(
+					rowStructs[i].tbodyEl
+				);
+			});
+
+			return segs; // return only the segs that were actually rendered
+		},
+
+
+		// Unrenders all currently rendered foreground event segments
+		unrenderFgSegs: function() {
+			var rowStructs = this.rowStructs || [];
+			var rowStruct;
+
+			while ((rowStruct = rowStructs.pop())) {
+				rowStruct.tbodyEl.remove();
+			}
+
+			this.rowStructs = null;
+		},
+
+
+		// Uses the given events array to generate <tbody> elements that should be appended to each row's content skeleton.
+		// Returns an array of rowStruct objects (see the bottom of `renderSegRow`).
+		// PRECONDITION: each segment shoud already have a rendered and assigned `.el`
+		renderSegRows: function(segs) {
+			var rowStructs = [];
+			var segRows;
+			var row;
+
+			segRows = this.groupSegRows(segs); // group into nested arrays
+
+			// iterate each row of segment groupings
+			for (row = 0; row < segRows.length; row++) {
+				rowStructs.push(
+					this.renderSegRow(row, segRows[row])
+				);
+			}
+
+			return rowStructs;
+		},
+
+
+		// Given a row # and an array of segments all in the same row, render a <tbody> element, a skeleton that contains
+		// the segments. Returns object with a bunch of internal data about how the render was calculated.
+		// NOTE: modifies rowSegs
+		renderSegRow: function(row, rowSegs) {
+			var colCnt = this.dayGrid.colCnt;
+			var segLevels = this.buildSegLevels(rowSegs); // group into sub-arrays of levels
+			var levelCnt = Math.max(1, segLevels.length); // ensure at least one level
+			var tbody = $('<tbody/>');
+			var segMatrix = []; // lookup for which segments are rendered into which level+col cells
+			var cellMatrix = []; // lookup for all <td> elements of the level+col matrix
+			var loneCellMatrix = []; // lookup for <td> elements that only take up a single column
+			var i, levelSegs;
+			var col;
+			var tr;
+			var j, seg;
+			var td;
+
+			// populates empty cells from the current column (`col`) to `endCol`
+			function emptyCellsUntil(endCol) {
+				while (col < endCol) {
+					// try to grab a cell from the level above and extend its rowspan. otherwise, create a fresh cell
+					td = (loneCellMatrix[i - 1] || [])[col];
+					if (td) {
+						td.attr(
+							'rowspan',
+							parseInt(td.attr('rowspan') || 1, 10) + 1
+						);
+					}
+					else {
+						td = $('<td/>');
+						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 = $('<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 = $('<td class="fc-event-container"/>').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.dayGrid.bookendCells(tr);
+				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.
+		// NOTE: modifies segs
+		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.
+			this.sortEventSegs(segs);
+
+			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 segRows = [];
+			var i;
+
+			for (i = 0; i < this.dayGrid.rowCnt; i++) {
+				segRows.push([]);
+			}
+
+			for (i = 0; i < segs.length; i++) {
+				segRows[segs[i].row].push(segs[i]);
+			}
+
+			return segRows;
+		},
+
+
 		// Computes a default event time formatting string if `timeFormat` is not explicitly defined
 		computeEventTimeFormat: function() {
 			return this.opt('extraSmallTimeFormat'); // like "6p" or "6:30p"
@@ -115,204 +311,6 @@ DayGrid.mixin({
 		});
 
 		return SegChronoComponentMixin.renderBgSegs.call(this, allDaySegs); // call the super-method
-	},
-
-
-	// Renders the given foreground event segments onto the grid
-	renderFgSegs: function(segs) {
-		var rowStructs;
-
-		// render an `.el` on each seg
-		// returns a subset of the segs. segs that were actually rendered
-		segs = this.eventRenderUtils.renderFgSegEls(segs);
-
-		rowStructs = this.rowStructs = this.renderSegRows(segs);
-
-		// append to each row's content skeleton
-		this.rowEls.each(function(i, rowNode) {
-			$(rowNode).find('.fc-content-skeleton > table').append(
-				rowStructs[i].tbodyEl
-			);
-		});
-
-		return segs; // return only the segs that were actually rendered
-	},
-
-
-	// Unrenders all currently rendered foreground event segments
-	unrenderFgSegs: function() {
-		var rowStructs = this.rowStructs || [];
-		var rowStruct;
-
-		while ((rowStruct = rowStructs.pop())) {
-			rowStruct.tbodyEl.remove();
-		}
-
-		this.rowStructs = null;
-	},
-
-
-	// Uses the given events array to generate <tbody> elements that should be appended to each row's content skeleton.
-	// Returns an array of rowStruct objects (see the bottom of `renderSegRow`).
-	// PRECONDITION: each segment shoud already have a rendered and assigned `.el`
-	renderSegRows: function(segs) {
-		var rowStructs = [];
-		var segRows;
-		var row;
-
-		segRows = this.groupSegRows(segs); // group into nested arrays
-
-		// iterate each row of segment groupings
-		for (row = 0; row < segRows.length; row++) {
-			rowStructs.push(
-				this.renderSegRow(row, segRows[row])
-			);
-		}
-
-		return rowStructs;
-	},
-
-
-	// Given a row # and an array of segments all in the same row, render a <tbody> element, a skeleton that contains
-	// the segments. Returns object with a bunch of internal data about how the render was calculated.
-	// NOTE: modifies rowSegs
-	renderSegRow: function(row, rowSegs) {
-		var colCnt = this.colCnt;
-		var segLevels = this.buildSegLevels(rowSegs); // group into sub-arrays of levels
-		var levelCnt = Math.max(1, segLevels.length); // ensure at least one level
-		var tbody = $('<tbody/>');
-		var segMatrix = []; // lookup for which segments are rendered into which level+col cells
-		var cellMatrix = []; // lookup for all <td> elements of the level+col matrix
-		var loneCellMatrix = []; // lookup for <td> elements that only take up a single column
-		var i, levelSegs;
-		var col;
-		var tr;
-		var j, seg;
-		var td;
-
-		// populates empty cells from the current column (`col`) to `endCol`
-		function emptyCellsUntil(endCol) {
-			while (col < endCol) {
-				// try to grab a cell from the level above and extend its rowspan. otherwise, create a fresh cell
-				td = (loneCellMatrix[i - 1] || [])[col];
-				if (td) {
-					td.attr(
-						'rowspan',
-						parseInt(td.attr('rowspan') || 1, 10) + 1
-					);
-				}
-				else {
-					td = $('<td/>');
-					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 = $('<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 = $('<td class="fc-event-container"/>').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);
-			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.
-	// NOTE: modifies segs
-	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.
-		this.sortEventSegs(segs);
-		
-		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 segRows = [];
-		var i;
-
-		for (i = 0; i < this.rowCnt; i++) {
-			segRows.push([]);
-		}
-
-		for (i = 0; i < segs.length; i++) {
-			segRows[segs[i].row].push(segs[i]);
-		}
-
-		return segRows;
 	}
 
 });

+ 1 - 1
src/common/DayGrid.js

@@ -384,7 +384,7 @@ var DayGrid = FC.DayGrid = ChronoComponent.extend(CoordChronoComponentMixin, Seg
 		var rowStructs;
 
 		segs = this.eventRenderUtils.renderFgSegEls(segs); // assigns each seg's el and returns a subset of segs that were rendered
-		rowStructs = this.renderSegRows(segs);
+		rowStructs = this.eventRenderUtils.renderSegRows(segs);
 
 		// inject each new event skeleton into each associated row
 		this.rowEls.each(function(row, rowNode) {

+ 6 - 6
src/common/DayGrid.limit.js

@@ -19,7 +19,7 @@ DayGrid.mixin({
 	// 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 rowStructs = this.eventRenderUtils.rowStructs || [];
 		var row; // row #
 		var rowLevelLimit;
 
@@ -49,7 +49,7 @@ DayGrid.mixin({
 	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 trEls = this.eventRenderUtils.rowStructs[row].tbodyEl.children();
 		var i, trEl;
 		var trHeight;
 
@@ -80,7 +80,7 @@ DayGrid.mixin({
 	// `levelLimit` is a number for the maximum (inclusive) number of levels allowed.
 	limitRow: function(row, levelLimit) {
 		var _this = this;
-		var rowStruct = this.rowStructs[row];
+		var rowStruct = this.eventRenderUtils.rowStructs[row];
 		var moreNodes = []; // array of "more" <a> links and <td> DOM nodes
 		var col = 0; // col #, left-to-right (not chronologically)
 		var levelSegs; // array of segment objects in the last allowable level, ordered left-to-right
@@ -167,7 +167,7 @@ DayGrid.mixin({
 	// 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];
+		var rowStruct = this.eventRenderUtils.rowStructs[row];
 
 		if (rowStruct.moreEls) {
 			rowStruct.moreEls.remove();
@@ -349,7 +349,7 @@ DayGrid.mixin({
 
 		// force an order because eventsToSegs doesn't guarantee one
 		// TODO: research if still needed
-		this.sortEventSegs(newSegs);
+		this.eventRenderUtils.sortEventSegs(newSegs);
 
 		return newSegs;
 	},
@@ -371,7 +371,7 @@ DayGrid.mixin({
 	// Returns segments within a given cell.
 	// If `startLevel` is specified, returns only events including and below that level. Otherwise returns all segs.
 	getCellSegs: function(row, col, startLevel) {
-		var segMatrix = this.rowStructs[row].segMatrix;
+		var segMatrix = this.eventRenderUtils.rowStructs[row].segMatrix;
 		var level = startLevel || 0;
 		var segs = [];
 		var seg;

+ 35 - 0
src/common/EventRenderUtils.js

@@ -44,6 +44,18 @@ var EventRenderUtils = Class.extend({
 	},
 
 
+	// Renders foreground event segments onto the grid. May return a subset of segs that were rendered.
+	renderFgSegs: function(segs) {
+		// subclasses must implement
+	},
+
+
+	// Unrenders all currently rendered foreground segments
+	unrenderFgSegs: function() {
+		// subclasses must implement
+	},
+
+
 	// Renders and assigns an `el` property for each foreground event segment.
 	// Only returns segments that successfully rendered.
 	// A utility that subclasses may use.
@@ -267,6 +279,29 @@ var EventRenderUtils = Class.extend({
 		var source = eventFootprint.eventDef.source;
 
 		return source.textColor || this.opt('eventTextColor');
+	},
+
+
+	sortEventSegs: function(segs) {
+		segs.sort(proxy(this, 'compareEventSegs'));
+	},
+
+
+	// A cmp function for determining which segments should take visual priority
+	compareEventSegs: function(seg1, seg2) {
+		var f1 = seg1.footprint.componentFootprint;
+		var r1 = f1.unzonedRange;
+		var f2 = seg2.footprint.componentFootprint;
+		var r2 = f2.unzonedRange;
+
+		return r1.startMs - r2.startMs || // earlier events go first
+			(r2.endMs - r2.startMs) - (r1.endMs - r1.startMs) || // tie? longer events go first
+			f2.isAllDay - f1.isAllDay || // tie? put all-day events first (booleans cast to 0/1)
+			compareByFieldSpecs(
+				seg1.footprint.eventDef,
+				seg2.footprint.eventDef,
+				this.view.eventOrderSpecs
+			);
 	}
 
 });

+ 2 - 26
src/common/SegChronoComponentMixin.js

@@ -83,13 +83,13 @@ var SegChronoComponentMixin = {
 
 	// Renders foreground event segments onto the grid. May return a subset of segs that were rendered.
 	renderFgSegs: function(segs) {
-		// subclasses must implement
+		return this.eventRenderUtils.renderFgSegs(segs);
 	},
 
 
 	// Unrenders all currently rendered foreground segments
 	unrenderFgSegs: function() {
-		// subclasses must implement
+		this.eventRenderUtils.unrenderFgSegs();
 	},
 
 
@@ -237,30 +237,6 @@ var SegChronoComponentMixin = {
 	eventRenderUtilsClass: EventRenderUtils,
 
 
-	sortEventSegs: function(segs) {
-		segs.sort(proxy(this, 'compareEventSegs'));
-	},
-
-
-	// A cmp function for determining which segments should take visual priority
-	compareEventSegs: function(seg1, seg2) {
-		var view = this._getView(); // TODO: not optimal!
-		var f1 = seg1.footprint.componentFootprint;
-		var r1 = f1.unzonedRange;
-		var f2 = seg2.footprint.componentFootprint;
-		var r2 = f2.unzonedRange;
-
-		return r1.startMs - r2.startMs || // earlier events go first
-			(r2.endMs - r2.startMs) - (r1.endMs - r1.startMs) || // tie? longer events go first
-			f2.isAllDay - f1.isAllDay || // tie? put all-day events first (booleans cast to 0/1)
-			compareByFieldSpecs(
-				seg1.footprint.eventDef,
-				seg2.footprint.eventDef,
-				view.eventOrderSpecs
-			);
-	},
-
-
 	/* Converting componentFootprint/eventFootprint -> segs
 	------------------------------------------------------------------------------------------------------------------*/
 

+ 192 - 194
src/common/TimeGrid.events.js

@@ -15,7 +15,6 @@ TimeGrid.mixin({
 	businessContainerEls: null,
 
 	// arrays of different types of displayed segments
-	fgSegs: null,
 	bgSegs: null,
 	helperSegs: null,
 	highlightSegs: null,
@@ -24,6 +23,49 @@ TimeGrid.mixin({
 
 	eventRenderUtilsClass: SegChronoComponentMixin.eventRenderUtilsClass.extend({
 
+		timeGrid: null,
+		fgSegs: null,
+
+
+		constructor: function(timeGrid) {
+			SegChronoComponentMixin.eventRenderUtilsClass.call(this, timeGrid);
+			this.timeGrid = timeGrid;
+		},
+
+
+		renderFgSegs: function(segs) {
+			segs = this.renderFgSegsIntoContainers(segs, this.timeGrid.fgContainerEls);
+
+			this.fgSegs = segs;
+
+			return segs; // needed for Grid::renderEvents
+		},
+
+
+		// 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);
+			segsByCol = this.timeGrid.groupSegsByCol(segs);
+
+			for (col = 0; col < this.timeGrid.colCnt; col++) {
+				this.updateFgSegCoords(segsByCol[col]);
+			}
+
+			this.timeGrid.attachSegsByCol(segsByCol, containerEls);
+
+			return segs;
+		},
+
+
+		unrenderFgSegs: function() {
+			this.timeGrid.unrenderNamedSegs('fgSegs');
+		},
+
+
 		// Computes a default event time formatting string if `timeFormat` is not explicitly defined
 		computeEventTimeFormat: function() {
 			return this.opt('noMeridiemTimeFormat'); // like "6:30" (no AM/PM)
@@ -113,6 +155,154 @@ TimeGrid.mixin({
 						''
 						) +
 				'</a>';
+		},
+
+
+		// 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.timeGrid.computeSegVerticals(segs); // horizontals relies on this
+			this.computeFgSegHorizontals(segs); // compute horizontal coordinates, z-index's, and reorder the array
+			this.timeGrid.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.timeGrid.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.timeGrid.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;
 		}
 
 	}),
@@ -157,22 +347,6 @@ TimeGrid.mixin({
 	},
 
 
-	/* 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
 	------------------------------------------------------------------------------------------------------------------*/
 
@@ -182,7 +356,7 @@ TimeGrid.mixin({
 		var i, seg;
 		var sourceEl;
 
-		segs = this.renderFgSegsIntoContainers(segs, this.helperContainerEls);
+		segs = this.eventRenderUtils.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++) {
@@ -315,30 +489,6 @@ TimeGrid.mixin({
 	},
 
 
-
-	/* 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.eventRenderUtils.renderFgSegEls(segs);
-		segsByCol = this.groupSegsByCol(segs);
-
-		for (col = 0; col < this.colCnt; col++) {
-			this.updateFgSegCoords(segsByCol[col]);
-		}
-
-		this.attachSegsByCol(segsByCol, containerEls);
-
-		return segs;
-	},
-
-
 	/* Seg Position Utils
 	------------------------------------------------------------------------------------------------------------------*/
 
@@ -384,158 +534,6 @@ TimeGrid.mixin({
 			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;
 	}
 
 });

+ 5 - 1
src/common/TimeGrid.js

@@ -327,7 +327,11 @@ var TimeGrid = FC.TimeGrid = ChronoComponent.extend(CoordChronoComponentMixin, S
 
 		if (isResize) {
 			this.updateSegVerticals(
-				[].concat(this.fgSegs || [], this.bgSegs || [], this.businessSegs || [])
+				[].concat(
+					this.eventRenderUtils.fgSegs || [],
+					this.bgSegs || [],
+					this.businessSegs || []
+				)
 			);
 		}
 	},

+ 1 - 1
src/list/ListView.js

@@ -273,7 +273,7 @@ var ListView = View.extend(CoordChronoComponentMixin, SegChronoComponentMixin, {
 				// append a day header
 				tbodyEl.append(this.dayHeaderHtml(this.dayDates[dayIndex]));
 
-				this.sortEventSegs(daySegs);
+				this.eventRenderUtils.sortEventSegs(daySegs);
 
 				for (i = 0; i < daySegs.length; i++) {
 					tbodyEl.append(daySegs[i].el); // append event row