瀏覽代碼

"more" link on a cell when too many events. accompanying new options

Adam Shaw 11 年之前
父節點
當前提交
2053f4dc21

+ 1 - 0
lumbar.json

@@ -30,6 +30,7 @@
         "src/common/Grid.events.js",
         "src/common/DayGrid.js",
         "src/common/DayGrid.events.js",
+        "src/common/DayGrid.limit.js",
         "src/common/TimeGrid.js",
         "src/common/TimeGrid.events.js",
         "src/common/View.js",

+ 21 - 0
src/Calendar.js

@@ -52,6 +52,7 @@ function Calendar(element, instanceOptions) {
 	t.today = today;
 	t.gotoDate = gotoDate;
 	t.incrementDate = incrementDate;
+	t.zoomToDay = zoomToDay;
 	t.getDate = getDate;
 	t.getCalendar = getCalendar;
 	t.getView = getView;
@@ -622,6 +623,26 @@ function Calendar(element, instanceOptions) {
 		date.add(moment.duration(delta));
 		renderView();
 	}
+
+
+	// Forces navigation to a day-view on the given date. `viewName` is the name of an explicit view to go to.
+	// If not specified, or 'auto', it will guess the best view based on which buttons are in the header toolbar.
+	function zoomToDay(newDate, viewName) {
+		var viewsWithButtons;
+
+		if (fcViews[viewName] === undefined) { // not an available view, or 'auto'
+			viewsWithButtons = header.getViewsWithButtons();
+			if ($.inArray('basicDay', viewsWithButtons) !== -1) {
+				viewName = 'basicDay';
+			}
+			else {
+				viewName = 'agendaDay'; // the fallback
+			}
+		}
+
+		date = newDate;
+		changeView(viewName);
+	}
 	
 	
 	function getDate() {

+ 8 - 0
src/Header.js

@@ -14,9 +14,11 @@ function Header(calendar, options) {
 	t.deactivateButton = deactivateButton;
 	t.disableButton = disableButton;
 	t.enableButton = enableButton;
+	t.getViewsWithButtons = getViewsWithButtons;
 	
 	// locals
 	var el = $();
+	var viewsWithButtons = [];
 	var tm;
 
 
@@ -76,6 +78,7 @@ function Header(calendar, options) {
 								button.removeClass(tm + '-state-hover'); // forget why
 								calendar.changeView(buttonName);
 							};
+							viewsWithButtons.push(buttonName);
 						}
 						if (buttonClick) {
 
@@ -196,4 +199,9 @@ function Header(calendar, options) {
 			.removeClass(tm + '-state-disabled');
 	}
 
+
+	function getViewsWithButtons() {
+		return viewsWithButtons;
+	}
+
 }

+ 3 - 4
src/agenda/AgendaView.js

@@ -336,10 +336,9 @@ $.extend(AgendaView.prototype, {
 			this.dayGrid.destroyEvents();
 		}
 
-		// When rerendering events in IE8, the event elements flash because of this line.
-		// Comment it out. It's not necessary because a renderEvents is always called subsequently,
-		// which updates the height.
-		//this.updateHeight();
+		// 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
 
 		View.prototype.destroyEvents.call(this); // call the super-method. will kill `this.segs`
 	},

+ 10 - 2
src/basic/BasicView.js

@@ -44,7 +44,7 @@ $.extend(BasicView.prototype, {
 		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.dayGrid.render(this.hasRigidRows());
 
 		View.prototype.render.call(this); // call the super-method
 	},
@@ -150,6 +150,12 @@ $.extend(BasicView.prototype, {
 	},
 
 
+	// Determines whether each row should have a constant height. Overridable by subclasses.
+	hasRigidRows: function() {
+		return false;
+	},
+
+
 	/* Dimensions
 	------------------------------------------------------------------------------------------------------------------*/
 
@@ -221,7 +227,9 @@ $.extend(BasicView.prototype, {
 		this.recordScroll(); // removing events will reduce height and mess with the scroll, so record beforehand
 		this.dayGrid.destroyEvents();
 
-		this.updateHeight();
+		// 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
 
 		View.prototype.destroyEvents.call(this); // call the super-method
 	},

+ 23 - 1
src/basic/MonthView.js

@@ -3,7 +3,10 @@
 ----------------------------------------------------------------------------------------------------------------------*/
 
 setDefaults({
-	fixedWeekCount: true
+	fixedWeekCount: true,
+	eventLimit: false,
+	eventLimitText: 'more',
+	eventLimitClick: 'auto'
 });
 
 fcViews.month = MonthView; // register the view
@@ -56,6 +59,8 @@ $.extend(MonthView.prototype, {
 
 	// Overrides the default BasicView behavior to have special multi-week auto-height logic
 	setGridHeight: function(height, isAuto) {
+		var eventLimit = this.opt('eventLimit');
+
 		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
@@ -63,7 +68,17 @@ $.extend(MonthView.prototype, {
 			height *= this.rowCnt / 6;
 		}
 
+		// 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
+		}
+
 		distributeHeight(this.dayGrid.rowEls, height, !isAuto); // if auto, don't compensate for height-hogging rows
+
+		// 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
+		}
 	},
 
 
@@ -74,6 +89,13 @@ $.extend(MonthView.prototype, {
 		}
 
 		return this.opt('fixedWeekCount');
+	},
+
+
+	// If dynamically limiting events, signals that all rows need to be a constant height.
+	hasRigidRows: function() {
+		var eventLimit = this.opt('eventLimit');
+		return eventLimit && typeof eventLimit !== 'number';
 	}
 
 });

+ 30 - 0
src/basic/basic.css

@@ -15,6 +15,19 @@
 	min-height: 4em; /* ensure that all rows are at least this tall */
 }
 
+/* a "rigid" row will take up a constant amount of height because content-skeleton is absolute */
+
+.fc-row.fc-rigid {
+	overflow: hidden;
+}
+
+.fc-row.fc-rigid .fc-content-skeleton {
+	position: absolute;
+	top: 0;
+	left: 0;
+	right: 0;
+}
+
 /* week and day number styling */
 
 .fc-basic-view .fc-week-number,
@@ -45,3 +58,20 @@
 .fc-rtl .fc-basic-view .fc-day-number {
 	text-align: left;
 }
+
+/* "more" link that represents hidden events */
+
+a.fc-more {
+	margin: 1px 2px;
+	font-size: .85em;
+	cursor: pointer;
+	text-decoration: none;
+}
+
+a.fc-more:hover {
+	text-decoration: underline;
+}
+
+.fc-limited { /* rows and cells that are hidden because of a "more" link */
+	display: none;
+}

+ 60 - 37
src/common/DayGrid.events.js

@@ -4,43 +4,49 @@
 
 $.extend(DayGrid.prototype, {
 
-	// A jQuery set of <tbody> elements, one for each row, with events inside. Attached to the content skeletons.
-	eventTbodyEls: null,
+
+	rowStructs: null, // an array of objects, each holding information about a row's event-rendering
 
 
 	// Render the given events onto the Grid and return the rendered segments
 	renderEvents: function(events) {
-		var res = this.renderEventRows(events);
-		var tbodyEls = this.eventTbodyEls = res.tbodyEls;
+		var rowStructs = this.rowStructs = this.renderEventRows(events);
+		var allSegs = [];
 
 		// append to each row's content skeleton
 		this.rowEls.each(function(i, rowNode) {
-			$(rowNode).find('.fc-content-skeleton > table').append(tbodyEls[i]);
+			$(rowNode).find('.fc-content-skeleton > table').append(
+				rowStructs[i].tbodyEl
+			);
+			allSegs.push.apply(allSegs, rowStructs[i].segs);
 		});
 
-		return res.segs; // return segment objects. for the view
+		return allSegs; // return segment objects. for the view
 	},
 
 
 	// Removes all rendered event elements
 	destroyEvents: function() {
-		if (this.eventTbodyEls) {
-			this.eventTbodyEls.remove();
-			this.eventTbodyEls = null;
+		var rowStructs = this.rowStructs || [];
+		var rowStruct;
+
+		while ((rowStruct = rowStructs.pop())) {
+			rowStruct.tbodyEl.remove();
 		}
 	},
 
 
 	// Uses the given events array to generate <tbody> elements that should be appended to each row's content skeleton.
-	// Returns an object with properties 'tbodyEls' and 'segs' (which contains all the rendered segment objects).
+	// Returns an array of rowStruct objects (see the bottom of `renderEventRow`).
 	renderEventRows: function(events) {
 		var view = this.view;
 		var allSegs = this.eventsToSegs(events);
 		var segRows = this.groupSegRows(allSegs); // group into nested arrays
 		var html = '';
-		var tbodyNodes = [];
+		var rowStructs = [];
 		var i;
 		var row;
+		var rowSegs;
 
 		// build a large concatenation of event segment HTML
 		for (i = 0; i < allSegs.length; i++) {
@@ -55,16 +61,14 @@ $.extend(DayGrid.prototype, {
 
 		// iterate each row of segment groupings
 		for (row = 0; row < segRows.length; row++) {
-			segRows[row] = $.grep(segRows[row], renderedSegFilter); // filter out non-rendered segments. reassign array
-			tbodyNodes.push(
-				this.renderSegSkeleton(segRows[row])[0]
+			rowSegs = segRows[row];
+			rowSegs = $.grep(rowSegs, renderedSegFilter); // filter out non-rendered segments
+			rowStructs.push(
+				this.renderEventRow(row, rowSegs)
 			);
 		}
 
-		return {
-			tbodyEls: $(tbodyNodes), // array -> jQuery set
-			segs: flattenArray(segRows) // flatten all rendered segments into one array
-		};
+		return rowStructs;
 	},
 
 
@@ -116,14 +120,17 @@ $.extend(DayGrid.prototype, {
 	},
 
 
-	// Given an array of segments all in the same row, render a <tbody> element, a skeleton that contains the segments
-	renderSegSkeleton: function(rowSegs) {
+	// 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.
+	renderEventRow: function(row, rowSegs) {
 		var view = this.view;
 		var colCnt = view.colCnt;
-		var levels = this.buildSegLevels(rowSegs); // group into sub-arrays of levels
+		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 emptyTds = []; // a sparse array of references to the current row's empty cells, indexed by column
-		var aboveEmptyTds; // like emptyTds, but for the level above
+		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;
@@ -133,8 +140,8 @@ $.extend(DayGrid.prototype, {
 		// populates empty cells from the current column (`col`) to `endCol`
 		function emptyCellsUntil(endCol) {
 			while (col < endCol) {
-				// try to grab an empty cell from the level above and extend its rowspan. otherwise, create a fresh cell
-				td = aboveEmptyTds[col];
+				// 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',
@@ -145,22 +152,24 @@ $.extend(DayGrid.prototype, {
 					td = $('<td/>');
 					tr.append(td);
 				}
-				emptyTds[col] = td;
+				cellMatrix[i][col] = td;
+				loneCellMatrix[i][col] = td;
 				col++;
 			}
 		}
 
-		// Iterate through all levels, and then beyond one. Do this so we have an empty row at the end.
-		// This empty row comes in handy when styling the height of the content skeleton.
-		for (i = 0; i < levels.length + 1; i++) {
-			levelSegs = levels[i];
+		for (i = 0; i < levelCnt; i++) { // iterate through all levels
+			levelSegs = segLevels[i];
 			col = 0;
 			tr = $('<tr/>');
 
-			aboveEmptyTds = emptyTds;
-			emptyTds = [];
+			segMatrix.push([]);
+			cellMatrix.push([]);
+			loneCellMatrix.push([]);
 
-			if (levelSegs) { // protect against non-existent last level
+			// 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];
 
@@ -168,22 +177,36 @@ $.extend(DayGrid.prototype, {
 
 					// create a container that occupies or more columns. append the event element.
 					td = $('<td class="fc-event-container"/>').append(seg.el);
-					if (seg.rightCol > seg.leftCol) {
+					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);
-					col = seg.rightCol + 1;
 				}
 			}
 
 			emptyCellsUntil(colCnt); // finish off the row
-
 			this.bookendCells(tr, 'eventSkeleton');
 			tbody.append(tr);
 		}
 
-		return tbody;
+		return { // a "rowStruct"
+			row: row, // the row number
+			tbodyEl: tbody,
+			cellMatrix: cellMatrix,
+			segMatrix: segMatrix,
+			segLevels: segLevels,
+			segs: rowSegs
+		};
 	},
 
 

+ 10 - 5
src/common/DayGrid.js

@@ -21,14 +21,15 @@ $.extend(DayGrid.prototype, {
 
 
 	// 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.
-	render: function() {
+	render: function(isRigid) {
 		var view = this.view;
 		var html = '';
 		var row;
 
 		for (row = 0; row < view.rowCnt; row++) {
-			html += this.dayRowHtml(row);
+			html += this.dayRowHtml(row, isRigid);
 		}
 		this.el.html(html);
 
@@ -46,10 +47,14 @@ $.extend(DayGrid.prototype, {
 
 
 	// Generates the HTML for a single row. `row` is the row number.
-	dayRowHtml: function(row) {
+	dayRowHtml: function(row, isRigid) {
 		var view = this.view;
 		var classes = [ 'fc-row', 'fc-week' ];
 
+		if (isRigid) {
+			classes.push('fc-rigid');
+		}
+
 		if (view.dayRowThemeClass) { // provides the view a hook to inject a theme className
 			classes.push(view.dayRowThemeClass);
 		}
@@ -197,7 +202,7 @@ $.extend(DayGrid.prototype, {
 	// Renders a mock "helper" event. `sourceSeg` is the associated internal segment object. It can be null.
 	renderHelper: function(event, sourceSeg) {
 		var helperNodes = [];
-		var tbodyEls = this.renderEventRows([ event ]).tbodyEls; // render events as usual, receiving tbodys to inject
+		var rowStructs = this.renderEventRows([ event ]);
 
 		// inject each new event skeleton into each associated row
 		this.rowEls.each(function(row, rowNode) {
@@ -215,7 +220,7 @@ $.extend(DayGrid.prototype, {
 
 			skeletonEl.css('top', skeletonTop)
 				.find('table')
-					.append(tbodyEls[row]);
+					.append(rowStructs[row].tbodyEl);
 
 			rowEl.append(skeletonEl);
 			helperNodes.push(skeletonEl[0]);

+ 230 - 0
src/common/DayGrid.limit.js

@@ -0,0 +1,230 @@
+
+/* Methods relate to limiting the number events for a given day on a DayGrid
+----------------------------------------------------------------------------------------------------------------------*/
+
+$.extend(DayGrid.prototype, {
+
+
+	// 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 (levelLimit !== 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 <tr> at a time and stop when we find one out of bounds
+		for (i = 0; i < trEls.length; i++) {
+			trEl = trEls.eq(i).removeClass('fc-limited'); // get and reveal
+			if (trEl.position().top + trEl.outerHeight() > rowHeight) {
+				return i;
+			}
+		}
+
+		return false; // should not limit at all
+	},
+
+
+	// Limits the given grid row to the maximum number of levels and injects "more" links if necessary.
+	// `row` is the row number.
+	// `levelLimit` is a number for the maximum (inclusive) number of levels allowed.
+	limitRow: function(row, levelLimit) {
+		var _this = this;
+		var view = this.view;
+		var rowStruct = this.rowStructs[row];
+		var moreNodes = []; // array of "more" <a> links and <td> DOM nodes
+		var col = 0; // col #
+		var cell;
+		var levelSegs; // array of segment objects in the last allowable level, ordered left-to-right
+		var cellMatrix; // a matrix (by level, then column) of all <td> jQuery elements in the row
+		var limitedNodes; // array of temporarily hidden level <tr> and segment <td> DOM nodes
+		var i, seg;
+		var segsBelow; // array of segment objects below `seg` in the current `col`
+		var totalSegsBelow; // total number of segments below `seg` in any of the columns `seg` occupies
+		var colSegsBelow; // array of segment arrays, below seg, one for each column (offset from segs's first column)
+		var td, rowspan;
+		var segMoreNodes; // array of "more" <td> cells that will stand-in for the current seg's cell
+		var j;
+		var moreTd, moreLink;
+
+		// Iterates through empty level cells and places "more" links inside if need be
+		function emptyCellsUntil(endCol) { // goes from current `col` to `endCol`
+			while (col < endCol) {
+				cell = { row: row, col: col };
+				segsBelow = _this.getCellSegs(cell, levelLimit);
+				if (segsBelow.length) {
+					td = cellMatrix[levelLimit - 1][col];
+					moreLink = _this.renderMoreLink(cell, segsBelow);
+					td.append(moreLink);
+					moreNodes.push(moreLink[0]);
+				}
+				col++;
+			}
+		}
+
+		if (levelLimit && levelLimit < rowStruct.segLevels.length) { // is it actually over the limit?
+			levelSegs = rowStruct.segLevels[levelLimit - 1];
+			cellMatrix = rowStruct.cellMatrix;
+
+			limitedNodes = rowStruct.tbodyEl.children().slice(levelLimit) // get level <tr> 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 <td> for each column the segment occupies. will be one for each colspan
+					for (j = 0; j < colSegsBelow.length; j++) {
+						moreTd = $('<td class="fc-more-cell"/>').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
+						moreTd.append(moreLink);
+						segMoreNodes.push(moreTd[0]);
+						moreNodes.push(moreTd[0]);
+					}
+
+					td.addClass('fc-limited').after($(segMoreNodes)); // hide original <td> 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 <a> 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 $('<a class="fc-more"/>')
+			.text(
+				this.getMoreLinkText(hiddenSegs.length)
+			)
+			.on('click', function() {
+				var date = view.cellToDate(cell);
+				var clickOption = view.opt('eventLimitClick');
+
+				if (typeof clickOption === 'string') { // a view name or 'auto'
+					view.calendar.zoomToDay(date, clickOption);
+				}
+				else {
+					view.trigger(
+						'eventLimitClick',
+						null,
+						date,
+						_this.getCellDayEl(cell), // element for the cell's day
+						hiddenSegs,
+						_this.getCellSegs(cell) // all segments on that day
+					);
+				}
+			});
+	},
+
+
+	// 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 {
+			if (view.opt('isRTL')) {
+				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;
+	}
+
+});

+ 1 - 1
src/common/Grid.js

@@ -62,7 +62,7 @@ $.extend(Grid.prototype, {
 		var _this = this;
 
 		this.el.on('mousedown', function(ev) {
-			if (!$(ev.target).is('.fc-event-container *')) { // not an event element
+			if (!$(ev.target).is('.fc-event-container *, .fc-more')) { // not an event element or "more" link
 				_this.dayMousedown(ev);
 			}
 		});

+ 29 - 13
src/common/print.css

@@ -51,31 +51,47 @@ tbody,
 
 /* don't force a min-height on rows (for DayGrid) */
 .fc tbody .fc-row {
-	min-height: 0 !important;
+	height: auto !important; /* undo height that JS set in distributeHeight */
+	min-height: 0 !important; /* undo the min-height from each view's specific stylesheet */
 }
 
-/* don't have chunky padding underneath events (for Agenda, basicWeek, basicDay) */
 .fc tbody .fc-row .fc-content-skeleton {
+	position: static; /* undo .fc-rigid */
+	/* don't have chunky padding underneath events (for Agenda, basicWeek, basicDay) */
 	padding-bottom: 0 !important;
 }
 
-/* Make space at the bottom by putting a non-breaking space in the last row's cells.
-   :last-child only works with newer browsers */
-.fc tbody .fc-row .fc-content-skeleton tbody tr:last-child td:after {
-	content: "\000A0";
+/* give back the bottom spacing that was taken away from the content-skeleton's padding (above) */
+/* only works on modern browsers */
+.fc tbody .fc-row .fc-content-skeleton table tr:last-child td {
+	padding-bottom: 1px;
 }
 
-/* For IE8, which doesn't understand the above line, give a min-height.
-   For some reason, IE8 really overexaggerates this value.
-   Other browsers will already be taller than this. */
+/* sets a min-height on the event skeleton. for IE8. was overexaggerating this, so make small */
 .fc tbody .fc-row .fc-content-skeleton table {
 	height: 2em;
 }
 
-/* For IE8, the table height might get too tall and create gaps between events and day lines.
-   Vertically centering events within these cells makes it look better */
-.fc-day-grid .fc-event-container {
-	vertical-align: middle !important;
+/* sets a min-height on the event skeleton. for modern browsers (not IE8) */
+.fc tbody .fc-row .fc-content-skeleton table:last-child {
+	height: 4em;
+}
+
+
+/* Undo month-view event limiting. Display all events and hide the "more" links
+--------------------------------------------------------------------------------------------------*/
+
+.fc-more-cell,
+.fc-more {
+	display: none !important;
+}
+
+.fc tr.fc-limited {
+	display: table-row !important;
+}
+
+.fc td.fc-limited {
+	display: table-cell !important;
 }
 
 

+ 2 - 2
src/util.js

@@ -82,7 +82,7 @@ function distributeHeight(els, availableHeight, shouldRedistribute) {
 		var newHeight = minOffset - (naturalOffset - naturalHeight); // subtract the margin/padding
 
 		if (naturalOffset < minOffset) { // we check this again because redistribution might have changed things
-			$(el).css('min-height', newHeight);
+			$(el).height(newHeight);
 		}
 	});
 }
@@ -90,7 +90,7 @@ function distributeHeight(els, availableHeight, shouldRedistribute) {
 
 // Undoes distrubuteHeight, restoring all els to their natural height
 function undistributeHeight(els) {
-	els.css('min-height', '');
+	els.height('');
 }