aboutsummaryrefslogtreecommitdiffstats
path: root/library/fullcalendar/fullcalendar.js
diff options
context:
space:
mode:
Diffstat (limited to 'library/fullcalendar/fullcalendar.js')
-rw-r--r--library/fullcalendar/fullcalendar.js1726
1 files changed, 1080 insertions, 646 deletions
diff --git a/library/fullcalendar/fullcalendar.js b/library/fullcalendar/fullcalendar.js
index 4d17f353e..d6042cc17 100644
--- a/library/fullcalendar/fullcalendar.js
+++ b/library/fullcalendar/fullcalendar.js
@@ -1,5 +1,5 @@
/*!
- * FullCalendar v2.5.0-beta
+ * FullCalendar v2.6.1
* Docs & License: http://fullcalendar.io/
* (c) 2015 Adam Shaw
*/
@@ -18,7 +18,10 @@
;;
-var FC = $.fullCalendar = { version: "2.5.0-beta" };
+var FC = $.fullCalendar = {
+ version: "2.6.1",
+ internalApiVersion: 3
+};
var fcViews = FC.views = {};
@@ -121,7 +124,7 @@ function massageOverrides(input) {
;;
// exports
-FC.intersectionToSeg = intersectionToSeg;
+FC.intersectRanges = intersectRanges;
FC.applyAll = applyAll;
FC.debounce = debounce;
FC.isInt = isInt;
@@ -244,7 +247,7 @@ function undistributeHeight(els) {
function matchCellWidths(els) {
var maxInnerWidth = 0;
- els.find('> *').each(function(i, innerEl) {
+ els.find('> span').each(function(i, innerEl) {
var innerWidth = $(innerEl).outerWidth();
if (innerWidth > maxInnerWidth) {
maxInnerWidth = innerWidth;
@@ -553,10 +556,10 @@ function flexibleCompare(a, b) {
----------------------------------------------------------------------------------------------------------------------*/
-// Creates a basic segment with the intersection of the two ranges. Returns undefined if no intersection.
+// Computes the intersection of the two ranges. Returns undefined if no intersection.
// Expects all dates to be normalized to the same timezone beforehand.
// TODO: move to date section?
-function intersectionToSeg(subjectRange, constraintRange) {
+function intersectRanges(subjectRange, constraintRange) {
var subjectStart = subjectRange.start;
var subjectEnd = subjectRange.end;
var constraintStart = constraintRange.start;
@@ -1580,6 +1583,8 @@ FC.formatRange = formatRange; // expose
function formatRangeWithChunks(date1, date2, chunks, separator, isRTL) {
+ var unzonedDate1 = date1.clone().stripZone(); // for formatSimilarChunk
+ var unzonedDate2 = date2.clone().stripZone(); // "
var chunkStr; // the rendering of the chunk
var leftI;
var leftStr = '';
@@ -1593,7 +1598,7 @@ function formatRangeWithChunks(date1, date2, chunks, separator, isRTL) {
// Start at the leftmost side of the formatting string and continue until you hit a token
// that is not the same between dates.
for (leftI=0; leftI<chunks.length; leftI++) {
- chunkStr = formatSimilarChunk(date1, date2, chunks[leftI]);
+ chunkStr = formatSimilarChunk(date1, date2, unzonedDate1, unzonedDate2, chunks[leftI]);
if (chunkStr === false) {
break;
}
@@ -1602,7 +1607,7 @@ function formatRangeWithChunks(date1, date2, chunks, separator, isRTL) {
// Similarly, start at the rightmost side of the formatting string and move left
for (rightI=chunks.length-1; rightI>leftI; rightI--) {
- chunkStr = formatSimilarChunk(date1, date2, chunks[rightI]);
+ chunkStr = formatSimilarChunk(date1, date2, unzonedDate1, unzonedDate2, chunks[rightI]);
if (chunkStr === false) {
break;
}
@@ -1649,7 +1654,7 @@ var similarUnitMap = {
// Given a formatting chunk, and given that both dates are similar in the regard the
// formatting chunk is concerned, format date1 against `chunk`. Otherwise, return `false`.
-function formatSimilarChunk(date1, date2, chunk) {
+function formatSimilarChunk(date1, date2, unzonedDate1, unzonedDate2, chunk) {
var token;
var unit;
@@ -1658,8 +1663,10 @@ function formatSimilarChunk(date1, date2, chunk) {
}
else if ((token = chunk.token)) {
unit = similarUnitMap[token.charAt(0)];
+
// are the dates the same for this unit of measurement?
- if (unit && date1.isSame(date2, unit)) {
+ // use the unzoned dates for this calculation because unreliable when near DST (bug #2396)
+ if (unit && unzonedDate1.isSame(unzonedDate2, unit)) {
return oldMomentFormat(date1, token); // would be the same if we used `date2`
// BTW, don't support custom tokens
}
@@ -2008,6 +2015,7 @@ options:
var CoordCache = FC.CoordCache = Class.extend({
els: null, // jQuery set (assumed to be siblings)
+ forcedOffsetParentEl: null, // options can override the natural offsetParent
origin: null, // {left,top} position of offsetParent of els
boundingRect: null, // constrain cordinates to this rectangle. {left,right,top,bottom} or null
isHorizontal: false, // whether to query for left/right/width
@@ -2024,13 +2032,16 @@ var CoordCache = FC.CoordCache = Class.extend({
this.els = $(options.els);
this.isHorizontal = options.isHorizontal;
this.isVertical = options.isVertical;
+ this.forcedOffsetParentEl = options.offsetParent ? $(options.offsetParent) : null;
},
// Queries the els for coordinates and stores them.
// Call this method before using and of the get* methods below.
build: function() {
- this.origin = this.els.eq(0).offsetParent().offset();
+ var offsetParentEl = this.forcedOffsetParentEl || this.els.eq(0).offsetParent();
+
+ this.origin = offsetParentEl.offset();
this.boundingRect = this.queryBoundingRect();
if (this.isHorizontal) {
@@ -2053,6 +2064,14 @@ var CoordCache = FC.CoordCache = Class.extend({
},
+ // When called, if coord caches aren't built, builds them
+ ensureBuilt: function() {
+ if (!this.origin) {
+ this.build();
+ }
+ },
+
+
// Compute and return what the elements' bounding rectangle is, from the user's perspective.
// Right now, only returns a rectangle if constrained by an overflow:scroll element.
queryBoundingRect: function() {
@@ -2105,6 +2124,8 @@ var CoordCache = FC.CoordCache = Class.extend({
// Given a left offset (from document left), returns the index of the el that it horizontally intersects.
// If no intersection is made, or outside of the boundingRect, returns undefined.
getHorizontalIndex: function(leftOffset) {
+ this.ensureBuilt();
+
var boundingRect = this.boundingRect;
var lefts = this.lefts;
var rights = this.rights;
@@ -2124,6 +2145,8 @@ var CoordCache = FC.CoordCache = Class.extend({
// Given a top offset (from document top), returns the index of the el that it vertically intersects.
// If no intersection is made, or outside of the boundingRect, returns undefined.
getVerticalIndex: function(topOffset) {
+ this.ensureBuilt();
+
var boundingRect = this.boundingRect;
var tops = this.tops;
var bottoms = this.bottoms;
@@ -2142,12 +2165,14 @@ var CoordCache = FC.CoordCache = Class.extend({
// Gets the left offset (from document left) of the element at the given index
getLeftOffset: function(leftIndex) {
+ this.ensureBuilt();
return this.lefts[leftIndex];
},
// Gets the left position (from offsetParent left) of the element at the given index
getLeftPosition: function(leftIndex) {
+ this.ensureBuilt();
return this.lefts[leftIndex] - this.origin.left;
},
@@ -2155,6 +2180,7 @@ var CoordCache = FC.CoordCache = Class.extend({
// Gets the right offset (from document left) of the element at the given index.
// This value is NOT relative to the document's right edge, like the CSS concept of "right" would be.
getRightOffset: function(leftIndex) {
+ this.ensureBuilt();
return this.rights[leftIndex];
},
@@ -2162,30 +2188,35 @@ var CoordCache = FC.CoordCache = Class.extend({
// Gets the right position (from offsetParent left) of the element at the given index.
// This value is NOT relative to the offsetParent's right edge, like the CSS concept of "right" would be.
getRightPosition: function(leftIndex) {
+ this.ensureBuilt();
return this.rights[leftIndex] - this.origin.left;
},
// Gets the width of the element at the given index
getWidth: function(leftIndex) {
+ this.ensureBuilt();
return this.rights[leftIndex] - this.lefts[leftIndex];
},
// Gets the top offset (from document top) of the element at the given index
getTopOffset: function(topIndex) {
+ this.ensureBuilt();
return this.tops[topIndex];
},
// Gets the top position (from offsetParent top) of the element at the given position
getTopPosition: function(topIndex) {
+ this.ensureBuilt();
return this.tops[topIndex] - this.origin.top;
},
// Gets the bottom offset (from the document top) of the element at the given index.
// This value is NOT relative to the offsetParent's bottom edge, like the CSS concept of "bottom" would be.
getBottomOffset: function(topIndex) {
+ this.ensureBuilt();
return this.bottoms[topIndex];
},
@@ -2193,12 +2224,14 @@ var CoordCache = FC.CoordCache = Class.extend({
// Gets the bottom position (from the offsetParent top) of the element at the given index.
// This value is NOT relative to the offsetParent's bottom edge, like the CSS concept of "bottom" would be.
getBottomPosition: function(topIndex) {
+ this.ensureBuilt();
return this.bottoms[topIndex] - this.origin.top;
},
// Gets the height of the element at the given index
getHeight: function(topIndex) {
+ this.ensureBuilt();
return this.bottoms[topIndex] - this.tops[topIndex];
}
@@ -3102,8 +3135,9 @@ var Grid = FC.Grid = Class.extend({
},
- // Converts a range with an inclusive `start` and an exclusive `end` into an array of segment objects
- rangeToSegs: function(range) {
+ // Converts a span (has unzoned start/end and any other grid-specific location information)
+ // into an array of segments (pieces of events whose format is decided by the grid).
+ spanToSegs: function(span) {
// subclasses must implement
},
@@ -3273,7 +3307,7 @@ var Grid = FC.Grid = Class.extend({
listenStop: function(ev) {
if (dayClickHit) {
view.triggerDayClick(
- _this.getHitSpan(dayClickHit).start,
+ _this.getHitSpan(dayClickHit),
_this.getHitEl(dayClickHit),
ev
);
@@ -3295,24 +3329,24 @@ var Grid = FC.Grid = Class.extend({
// TODO: should probably move this to Grid.events, like we did event dragging / resizing
- // Renders a mock event over the given range
- renderRangeHelper: function(range, sourceSeg) {
- var fakeEvent = this.fabricateHelperEvent(range, sourceSeg);
+ // Renders a mock event at the given event location, which contains zoned start/end properties.
+ renderEventLocationHelper: function(eventLocation, sourceSeg) {
+ var fakeEvent = this.fabricateHelperEvent(eventLocation, sourceSeg);
this.renderHelper(fakeEvent, sourceSeg); // do the actual rendering
},
- // Builds a fake event given a date range it should cover, and a segment is should be inspired from.
+ // Builds a fake event given zoned event date properties and a segment is should be inspired from.
// The range's end can be null, in which case the mock event that is rendered will have a null end time.
// `sourceSeg` is the internal segment object involved in the drag. If null, something external is dragging.
- fabricateHelperEvent: function(range, sourceSeg) {
+ fabricateHelperEvent: function(eventLocation, sourceSeg) {
var fakeEvent = sourceSeg ? createObject(sourceSeg.event) : {}; // mask the original event object if possible
- fakeEvent.start = range.start.clone();
- fakeEvent.end = range.end ? range.end.clone() : null;
- fakeEvent.allDay = null; // force it to be freshly computed by normalizeEventRange
- this.view.calendar.normalizeEventRange(fakeEvent);
+ fakeEvent.start = eventLocation.start.clone();
+ fakeEvent.end = eventLocation.end ? eventLocation.end.clone() : null;
+ fakeEvent.allDay = null; // force it to be freshly computed by normalizeEventDates
+ this.view.calendar.normalizeEventDates(fakeEvent);
// this extra className will be useful for differentiating real events from mock events in CSS
fakeEvent.className = (fakeEvent.className || []).concat('fc-helper');
@@ -3326,8 +3360,8 @@ var Grid = FC.Grid = Class.extend({
},
- // Renders a mock event
- renderHelper: function(event, sourceSeg) {
+ // Renders a mock event. Given zoned event date properties.
+ renderHelper: function(eventLocation, sourceSeg) {
// subclasses must implement
},
@@ -3343,8 +3377,9 @@ var Grid = FC.Grid = Class.extend({
// Renders a visual indication of a selection. Will highlight by default but can be overridden by subclasses.
- renderSelection: function(range) {
- this.renderHighlight(this.selectionRangeToSegs(range));
+ // Given a span (unzoned start/end and other misc data)
+ renderSelection: function(span) {
+ this.renderHighlight(span);
},
@@ -3359,22 +3394,24 @@ var Grid = FC.Grid = Class.extend({
// Will return false if the selection is invalid and this should be indicated to the user.
// Will return null/undefined if a selection invalid but no error should be reported.
computeSelection: function(span0, span1) {
- var dates = [ span0.start, span0.end, span1.start, span1.end ];
- var combinedSpan;
+ var span = this.computeSelectionSpan(span0, span1);
- dates.sort(compareNumbers); // sorts chronologically. works with Moments
- combinedSpan = { start: dates[0].clone(), end: dates[3].clone() };
-
- if (!this.view.calendar.isSelectionRangeAllowed(combinedSpan)) {
+ if (span && !this.view.calendar.isSelectionSpanAllowed(span)) {
return false;
}
- return combinedSpan;
+ return span;
},
- selectionRangeToSegs: function(range) {
- return this.rangeToSegs(range);
+ // Given two spans, must return the combination of the two.
+ // TODO: do this separation of concerns (combining VS validation) for event dnd/resize too.
+ computeSelectionSpan: function(span0, span1) {
+ var dates = [ span0.start, span0.end, span1.start, span1.end ];
+
+ dates.sort(compareNumbers); // sorts chronologically. works with Moments
+
+ return { start: dates[0].clone(), end: dates[3].clone() };
},
@@ -3382,9 +3419,9 @@ var Grid = FC.Grid = Class.extend({
------------------------------------------------------------------------------------------------------------------*/
- // Renders an emphasis on the given date range. Given an array of segments.
- renderHighlight: function(segs) {
- this.renderFill('highlight', segs);
+ // Renders an emphasis on the given date range. Given a span (unzoned start/end and other misc data)
+ renderHighlight: function(span) {
+ this.renderFill('highlight', this.spanToSegs(span));
},
@@ -3400,10 +3437,40 @@ var Grid = FC.Grid = Class.extend({
},
- /* Fill System (highlight, background events, business hours)
+ /* Business Hours
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ renderBusinessHours: function() {
+ },
+
+
+ unrenderBusinessHours: function() {
+ },
+
+
+ /* Now Indicator
------------------------------------------------------------------------------------------------------------------*/
+ getNowIndicatorUnit: function() {
+ },
+
+
+ renderNowIndicator: function(date) {
+ },
+
+
+ unrenderNowIndicator: function() {
+ },
+
+
+ /* Fill System (highlight, background events, business hours)
+ --------------------------------------------------------------------------------------------------------------------
+ TODO: remove this system. like we did in TimeGrid
+ */
+
+
// Renders a set of rectangles over the given segments of time.
// MUST RETURN a subset of segs, the segs that were actually rendered.
// Responsible for populating this.elsByFill. TODO: better API for expressing this requirement
@@ -3496,7 +3563,7 @@ var Grid = FC.Grid = Class.extend({
// Computes HTML classNames for a single-day element
getDayClasses: function(date) {
var view = this.view;
- var today = view.calendar.getNow().stripTime();
+ var today = view.calendar.getNow();
var classes = [ 'fc-' + dayIDs[date.day()] ];
if (
@@ -3535,33 +3602,39 @@ Grid.mixin({
isDraggingSeg: false, // is a segment being dragged? boolean
isResizingSeg: false, // is a segment being resized? boolean
isDraggingExternal: false, // jqui-dragging an external element? boolean
- segs: null, // the event segments currently rendered in the grid
+ segs: null, // the *event* segments currently rendered in the grid. TODO: rename to `eventSegs`
// Renders the given events onto the grid
renderEvents: function(events) {
+ var bgEvents = [];
+ var fgEvents = [];
+ var i;
+
+ for (i = 0; i < events.length; i++) {
+ (isBgEvent(events[i]) ? bgEvents : fgEvents).push(events[i]);
+ }
+
+ this.segs = [].concat( // record all segs
+ this.renderBgEvents(bgEvents),
+ this.renderFgEvents(fgEvents)
+ );
+ },
+
+
+ renderBgEvents: function(events) {
var segs = this.eventsToSegs(events);
- var bgSegs = [];
- var fgSegs = [];
- var i, seg;
- for (i = 0; i < segs.length; i++) {
- seg = segs[i];
+ // renderBgSegs might return a subset of segs, segs that were actually rendered
+ return this.renderBgSegs(segs) || segs;
+ },
- if (isBgEvent(seg.event)) {
- bgSegs.push(seg);
- }
- else {
- fgSegs.push(seg);
- }
- }
- // Render each different type of segment.
- // Each function may return a subset of the segs, segs that were actually rendered.
- bgSegs = this.renderBgSegs(bgSegs) || bgSegs;
- fgSegs = this.renderFgSegs(fgSegs) || fgSegs;
+ renderFgEvents: function(events) {
+ var segs = this.eventsToSegs(events);
- this.segs = bgSegs.concat(fgSegs);
+ // renderFgSegs might return a subset of segs, segs that were actually rendered
+ return this.renderFgSegs(segs) || segs;
},
@@ -3676,20 +3749,9 @@ Grid.mixin({
// Generates a semicolon-separated CSS string to be used for the default rendering of a background event.
// Called by the fill system.
- // TODO: consolidate with getEventSkinCss?
bgEventSegCss: function(seg) {
- var view = this.view;
- var event = seg.event;
- var source = event.source || {};
-
return {
- 'background-color':
- event.backgroundColor ||
- event.color ||
- source.backgroundColor ||
- source.color ||
- view.opt('eventBackgroundColor') ||
- view.opt('eventColor')
+ 'background-color': this.getSegSkinCss(seg)['background-color']
};
},
@@ -3778,7 +3840,7 @@ Grid.mixin({
var calendar = view.calendar;
var el = seg.el;
var event = seg.event;
- var dropLocation; // a "span" (start/end possibly with additional properties)
+ var dropLocation; // zoned event date properties
// A clone of the original element that will move with the mouse
var mouseFollower = new MouseFollower(seg.el, {
@@ -3818,7 +3880,7 @@ Grid.mixin({
event
);
- if (dropLocation && !calendar.isEventRangeAllowed(dropLocation, event)) {
+ if (dropLocation && !calendar.isEventSpanAllowed(_this.eventToSpan(dropLocation), event)) {
disableCursor();
dropLocation = null;
}
@@ -3878,15 +3940,16 @@ Grid.mixin({
},
- // Given the spans an event drag began, and the span event was dropped, calculates the new start/end/allDay
+ // Given the spans an event drag began, and the span event was dropped, calculates the new zoned start/end/allDay
// values for the event. Subclasses may override and set additional properties to be used by renderDrag.
// A falsy returned value indicates an invalid drop.
+ // DOES NOT consider overlap/constraint.
computeEventDrop: function(startSpan, endSpan, event) {
var calendar = this.view.calendar;
var dragStart = startSpan.start;
var dragEnd = endSpan.start;
var delta;
- var dropLocation;
+ var dropLocation; // zoned event date properties
if (dragStart.hasTime() === dragEnd.hasTime()) {
delta = this.diffDates(dragEnd, dragStart);
@@ -3897,9 +3960,9 @@ Grid.mixin({
dropLocation = {
start: event.start.clone(),
end: calendar.getEventEnd(event), // will be an ambig day
- allDay: false // for normalizeEventRangeTimes
+ allDay: false // for normalizeEventTimes
};
- calendar.normalizeEventRangeTimes(dropLocation);
+ calendar.normalizeEventTimes(dropLocation);
}
// othewise, work off existing values
else {
@@ -3970,6 +4033,7 @@ Grid.mixin({
// Called when a jQuery UI drag starts and it needs to be monitored for dropping
listenToExternalDrag: function(el, ev, ui) {
var _this = this;
+ var calendar = this.view.calendar;
var meta = getDraggedElMeta(el); // extra data about event drop, including possible event to create
var dropLocation; // a null value signals an unsuccessful drag
@@ -3983,22 +4047,27 @@ Grid.mixin({
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)
+ ) {
+ disableCursor();
+ dropLocation = null;
+ }
+
if (dropLocation) {
_this.renderDrag(dropLocation); // called without a seg parameter
}
- else { // invalid hit
- disableCursor();
- }
},
hitOut: function() {
dropLocation = null; // signal unsuccessful
- _this.unrenderDrag();
+ },
+ hitDone: function() { // Called after a hitOut OR before a dragStop
enableCursor();
+ _this.unrenderDrag();
},
dragStop: function() {
- _this.unrenderDrag();
- enableCursor();
-
if (dropLocation) { // element was dropped on a valid hit
_this.view.reportExternalDrop(meta, dropLocation, el, ev, ui);
}
@@ -4013,11 +4082,13 @@ Grid.mixin({
// Given a hit to be dropped upon, and misc data associated with the jqui drag (guaranteed to be a plain object),
- // returns start/end dates for the event that would result from the hypothetical drop. end might be null.
+ // returns the zoned start/end dates for the event that would result from the hypothetical drop. end might be null.
// Returning a null value signals an invalid drop hit.
+ // DOES NOT consider overlap/constraint.
computeExternalDrop: function(span, meta) {
+ var calendar = this.view.calendar;
var dropLocation = {
- start: span.start,
+ start: calendar.applyTimezone(span.start), // simulate a zoned event start date
end: null
};
@@ -4030,10 +4101,6 @@ Grid.mixin({
dropLocation.end = dropLocation.start.clone().add(meta.duration);
}
- if (!this.view.calendar.isExternalDropRangeAllowed(dropLocation, meta.eventProps)) {
- return null;
- }
-
return dropLocation;
},
@@ -4071,7 +4138,7 @@ Grid.mixin({
var el = seg.el;
var event = seg.event;
var eventEnd = calendar.getEventEnd(event);
- var resizeLocation; // falsy if invalid resize
+ var resizeLocation; // zoned event date properties. falsy if invalid resize
// Tracks mouse movement over the *grid's* coordinate map
var dragListener = new HitDragListener(this, {
@@ -4091,7 +4158,7 @@ Grid.mixin({
_this.computeEventEndResize(origHitSpan, hitSpan, event);
if (resizeLocation) {
- if (!calendar.isEventRangeAllowed(resizeLocation, event)) {
+ if (!calendar.isEventSpanAllowed(_this.eventToSpan(resizeLocation), event)) {
disableCursor();
resizeLocation = null;
}
@@ -4153,31 +4220,32 @@ Grid.mixin({
},
- // Returns new date-information for an event segment being resized from its start OR end
- // `type` is either 'start' or 'end'
+ // Returns new zoned date information for an event segment being resized from its start OR end
+ // `type` is either 'start' or 'end'.
+ // DOES NOT consider overlap/constraint.
computeEventResize: function(type, startSpan, endSpan, event) {
var calendar = this.view.calendar;
var delta = this.diffDates(endSpan[type], startSpan[type]);
- var range;
+ var resizeLocation; // zoned event date properties
var defaultDuration;
// build original values to work from, guaranteeing a start and end
- range = {
+ resizeLocation = {
start: event.start.clone(),
end: calendar.getEventEnd(event),
allDay: event.allDay
};
// if an all-day event was in a timed area and was resized to a time, adjust start/end to have times
- if (range.allDay && durationHasTime(delta)) {
- range.allDay = false;
- calendar.normalizeEventRangeTimes(range);
+ if (resizeLocation.allDay && durationHasTime(delta)) {
+ resizeLocation.allDay = false;
+ calendar.normalizeEventTimes(resizeLocation);
}
- range[type].add(delta); // apply delta to start or end
+ resizeLocation[type].add(delta); // apply delta to start or end
// if the event was compressed too small, find a new reasonable duration for it
- if (!range.start.isBefore(range.end)) {
+ if (!resizeLocation.start.isBefore(resizeLocation.end)) {
defaultDuration =
this.minResizeDuration || // TODO: hack
@@ -4186,14 +4254,14 @@ Grid.mixin({
calendar.defaultTimedEventDuration);
if (type == 'start') { // resizing the start?
- range.start = range.end.clone().subtract(defaultDuration);
+ resizeLocation.start = resizeLocation.end.clone().subtract(defaultDuration);
}
else { // resizing the end?
- range.end = range.start.clone().add(defaultDuration);
+ resizeLocation.end = resizeLocation.start.clone().add(defaultDuration);
}
}
- return range;
+ return resizeLocation;
},
@@ -4266,7 +4334,8 @@ Grid.mixin({
// Utility for generating event skin-related CSS properties
- getEventSkinCss: function(event) {
+ getSegSkinCss: function(seg) {
+ var event = seg.event;
var view = this.view;
var source = event.source || {};
var eventColor = event.color;
@@ -4296,116 +4365,160 @@ Grid.mixin({
},
- /* Converting events -> ranges -> segs
+ /* Converting events -> eventRange -> eventSpan -> eventSegs
------------------------------------------------------------------------------------------------------------------*/
+ // Generates an array of segments for the given single event
+ // Can accept an event "location" as well (which only has start/end and no allDay)
+ eventToSegs: function(event) {
+ return this.eventsToSegs([ event ]);
+ },
+
+
+ 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);
+ },
+
+
+
// Converts an array of event objects into an array of event segment objects.
- // A custom `rangeToSegsFunc` may be given for arbitrarily slicing up events.
+ // A custom `segSliceFunc` may be given for arbitrarily slicing up events.
// Doesn't guarantee an order for the resulting array.
- eventsToSegs: function(events, rangeToSegsFunc) {
- var eventRanges = this.eventsToRanges(events);
+ eventsToSegs: function(allEvents, segSliceFunc) {
+ var _this = this;
+ var eventsById = groupEventsById(allEvents);
+ var segs = [];
+
+ $.each(eventsById, function(id, events) {
+ var ranges = [];
+ var i;
+
+ for (i = 0; i < events.length; i++) {
+ ranges.push(_this.eventToRange(events[i]));
+ }
+
+ // inverse-background events (utilize only the first event in calculations)
+ if (isInverseBgEvent(events[0])) {
+ ranges = _this.invertRanges(ranges);
+
+ for (i = 0; i < ranges.length; i++) {
+ segs.push.apply(segs, // append to
+ _this.eventRangeToSegs(ranges[i], events[0], segSliceFunc));
+ }
+ }
+ // normal event ranges
+ else {
+ for (i = 0; i < ranges.length; i++) {
+ segs.push.apply(segs, // append to
+ _this.eventRangeToSegs(ranges[i], events[i], segSliceFunc));
+ }
+ }
+ });
+
+ return segs;
+ },
+
+
+ // 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)
+ eventToRange: function(event) {
+ return {
+ start: event.start.clone().stripZone(),
+ end: (
+ event.end ?
+ event.end.clone() :
+ // derive the end from the start and allDay. compute allDay if necessary
+ this.view.calendar.getDefaultEventEnd(
+ event.allDay != null ?
+ event.allDay :
+ !event.start.hasTime(),
+ event.start
+ )
+ ).stripZone()
+ };
+ },
+
+
+ // 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);
var segs = [];
var i;
- for (i = 0; i < eventRanges.length; i++) {
- segs.push.apply(
- segs,
- this.eventRangeToSegs(eventRanges[i], rangeToSegsFunc)
- );
+ for (i = 0; i < spans.length; i++) {
+ segs.push.apply(segs, // append to
+ this.eventSpanToSegs(spans[i], event, segSliceFunc));
}
return segs;
},
- // Converts an array of events into an array of "range" objects.
- // A "range" object is a plain object with start/end properties denoting the time it covers. Also an event property.
- // For "normal" events, this will be identical to the event's start/end, but for "inverse-background" events,
- // will create an array of ranges that span the time *not* covered by the given event.
- // Doesn't guarantee an order for the resulting array.
- eventsToRanges: function(events) {
- var _this = this;
- var eventsById = groupEventsById(events);
- var ranges = [];
-
- // group by ID so that related inverse-background events can be rendered together
- $.each(eventsById, function(id, eventGroup) {
- if (eventGroup.length) {
- ranges.push.apply(
- ranges,
- isInverseBgEvent(eventGroup[0]) ?
- _this.eventsToInverseRanges(eventGroup) :
- _this.eventsToNormalRanges(eventGroup)
- );
- }
- });
-
- return ranges;
+ // Given an event's unzoned date range, return an array of "span" objects.
+ // Subclasses can override.
+ eventRangeToSpans: function(range, event) {
+ return [ $.extend({}, range) ]; // copy into a single-item array
},
- // Converts an array of "normal" events (not inverted rendering) into a parallel array of ranges
- eventsToNormalRanges: function(events) {
- var calendar = this.view.calendar;
- var ranges = [];
- var i, event;
- var eventStart, eventEnd;
+ // 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);
+ var i, seg;
- for (i = 0; i < events.length; i++) {
- event = events[i];
-
- // make copies and normalize by stripping timezone
- eventStart = event.start.clone().stripZone();
- eventEnd = calendar.getEventEnd(event).stripZone();
-
- ranges.push({
- event: event,
- start: eventStart,
- end: eventEnd,
- eventStartMS: +eventStart,
- eventDurationMS: eventEnd - eventStart
- });
+ for (i = 0; i < segs.length; i++) {
+ seg = segs[i];
+ seg.event = event;
+ seg.eventStartMS = +span.start; // TODO: not the best name after making spans unzoned
+ seg.eventDurationMS = span.end - span.start;
}
- return ranges;
+ return segs;
},
- // Converts an array of events, with inverse-background rendering, into an array of range objects.
- // The range objects will cover all the time NOT covered by the events.
- eventsToInverseRanges: function(events) {
+ // Produces a new array of range objects that will cover all the time NOT covered by the given ranges.
+ // 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().stripZone(); // normalize timezone
- var viewEnd = view.end.clone().stripZone(); // normalize timezone
- var normalRanges = this.eventsToNormalRanges(events); // will give us normalized dates we can use w/o copies
+ var viewStart = view.start.clone(); // need a copy
+ var viewEnd = view.end.clone(); // need a copy
var inverseRanges = [];
- var event0 = events[0]; // assign this to each range's `.event`
var start = viewStart; // the end of the previous range. the start of the new range
- var i, normalRange;
+ var i, range;
// ranges need to be in order. required for our date-walking algorithm
- normalRanges.sort(compareNormalRanges);
+ ranges.sort(compareRanges);
- for (i = 0; i < normalRanges.length; i++) {
- normalRange = normalRanges[i];
+ for (i = 0; i < ranges.length; i++) {
+ range = ranges[i];
// add the span of time before the event (if there is any)
- if (normalRange.start > start) { // compare millisecond time (skip any ambig logic)
+ if (range.start > start) { // compare millisecond time (skip any ambig logic)
inverseRanges.push({
- event: event0,
start: start,
- end: normalRange.start
+ end: range.start
});
}
- start = normalRange.end;
+ start = range.end;
}
// add the span of time after the last event (if there is any)
if (start < viewEnd) { // compare millisecond time (skip any ambig logic)
inverseRanges.push({
- event: event0,
start: start,
end: viewEnd
});
@@ -4415,40 +4528,13 @@ Grid.mixin({
},
- // Slices the given event range into one or more segment objects.
- // A `rangeToSegsFunc` custom slicing function can be given.
- eventRangeToSegs: function(eventRange, rangeToSegsFunc) {
- var segs;
- var i, seg;
-
- eventRange = this.view.calendar.ensureVisibleEventRange(eventRange);
-
- if (rangeToSegsFunc) {
- segs = rangeToSegsFunc(eventRange);
- }
- else {
- segs = this.rangeToSegs(eventRange); // defined by the subclass
- }
-
- for (i = 0; i < segs.length; i++) {
- seg = segs[i];
- seg.event = eventRange.event;
- seg.eventStartMS = eventRange.eventStartMS;
- seg.eventDurationMS = eventRange.eventDurationMS;
- }
-
- return segs;
- },
-
-
- sortSegs: function(segs) {
- segs.sort(proxy(this, 'compareSegs'));
+ sortEventSegs: function(segs) {
+ segs.sort(proxy(this, 'compareEventSegs'));
},
// A cmp function for determining which segments should take visual priority
- // DOES NOT WORK ON INVERTED BACKGROUND EVENTS because they have no eventStartMS/eventDurationMS
- compareSegs: function(seg1, seg2) {
+ compareEventSegs: function(seg1, seg2) {
return seg1.eventStartMS - seg2.eventStartMS || // earlier events go first
seg2.eventDurationMS - seg1.eventDurationMS || // tie? longer events go first
seg2.event.allDay - seg1.event.allDay || // tie? put all-day events first (booleans cast to 0/1)
@@ -4466,6 +4552,7 @@ function isBgEvent(event) { // returns true if background OR inverse-background
var rendering = getEventRendering(event);
return rendering === 'background' || rendering === 'inverse-background';
}
+FC.isBgEvent = isBgEvent; // export
function isInverseBgEvent(event) {
@@ -4492,8 +4579,8 @@ function groupEventsById(events) {
// A cmp function for determining which non-inverted "ranges" (see above) happen earlier
-function compareNormalRanges(range1, range2) {
- return range1.eventStartMS - range2.eventStartMS; // earlier ranges go first
+function compareRanges(range1, range2) {
+ return range1.start - range2.start; // earlier ranges go first
}
@@ -4844,13 +4931,23 @@ var DayTableMixin = FC.DayTableMixin = {
},
- renderHeadDateCellHtml: function(date, colspan) {
+ // TODO: when internalApiVersion, accept an object for HTML attributes
+ // (colspan should be no different)
+ renderHeadDateCellHtml: function(date, colspan, otherAttrs) {
var view = this.view;
return '' +
'<th class="fc-day-header ' + view.widgetHeaderClass + ' fc-' + dayIDs[date.day()] + '"' +
- (colspan > 1 ? ' colspan="' + colspan + '"' : '') +
- '>' +
+ (this.rowCnt == 1 ?
+ ' data-date="' + date.format('YYYY-MM-DD') + '"' :
+ '') +
+ (colspan > 1 ?
+ ' colspan="' + colspan + '"' :
+ '') +
+ (otherAttrs ?
+ ' ' + otherAttrs :
+ '') +
+ '>' +
htmlEscape(date.format(this.colHeadFormat)) +
'</th>';
},
@@ -4888,7 +4985,7 @@ var DayTableMixin = FC.DayTableMixin = {
},
- renderBgCellHtml: function(date) {
+ renderBgCellHtml: function(date, otherAttrs) {
var view = this.view;
var classes = this.getDayClasses(date);
@@ -4896,6 +4993,9 @@ var DayTableMixin = FC.DayTableMixin = {
return '<td class="' + classes.join(' ') + '"' +
' data-date="' + date.format('YYYY-MM-DD') + '"' + // if date has a time, won't format it
+ (otherAttrs ?
+ ' ' + otherAttrs :
+ '') +
'></td>';
},
@@ -4909,6 +5009,11 @@ var DayTableMixin = FC.DayTableMixin = {
},
+ // TODO: a generic method for dealing with <tr>, RTL, intro
+ // when increment internalApiVersion
+ // wrapTr (scheduler)
+
+
/* Utils
------------------------------------------------------------------------------------------------------------------*/
@@ -5110,9 +5215,9 @@ var DayGrid = FC.DayGrid = Grid.extend(DayTableMixin, {
},
- // Slices up a date range by row into an array of segments
- rangeToSegs: function(range) {
- var segs = this.sliceRangeByRow(range);
+ // Slices up the given span (unzoned start/end with other misc data) into an array of segments
+ spanToSegs: function(span) {
+ var segs = this.sliceRangeByRow(span);
var i, seg;
for (i = 0; i < segs.length; i++) {
@@ -5197,16 +5302,16 @@ var DayGrid = FC.DayGrid = Grid.extend(DayTableMixin, {
// Renders a visual indication of an event or external element being dragged.
- // The dropLocation's end can be null. seg can be null. See Grid::renderDrag for more info.
- renderDrag: function(dropLocation, seg) {
+ // `eventLocation` has zoned start and end (optional)
+ renderDrag: function(eventLocation, seg) {
// always render a highlight underneath
- this.renderHighlight(this.eventRangeToSegs(dropLocation));
+ this.renderHighlight(this.eventToSpan(eventLocation));
// if a segment from the same calendar but another component is being dragged, render a helper event
if (seg && !seg.el.closest(this.el).length) {
- this.renderRangeHelper(dropLocation, seg);
+ this.renderEventLocationHelper(eventLocation, seg);
this.applyDragOpacity(this.helperEls);
return true; // a helper has been rendered
@@ -5226,9 +5331,9 @@ var DayGrid = FC.DayGrid = Grid.extend(DayTableMixin, {
// Renders a visual indication of an event being resized
- renderEventResize: function(range, seg) {
- this.renderHighlight(this.eventRangeToSegs(range));
- this.renderRangeHelper(range, seg);
+ renderEventResize: function(eventLocation, seg) {
+ this.renderHighlight(this.eventToSpan(eventLocation));
+ this.renderEventLocationHelper(eventLocation, seg);
},
@@ -5246,7 +5351,7 @@ var DayGrid = FC.DayGrid = Grid.extend(DayTableMixin, {
// Renders a mock "helper" event. `sourceSeg` is the associated internal segment object. It can be null.
renderHelper: function(event, sourceSeg) {
var helperNodes = [];
- var segs = this.eventsToSegs([ event ]);
+ var segs = this.eventToSegs(event);
var rowStructs;
segs = this.renderFgSegEls(segs); // assigns each seg's el and returns a subset of segs that were rendered
@@ -5453,7 +5558,7 @@ DayGrid.mixin({
var isResizableFromEnd = !disableResizing && event.allDay &&
seg.isEnd && view.isEventResizableFromEnd(event);
var classes = this.getSegClasses(seg, isDraggable, isResizableFromStart || isResizableFromEnd);
- var skinCss = cssToStr(this.getEventSkinCss(event));
+ var skinCss = cssToStr(this.getSegSkinCss(seg));
var timeHtml = '';
var timeText;
var titleHtml;
@@ -5600,7 +5705,7 @@ DayGrid.mixin({
// Give preference to elements with certain criteria, so they have
// a chance to be closer to the top.
- this.sortSegs(segs);
+ this.sortEventSegs(segs);
for (i = 0; i < segs.length; i++) {
seg = segs[i];
@@ -5989,7 +6094,7 @@ DayGrid.mixin({
return seg.event;
});
- var dayStart = dayDate.clone().stripTime();
+ var dayStart = dayDate.clone();
var dayEnd = dayStart.clone().add(1, 'days');
var dayRange = { start: dayStart, end: dayEnd };
@@ -5997,13 +6102,13 @@ DayGrid.mixin({
segs = this.eventsToSegs(
events,
function(range) {
- var seg = intersectionToSeg(range, dayRange); // undefind if no intersection
+ var seg = intersectRanges(range, dayRange); // undefind if no intersection
return seg ? [ seg ] : []; // must return an array of segments
}
);
// force an order because eventsToSegs doesn't guarantee one
- this.sortSegs(segs);
+ this.sortEventSegs(segs);
return segs;
},
@@ -6061,13 +6166,11 @@ var TimeGrid = FC.TimeGrid = Grid.extend(DayTableMixin, {
colEls: null, // cells elements in the day-row background
slatEls: null, // elements running horizontally across all columns
- helperEl: null, // cell skeleton element for rendering the mock event "helper"
+ nowIndicatorEls: null,
colCoordCache: null,
slatCoordCache: null,
- businessHourSegs: null,
-
constructor: function() {
Grid.apply(this, arguments); // call the super-constructor
@@ -6091,12 +6194,8 @@ var TimeGrid = FC.TimeGrid = Grid.extend(DayTableMixin, {
els: this.slatEls,
isVertical: true
});
- },
-
- renderBusinessHours: function() {
- var events = this.view.calendar.getBusinessHoursEvents();
- this.businessHourSegs = this.renderFill('businessHours', this.eventsToSegs(events), 'bgevent');
+ this.renderContentSkeleton();
},
@@ -6128,7 +6227,7 @@ var TimeGrid = FC.TimeGrid = Grid.extend(DayTableMixin, {
// Calculate the time for each slot
while (slotTime < this.maxTime) {
- slotDate = this.start.clone().time(slotTime); // after .time() will be in UTC. but that's good, avoids DST issues
+ slotDate = this.start.clone().time(slotTime);
isLabeled = isInt(divideDurationByDuration(slotTime, this.labelInterval));
axisHtml =
@@ -6142,7 +6241,9 @@ var TimeGrid = FC.TimeGrid = Grid.extend(DayTableMixin, {
'</td>';
html +=
- '<tr ' + (isLabeled ? '' : 'class="fc-minor"') + '>' +
+ '<tr data-time="' + slotDate.format('HH:mm:ss') + '"' +
+ (isLabeled ? '' : ' class="fc-minor"') +
+ '>' +
(!isRTL ? axisHtml : '') +
'<td class="' + view.widgetContentClass + '"/>' +
(isRTL ? axisHtml : '') +
@@ -6274,9 +6375,8 @@ var TimeGrid = FC.TimeGrid = Grid.extend(DayTableMixin, {
getHitSpan: function(hit) {
- var date = this.getCellDate(0, hit.col); // row=0
+ var start = this.getCellDate(0, hit.col); // row=0
var time = this.computeSnapTime(hit.snap); // pass in the snap-index
- var start = this.view.calendar.rezoneDate(date); // gives it a 00:00 time
var end;
start.time(time);
@@ -6306,9 +6406,9 @@ var TimeGrid = FC.TimeGrid = Grid.extend(DayTableMixin, {
},
- // Slices up a date range by column into an array of segments
- rangeToSegs: function(range) {
- var segs = this.sliceRangeByTimes(range);
+ // Slices up the given span (unzoned start/end with other misc data) into an array of segments
+ spanToSegs: function(span) {
+ var segs = this.sliceRangeByTimes(span);
var i;
for (i = 0; i < segs.length; i++) {
@@ -6331,19 +6431,13 @@ var TimeGrid = FC.TimeGrid = Grid.extend(DayTableMixin, {
var dayDate;
var dayRange;
- // normalize :(
- range = {
- start: range.start.clone().stripZone(),
- end: range.end.clone().stripZone()
- };
-
for (dayIndex = 0; dayIndex < this.daysPerRow; dayIndex++) {
dayDate = this.dayDates[dayIndex].clone(); // TODO: better API for this?
dayRange = {
start: dayDate.clone().time(this.minTime),
end: dayDate.clone().time(this.maxTime)
};
- seg = intersectionToSeg(range, dayRange); // both will be ambig timezone
+ seg = intersectRanges(range, dayRange); // both will be ambig timezone
if (seg) {
seg.dayIndex = dayIndex;
segs.push(seg);
@@ -6362,7 +6456,9 @@ var TimeGrid = FC.TimeGrid = Grid.extend(DayTableMixin, {
this.slatCoordCache.build();
if (isResize) {
- this.updateSegVerticals();
+ this.updateSegVerticals(
+ [].concat(this.fgSegs || [], this.bgSegs || [], this.businessSegs || [])
+ );
}
},
@@ -6372,7 +6468,7 @@ var TimeGrid = FC.TimeGrid = Grid.extend(DayTableMixin, {
computeDateTop: function(date, startOfDayDate) {
return this.computeTimeTop(
moment.duration(
- date.clone().stripZone() - startOfDayDate.clone().stripTime()
+ date - startOfDayDate.clone().stripTime()
)
);
},
@@ -6411,19 +6507,21 @@ var TimeGrid = FC.TimeGrid = Grid.extend(DayTableMixin, {
// Renders a visual indication of an event being dragged over the specified date(s).
- // dropLocation's end might be null, as well as `seg`. See Grid::renderDrag for more info.
// A returned value of `true` signals that a mock "helper" event has been rendered.
- renderDrag: function(dropLocation, seg) {
+ renderDrag: function(eventLocation, seg) {
if (seg) { // if there is event information for this drag, render a helper event
- this.renderRangeHelper(dropLocation, seg);
- this.applyDragOpacity(this.helperEl);
+ this.renderEventLocationHelper(eventLocation, seg);
+
+ for (var i = 0; i < this.helperSegs.length; i++) {
+ this.applyDragOpacity(this.helperSegs[i].el);
+ }
return true; // signal that a helper has been rendered
}
else {
// otherwise, just render a highlight
- this.renderHighlight(this.eventRangeToSegs(dropLocation));
+ this.renderHighlight(this.eventToSpan(eventLocation));
}
},
@@ -6440,8 +6538,8 @@ var TimeGrid = FC.TimeGrid = Grid.extend(DayTableMixin, {
// Renders a visual indication of an event being resized
- renderEventResize: function(range, seg) {
- this.renderRangeHelper(range, seg);
+ renderEventResize: function(eventLocation, seg) {
+ this.renderEventLocationHelper(eventLocation, seg);
},
@@ -6457,39 +6555,72 @@ var TimeGrid = FC.TimeGrid = Grid.extend(DayTableMixin, {
// Renders a mock "helper" event. `sourceSeg` is the original segment object and might be null (an external drag)
renderHelper: function(event, sourceSeg) {
- var segs = this.eventsToSegs([ event ]);
- var tableEl;
- var i, seg;
- var sourceEl;
+ this.renderHelperSegs(this.eventToSegs(event), sourceSeg);
+ },
- segs = this.renderFgSegEls(segs); // assigns each seg's el and returns a subset of segs that were rendered
- tableEl = this.renderSegTable(segs);
- // Try to make the segment that is in the same row as sourceSeg look the same
+ // Unrenders any mock helper event
+ unrenderHelper: function() {
+ this.unrenderHelperSegs();
+ },
+
+
+ /* Business Hours
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ renderBusinessHours: function() {
+ var events = this.view.calendar.getBusinessHoursEvents();
+ var segs = this.eventsToSegs(events);
+
+ this.renderBusinessSegs(segs);
+ },
+
+
+ unrenderBusinessHours: function() {
+ this.unrenderBusinessSegs();
+ },
+
+
+ /* Now Indicator
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ getNowIndicatorUnit: function() {
+ return 'minute'; // will refresh on the minute
+ },
+
+
+ renderNowIndicator: function(date) {
+ // seg system might be overkill, but it handles scenario where line needs to be rendered
+ // more than once because of columns with the same date (resources columns for example)
+ var segs = this.spanToSegs({ start: date, end: date });
+ var top = this.computeDateTop(date, date);
+ var nodes = [];
+ var i;
+
+ // render lines within the columns
for (i = 0; i < segs.length; i++) {
- seg = segs[i];
- if (sourceSeg && sourceSeg.col === seg.col) {
- sourceEl = sourceSeg.el;
- seg.el.css({
- left: sourceEl.css('left'),
- right: sourceEl.css('right'),
- 'margin-left': sourceEl.css('margin-left'),
- 'margin-right': sourceEl.css('margin-right')
- });
- }
+ nodes.push($('<div class="fc-now-indicator fc-now-indicator-line"></div>')
+ .css('top', top)
+ .appendTo(this.colContainerEls.eq(segs[i].col))[0]);
}
- this.helperEl = $('<div class="fc-helper-skeleton"/>')
- .append(tableEl)
- .appendTo(this.el);
+ // render an arrow over the axis
+ if (segs.length > 0) { // is the current time in view?
+ nodes.push($('<div class="fc-now-indicator fc-now-indicator-arrow"></div>')
+ .css('top', top)
+ .appendTo(this.el.find('.fc-content-skeleton'))[0]);
+ }
+
+ this.nowIndicatorEls = $(nodes);
},
- // Unrenders any mock helper event
- unrenderHelper: function() {
- if (this.helperEl) {
- this.helperEl.remove();
- this.helperEl = null;
+ unrenderNowIndicator: function() {
+ if (this.nowIndicatorEls) {
+ this.nowIndicatorEls.remove();
+ this.nowIndicatorEls = null;
}
},
@@ -6499,12 +6630,14 @@ var TimeGrid = FC.TimeGrid = Grid.extend(DayTableMixin, {
// Renders a visual indication of a selection. Overrides the default, which was to simply render a highlight.
- renderSelection: function(range) {
+ renderSelection: function(span) {
if (this.view.opt('selectHelper')) { // this setting signals that a mock helper event should be rendered
- this.renderRangeHelper(range);
+
+ // normally acceps an eventLocation, span has a start/end, which is good enough
+ this.renderEventLocationHelper(span);
}
else {
- this.renderHighlight(this.selectionRangeToSegs(range));
+ this.renderHighlight(span);
}
},
@@ -6516,233 +6649,260 @@ var TimeGrid = FC.TimeGrid = Grid.extend(DayTableMixin, {
},
- /* Fill System (highlight, background events, business hours)
+ /* Highlight
------------------------------------------------------------------------------------------------------------------*/
- // Renders a set of rectangles over the given time segments.
- // Only returns segments that successfully rendered.
- renderFill: function(type, segs, className) {
- var segCols;
- var skeletonEl;
- var trEl;
- var col, colSegs;
- var tdEl;
- var containerEl;
- var dayDate;
- var i, seg;
-
- if (segs.length) {
-
- segs = this.renderFillSegEls(type, segs); // assignes `.el` to each seg. returns successfully rendered segs
- segCols = this.groupSegCols(segs); // group into sub-arrays, and assigns 'col' to each seg
-
- className = className || type.toLowerCase();
- skeletonEl = $(
- '<div class="fc-' + className + '-skeleton">' +
- '<table><tr/></table>' +
- '</div>'
- );
- trEl = skeletonEl.find('tr');
-
- for (col = 0; col < segCols.length; col++) {
- colSegs = segCols[col];
- tdEl = $('<td/>').appendTo(trEl);
-
- if (colSegs.length) {
- containerEl = $('<div class="fc-' + className + '-container"/>').appendTo(tdEl);
- dayDate = this.getCellDate(0, col); // row=0
-
- for (i = 0; i < colSegs.length; i++) {
- seg = colSegs[i];
- containerEl.append(
- seg.el.css({
- top: this.computeDateTop(seg.start, dayDate),
- bottom: -this.computeDateTop(seg.end, dayDate) // the y position of the bottom edge
- })
- );
- }
- }
- }
-
- this.bookendCells(trEl);
+ renderHighlight: function(span) {
+ this.renderHighlightSegs(this.spanToSegs(span));
+ },
- this.el.append(skeletonEl);
- this.elsByFill[type] = skeletonEl;
- }
- return segs;
+ unrenderHighlight: function() {
+ this.unrenderHighlightSegs();
}
});
;;
-/* Event-rendering methods for the TimeGrid class
+/* Methods for rendering SEGMENTS, pieces of content that live on the view
+ ( this file is no longer just for events )
----------------------------------------------------------------------------------------------------------------------*/
TimeGrid.mixin({
- eventSkeletonEl: null, // has cells with event-containers, which contain absolutely positioned event elements
+ colContainerEls: null, // containers for each column
+ // inner-containers for each column where different types of segs live
+ fgContainerEls: null,
+ bgContainerEls: null,
+ helperContainerEls: null,
+ highlightContainerEls: null,
+ businessContainerEls: null,
+
+ // arrays of different types of displayed segments
+ fgSegs: null,
+ bgSegs: null,
+ helperSegs: null,
+ highlightSegs: null,
+ businessSegs: null,
- // Renders the given foreground event segments onto the grid
- renderFgSegs: function(segs) {
- segs = this.renderFgSegEls(segs); // returns a subset of the segs. segs that were actually rendered
- this.el.append(
- this.eventSkeletonEl = $('<div class="fc-content-skeleton"/>')
- .append(this.renderSegTable(segs))
+ // Renders the DOM that the view's content will live in
+ renderContentSkeleton: function() {
+ var cellHtml = '';
+ var i;
+ var skeletonEl;
+
+ for (i = 0; i < this.colCnt; i++) {
+ cellHtml +=
+ '<td>' +
+ '<div class="fc-content-col">' +
+ '<div class="fc-event-container fc-helper-container"></div>' +
+ '<div class="fc-event-container"></div>' +
+ '<div class="fc-highlight-container"></div>' +
+ '<div class="fc-bgevent-container"></div>' +
+ '<div class="fc-business-container"></div>' +
+ '</div>' +
+ '</td>';
+ }
+
+ skeletonEl = $(
+ '<div class="fc-content-skeleton">' +
+ '<table>' +
+ '<tr>' + cellHtml + '</tr>' +
+ '</table>' +
+ '</div>'
);
- return segs; // return only the segs that were actually rendered
+ this.colContainerEls = skeletonEl.find('.fc-content-col');
+ this.helperContainerEls = skeletonEl.find('.fc-helper-container');
+ this.fgContainerEls = skeletonEl.find('.fc-event-container:not(.fc-helper-container)');
+ this.bgContainerEls = skeletonEl.find('.fc-bgevent-container');
+ this.highlightContainerEls = skeletonEl.find('.fc-highlight-container');
+ this.businessContainerEls = skeletonEl.find('.fc-business-container');
+
+ this.bookendCells(skeletonEl.find('tr')); // TODO: do this on string level
+ this.el.append(skeletonEl);
},
- // Unrenders all currently rendered foreground event segments
- unrenderFgSegs: function(segs) {
- if (this.eventSkeletonEl) {
- this.eventSkeletonEl.remove();
- this.eventSkeletonEl = null;
- }
+ /* Foreground Events
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ renderFgSegs: function(segs) {
+ segs = this.renderFgSegsIntoContainers(segs, this.fgContainerEls);
+ this.fgSegs = segs;
+ return segs; // needed for Grid::renderEvents
},
- // Renders and returns the <table> portion of the event-skeleton.
- // Returns an object with properties 'tbodyEl' and 'segs'.
- renderSegTable: function(segs) {
- var tableEl = $('<table><tr/></table>');
- var trEl = tableEl.find('tr');
- var segCols;
+ unrenderFgSegs: function() {
+ this.unrenderNamedSegs('fgSegs');
+ },
+
+
+ /* Foreground Helper Events
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ renderHelperSegs: function(segs, sourceSeg) {
var i, seg;
- var col, colSegs;
- var containerEl;
+ var sourceEl;
- segCols = this.groupSegCols(segs); // group into sub-arrays, and assigns 'col' to each seg
+ segs = this.renderFgSegsIntoContainers(segs, this.helperContainerEls);
- this.computeSegVerticals(segs); // compute and assign top/bottom
+ // Try to make the segment that is in the same row as sourceSeg look the same
+ for (i = 0; i < segs.length; i++) {
+ seg = segs[i];
+ if (sourceSeg && sourceSeg.col === seg.col) {
+ sourceEl = sourceSeg.el;
+ seg.el.css({
+ left: sourceEl.css('left'),
+ right: sourceEl.css('right'),
+ 'margin-left': sourceEl.css('margin-left'),
+ 'margin-right': sourceEl.css('margin-right')
+ });
+ }
+ }
- for (col = 0; col < segCols.length; col++) { // iterate each column grouping
- colSegs = segCols[col];
- this.placeSlotSegs(colSegs); // compute horizontal coordinates, z-index's, and reorder the array
+ this.helperSegs = segs;
+ },
- containerEl = $('<div class="fc-event-container"/>');
- // assign positioning CSS and insert into container
- for (i = 0; i < colSegs.length; i++) {
- seg = colSegs[i];
- seg.el.css(this.generateSegPositionCss(seg));
+ unrenderHelperSegs: function() {
+ this.unrenderNamedSegs('helperSegs');
+ },
- // if the height is short, add a className for alternate styling
- if (seg.bottom - seg.top < 30) {
- seg.el.addClass('fc-short');
- }
- containerEl.append(seg.el);
- }
+ /* Background Events
+ ------------------------------------------------------------------------------------------------------------------*/
- trEl.append($('<td/>').append(containerEl));
- }
- this.bookendCells(trEl);
+ renderBgSegs: function(segs) {
+ segs = this.renderFillSegEls('bgEvent', segs); // TODO: old fill system
+ this.updateSegVerticals(segs);
+ this.attachSegsByCol(this.groupSegsByCol(segs), this.bgContainerEls);
+ this.bgSegs = segs;
+ return segs; // needed for Grid::renderEvents
+ },
- return tableEl;
+
+ unrenderBgSegs: function() {
+ this.unrenderNamedSegs('bgSegs');
},
- // 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!
- placeSlotSegs: function(segs) {
- var levels;
- var level0;
- var i;
+ /* Highlight
+ ------------------------------------------------------------------------------------------------------------------*/
- this.sortSegs(segs); // order by date
- levels = buildSlotSegLevels(segs);
- computeForwardSlotSegs(levels);
- if ((level0 = levels[0])) {
+ renderHighlightSegs: function(segs) {
+ segs = this.renderFillSegEls('highlight', segs); // TODO: old fill system
+ this.updateSegVerticals(segs);
+ this.attachSegsByCol(this.groupSegsByCol(segs), this.highlightContainerEls);
+ this.highlightSegs = segs;
+ },
- for (i = 0; i < level0.length; i++) {
- computeSlotSegPressures(level0[i]);
- }
- for (i = 0; i < level0.length; i++) {
- this.computeSlotSegCoords(level0[i], 0, 0);
- }
- }
+ unrenderHighlightSegs: function() {
+ this.unrenderNamedSegs('highlightSegs');
},
- // 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.
- computeSlotSegCoords: function(seg, seriesBackwardPressure, seriesBackwardCoord) {
- var forwardSegs = seg.forwardSegs;
+ /* Business Hours
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ renderBusinessSegs: function(segs) {
+ segs = this.renderFillSegEls('businessHours', segs); // TODO: old fill system
+ this.updateSegVerticals(segs);
+ this.attachSegsByCol(this.groupSegsByCol(segs), this.businessContainerEls);
+ this.businessSegs = segs;
+ },
+
+
+ unrenderBusinessSegs: function() {
+ this.unrenderNamedSegs('businessSegs');
+ },
+
+
+ /* Seg Rendering Utils
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Given a flat array of segments, return an array of sub-arrays, grouped by each segment's col
+ groupSegsByCol: function(segs) {
+ var segsByCol = [];
var i;
- if (seg.forwardCoord === undefined) { // not already computed
+ for (i = 0; i < this.colCnt; i++) {
+ segsByCol.push([]);
+ }
- if (!forwardSegs.length) {
+ for (i = 0; i < segs.length; i++) {
+ segsByCol[segs[i].col].push(segs[i]);
+ }
- // if there are no forward segments, this segment should butt up against the edge
- seg.forwardCoord = 1;
- }
- else {
+ return segsByCol;
+ },
- // sort highest pressure first
- this.sortForwardSlotSegs(forwardSegs);
- // this segment's forwardCoord will be calculated from the backwardCoord of the
- // highest-pressure forward segment.
- this.computeSlotSegCoords(forwardSegs[0], seriesBackwardPressure + 1, seriesBackwardCoord);
- seg.forwardCoord = forwardSegs[0].backwardCoord;
- }
+ // Given segments grouped by column, insert the segments' elements into a parallel array of container
+ // elements, each living within a column.
+ attachSegsByCol: function(segsByCol, containerEls) {
+ var col;
+ var segs;
+ var i;
- // 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
+ for (col = 0; col < this.colCnt; col++) { // iterate each column grouping
+ segs = segsByCol[col];
- // use this segment's coordinates to computed the coordinates of the less-pressurized
- // forward segments
- for (i=0; i<forwardSegs.length; i++) {
- this.computeSlotSegCoords(forwardSegs[i], 0, seg.forwardCoord);
+ for (i = 0; i < segs.length; i++) {
+ containerEls.eq(col).append(segs[i].el);
}
}
},
- // Refreshes the CSS top/bottom coordinates for each segment element. Probably after a window resize/zoom.
- // Repositions business hours segs too, so not just for events. Maybe shouldn't be here.
- updateSegVerticals: function() {
- var allSegs = (this.segs || []).concat(this.businessHourSegs || []);
+ // Given the name of a property of `this` object, assumed to be an array of segments,
+ // loops through each segment and removes from DOM. Will null-out the property afterwards.
+ unrenderNamedSegs: function(propName) {
+ var segs = this[propName];
var i;
- this.computeSegVerticals(allSegs);
-
- for (i = 0; i < allSegs.length; i++) {
- allSegs[i].el.css(
- this.generateSegVerticalCss(allSegs[i])
- );
+ if (segs) {
+ for (i = 0; i < segs.length; i++) {
+ segs[i].el.remove();
+ }
+ this[propName] = null;
}
},
- // For each segment in an array, computes and assigns its top and bottom properties
- computeSegVerticals: function(segs) {
- var i, seg;
- for (i = 0; i < segs.length; i++) {
- seg = segs[i];
- seg.top = this.computeDateTop(seg.start, seg.start);
- seg.bottom = this.computeDateTop(seg.end, seg.start);
+ /* 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.renderFgSegEls(segs); // will call fgSegHtml
+ segsByCol = this.groupSegsByCol(segs);
+
+ for (col = 0; col < this.colCnt; col++) {
+ this.updateFgSegCoords(segsByCol[col]);
}
+
+ this.attachSegsByCol(segsByCol, containerEls);
+
+ return segs;
},
@@ -6754,7 +6914,7 @@ TimeGrid.mixin({
var isResizableFromStart = !disableResizing && seg.isStart && view.isEventResizableFromStart(event);
var isResizableFromEnd = !disableResizing && seg.isEnd && view.isEventResizableFromEnd(event);
var classes = this.getSegClasses(seg, isDraggable, isResizableFromStart || isResizableFromEnd);
- var skinCss = cssToStr(this.getEventSkinCss(event));
+ var skinCss = cssToStr(this.getSegSkinCss(seg));
var timeText;
var fullTimeText; // more verbose time text. for the print stylesheet
var startTimeText; // just the start time text
@@ -6819,40 +6979,39 @@ TimeGrid.mixin({
},
- // 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.
- generateSegPositionCss: function(seg) {
- var shouldOverlap = this.view.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
+ /* Seg Position Utils
+ ------------------------------------------------------------------------------------------------------------------*/
- 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;
- }
+ // Refreshes the CSS top/bottom coordinates for each segment element.
+ // Works when called after initial render, after a window resize/zoom for example.
+ updateSegVerticals: function(segs) {
+ this.computeSegVerticals(segs);
+ this.assignSegVerticals(segs);
+ },
- 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
+ // For each segment in an array, computes and assigns its top and bottom properties
+ computeSegVerticals: function(segs) {
+ var i, seg;
+
+ for (i = 0; i < segs.length; i++) {
+ seg = segs[i];
+ seg.top = this.computeDateTop(seg.start, seg.start);
+ seg.bottom = this.computeDateTop(seg.end, seg.start);
}
+ },
- return props;
+
+ // Given segments that already have their top/bottom properties computed, applies those values to
+ // the segments' elements.
+ assignSegVerticals: function(segs) {
+ var i, seg;
+
+ for (i = 0; i < segs.length; i++) {
+ seg = segs[i];
+ seg.el.css(this.generateSegVerticalCss(seg));
+ }
},
@@ -6865,36 +7024,155 @@ TimeGrid.mixin({
},
- // Given a flat array of segments, return an array of sub-arrays, grouped by each segment's col
- groupSegCols: function(segs) {
- var segCols = [];
+ /* 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;
- for (i = 0; i < this.colCnt; i++) {
- segCols.push([]);
- }
+ this.sortEventSegs(segs); // order by certain criteria
+ levels = buildSlotSegLevels(segs);
+ computeForwardSlotSegs(levels);
- for (i = 0; i < segs.length; i++) {
- segCols[segs[i].col].push(segs[i]);
+ 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;
+ }
- return segCols;
+ // 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);
+ }
+ }
},
- sortForwardSlotSegs: function(forwardSegs) {
- forwardSegs.sort(proxy(this, 'compareForwardSlotSegs'));
+ sortForwardSegs: function(forwardSegs) {
+ forwardSegs.sort(proxy(this, 'compareForwardSegs'));
},
// A cmp function for determining which forward segment to rely on more when computing coordinates.
- compareForwardSlotSegs: function(seg1, seg2) {
+ 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.compareSegs(seg1, seg2);
+ 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.view.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;
}
});
@@ -7047,6 +7325,13 @@ var View = FC.View = Class.extend({
// document handlers, bound to `this` object
documentMousedownProxy: null, // TODO: doesn't work with touch
+ // now indicator
+ isNowIndicatorRendered: null,
+ initialNowDate: null, // result first getNow call
+ initialNowQueriedMs: null, // ms time the getNow was called
+ nowIndicatorTimeoutID: null, // for refresh timing of now indicator
+ nowIndicatorIntervalID: null, // "
+
constructor: function(calendar, type, options, intervalDuration) {
@@ -7098,21 +7383,20 @@ var View = FC.View = Class.extend({
------------------------------------------------------------------------------------------------------------------*/
- // Updates all internal dates to center around the given current date
+ // Updates all internal dates to center around the given current unzoned date.
setDate: function(date) {
this.setRange(this.computeRange(date));
},
- // Updates all internal dates for displaying the given range.
- // Expects all values to be normalized (like what computeRange does).
+ // Updates all internal dates for displaying the given unzoned range.
setRange: function(range) {
- $.extend(this, range);
+ $.extend(this, range); // assigns every property to this object's member variables
this.updateTitle();
},
- // Given a single current date, produce information about what range to display.
+ // 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);
@@ -7127,10 +7411,10 @@ var View = FC.View = Class.extend({
}
else { // needs to have a time?
if (!intervalStart.hasTime()) {
- intervalStart = this.calendar.rezoneDate(intervalStart); // convert to current timezone, with 00:00
+ intervalStart = this.calendar.time(0); // give 00:00 time
}
if (!intervalEnd.hasTime()) {
- intervalEnd = this.calendar.rezoneDate(intervalEnd); // convert to current timezone, with 00:00
+ intervalEnd = this.calendar.time(0); // give 00:00 time
}
}
@@ -7193,7 +7477,11 @@ var View = FC.View = Class.extend({
// Computes what the title at the top of the calendar should be for this view
computeTitle: function() {
return this.formatRange(
- { start: this.intervalStart, end: this.intervalEnd },
+ {
+ // in case intervalStart/End has a time, make sure timezone is correct
+ start: this.calendar.applyTimezone(this.intervalStart),
+ end: this.calendar.applyTimezone(this.intervalEnd)
+ },
this.opt('titleFormat') || this.computeTitleFormat(),
this.opt('titleRangeSeparator')
);
@@ -7220,6 +7508,7 @@ var View = FC.View = Class.extend({
// Utility for formatting a range. Accepts a range object, formatting string, and optional separator.
// Displays all-day ranges naturally, with an inclusive end. Takes the current isRTL into account.
+ // The timezones of the dates within `range` will be respected.
formatRange: function(range, formatStr, separator) {
var end = range.end;
@@ -7264,7 +7553,7 @@ var View = FC.View = Class.extend({
},
- // Does everything necessary to display the view centered around the given date.
+ // Does everything necessary to display the view centered around the given unzoned date.
// Does every type of rendering EXCEPT rendering events.
// Is asychronous and returns a promise.
display: function(date) {
@@ -7275,12 +7564,15 @@ var View = FC.View = Class.extend({
scrollState = this.queryScroll();
}
+ this.calendar.freezeContentHeight();
+
return this.clear().then(function() { // clear the content first (async)
return (
_this.displaying =
$.when(_this.displayView(date)) // displayView might return a promise
.then(function() {
_this.forceScroll(_this.computeInitialScroll(scrollState));
+ _this.calendar.unfreezeContentHeight();
_this.triggerRender();
})
);
@@ -7316,13 +7608,16 @@ var View = FC.View = Class.extend({
this.renderSkeleton();
this.isSkeletonRendered = true;
}
- this.setDate(date);
+ if (date) {
+ this.setDate(date);
+ }
if (this.render) {
this.render(); // TODO: deprecate
}
this.renderDates();
this.updateSize();
this.renderBusinessHours(); // might need coordinates, so should go after updateSize()
+ this.startNowIndicator();
},
@@ -7330,6 +7625,7 @@ var View = FC.View = Class.extend({
// Can be asynchronous and return a promise.
clearView: function() {
this.unselect();
+ this.stopNowIndicator();
this.triggerUnrender();
this.unrenderBusinessHours();
this.unrenderDates();
@@ -7364,18 +7660,6 @@ var View = FC.View = Class.extend({
},
- // Renders business-hours onto the view. Assumes updateSize has already been called.
- renderBusinessHours: function() {
- // subclasses should implement
- },
-
-
- // Unrenders previously-rendered business-hours
- unrenderBusinessHours: function() {
- // subclasses should implement
- },
-
-
// Signals that the view's content has been rendered
triggerRender: function() {
this.trigger('viewRender', this, this, this.el);
@@ -7410,6 +7694,110 @@ var View = FC.View = Class.extend({
},
+ /* Business Hours
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Renders business-hours onto the view. Assumes updateSize has already been called.
+ renderBusinessHours: function() {
+ // subclasses should implement
+ },
+
+
+ // Unrenders previously-rendered business-hours
+ unrenderBusinessHours: function() {
+ // subclasses should implement
+ },
+
+
+ /* Now Indicator
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Immediately render the current time indicator and begins re-rendering it at an interval,
+ // which is defined by this.getNowIndicatorUnit().
+ // TODO: somehow do this for the current whole day's background too
+ startNowIndicator: function() {
+ var _this = this;
+ var unit;
+ var update;
+ var delay; // ms wait value
+
+ if (this.opt('nowIndicator')) {
+ unit = this.getNowIndicatorUnit();
+ if (unit) {
+ update = proxy(this, 'updateNowIndicator'); // bind to `this`
+
+ this.initialNowDate = this.calendar.getNow();
+ this.initialNowQueriedMs = +new Date();
+ this.renderNowIndicator(this.initialNowDate);
+ this.isNowIndicatorRendered = true;
+
+ // wait until the beginning of the next interval
+ delay = this.initialNowDate.clone().startOf(unit).add(1, unit) - this.initialNowDate;
+ this.nowIndicatorTimeoutID = setTimeout(function() {
+ _this.nowIndicatorTimeoutID = null;
+ update();
+ delay = +moment.duration(1, unit);
+ delay = Math.max(100, delay); // prevent too frequent
+ _this.nowIndicatorIntervalID = setInterval(update, delay); // update every interval
+ }, delay);
+ }
+ }
+ },
+
+
+ // rerenders the now indicator, computing the new current time from the amount of time that has passed
+ // since the initial getNow call.
+ updateNowIndicator: function() {
+ if (this.isNowIndicatorRendered) {
+ this.unrenderNowIndicator();
+ this.renderNowIndicator(
+ this.initialNowDate.clone().add(new Date() - this.initialNowQueriedMs) // add ms
+ );
+ }
+ },
+
+
+ // Immediately unrenders the view's current time indicator and stops any re-rendering timers.
+ // Won't cause side effects if indicator isn't rendered.
+ stopNowIndicator: function() {
+ if (this.isNowIndicatorRendered) {
+
+ if (this.nowIndicatorTimeoutID) {
+ clearTimeout(this.nowIndicatorTimeoutID);
+ this.nowIndicatorTimeoutID = null;
+ }
+ if (this.nowIndicatorIntervalID) {
+ clearTimeout(this.nowIndicatorIntervalID);
+ this.nowIndicatorIntervalID = null;
+ }
+
+ this.unrenderNowIndicator();
+ this.isNowIndicatorRendered = false;
+ }
+ },
+
+
+ // Returns a string unit, like 'second' or 'minute' that defined how often the current time indicator
+ // should be refreshed. If something falsy is returned, no time indicator is rendered at all.
+ getNowIndicatorUnit: function() {
+ // subclasses should implement
+ },
+
+
+ // Renders a current time indicator at the given datetime
+ renderNowIndicator: function(date) {
+ // subclasses should implement
+ },
+
+
+ // Undoes the rendering actions from renderNowIndicator
+ unrenderNowIndicator: function() {
+ // subclasses should implement
+ },
+
+
/* Dimensions
------------------------------------------------------------------------------------------------------------------*/
@@ -7424,6 +7812,7 @@ var View = FC.View = Class.extend({
this.updateHeight(isResize);
this.updateWidth(isResize);
+ this.updateNowIndicator();
if (isResize) {
this.setScroll(scrollState);
@@ -7532,12 +7921,19 @@ var View = FC.View = Class.extend({
// Does everything necessary to clear the view's currently-rendered events
clearEvents: function() {
+ var scrollState;
+
if (this.isEventsRendered) {
+
+ // TODO: optimize: if we know this is part of a displayEvents call, don't queryScroll/setScroll
+ scrollState = this.queryScroll();
+
this.triggerEventUnrender();
if (this.destroyEvents) {
this.destroyEvents(); // TODO: deprecate
}
this.unrenderEvents();
+ this.setScroll(scrollState);
this.isEventsRendered = false;
}
},
@@ -7648,7 +8044,7 @@ var View = FC.View = Class.extend({
// Must be called when an event in the view is dropped onto new location.
- // `dropLocation` is an object that contains the new start/end/allDay values for the event.
+ // `dropLocation` is an object that contains the new zoned start/end/allDay values for the event.
reportEventDrop: function(event, dropLocation, largeUnit, el, ev) {
var calendar = this.calendar;
var mutateResult = calendar.mutateEvent(event, dropLocation, largeUnit);
@@ -7674,7 +8070,7 @@ var View = FC.View = Class.extend({
// Must be called when an external element, via jQuery UI, has been dropped onto the calendar.
// `meta` is the parsed data that has been embedded into the dragging event.
- // `dropLocation` is an object that contains the new start/end/allDay values for the event.
+ // `dropLocation` is an object that contains the new zoned start/end/allDay values for the event.
reportExternalDrop: function(meta, dropLocation, el, ev, ui) {
var eventProps = meta.eventProps;
var eventInput;
@@ -7774,31 +8170,37 @@ var View = FC.View = Class.extend({
------------------------------------------------------------------------------------------------------------------*/
- // Selects a date range on the view. `start` and `end` are both Moments.
+ // Selects a date span on the view. `start` and `end` are both Moments.
// `ev` is the native mouse event that begin the interaction.
- select: function(range, ev) {
+ select: function(span, ev) {
this.unselect(ev);
- this.renderSelection(range);
- this.reportSelection(range, ev);
+ this.renderSelection(span);
+ this.reportSelection(span, ev);
},
// Renders a visual indication of the selection
- renderSelection: function(range) {
+ renderSelection: function(span) {
// subclasses should implement
},
// Called when a new selection is made. Updates internal state and triggers handlers.
- reportSelection: function(range, ev) {
+ reportSelection: function(span, ev) {
this.isSelected = true;
- this.triggerSelect(range, ev);
+ this.triggerSelect(span, ev);
},
// Triggers handlers to 'select'
- triggerSelect: function(range, ev) {
- this.trigger('select', null, range.start, range.end, ev);
+ triggerSelect: function(span, ev) {
+ this.trigger(
+ 'select',
+ null,
+ this.calendar.applyTimezone(span.start), // convert to calendar's tz for external API
+ this.calendar.applyTimezone(span.end), // "
+ ev
+ );
},
@@ -7843,8 +8245,14 @@ var View = FC.View = Class.extend({
// Triggers handlers to 'dayClick'
- triggerDayClick: function(date, dayEl, ev) {
- this.trigger('dayClick', dayEl, date, ev);
+ // Span has start/end of the clicked area. Only the start is useful.
+ triggerDayClick: function(span, dayEl, ev) {
+ this.trigger(
+ 'dayClick',
+ dayEl,
+ this.calendar.applyTimezone(span.start), // convert to calendar's timezone for external API
+ ev
+ );
},
@@ -8179,12 +8587,13 @@ var Calendar = FC.Calendar = Class.extend({
},
- // Given arguments to the select method in the API, returns a range
- buildSelectRange: function(start, end) {
+ // Given arguments to the select method in the API, returns a span (unzoned start/end and other info)
+ buildSelectSpan: function(zonedStartInput, zonedEndInput) {
+ var start = this.moment(zonedStartInput).stripZone();
+ var end;
- start = this.moment(start);
- if (end) {
- end = this.moment(end);
+ if (zonedEndInput) {
+ end = this.moment(zonedEndInput).stripZone();
}
else if (start.hasTime()) {
end = start.clone().add(this.defaultTimedEventDuration);
@@ -8326,21 +8735,36 @@ function Calendar_constructor(element, overrides) {
};
- // Returns a copy of the given date in the current timezone of it is ambiguously zoned.
- // This will also give the date an unambiguous time.
- t.rezoneDate = function(date) {
- return t.moment(date.toArray());
+ // Returns a copy of the given date in the current timezone. Has no effect on dates without times.
+ t.applyTimezone = function(date) {
+ if (!date.hasTime()) {
+ return date.clone();
+ }
+
+ var zonedDate = t.moment(date.toArray());
+ var timeAdjust = date.time() - zonedDate.time();
+ var adjustedZonedDate;
+
+ // Safari sometimes has problems with this coersion when near DST. Adjust if necessary. (bug #2396)
+ if (timeAdjust) { // is the time result different than expected?
+ adjustedZonedDate = zonedDate.clone().add(timeAdjust); // add milliseconds
+ if (date.time() - adjustedZonedDate.time() === 0) { // does it match perfectly now?
+ zonedDate = adjustedZonedDate;
+ }
+ }
+
+ return zonedDate;
};
- // Returns a moment for the current date, as defined by the client's computer,
- // or overridden by the `now` option.
+ // Returns a moment for the current date, as defined by the client's computer or from the `now` option.
+ // Will return an moment with an ambiguous timezone.
t.getNow = function() {
var now = options.now;
if (typeof now === 'function') {
now = now();
}
- return t.moment(now);
+ return t.moment(now).stripZone();
};
@@ -8355,9 +8779,10 @@ function Calendar_constructor(element, overrides) {
};
- // Given an event's allDay status and start date, return swhat its fallback end date should be.
- t.getDefaultEventEnd = function(allDay, start) { // TODO: rename to computeDefaultEventEnd
- var end = start.clone();
+ // Given an event's allDay status and start date, return what its fallback end date should be.
+ // TODO: rename to computeDefaultEventEnd
+ t.getDefaultEventEnd = function(allDay, zonedStart) {
+ var end = zonedStart.clone();
if (allDay) {
end.stripTime().add(t.defaultAllDayEventDuration);
@@ -8407,8 +8832,8 @@ function Calendar_constructor(element, overrides) {
var suggestedViewHeight;
var windowResizeProxy; // wraps the windowResize function
var ignoreWindowResize = 0;
- var date;
var events = [];
+ var date; // unzoned
@@ -8416,11 +8841,12 @@ function Calendar_constructor(element, overrides) {
// -----------------------------------------------------------------------------------
+ // compute the initial ambig-timezone date
if (options.defaultDate != null) {
- date = t.moment(options.defaultDate);
+ date = t.moment(options.defaultDate).stripZone();
}
else {
- date = t.getNow();
+ date = t.getNow(); // getNow already returns unzoned
}
@@ -8537,8 +8963,7 @@ function Calendar_constructor(element, overrides) {
) {
if (elementVisible()) {
- freezeContentHeight();
- currentView.display(date);
+ currentView.display(date); // will call freezeContentHeight
unfreezeContentHeight(); // immediately unfreeze regardless of whether display is async
// need to do this after View::render, so dates are calculated
@@ -8706,9 +9131,10 @@ function Calendar_constructor(element, overrides) {
-----------------------------------------------------------------------------*/
- function select(start, end) {
+ // this public method receives start/end dates in any format, with any timezone
+ function select(zonedStartInput, zonedEndInput) {
currentView.select(
- t.buildSelectRange.apply(t, arguments)
+ t.buildSelectSpan.apply(t, arguments)
);
}
@@ -8755,8 +9181,8 @@ function Calendar_constructor(element, overrides) {
}
- function gotoDate(dateInput) {
- date = t.moment(dateInput);
+ function gotoDate(zonedDateInput) {
+ date = t.moment(zonedDateInput).stripZone();
renderView();
}
@@ -8775,13 +9201,14 @@ function Calendar_constructor(element, overrides) {
viewType = viewType || 'day'; // day is default zoom
spec = t.getViewSpec(viewType) || t.getUnitViewSpec(viewType);
- date = newDate;
+ date = newDate.clone();
renderView(spec ? spec.type : null);
}
+ // for external API
function getDate() {
- return date.clone();
+ return t.applyTimezone(date); // infuse the calendar's timezone
}
@@ -8790,6 +9217,9 @@ function Calendar_constructor(element, overrides) {
-----------------------------------------------------------------------------*/
// TODO: move this into the view
+ t.freezeContentHeight = freezeContentHeight;
+ t.unfreezeContentHeight = unfreezeContentHeight;
+
function freezeContentHeight() {
content.css({
@@ -8877,6 +9307,8 @@ Calendar.defaults = {
//editable: false,
+ //nowIndicator: false,
+
scrollTime: '06:00:00',
// event ajax
@@ -9443,9 +9875,8 @@ function EventManager(options) { // assumed to be a calendar
t.removeEvents = removeEvents;
t.clientEvents = clientEvents;
t.mutateEvent = mutateEvent;
- t.normalizeEventRange = normalizeEventRange;
- t.normalizeEventRangeTimes = normalizeEventRangeTimes;
- t.ensureVisibleEventRange = ensureVisibleEventRange;
+ t.normalizeEventDates = normalizeEventDates;
+ t.normalizeEventTimes = normalizeEventTimes;
// imports
@@ -9475,13 +9906,12 @@ function EventManager(options) { // assumed to be a calendar
/* Fetching
-----------------------------------------------------------------------------*/
-
-
+
+
+ // start and end are assumed to be unzoned
function isFetchNeeded(start, end) {
return !rangeStart || // nothing has been fetched yet?
- // or, a part of the new range is outside of the old range? (after normalizing)
- start.clone().stripZone() < rangeStart.clone().stripZone() ||
- end.clone().stripZone() > rangeEnd.clone().stripZone();
+ start < rangeStart || end > rangeEnd; // is part of the new range outside of the old range?
}
@@ -9932,7 +10362,7 @@ function EventManager(options) { // assumed to be a calendar
source ? source.allDayDefault : undefined,
options.allDayDefault
);
- // still undefined? normalizeEventRange will calculate it
+ // still undefined? normalizeEventDates will calculate it
}
assignDatesToEvent(start, end, allDay, out);
@@ -9948,76 +10378,56 @@ function EventManager(options) { // assumed to be a calendar
event.start = start;
event.end = end;
event.allDay = allDay;
- normalizeEventRange(event);
+ normalizeEventDates(event);
backupEventDates(event);
}
// Ensures proper values for allDay/start/end. Accepts an Event object, or a plain object with event-ish properties.
// NOTE: Will modify the given object.
- function normalizeEventRange(props) {
+ function normalizeEventDates(eventProps) {
- normalizeEventRangeTimes(props);
+ normalizeEventTimes(eventProps);
- if (props.end && !props.end.isAfter(props.start)) {
- props.end = null;
+ if (eventProps.end && !eventProps.end.isAfter(eventProps.start)) {
+ eventProps.end = null;
}
- if (!props.end) {
+ if (!eventProps.end) {
if (options.forceEventDuration) {
- props.end = t.getDefaultEventEnd(props.allDay, props.start);
+ eventProps.end = t.getDefaultEventEnd(eventProps.allDay, eventProps.start);
}
else {
- props.end = null;
+ eventProps.end = null;
}
}
}
// Ensures the allDay property exists and the timeliness of the start/end dates are consistent
- function normalizeEventRangeTimes(range) {
- if (range.allDay == null) {
- range.allDay = !(range.start.hasTime() || (range.end && range.end.hasTime()));
+ function normalizeEventTimes(eventProps) {
+ if (eventProps.allDay == null) {
+ eventProps.allDay = !(eventProps.start.hasTime() || (eventProps.end && eventProps.end.hasTime()));
}
- if (range.allDay) {
- range.start.stripTime();
- if (range.end) {
+ if (eventProps.allDay) {
+ eventProps.start.stripTime();
+ if (eventProps.end) {
// TODO: consider nextDayThreshold here? If so, will require a lot of testing and adjustment
- range.end.stripTime();
+ eventProps.end.stripTime();
}
}
else {
- if (!range.start.hasTime()) {
- range.start = t.rezoneDate(range.start); // will assign a 00:00 time
+ if (!eventProps.start.hasTime()) {
+ eventProps.start = t.applyTimezone(eventProps.start.time(0)); // will assign a 00:00 time
}
- if (range.end && !range.end.hasTime()) {
- range.end = t.rezoneDate(range.end); // will assign a 00:00 time
+ if (eventProps.end && !eventProps.end.hasTime()) {
+ eventProps.end = t.applyTimezone(eventProps.end.time(0)); // will assign a 00:00 time
}
}
}
- // If `range` is a proper range with a start and end, returns the original object.
- // If missing an end, computes a new range with an end, computing it as if it were an event.
- // TODO: make this a part of the event -> eventRange system
- function ensureVisibleEventRange(range) {
- var allDay;
-
- if (!range.end) {
-
- allDay = range.allDay; // range might be more event-ish than we think
- if (allDay == null) {
- allDay = !range.start.hasTime();
- }
-
- range = $.extend({}, range); // make a copy, copying over other misc properties
- range.end = t.getDefaultEventEnd(allDay, range.start);
- }
- return range;
- }
-
-
// If the given event is a recurring event, break it down into an array of individual instances.
// If not a recurring event, return an array with the single original event.
// If given a falsy input (probably because of a failed buildEventFromInput call), returns an empty array.
@@ -10131,7 +10541,7 @@ function EventManager(options) { // assumed to be a calendar
if (newProps.allDay == null) { // is null or undefined?
newProps.allDay = event.allDay;
}
- normalizeEventRange(newProps);
+ normalizeEventDates(newProps);
// create normalized versions of the original props to compare against
// need a real end value, for diffing
@@ -10140,7 +10550,7 @@ function EventManager(options) { // assumed to be a calendar
end: event._end ? event._end.clone() : t.getDefaultEventEnd(event._allDay, event._start),
allDay: newProps.allDay // normalize the dates in the same regard as the new properties
};
- normalizeEventRange(oldProps);
+ normalizeEventDates(oldProps);
// need to clear the end date if explicitly changed to null
clearEnd = event._end !== null && newProps.end === null;
@@ -10225,7 +10635,7 @@ function EventManager(options) { // assumed to be a calendar
end: event._end,
allDay: allDay // normalize the dates in the same regard as the new properties
};
- normalizeEventRange(newProps); // massages start/end/allDay
+ normalizeEventDates(newProps); // massages start/end/allDay
// strip or ensure the end date
if (clearEnd) {
@@ -10326,12 +10736,13 @@ function EventManager(options) { // assumed to be a calendar
/* Overlapping / Constraining
-----------------------------------------------------------------------------------------*/
- t.isEventRangeAllowed = isEventRangeAllowed;
- t.isSelectionRangeAllowed = isSelectionRangeAllowed;
- t.isExternalDropRangeAllowed = isExternalDropRangeAllowed;
+ t.isEventSpanAllowed = isEventSpanAllowed;
+ t.isExternalSpanAllowed = isExternalSpanAllowed;
+ t.isSelectionSpanAllowed = isSelectionSpanAllowed;
- function isEventRangeAllowed(range, event) {
+ // Determines if the given event can be relocated to the given span (unzoned start/end with other misc data)
+ function isEventSpanAllowed(span, event) {
var source = event.source || {};
var constraint = firstDefined(
event.constraint,
@@ -10343,57 +10754,47 @@ function EventManager(options) { // assumed to be a calendar
source.overlap,
options.eventOverlap
);
-
- range = ensureVisibleEventRange(range); // ensure a proper range with an end for isRangeAllowed
-
- return isRangeAllowed(range, constraint, overlap, event);
- }
-
-
- function isSelectionRangeAllowed(range) {
- return isRangeAllowed(range, options.selectConstraint, options.selectOverlap);
+ return isSpanAllowed(span, constraint, overlap, event);
}
- // when `eventProps` is defined, consider this an event.
- // `eventProps` can contain misc non-date-related info about the event.
- function isExternalDropRangeAllowed(range, eventProps) {
+ // Determines if an external event can be relocated to the given span (unzoned start/end with other misc data)
+ function isExternalSpanAllowed(eventSpan, eventLocation, eventProps) {
var eventInput;
var event;
// note: very similar logic is in View's reportExternalDrop
if (eventProps) {
- eventInput = $.extend({}, eventProps, range);
+ eventInput = $.extend({}, eventProps, eventLocation);
event = expandEvent(buildEventFromInput(eventInput))[0];
}
if (event) {
- return isEventRangeAllowed(range, event);
+ return isEventSpanAllowed(eventSpan, event);
}
else { // treat it as a selection
- range = ensureVisibleEventRange(range); // ensure a proper range with an end for isSelectionRangeAllowed
-
- return isSelectionRangeAllowed(range);
+ return isSelectionSpanAllowed(eventSpan);
}
}
- // Returns true if the given range (caused by an event drop/resize or a selection) is allowed to exist
+ // Determines the given span (unzoned start/end with other misc data) can be selected.
+ function isSelectionSpanAllowed(span) {
+ return isSpanAllowed(span, options.selectConstraint, options.selectOverlap);
+ }
+
+
+ // Returns true if the given span (caused by an event drop/resize or a selection) is allowed to exist
// according to the constraint/overlap settings.
// `event` is not required if checking a selection.
- function isRangeAllowed(range, constraint, overlap, event) {
+ function isSpanAllowed(span, constraint, overlap, event) {
var constraintEvents;
var anyContainment;
var peerEvents;
var i, peerEvent;
var peerOverlap;
- // normalize. fyi, we're normalizing in too many places :(
- range = $.extend({}, range); // copy all properties in case there are misc non-date properties
- range.start = range.start.clone().stripZone();
- range.end = range.end.clone().stripZone();
-
// the range must be fully contained by at least one of produced constraint events
if (constraint != null) {
@@ -10403,7 +10804,7 @@ function EventManager(options) { // assumed to be a calendar
anyContainment = false;
for (i = 0; i < constraintEvents.length; i++) {
- if (eventContainsRange(constraintEvents[i], range)) {
+ if (eventContainsRange(constraintEvents[i], span)) {
anyContainment = true;
break;
}
@@ -10414,13 +10815,13 @@ function EventManager(options) { // assumed to be a calendar
}
}
- peerEvents = t.getPeerEvents(event, range);
+ peerEvents = t.getPeerEvents(span, event);
for (i = 0; i < peerEvents.length; i++) {
peerEvent = peerEvents[i];
// there needs to be an actual intersection before disallowing anything
- if (eventIntersectsRange(peerEvent, range)) {
+ if (eventIntersectsRange(peerEvent, span)) {
// evaluate overlap for the given range and short-circuit if necessary
if (overlap === false) {
@@ -10500,8 +10901,8 @@ function EventManager(options) { // assumed to be a calendar
// Returns a list of events that the given event should be compared against when being considered for a move to
-// the specified range. Attached to the Calendar's prototype because EventManager is a mixin for a Calendar.
-Calendar.prototype.getPeerEvents = function(event, range) {
+// the specified span. Attached to the Calendar's prototype because EventManager is a mixin for a Calendar.
+Calendar.prototype.getPeerEvents = function(span, event) {
var cache = this.getEventCache();
var peerEvents = [];
var i, otherEvent;
@@ -10810,8 +11211,8 @@ var BasicView = FC.BasicView = View.extend({
// Renders a visual indication of a selection
- renderSelection: function(range) {
- this.dayGrid.renderSelection(range);
+ renderSelection: function(span) {
+ this.dayGrid.renderSelection(span);
},
@@ -11072,15 +11473,6 @@ var AgendaView = FC.AgendaView = View.extend({
},
- renderBusinessHours: function() {
- this.timeGrid.renderBusinessHours();
-
- if (this.dayGrid) {
- this.dayGrid.renderBusinessHours();
- }
- },
-
-
// Builds the HTML skeleton for the view.
// The day-grid and time-grid components will render inside containers defined by this HTML.
renderSkeletonHtml: function() {
@@ -11118,6 +11510,47 @@ var AgendaView = FC.AgendaView = View.extend({
},
+ /* Business Hours
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ renderBusinessHours: function() {
+ this.timeGrid.renderBusinessHours();
+
+ if (this.dayGrid) {
+ this.dayGrid.renderBusinessHours();
+ }
+ },
+
+
+ unrenderBusinessHours: function() {
+ this.timeGrid.unrenderBusinessHours();
+
+ if (this.dayGrid) {
+ this.dayGrid.unrenderBusinessHours();
+ }
+ },
+
+
+ /* Now Indicator
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ getNowIndicatorUnit: function() {
+ return this.timeGrid.getNowIndicatorUnit();
+ },
+
+
+ renderNowIndicator: function(date) {
+ this.timeGrid.renderNowIndicator(date);
+ },
+
+
+ unrenderNowIndicator: function() {
+ this.timeGrid.unrenderNowIndicator();
+ },
+
+
/* Dimensions
------------------------------------------------------------------------------------------------------------------*/
@@ -11331,12 +11764,12 @@ var AgendaView = FC.AgendaView = View.extend({
// Renders a visual indication of a selection
- renderSelection: function(range) {
- if (range.start.hasTime() || range.end.hasTime()) {
- this.timeGrid.renderSelection(range);
+ renderSelection: function(span) {
+ if (span.start.hasTime() || span.end.hasTime()) {
+ this.timeGrid.renderSelection(span);
}
else if (this.dayGrid) {
- this.dayGrid.renderSelection(range);
+ this.dayGrid.renderSelection(span);
}
},
@@ -11353,6 +11786,7 @@ var AgendaView = FC.AgendaView = View.extend({
// Methods that will customize the rendering behavior of the AgendaView's timeGrid
+// TODO: move into TimeGrid
var agendaTimeGridMethods = {