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

wip for disableNonCurrentDates

Adam Shaw 9 лет назад
Родитель
Сommit
fe46925e1a
6 измененных файлов с 229 добавлено и 66 удалено
  1. 15 4
      src/common/DayGrid.js
  2. 153 35
      src/common/Grid.events.js
  3. 27 18
      src/common/Grid.js
  4. 8 3
      src/common/TimeGrid.js
  5. 15 6
      src/common/View.js
  6. 11 0
      src/common/common.css

+ 15 - 4
src/common/DayGrid.js

@@ -140,10 +140,11 @@ var DayGrid = FC.DayGrid = Grid.extend(DayTableMixin, {
 	// The number row will only exist if either day numbers or week numbers are turned on.
 	renderNumberCellHtml: function(date) {
 		var html = '';
+		var isDayNumberVisible = this.view.dayNumbersVisible && !this.view.isDisabledDate(date);
 		var classes;
 		var weekCalcFirstDoW;
 
-		if (!this.view.dayNumbersVisible && !this.view.cellWeekNumbersVisible) {
+		if (!isDayNumberVisible && !this.view.cellWeekNumbersVisible) {
 			// no numbers in day cell (week number must be along the side)
 			return '<td/>'; //  will create an empty space above events :(
 		}
@@ -175,7 +176,7 @@ var DayGrid = FC.DayGrid = Grid.extend(DayTableMixin, {
 			);
 		}
 
-		if (this.view.dayNumbersVisible) {
+		if (isDayNumberVisible) {
 			html += this.view.buildGotoAnchorHtml(
 				date,
 				{ 'class': 'fc-day-number' },
@@ -305,9 +306,13 @@ var DayGrid = FC.DayGrid = Grid.extend(DayTableMixin, {
 	// Renders a visual indication of an event or external element being dragged.
 	// `eventLocation` has zoned start and end (optional)
 	renderDrag: function(eventLocation, seg) {
+		var eventSpans = this.eventToSpans(eventLocation);
+		var i;
 
 		// always render a highlight underneath
-		this.renderHighlight(this.eventToSpan(eventLocation));
+		for (i = 0; i < eventSpans.length; i++) {
+			this.renderHighlight(eventSpans[i]);
+		}
 
 		// if a segment from the same calendar but another component is being dragged, render a helper event
 		if (seg && seg.component !== this) {
@@ -329,7 +334,13 @@ var DayGrid = FC.DayGrid = Grid.extend(DayTableMixin, {
 
 	// Renders a visual indication of an event being resized
 	renderEventResize: function(eventLocation, seg) {
-		this.renderHighlight(this.eventToSpan(eventLocation));
+		var eventSpans = this.eventToSpans(eventLocation);
+		var i;
+
+		for (i = 0; i < eventSpans.length; i++) {
+			this.renderHighlight(eventSpans[i]);
+		}
+
 		return this.renderEventLocationHelper(eventLocation, seg); // returns mock event elements
 	},
 

+ 153 - 35
src/common/Grid.events.js

@@ -357,6 +357,78 @@ Grid.mixin({
 	------------------------------------------------------------------------------------------------------------------*/
 
 
+
+	/*
+
+	TODO: cursor not disabled when dragging?
+	TODO: should multi-day event be allowed to drag into non-current-month range?
+		> YES ... might be hard. still needs to respect minDate/maxDate
+		> ... need to come up with a different system?
+
+	event - { title, id, start, (end) }
+	location? - { start, (end), allDay } **
+	unsafeEventRange - { start, end, allDay }
+	eventRange - { start, end, allDay, isStart, isEnd }
+	unsafeEventSpan - { start, end, allDay, whatever }
+	eventSpan - { start, end, allDay, isStart, isEnd, event, whatever } ***
+	eventSeg - { whatever, event }
+	seg - { whatever }
+
+	*/
+	isEventLocationValid: function(eventLocation, event) {
+		var calendar = this.view.calendar;
+		var rawEventRange = this.eventToRawEventRange(eventLocation); // always returns some value
+
+		if (!this.view.isValidRange(rawEventRange)) {
+			return false;
+		}
+
+		var eventRange = this.refineRawEventRange(rawEventRange);
+
+		if (!eventRange) {
+			return false;
+		}
+
+		var eventSpans = this.eventRangeToSpans(eventRange);
+		var i;
+
+		for (i = 0; i < eventSpans.length; i++) {
+			if (!calendar.isEventSpanAllowed(eventSpans[i], event)) {
+				return false;
+			}
+		}
+
+		return true;
+	},
+
+	isExternalLocationValid: function(eventLocation, metaProps) { // FOR the external element
+		var calendar = this.view.calendar;
+		var rawEventRange = this.eventToRawEventRange(eventLocation); // always returns some value
+
+		if (!this.view.isValidRange(rawEventRange)) {
+			return false;
+		}
+
+		var eventRange = this.refineRawEventRange(rawEventRange);
+
+		if (!eventRange) {
+			return false;
+		}
+
+		var eventSpans = this.eventRangeToSpans(eventRange);
+		var i;
+
+		for (i = 0; i < eventSpans.length; i++) {
+			if (!calendar.isExternalSpanAllowed(eventSpans[i], eventLocation, metaProps)) {
+				return false;
+			}
+		}
+
+		return true;
+	},
+
+
+
 	// Builds a listener that will track user-dragging on an event segment.
 	// Generic enough to work with any type of Grid.
 	// Has side effect of setting/unsetting `segDragListener`
@@ -418,7 +490,7 @@ Grid.mixin({
 					event
 				);
 
-				if (dropLocation && !calendar.isEventSpanAllowed(_this.eventToSpan(dropLocation), event)) {
+				if (dropLocation && !_this.isEventLocationValid(dropLocation, event)) {
 					disableCursor();
 					dropLocation = null;
 				}
@@ -612,15 +684,13 @@ Grid.mixin({
 				_this.isDraggingExternal = true;
 			},
 			hitOver: function(hit) {
+
 				dropLocation = _this.computeExternalDrop(
 					hit.component.getHitSpan(hit), // since we are querying the parent view, might not belong to this grid
 					meta
 				);
 
-				if ( // invalid hit?
-					dropLocation &&
-					!calendar.isExternalSpanAllowed(_this.eventToSpan(dropLocation), dropLocation, meta.eventProps)
-				) {
+				if (dropLocation && !this.isExternalLocationValid(dropLocation, meta.eventProps)) {
 					disableCursor();
 					dropLocation = null;
 				}
@@ -731,7 +801,7 @@ Grid.mixin({
 					_this.computeEventEndResize(origHitSpan, hitSpan, event);
 
 				if (resizeLocation) {
-					if (!calendar.isEventSpanAllowed(_this.eventToSpan(resizeLocation), event)) {
+					if (!_this.isEventLocationValid(resizeLocation, event)) {
 						disableCursor();
 						resizeLocation = null;
 					}
@@ -1002,17 +1072,18 @@ Grid.mixin({
 	},
 
 
-	eventToSpan: function(event) {
-		return this.eventToSpans(event)[0];
-	},
-
-
 	// Generates spans (always unzoned) for the given event.
 	// Does not do any inverting for inverse-background events.
 	// Can accept an event "location" as well (which only has start/end and no allDay)
 	eventToSpans: function(event) {
-		var range = this.eventToRange(event);
-		return this.eventRangeToSpans(range, event);
+		var eventRange = this.eventToRange(event); // { start, end, isStart, isEnd }
+
+		if (eventRange) {
+			return this.eventRangeToSpans(eventRange, event);
+		}
+		else { // out of view's valid range
+			return [];
+		}
 	},
 
 
@@ -1026,27 +1097,34 @@ Grid.mixin({
 		var segs = [];
 
 		$.each(eventsById, function(id, events) {
-			var ranges = [];
+			var eventRanges = [];
+			var eventRange; // { start, end, isStart, isEnd }
 			var i;
 
 			for (i = 0; i < events.length; i++) {
-				ranges.push(_this.eventToRange(events[i]));
+				eventRange = _this.eventToRange(events[i]);
+
+				if (eventRange) { // otherwise, out of view's valid range
+					eventRanges.push(eventRange);
+				}
 			}
 
 			// inverse-background events (utilize only the first event in calculations)
 			if (isInverseBgEvent(events[0])) {
-				ranges = _this.invertRanges(ranges);
+				eventRanges = _this.invertRanges(eventRanges); // will lose isStart/isEnd
 
-				for (i = 0; i < ranges.length; i++) {
+				for (i = 0; i < eventRanges.length; i++) {
 					segs.push.apply(segs, // append to
-						_this.eventRangeToSegs(ranges[i], events[0], segSliceFunc));
+						_this.eventRangeToSegs(eventRanges[i], events[0], segSliceFunc)
+					);
 				}
 			}
 			// normal event ranges
 			else {
-				for (i = 0; i < ranges.length; i++) {
+				for (i = 0; i < eventRanges.length; i++) {
 					segs.push.apply(segs, // append to
-						_this.eventRangeToSegs(ranges[i], events[i], segSliceFunc));
+						_this.eventRangeToSegs(eventRanges[i], events[i], segSliceFunc)
+					);
 				}
 			}
 		});
@@ -1057,7 +1135,37 @@ Grid.mixin({
 
 	// Generates the unzoned start/end dates an event appears to occupy
 	// Can accept an event "location" as well (which only has start/end and no allDay)
+	// returns { start, end, isStart, isEnd }
+	// If the event is completely outside of the grid's valid range, will return undefined.
 	eventToRange: function(event) {
+		return this.refineRawEventRange(
+			this.eventToRawEventRange(event)
+		);
+	},
+
+
+	refineRawEventRange: function(rawRange) {
+		var view = this.view;
+		var calendar = view.calendar;
+		var range = intersectRanges(rawRange, {
+			start: view.validStart,
+			end: view.validEnd
+		});
+
+		if (range) { // otherwise, event doesn't have valid range
+
+			// hack: dynamic locale change forgets to upate stored event localed
+			calendar.localizeMoment(range.start);
+			calendar.localizeMoment(range.end);
+
+			return range;
+		}
+	},
+
+
+	// not constrained to valid dates
+	// not given localizeMoment hack
+	eventToRawEventRange: function(event) {
 		var calendar = this.view.calendar;
 		var start = event.start.clone().stripZone();
 		var end = (
@@ -1072,48 +1180,58 @@ Grid.mixin({
 					)
 			).stripZone();
 
-		// hack: dynamic locale change forgets to upate stored event localed
-		calendar.localizeMoment(start);
-		calendar.localizeMoment(end);
-
 		return { start: start, end: end };
 	},
 
 
 	// Given an event's range (unzoned start/end), and the event itself,
 	// slice into segments (using the segSliceFunc function if specified)
-	eventRangeToSegs: function(range, event, segSliceFunc) {
-		var spans = this.eventRangeToSpans(range, event);
+	// eventRange - { start, end, isStart, isEnd }
+	eventRangeToSegs: function(eventRange, event, segSliceFunc) {
+		var eventSpans = this.eventRangeToSpans(eventRange, event);
 		var segs = [];
 		var i;
 
-		for (i = 0; i < spans.length; i++) {
+		for (i = 0; i < eventSpans.length; i++) {
 			segs.push.apply(segs, // append to
-				this.eventSpanToSegs(spans[i], event, segSliceFunc));
+				this.eventSpanToSegs(eventSpans[i], event, segSliceFunc)
+			);
 		}
 
 		return segs;
 	},
 
 
-	// Given an event's unzoned date range, return an array of "span" objects.
+	// Given an event's unzoned date range, return an array of eventSpan objects.
+	// eventSpan - { start, end, isStart, isEnd, otherthings... }
 	// Subclasses can override.
-	eventRangeToSpans: function(range, event) {
-		return [ $.extend({}, range) ]; // copy into a single-item array
+	// Subclasses are obligated to forward eventRange.isStart/isEnd to the resulting spans.
+	eventRangeToSpans: function(eventRange, event) {
+		return [ $.extend({}, eventRange) ]; // copy into a single-item array
 	},
 
 
 	// Given an event's span (unzoned start/end and other misc data), and the event itself,
 	// slices into segments and attaches event-derived properties to them.
-	eventSpanToSegs: function(span, event, segSliceFunc) {
-		var segs = segSliceFunc ? segSliceFunc(span) : this.spanToSegs(span);
+	// eventSpan - { start, end, isStart, isEnd, otherthings... }
+	eventSpanToSegs: function(eventSpan, event, segSliceFunc) {
+		var segs = segSliceFunc ? segSliceFunc(eventSpan) : this.spanToSegs(eventSpan);
 		var i, seg;
 
 		for (i = 0; i < segs.length; i++) {
 			seg = segs[i];
+
+			// the eventSpan's isStart/isEnd takes precedence over the seg's
+			if (!eventSpan.isStart) {
+				seg.isStart = false;
+			}
+			if (!eventSpan.isEnd) {
+				seg.isEnd = false;
+			}
+
 			seg.event = event;
-			seg.eventStartMS = +span.start; // TODO: not the best name after making spans unzoned
-			seg.eventDurationMS = span.end - span.start;
+			seg.eventStartMS = +eventSpan.start; // TODO: not the best name after making spans unzoned
+			seg.eventDurationMS = eventSpan.end - eventSpan.start;
 		}
 
 		return segs;

+ 27 - 18
src/common/Grid.js

@@ -670,28 +670,37 @@ var Grid = FC.Grid = Class.extend(ListenerMixin, {
 	// Computes HTML classNames for a single-day element
 	getDayClasses: function(date, noThemeHighlight) {
 		var view = this.view;
-		var today = view.calendar.getNow();
-		var classes = [ 'fc-' + dayIDs[date.day()] ];
-
-		if (
-			view.intervalDuration.as('months') == 1 &&
-			date.month() != view.intervalStart.month()
-		) {
-			classes.push('fc-other-month');
+		var classes = [];
+		var today;
+
+		if (view.isDisabledDate(date)) {
+			classes.push('fc-disabled-day'); // TODO: jQuery UI theme?
 		}
+		else {
+			classes.push('fc-' + dayIDs[date.day()]);
+
+			if (
+				view.intervalDuration.as('months') == 1 &&
+				date.month() != view.intervalStart.month()
+			) {
+				classes.push('fc-other-month');
+			}
+
+			today = view.calendar.getNow()
 
-		if (date.isSame(today, 'day')) {
-			classes.push('fc-today');
+			if (date.isSame(today, 'day')) {
+				classes.push('fc-today');
 
-			if (noThemeHighlight !== true) {
-				classes.push(view.highlightStateClass);
+				if (noThemeHighlight !== true) {
+					classes.push(view.highlightStateClass);
+				}
+			}
+			else if (date < today) {
+				classes.push('fc-past');
+			}
+			else {
+				classes.push('fc-future');
 			}
-		}
-		else if (date < today) {
-			classes.push('fc-past');
-		}
-		else {
-			classes.push('fc-future');
 		}
 
 		return classes;

+ 8 - 3
src/common/TimeGrid.js

@@ -367,6 +367,8 @@ var TimeGrid = FC.TimeGrid = Grid.extend(DayTableMixin, {
 	// Renders a visual indication of an event being dragged over the specified date(s).
 	// A returned value of `true` signals that a mock "helper" event has been rendered.
 	renderDrag: function(eventLocation, seg) {
+		var eventSpans;
+		var i;
 
 		if (seg) { // if there is event information for this drag, render a helper event
 
@@ -374,9 +376,12 @@ var TimeGrid = FC.TimeGrid = Grid.extend(DayTableMixin, {
 			// signal that a helper has been rendered
 			return this.renderEventLocationHelper(eventLocation, seg);
 		}
-		else {
-			// otherwise, just render a highlight
-			this.renderHighlight(this.eventToSpan(eventLocation));
+		else { // otherwise, just render a highlight
+			eventSpans = this.eventToSpans(eventLocation);
+
+			for (i = 0; i < eventSpans.length; i++) {
+				this.renderHighlight(eventSpans[i]);
+			}
 		}
 	},
 

+ 15 - 6
src/common/View.js

@@ -155,7 +155,7 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
 	setRangeFromDate: function(date) {
 		var intervalRange = this.computeIntervalRange(date);
 		var renderRange = this.computeRenderRange(intervalRange);
-		var validRange = this.computeValidRange(renderRange);
+		var validRange = this.computeValidRange(renderRange, intervalRange);
 
 		this.intervalStart = intervalRange.start;
 		this.intervalEnd = intervalRange.end;
@@ -207,7 +207,7 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
 		renderRange = this.trimHiddenDays(renderRange);
 
 		if (this.isOutOfRangeHidden) {
-			renderRange = this.intersectValidRange(renderRange);
+			renderRange = this.transformWithMinMaxDate(renderRange);
 		}
 
 		return renderRange;
@@ -216,12 +216,16 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
 
 	// 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) {
+	computeValidRange: function(renderRange, intervalRange) {
 		var validRange = cloneRange(renderRange);
 
+		if (this.opt('disableNonCurrentDates')) {
+			validRange = intersectRanges(validRange, intervalRange);
+		}
+
 		// probably already done in sanitizeRenderRange,
 		// but do again in case subclass added special behavior to computeRenderRange
-		validRange = this.intersectValidRange(validRange);
+		validRange = this.transformWithMinMaxDate(validRange);
 
 		return validRange;
 	},
@@ -266,7 +270,7 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
 	},
 
 
-	intersectValidRange: function(inputRange) {
+	transformWithMinMaxDate: function(inputRange) {
 		var range = cloneRange(inputRange);
 
 		if (this.minDate) {
@@ -1516,11 +1520,16 @@ 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) {
+	isDisabledDate: function(date) { // TODO: rename
 		return date < this.validStart || date >= this.validEnd;
 	},
 
 
+	isValidRange: function(range) {
+		return range.start >= this.validStart && range.end <= this.validEnd; // TODO: date util. also, what about timezone?
+	},
+
+
 	// 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.

+ 11 - 0
src/common/common.css

@@ -68,6 +68,17 @@ body .fc { /* extra precedence to overcome jqui */
 	background: #d7d7d7;
 }
 
+.fc-disabled-day {
+	background: #d7d7d7;
+	opacity: .3;
+}
+
+.fc td.fc-disabled-day { /* needed for precedence too */
+	border-left-style: none;
+	border-right-style: none;
+	/* is this specific to basic view? */
+}
+
 
 /* Icons (inline elements with styled text that mock arrow icons)
 --------------------------------------------------------------------------------------------------*/