| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790 |
- /* An abstract class from which other views inherit from
- ----------------------------------------------------------------------------------------------------------------------*/
- // Newer methods should be written as prototype methods, not in the monster `View` function at the bottom.
- View.prototype = {
- calendar: null, // owner Calendar object
- coordMap: null, // a CoordMap object for converting pixel regions to dates
- el: null, // the view's containing element. set by Calendar
- // important Moments
- start: null, // the date of the very first cell
- end: null, // the date after the very last cell
- intervalStart: null, // the start of the interval of time the view represents (1st of month for month view)
- intervalEnd: null, // the exclusive end of the interval of time the view represents
- // used for cell-to-date and date-to-cell calculations
- rowCnt: null, // # of weeks
- colCnt: null, // # of days displayed in a week
- segs: null, // array of rendered event segment objects
- isSelected: false, // boolean whether cells are user-selected or not
- // subclasses can optionally use a scroll container
- scrollerEl: null, // the element that will most likely scroll when content is too tall
- scrollTop: null, // cached vertical scroll value
- // classNames styled by jqui themes
- widgetHeaderClass: null,
- widgetContentClass: null,
- highlightStateClass: null,
- dayRowThemeClass: null, // sets the theme className applied to DayGrid rows (none by default)
- // document handlers, bound to `this` object
- documentMousedownProxy: null,
- documentDragStartProxy: null,
- // Serves as a "constructor" to suppliment the monster `View` constructor below
- init: function() {
- var tm = this.opt('theme') ? 'ui' : 'fc';
- this.widgetHeaderClass = tm + '-widget-header';
- this.widgetContentClass = tm + '-widget-content';
- this.highlightStateClass = tm + '-state-highlight';
- // save reference to `this`-bound handlers and attach to document
- $(document)
- .on('mousedown', this.documentMousedownProxy = $.proxy(this, 'documentMousedown'))
- .on('dragstart', this.documentDragStartProxy = $.proxy(this, 'documentDragStart')); // jqui drag
- },
- // Renders the view inside an already-defined `this.el`.
- // Subclasses should override this and then call the super method afterwards.
- render: function() {
- this.updateHeight();
- this.updateWidth();
- this.trigger('viewRender', this, this, this.el);
- },
- // Clears all view rendering, event elements, and unregisters handlers
- destroy: function() {
- this.unselect();
- this.trigger('viewDestroy', this, this, this.el);
- this.destroyEvents();
- this.el.empty(); // removes inner contents but leaves the element intact
- $(document)
- .off('mousedown', this.documentMousedownProxy)
- .off('dragstart', this.documentDragStartProxy);
- },
- // Used to determine what happens when the users clicks next/prev. Given -1 for prev, 1 for next.
- // Should apply the delta to `date` (a Moment) and return it.
- incrementDate: function(date, delta) {
- // subclasses should implement
- },
- /* Dimensions
- ------------------------------------------------------------------------------------------------------------------*/
- // Refreshes the horizontal dimensions of the calendar
- updateWidth: function() {
- // subclasses should implement
- },
- // Refreshes the vertical dimensions of the calendar
- updateHeight: function() {
- var calendar = this.calendar; // we poll the calendar for height information
- this.setHeight(
- calendar.getSuggestedViewHeight(),
- calendar.isHeightAuto()
- );
- },
- // Updates the vertical dimensions of the calendar to the specified height.
- // if `isAuto` is set to true, height becomes merely a suggestion and the view should use its "natural" height.
- setHeight: function(height, isAuto) {
- // subclasses should implement
- },
- // Given the total height of the view, return the number of pixels that should be used for the scroller.
- // Utility for subclasses.
- computeScrollerHeight: function(totalHeight) {
- // `otherHeight` is the cumulative height of everything that is not the scrollerEl in the view (header+borders)
- var otherHeight = this.el.outerHeight() - this.scrollerEl.height();
- return totalHeight - otherHeight;
- },
- // Called for remembering the current scroll value of the scroller.
- // Should be called before there is a destructive operation (like removing DOM elements) that might inadvertently
- // change the scroll of the container.
- recordScroll: function() {
- this.scrollTop = this.scrollerEl.scrollTop();
- },
- // Set the scroll value of the scroller to the previously recorded value.
- // Should be called after we know the view's dimensions have been restored following some type of destructive
- // operation (like temporarily removing DOM elements).
- restoreScroll: function() {
- if (this.scrollTop !== null) {
- this.scrollerEl.scrollTop(this.scrollTop);
- }
- },
- /* Events
- ------------------------------------------------------------------------------------------------------------------*/
- // Renders the events onto the view.
- // Should be overriden by subclasses. Subclasses should assign `this.segs` and call the super-method afterwards.
- renderEvents: function(events) {
- this.segEach(function(seg) {
- seg.el.data('fc-seg', seg); // store info about the segment object. used by handlers
- this.trigger('eventAfterRender', seg.event, seg.event, seg.el);
- });
- this.trigger('eventAfterAllRender');
- },
- // Removes event elements from the view.
- // Should be overridden by subclasses. Actual element destruction should happen first, then call super-method.
- destroyEvents: function() {
- this.segEach(function(seg) {
- this.trigger('eventDestroy', seg.event, seg.event, seg.el);
- });
- this.segs = [];
- },
- // Given an event and the default element used for rendering, returns the element that should actually be used.
- // Basically runs events and elements through the eventRender hook.
- resolveEventEl: function(event, el) {
- var custom = this.trigger('eventRender', event, event, el);
- if (custom === false) { // means don't render at all
- el = null;
- }
- else if (custom && custom !== true) {
- el = $(custom);
- }
- return el;
- },
- // Hides all rendered event segments linked to the given event
- showEvent: function(event) {
- this.segEach(function(seg) {
- seg.el.css('visibility', '');
- }, event);
- },
- // Shows all rendered event segments linked to the given event
- hideEvent: function(event) {
- this.segEach(function(seg) {
- seg.el.css('visibility', 'hidden');
- }, event);
- },
- // Iterates through event segments. Goes through all by default.
- // If the optional `event` argument is specified, only iterates through segments linked to that event.
- // The `this` value of the callback function will be the view.
- segEach: function(func, event) {
- var segs = this.segs || [];
- var i;
- for (i = 0; i < segs.length; i++) {
- if (!event || segs[i].event._id === event._id) {
- func.call(this, segs[i]);
- }
- }
- },
- /* Event Drag Visualization
- ------------------------------------------------------------------------------------------------------------------*/
- // Renders a visual indication of an event hovering over the specified date.
- // `end` is a Moment and might be null.
- // `seg` might be null. if specified, it is the segment object of the event being dragged.
- // otherwise, an external event from outside the calendar is being dragged.
- renderDrag: function(start, end, seg) {
- // subclasses should implement
- },
- // Unrenders a visual indication of event hovering
- destroyDrag: function() {
- // subclasses should implement
- },
- // Handler for accepting externally dragged events being dropped in the view.
- // Gets called when jqui's 'dragstart' is fired.
- documentDragStart: function(ev, ui) {
- var _this = this;
- var dropDate = null;
- var dragListener;
- if (this.opt('droppable')) { // only listen if this setting is on
- // listener that tracks mouse movement over date-associated pixel regions
- dragListener = new DragListener(this.coordMap, {
- cellOver: function(cell, date) {
- dropDate = date;
- _this.renderDrag(date);
- },
- cellOut: function() {
- dropDate = null;
- _this.destroyDrag();
- }
- });
- // gets called, only once, when jqui drag is finished
- $(document).one('dragstop', function(ev, ui) {
- _this.destroyDrag();
- if (dropDate) {
- _this.trigger('drop', ev.target, dropDate, ev, ui);
- }
- });
- dragListener.startDrag(ev); // start listening immediately
- }
- },
- /* Selection
- ------------------------------------------------------------------------------------------------------------------*/
- // Selects a date range on the view. `start` and `end` are both Moments.
- // `ev` is the native mouse event that begin the interaction.
- select: function(start, end, ev) {
- this.unselect(ev);
- this.renderSelection(start, end);
- this.reportSelection(start, end, ev);
- },
- // Renders a visual indication of the selection
- renderSelection: function(start, end) {
- // subclasses should implement
- },
- // Called when a new selection is made. Updates internal state and triggers handlers.
- reportSelection: function(start, end, ev) {
- this.isSelected = true;
- this.trigger('select', null, start, end, ev);
- },
- // Undoes a selection. updates in the internal state and triggers handlers.
- // `ev` is the native mouse event that began the interaction.
- unselect: function(ev) {
- if (this.isSelected) {
- this.isSelected = false;
- this.destroySelection();
- this.trigger('unselect', null, ev);
- }
- },
- // Unrenders a visual indication of selection
- destroySelection: function() {
- // subclasses should implement
- },
- // Handler for unselecting when the user clicks something and the 'unselectAuto' setting is on
- documentMousedown: function(ev) {
- var ignore;
- // is there a selection, and has the user made a proper left click?
- if (this.isSelected && this.opt('unselectAuto') && isPrimaryMouseButton(ev)) {
- // only unselect if the clicked element is not identical to or inside of an 'unselectCancel' element
- ignore = this.opt('unselectCancel');
- if (!ignore || !$(ev.target).closest(ignore).length) {
- this.unselect(ev);
- }
- }
- }
- };
- // We are mixing JavaScript OOP design patterns here by putting methods and member variables in the closed scope of the
- // constructor. Going forward, methods should be part of the prototype.
- function View(calendar) {
- var t = this;
-
- // exports
- t.calendar = calendar;
- t.opt = opt;
- t.trigger = trigger;
- t.isEventDraggable = isEventDraggable;
- t.isEventResizable = isEventResizable;
- t.eventDrop = eventDrop;
- t.eventResize = eventResize;
-
- // imports
- var reportEventChange = calendar.reportEventChange;
-
- // locals
- var options = calendar.options;
- var nextDayThreshold = moment.duration(options.nextDayThreshold);
- t.init(); // the "constructor" that concerns the prototype methods
-
-
- function opt(name) {
- var v = options[name];
- if ($.isPlainObject(v) && !isForcedAtomicOption(name)) {
- return smartProperty(v, t.name);
- }
- return v;
- }
-
- function trigger(name, thisObj) {
- return calendar.trigger.apply(
- calendar,
- [name, thisObj || t].concat(Array.prototype.slice.call(arguments, 2), [t])
- );
- }
-
- /* Event Editable Boolean Calculations
- ------------------------------------------------------------------------------*/
-
- function isEventDraggable(event) {
- var source = event.source || {};
- return firstDefined(
- event.startEditable,
- source.startEditable,
- opt('eventStartEditable'),
- event.editable,
- source.editable,
- opt('editable')
- );
- }
-
-
- function isEventResizable(event) {
- var source = event.source || {};
- return firstDefined(
- event.durationEditable,
- source.durationEditable,
- opt('eventDurationEditable'),
- event.editable,
- source.editable,
- opt('editable')
- );
- }
-
-
-
- /* Event Elements
- ------------------------------------------------------------------------------*/
- // Compute the text that should be displayed on an event's element.
- // Based off the settings of the view. Possible signatures:
- // .getEventTimeText(event, formatStr)
- // .getEventTimeText(startMoment, endMoment, formatStr)
- // .getEventTimeText(startMoment, null, formatStr)
- // `timeFormat` is used but the `formatStr` argument can be used to override.
- t.getEventTimeText = function(event, formatStr) {
- var start;
- var end;
- if (typeof event === 'object' && typeof formatStr === 'object') {
- // first two arguments are actually moments (or null). shift arguments.
- start = event;
- end = formatStr;
- formatStr = arguments[2];
- }
- else {
- // otherwise, an event object was the first argument
- start = event.start;
- end = event.end;
- }
- formatStr = formatStr || opt('timeFormat');
- if (end && opt('displayEventEnd')) {
- return calendar.formatRange(start, end, formatStr);
- }
- else {
- return calendar.formatDate(start, formatStr);
- }
- };
-
-
- /* Event Modification Reporting
- ---------------------------------------------------------------------------------*/
-
- function eventDrop(el, event, newStart, ev) {
- var mutateResult = calendar.mutateEvent(event, newStart, null);
- trigger(
- 'eventDrop',
- el,
- event,
- mutateResult.dateDelta,
- function() {
- mutateResult.undo();
- reportEventChange();
- },
- ev,
- {} // jqui dummy
- );
- reportEventChange();
- }
- function eventResize(el, event, newEnd, ev) {
- var mutateResult = calendar.mutateEvent(event, null, newEnd);
- trigger(
- 'eventResize',
- el,
- event,
- mutateResult.durationDelta,
- function() {
- mutateResult.undo();
- reportEventChange();
- },
- ev,
- {} // jqui dummy
- );
- reportEventChange();
- }
- // ====================================================================================================
- // Utilities for day "cells"
- // ====================================================================================================
- // The "basic" views are completely made up of day cells.
- // The "agenda" views have day cells at the top "all day" slot.
- // This was the obvious common place to put these utilities, but they should be abstracted out into
- // a more meaningful class (like DayEventRenderer).
- // ====================================================================================================
- // For determining how a given "cell" translates into a "date":
- //
- // 1. Convert the "cell" (row and column) into a "cell offset" (the # of the cell, cronologically from the first).
- // Keep in mind that column indices are inverted with isRTL. This is taken into account.
- //
- // 2. Convert the "cell offset" to a "day offset" (the # of days since the first visible day in the view).
- //
- // 3. Convert the "day offset" into a "date" (a Moment).
- //
- // The reverse transformation happens when transforming a date into a cell.
- // exports
- t.isHiddenDay = isHiddenDay;
- t.skipHiddenDays = skipHiddenDays;
- t.getCellsPerWeek = getCellsPerWeek;
- t.dateToCell = dateToCell;
- t.dateToDayOffset = dateToDayOffset;
- t.dayOffsetToCellOffset = dayOffsetToCellOffset;
- t.cellOffsetToCell = cellOffsetToCell;
- t.cellToDate = cellToDate;
- t.cellToCellOffset = cellToCellOffset;
- t.cellOffsetToDayOffset = cellOffsetToDayOffset;
- t.dayOffsetToDate = dayOffsetToDate;
- t.rangeToSegments = rangeToSegments;
- t.isMultiDayEvent = isMultiDayEvent;
- // internals
- var hiddenDays = opt('hiddenDays') || []; // array of day-of-week indices that are hidden
- var isHiddenDayHash = []; // is the day-of-week hidden? (hash with day-of-week-index -> bool)
- var cellsPerWeek;
- var dayToCellMap = []; // hash from dayIndex -> cellIndex, for one week
- var cellToDayMap = []; // hash from cellIndex -> dayIndex, for one week
- var isRTL = opt('isRTL');
- // initialize important internal variables
- (function() {
- if (opt('weekends') === false) {
- hiddenDays.push(0, 6); // 0=sunday, 6=saturday
- }
- // Loop through a hypothetical week and determine which
- // days-of-week are hidden. Record in both hashes (one is the reverse of the other).
- for (var dayIndex=0, cellIndex=0; dayIndex<7; dayIndex++) {
- dayToCellMap[dayIndex] = cellIndex;
- isHiddenDayHash[dayIndex] = $.inArray(dayIndex, hiddenDays) != -1;
- if (!isHiddenDayHash[dayIndex]) {
- cellToDayMap[cellIndex] = dayIndex;
- cellIndex++;
- }
- }
- cellsPerWeek = cellIndex;
- if (!cellsPerWeek) {
- throw 'invalid hiddenDays'; // all days were hidden? bad.
- }
- })();
- // Is the current day hidden?
- // `day` is a day-of-week index (0-6), or a Moment
- function isHiddenDay(day) {
- if (moment.isMoment(day)) {
- day = day.day();
- }
- return isHiddenDayHash[day];
- }
- function getCellsPerWeek() {
- return cellsPerWeek;
- }
- // Incrementing the current day until it is no longer a hidden day, returning a copy.
- // If the initial value of `date` is not a hidden day, don't do anything.
- // Pass `isExclusive` as `true` if you are dealing with an end date.
- // `inc` defaults to `1` (increment one day forward each time)
- function skipHiddenDays(date, inc, isExclusive) {
- var out = date.clone();
- inc = inc || 1;
- while (
- isHiddenDayHash[(out.day() + (isExclusive ? inc : 0) + 7) % 7]
- ) {
- out.add('days', inc);
- }
- return out;
- }
- //
- // TRANSFORMATIONS: cell -> cell offset -> day offset -> date
- //
- // cell -> date (combines all transformations)
- // Possible arguments:
- // - row, col
- // - { row:#, col: # }
- function cellToDate() {
- var cellOffset = cellToCellOffset.apply(null, arguments);
- var dayOffset = cellOffsetToDayOffset(cellOffset);
- var date = dayOffsetToDate(dayOffset);
- return date;
- }
- // cell -> cell offset
- // Possible arguments:
- // - row, col
- // - { row:#, col:# }
- function cellToCellOffset(row, col) {
- var colCnt = t.colCnt;
- // rtl variables. wish we could pre-populate these. but where?
- var dis = isRTL ? -1 : 1;
- var dit = isRTL ? colCnt - 1 : 0;
- if (typeof row == 'object') {
- col = row.col;
- row = row.row;
- }
- var cellOffset = row * colCnt + (col * dis + dit); // column, adjusted for RTL (dis & dit)
- return cellOffset;
- }
- // cell offset -> day offset
- function cellOffsetToDayOffset(cellOffset) {
- var day0 = t.start.day(); // first date's day of week
- cellOffset += dayToCellMap[day0]; // normlize cellOffset to beginning-of-week
- return Math.floor(cellOffset / cellsPerWeek) * 7 + // # of days from full weeks
- cellToDayMap[ // # of days from partial last week
- (cellOffset % cellsPerWeek + cellsPerWeek) % cellsPerWeek // crazy math to handle negative cellOffsets
- ] -
- day0; // adjustment for beginning-of-week normalization
- }
- // day offset -> date
- function dayOffsetToDate(dayOffset) {
- return t.start.clone().add('days', dayOffset);
- }
- //
- // TRANSFORMATIONS: date -> day offset -> cell offset -> cell
- //
- // date -> cell (combines all transformations)
- function dateToCell(date) {
- var dayOffset = dateToDayOffset(date);
- var cellOffset = dayOffsetToCellOffset(dayOffset);
- var cell = cellOffsetToCell(cellOffset);
- return cell;
- }
- // date -> day offset
- function dateToDayOffset(date) {
- return date.clone().stripTime().diff(t.start, 'days');
- }
- // day offset -> cell offset
- function dayOffsetToCellOffset(dayOffset) {
- var day0 = t.start.day(); // first date's day of week
- dayOffset += day0; // normalize dayOffset to beginning-of-week
- return Math.floor(dayOffset / 7) * cellsPerWeek + // # of cells from full weeks
- dayToCellMap[ // # of cells from partial last week
- (dayOffset % 7 + 7) % 7 // crazy math to handle negative dayOffsets
- ] -
- dayToCellMap[day0]; // adjustment for beginning-of-week normalization
- }
- // cell offset -> cell (object with row & col keys)
- function cellOffsetToCell(cellOffset) {
- var colCnt = t.colCnt;
- // rtl variables. wish we could pre-populate these. but where?
- var dis = isRTL ? -1 : 1;
- var dit = isRTL ? colCnt - 1 : 0;
- var row = Math.floor(cellOffset / colCnt);
- var col = ((cellOffset % colCnt + colCnt) % colCnt) * dis + dit; // column, adjusted for RTL (dis & dit)
- return {
- row: row,
- col: col
- };
- }
- //
- // Converts a date range into an array of segment objects.
- // "Segments" are horizontal stretches of time, sliced up by row.
- // A segment object has the following properties:
- // - row
- // - cols
- // - isStart
- // - isEnd
- //
- function rangeToSegments(start, end) {
- var rowCnt = t.rowCnt;
- var colCnt = t.colCnt;
- var segments = []; // array of segments to return
- // day offset for given date range
- var dayRange = computeDayRange(start, end); // convert to a whole-day range
- var rangeDayOffsetStart = dateToDayOffset(dayRange.start);
- var rangeDayOffsetEnd = dateToDayOffset(dayRange.end); // an exclusive value
- // first and last cell offset for the given date range
- // "last" implies inclusivity
- var rangeCellOffsetFirst = dayOffsetToCellOffset(rangeDayOffsetStart);
- var rangeCellOffsetLast = dayOffsetToCellOffset(rangeDayOffsetEnd) - 1;
- // loop through all the rows in the view
- for (var row=0; row<rowCnt; row++) {
- // first and last cell offset for the row
- var rowCellOffsetFirst = row * colCnt;
- var rowCellOffsetLast = rowCellOffsetFirst + colCnt - 1;
- // get the segment's cell offsets by constraining the range's cell offsets to the bounds of the row
- var segmentCellOffsetFirst = Math.max(rangeCellOffsetFirst, rowCellOffsetFirst);
- var segmentCellOffsetLast = Math.min(rangeCellOffsetLast, rowCellOffsetLast);
- // make sure segment's offsets are valid and in view
- if (segmentCellOffsetFirst <= segmentCellOffsetLast) {
- // translate to cells
- var segmentCellFirst = cellOffsetToCell(segmentCellOffsetFirst);
- var segmentCellLast = cellOffsetToCell(segmentCellOffsetLast);
- // view might be RTL, so order by leftmost column
- var cols = [ segmentCellFirst.col, segmentCellLast.col ].sort();
- // Determine if segment's first/last cell is the beginning/end of the date range.
- // We need to compare "day offset" because "cell offsets" are often ambiguous and
- // can translate to multiple days, and an edge case reveals itself when we the
- // range's first cell is hidden (we don't want isStart to be true).
- var isStart = cellOffsetToDayOffset(segmentCellOffsetFirst) == rangeDayOffsetStart;
- var isEnd = cellOffsetToDayOffset(segmentCellOffsetLast) + 1 == rangeDayOffsetEnd;
- // +1 for comparing exclusively
- segments.push({
- row: row,
- leftCol: cols[0],
- rightCol: cols[1],
- isStart: isStart,
- isEnd: isEnd
- });
- }
- }
- return segments;
- }
- // Returns the date range of the full days the given range visually appears to occupy.
- // Returns object with properties `start` (moment) and `end` (moment, exclusive end).
- function computeDayRange(start, end) {
- var startDay = start.clone().stripTime(); // the beginning of the day the range starts
- var endDay;
- var endTimeMS;
- if (end) {
- endDay = end.clone().stripTime(); // the beginning of the day the range exclusively ends
- endTimeMS = +end.time(); // # of milliseconds into `endDay`
- // If the end time is actually inclusively part of the next day and is equal to or
- // beyond the next day threshold, adjust the end to be the exclusive end of `endDay`.
- // Otherwise, leaving it as inclusive will cause it to exclude `endDay`.
- if (endTimeMS && endTimeMS >= nextDayThreshold) {
- endDay.add('days', 1);
- }
- }
- // If no end was specified, or if it is within `startDay` but not past nextDayThreshold,
- // assign the default duration of one day.
- if (!end || endDay <= startDay) {
- endDay = startDay.clone().add('days', 1);
- }
- return { start: startDay, end: endDay };
- }
- // Does the given event visually appear to occupy more than one day?
- function isMultiDayEvent(event) {
- var range = computeDayRange(event.start, event.end);
- return range.end.diff(range.start, 'days') > 1;
- }
- }
|