Просмотр исходного кода

move View to valid-range system

Adam Shaw 9 лет назад
Родитель
Сommit
998dadad0e
9 измененных файлов с 190 добавлено и 68 удалено
  1. 1 1
      src/Calendar.js
  2. 2 2
      src/EventManager.js
  3. 11 4
      src/agenda/AgendaView.js
  4. 17 14
      src/basic/BasicView.js
  5. 9 7
      src/basic/MonthView.js
  6. 4 4
      src/common/Grid.events.js
  7. 113 30
      src/common/View.js
  8. 9 6
      src/list/ListView.js
  9. 24 0
      src/util.js

+ 1 - 1
src/Calendar.js

@@ -797,7 +797,7 @@ function Calendar_constructor(element, overrides) {
 		if (
 			!ignoreWindowResize &&
 			ev.target === window && // so we don't process jqui "resize" events that have bubbled up
-			currentView.start // view has already been rendered
+			currentView.renderStart // view has already been rendered
 		) {
 			if (updateSize(true)) {
 				currentView.publiclyTrigger('windowResize', _element);

+ 2 - 2
src/EventManager.js

@@ -1401,8 +1401,8 @@ Calendar.prototype.expandBusinessHourEvents = function(wholeDay, inputs, ignoreN
 		events.push.apply(events, // append
 			this.expandEvent(
 				this.buildEventFromInput(input),
-				view.start,
-				view.end
+				view.validStart,
+				view.validEnd
 			)
 		);
 	}

+ 11 - 4
src/agenda/AgendaView.js

@@ -58,12 +58,19 @@ var AgendaView = FC.AgendaView = View.extend({
 
 
 	// Sets the display range and computes all necessary dates
-	setRange: function(range) {
-		View.prototype.setRange.call(this, range); // call the super-method
+	setRangeFromDate: function(date) {
+		View.prototype.setRangeFromDate.call(this, date); // call the super-method
+
+		this.timeGrid.setRange({
+			start: this.renderStart,
+			end: this.renderEnd
+		});
 
-		this.timeGrid.setRange(range);
 		if (this.dayGrid) {
-			this.dayGrid.setRange(range);
+			this.dayGrid.setRange({
+				start: this.renderStart,
+				end: this.renderEnd
+			});
 		}
 	},
 

+ 17 - 14
src/basic/BasicView.js

@@ -42,31 +42,34 @@ var BasicView = FC.BasicView = View.extend({
 
 
 	// Sets the display range and computes all necessary dates
-	setRange: function(range) {
-		View.prototype.setRange.call(this, range); // call the super-method
+	setRangeFromDate: function(date) {
+		View.prototype.setRangeFromDate.call(this, date); // call the super-method
 
-		this.dayGrid.breakOnWeeks = /year|month|week/.test(this.intervalUnit); // do before setRange
-		this.dayGrid.setRange(range);
+		this.dayGrid.breakOnWeeks = /year|month|week/.test(this.intervalUnit); // do before Grid::setRange
+		this.dayGrid.setRange({
+			start: this.renderStart,
+			end: this.renderEnd
+		});
 	},
 
 
-	// Compute the value to feed into setRange. Overrides superclass.
-	computeRange: function(date) {
-		var range = View.prototype.computeRange.call(this, date); // get value from the super-method
+	// Computes the date range that will be rendered.
+	computeRenderRange: function(intervalRange) {
+		var renderRange = View.prototype.computeRenderRange.call(this, intervalRange);
 
 		// year and month views should be aligned with weeks. this is already done for week
-		if (/year|month/.test(range.intervalUnit)) {
-			range.start.startOf('week');
-			range.start = this.skipHiddenDays(range.start);
+		if (/^(year|month)$/.test(this.intervalUnit)) {
+			renderRange.start.startOf('week');
 
 			// make end-of-week if not already
-			if (range.end.weekday()) {
-				range.end.add(1, 'week').startOf('week');
-				range.end = this.skipHiddenDays(range.end, -1, true); // exclusively move backwards
+			if (renderRange.end.weekday()) {
+				renderRange.end.add(1, 'week').startOf('week'); // exclusively move backwards
 			}
+
+			renderRange = this.sanitizeRenderRange(renderRange); // resanitize
 		}
 
-		return range;
+		return renderRange;
 	},
 
 

+ 9 - 7
src/basic/MonthView.js

@@ -4,18 +4,20 @@
 
 var MonthView = FC.MonthView = BasicView.extend({
 
-	// Produces information about what range to display
-	computeRange: function(date) {
-		var range = BasicView.prototype.computeRange.call(this, date); // get value from super-method
-		var rowCnt;
+
+	// Computes the date range that will be rendered.
+	computeRenderRange: function(intervalRange) {
+		var renderRange = BasicView.prototype.computeRenderRange.call(this, intervalRange);
 
 		// ensure 6 weeks
 		if (this.isFixedWeeks()) {
-			rowCnt = Math.ceil(range.end.diff(range.start, 'weeks', true)); // could be partial weeks due to hiddenDays
-			range.end.add(6 - rowCnt, 'weeks');
+			rowCnt = Math.ceil( // could be partial weeks due to hiddenDays
+				renderRange.end.diff(renderRange.start, 'weeks', true) // dontRound=true
+			);
+			renderRange.end.add(6 - rowCnt, 'weeks');
 		}
 
-		return range;
+		return renderRange;
 	},
 
 

+ 4 - 4
src/common/Grid.events.js

@@ -208,8 +208,8 @@ Grid.mixin({
 		if (!events.length && businessHours) {
 			events = [
 				$.extend({}, BUSINESS_HOUR_EVENT_DEFAULTS, {
-					start: this.view.end, // guaranteed out-of-range
-					end: this.view.end,   // "
+					start: this.view.validEnd, // guaranteed out-of-range
+					end: this.view.validEnd,   // "
 					dow: null
 				})
 			];
@@ -1124,8 +1124,8 @@ Grid.mixin({
 	// SIDE EFFECT: will mutate the given array and will use its date references.
 	invertRanges: function(ranges) {
 		var view = this.view;
-		var viewStart = view.start.clone(); // need a copy
-		var viewEnd = view.end.clone(); // need a copy
+		var viewStart = view.validStart.clone(); // need a copy
+		var viewEnd = view.validEnd.clone(); // need a copy
 		var inverseRanges = [];
 		var start = viewStart; // the end of the previous range. the start of the new range
 		var i, range;

+ 113 - 30
src/common/View.js

@@ -21,10 +21,6 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
 	isEventsRendered: false,
 	eventRenderQueue: null,
 
-	// range the view is actually displaying (moments)
-	start: null,
-	end: null, // exclusive
-
 	// range the view is formally responsible for (moments)
 	// may be different from start/end. for example, a month view might have 1st-31st, excluding padded dates
 	intervalStart: null,
@@ -32,6 +28,30 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
 	intervalDuration: null,
 	intervalUnit: null, // name of largest unit being displayed, like "month" or "week"
 
+	// date range with a rendered skeleton
+	// includes not-active days that need some sort of DOM
+	renderStart: null,
+	renderEnd: null,
+
+	// active dates that display events and accept drag-nd-drop
+	validStart: null,
+	validEnd: null,
+
+	// DEPRECATED: use validStart/validEnd instead
+	start: null,
+	end: null,
+
+	// date constraints
+	// TODO: render populate and use in rendering
+	// TODO: enforce this in prev/next/gotoDate
+	minDate: null,
+	maxDate: null,
+
+	// for dates that are outside of minDate/maxDate
+	// true = not rendered at all
+	// false = rendered, but disabled
+	isOutOfRangeHidden: false,
+
 	isRTL: false,
 	isSelected: false, // boolean whether a range of time is user-selected or not
 	selectedEvent: null,
@@ -60,7 +80,9 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
 		this.calendar = calendar;
 		this.type = this.name = type; // .name is deprecated
 		this.options = options;
+
 		this.intervalDuration = intervalDuration || moment.duration(1, 'day');
+		this.intervalUnit = computeIntervalUnit(this.intervalDuration);
 
 		this.nextDayThreshold = moment.duration(this.opt('nextDayThreshold'));
 		this.initThemingProps();
@@ -130,22 +152,33 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
 
 
 	// Updates all internal dates for displaying the given unzoned range.
-	setRange: function(range) {
-		$.extend(this, range); // assigns every property to this object's member variables
+	setRangeFromDate: function(date) {
+		var intervalRange = this.computeIntervalRange(date);
+		var renderRange = this.computeRenderRange(intervalRange);
+		var validRange = this.computeValidRange(renderRange);
+
+		this.intervalStart = intervalRange.start;
+		this.intervalEnd = intervalRange.end;
+		this.renderStart = renderRange.start;
+		this.renderEnd = renderRange.end;
+		this.validStart = validRange.start;
+		this.validEnd = validRange.end;
+
+		// DEPRECATED, but we need to keep it updated
+		// TODO: run automated tests with this commented out
+		this.start = this.validStart;
+		this.end = this.validEnd;
+
 		this.updateTitle();
 	},
 
 
-	// Given a single current unzoned date, produce information about what range to display.
-	// Subclasses can override. Must return all properties.
-	computeRange: function(date) {
-		var intervalUnit = computeIntervalUnit(this.intervalDuration);
-		var intervalStart = date.clone().startOf(intervalUnit);
+	computeIntervalRange: function(date) {
+		var intervalStart = date.clone().startOf(this.intervalUnit);
 		var intervalEnd = intervalStart.clone().add(this.intervalDuration);
-		var start, end;
 
 		// normalize the range's time-ambiguity
-		if (/year|month|week|day/.test(intervalUnit)) { // whole-days?
+		if (/^(year|month|week|day)$/.test(this.intervalUnit)) { // whole-days?
 			intervalStart.stripTime();
 			intervalEnd.stripTime();
 		}
@@ -158,18 +191,39 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
 			}
 		}
 
-		start = intervalStart.clone();
-		start = this.skipHiddenDays(start);
-		end = intervalEnd.clone();
-		end = this.skipHiddenDays(end, -1, true); // exclusively move backwards
+		return { start: intervalStart, end: intervalEnd };
+	},
 
-		return {
-			intervalUnit: intervalUnit,
-			intervalStart: intervalStart,
-			intervalEnd: intervalEnd,
-			start: start,
-			end: end
-		};
+
+	// Computes the date range that will be rendered.
+	computeRenderRange: function(intervalRange) {
+		return this.sanitizeRenderRange(
+			cloneRange(intervalRange)
+		);
+	},
+
+
+	sanitizeRenderRange: function(renderRange) {
+		renderRange = this.trimHiddenDays(renderRange);
+
+		if (this.isOutOfRangeHidden) {
+			renderRange = this.intersectValidRange(renderRange);
+		}
+
+		return renderRange;
+	},
+
+
+	// Computes the date range that will be fully visible (not greyed out),
+	// and that will contain events and allow drag-n-drop.
+	computeValidRange: function(renderRange) {
+		var validRange = cloneRange(renderRange);
+
+		// probably already done in sanitizeRenderRange,
+		// but do again in case subclass added special behavior to computeRenderRange
+		validRange = this.intersectValidRange(validRange);
+
+		return validRange;
 	},
 
 
@@ -204,6 +258,28 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
 	},
 
 
+	trimHiddenDays: function(inputRange) {
+		return {
+			start: this.skipHiddenDays(inputRange.start),
+			end: this.skipHiddenDays(inputRange.end, -1, true) // exclusively move backwards
+		};
+	},
+
+
+	intersectValidRange: function(inputRange) {
+		var range = cloneRange(inputRange);
+
+		if (this.minDate) {
+			range.start = maxMoment(range.start, this.minDate);
+		}
+		if (this.maxDate) {
+			range.end = minMoment(range.end, this.maxDate);
+		}
+
+		return range;
+	},
+
+
 	/* Title and Date Formatting
 	------------------------------------------------------------------------------------------------------------------*/
 
@@ -220,13 +296,13 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
 		var start, end;
 
 		// for views that span a large unit of time, show the proper interval, ignoring stray days before and after
-		if (this.intervalUnit === 'year' || this.intervalUnit === 'month') {
+		if (/^(year|month)$/.test(this.intervalUnit)) {
 			start = this.intervalStart;
 			end = this.intervalEnd;
 		}
 		else { // for day units or smaller, use the actual day range
-			start = this.start;
-			end = this.end;
+			start = this.validStart;
+			end = this.validEnd;
 		}
 
 		return this.formatRange(
@@ -400,7 +476,7 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
 
 		this.unbindEvents(); // will do nothing if not already bound
 		this.requestDateRender(date).then(function() {
-			// wish we could start earlier, but setRange/computeRange needs to execute first
+			// wish we could start earlier, but setRangeFromDate needs to execute first
 			_this.bindEvents(); // will request events
 		});
 	},
@@ -456,7 +532,7 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
 		return this.executeDateUnrender().then(function() {
 
 			if (date) {
-				_this.setRange(_this.computeRange(date));
+				_this.setRangeFromDate(date);
 			}
 
 			if (_this.render) {
@@ -1028,7 +1104,7 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
 
 
 	requestEvents: function() {
-		return this.calendar.requestEvents(this.start, this.end);
+		return this.calendar.requestEvents(this.validStart, this.validEnd);
 	},
 
 
@@ -1439,7 +1515,14 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
 	},
 
 
+	// Computes if a renderable date should be displayed as disabled because it's out of range
+	isDisabledDay: function(date) {
+		return date < this.validStart || date >= this.validEnd;
+	},
+
+
 	// Incrementing the current day until it is no longer a hidden day, returning a copy.
+	// DOES NOT CONSIDER minDate/maxDate RANGE!
 	// If the initial value of `date` is not a hidden day, don't do anything.
 	// Pass `isExclusive` as `true` if you are dealing with an end date.
 	// `inc` defaults to `1` (increment one day forward each time)

+ 9 - 6
src/list/ListView.js

@@ -15,10 +15,13 @@ var ListView = View.extend({
 		});
 	},
 
-	setRange: function(range) {
-		View.prototype.setRange.call(this, range); // super
+	setRangeFromDate: function(date) {
+		View.prototype.setRangeFromDate.call(this, date); // super
 
-		this.grid.setRange(range); // needs to process range-related options
+		this.grid.setRange({ // needs to process range-related options
+			start: this.renderStart,
+			end: this.renderEnd
+		});
 	},
 
 	renderSkeleton: function() {
@@ -76,12 +79,12 @@ var ListViewGrid = Grid.extend({
 	// slices by day
 	spanToSegs: function(span) {
 		var view = this.view;
-		var dayStart = view.start.clone().time(0); // timed, so segs get times!
+		var dayStart = view.renderStart.clone().time(0); // timed, so segs get times!
 		var dayIndex = 0;
 		var seg;
 		var segs = [];
 
-		while (dayStart < view.end) {
+		while (dayStart < view.renderEnd) {
 
 			seg = intersectRanges(span, {
 				start: dayStart,
@@ -173,7 +176,7 @@ var ListViewGrid = Grid.extend({
 
 				// append a day header
 				tbodyEl.append(this.dayHeaderHtml(
-					this.view.start.clone().add(dayIndex, 'days')
+					this.view.renderStart.clone().add(dayIndex, 'days')
 				));
 
 				this.sortEventSegs(daySegs);

+ 24 - 0
src/util.js

@@ -669,6 +669,30 @@ function multiplyDuration(dur, n) {
 }
 
 
+function cloneRange(range) {
+	return {
+		start: range.start.clone(),
+		end: range.end.clone()
+	};
+}
+
+
+function minMoment(mom1, mom2) {
+	if (mom1.isBefore(mom2)) {
+		return mom1;
+	}
+	return mom2;
+}
+
+
+function maxMoment(mom1, mom2) {
+	if (mom1.isAfter(mom2)) {
+		return mom1;
+	}
+	return mom2;
+}
+
+
 // Returns a boolean about whether the given duration has any time parts (hours/minutes/seconds/ms)
 function durationHasTime(dur) {
 	return Boolean(dur.hours() || dur.minutes() || dur.seconds() || dur.milliseconds());