| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101 |
- FC.sourceNormalizers = [];
- FC.sourceFetchers = [];
- var ajaxDefaults = {
- dataType: 'json',
- cache: false
- };
- var eventGUID = 1;
- function EventManager(options) { // assumed to be a calendar
- var t = this;
-
-
- // exports
- t.isFetchNeeded = isFetchNeeded;
- t.fetchEvents = fetchEvents;
- t.fetchEventSources = fetchEventSources;
- t.addEventSource = addEventSource;
- t.removeEventSource = removeEventSource;
- t.updateEvent = updateEvent;
- t.renderEvent = renderEvent;
- t.removeEvents = removeEvents;
- t.clientEvents = clientEvents;
- t.mutateEvent = mutateEvent;
- t.normalizeEventDates = normalizeEventDates;
- t.normalizeEventTimes = normalizeEventTimes;
-
-
- // imports
- var reportEvents = t.reportEvents;
-
-
- // locals
- var stickySource = { events: [] };
- var sources = [ stickySource ];
- var rangeStart, rangeEnd;
- var currentFetchID = 0;
- var pendingSourceCnt = 0;
- var cache = []; // holds events that have already been expanded
- $.each(
- (options.events ? [ options.events ] : []).concat(options.eventSources || []),
- function(i, sourceInput) {
- var source = buildEventSource(sourceInput);
- if (source) {
- sources.push(source);
- }
- }
- );
-
-
-
- /* Fetching
- -----------------------------------------------------------------------------*/
- // start and end are assumed to be unzoned
- function isFetchNeeded(start, end) {
- return !rangeStart || // nothing has been fetched yet?
- start < rangeStart || end > rangeEnd; // is part of the new range outside of the old range?
- }
-
-
- function fetchEvents(start, end) {
- rangeStart = start;
- rangeEnd = end;
- fetchEventSources(sources, true);
- }
- function fetchEventSources(specificSources, shouldClearAll) {
- if (shouldClearAll) {
- cache = [];
- }
- var fetchID = ++currentFetchID;
- var len = specificSources.length;
- pendingSourceCnt += len;
- function checkSources(e) {
- return e.source !== specificSources[i];
- }
- for (var i=0; i<len; i++) {
- if (!shouldClearAll) {
- // remove events from the cache that belong to the source being refetched
- cache = $.grep(cache, checkSources);
- }
- fetchEventSource(specificSources[i], fetchID);
- }
- }
- function fetchEventSource(source, fetchID) {
- _fetchEventSource(source, function(eventInputs) {
- var isArraySource = $.isArray(source.events);
- var i, eventInput;
- var abstractEvent;
- if (fetchID == currentFetchID) {
- if (eventInputs) {
- for (i = 0; i < eventInputs.length; i++) {
- eventInput = eventInputs[i];
- if (isArraySource) { // array sources have already been convert to Event Objects
- abstractEvent = eventInput;
- }
- else {
- abstractEvent = buildEventFromInput(eventInput, source);
- }
- if (abstractEvent) { // not false (an invalid event)
- cache.push.apply(
- cache,
- expandEvent(abstractEvent) // add individual expanded events to the cache
- );
- }
- }
- }
- pendingSourceCnt--;
- if (!pendingSourceCnt) {
- reportEvents(cache);
- }
- }
- });
- }
-
-
- function _fetchEventSource(source, callback) {
- var i;
- var fetchers = FC.sourceFetchers;
- var res;
- for (i=0; i<fetchers.length; i++) {
- res = fetchers[i].call(
- t, // this, the Calendar object
- source,
- rangeStart.clone(),
- rangeEnd.clone(),
- options.timezone,
- callback
- );
- if (res === true) {
- // the fetcher is in charge. made its own async request
- return;
- }
- else if (typeof res == 'object') {
- // the fetcher returned a new source. process it
- _fetchEventSource(res, callback);
- return;
- }
- }
- var events = source.events;
- if (events) {
- if ($.isFunction(events)) {
- t.pushLoading();
- events.call(
- t, // this, the Calendar object
- rangeStart.clone(),
- rangeEnd.clone(),
- options.timezone,
- function(events) {
- callback(events);
- t.popLoading();
- }
- );
- }
- else if ($.isArray(events)) {
- callback(events);
- }
- else {
- callback();
- }
- }else{
- var url = source.url;
- if (url) {
- var success = source.success;
- var error = source.error;
- var complete = source.complete;
- // retrieve any outbound GET/POST $.ajax data from the options
- var customData;
- if ($.isFunction(source.data)) {
- // supplied as a function that returns a key/value object
- customData = source.data();
- }
- else {
- // supplied as a straight key/value object
- customData = source.data;
- }
- // use a copy of the custom data so we can modify the parameters
- // and not affect the passed-in object.
- var data = $.extend({}, customData || {});
- var startParam = firstDefined(source.startParam, options.startParam);
- var endParam = firstDefined(source.endParam, options.endParam);
- var timezoneParam = firstDefined(source.timezoneParam, options.timezoneParam);
- if (startParam) {
- data[startParam] = rangeStart.format();
- }
- if (endParam) {
- data[endParam] = rangeEnd.format();
- }
- if (options.timezone && options.timezone != 'local') {
- data[timezoneParam] = options.timezone;
- }
- t.pushLoading();
- $.ajax($.extend({}, ajaxDefaults, source, {
- data: data,
- success: function(events) {
- events = events || [];
- var res = applyAll(success, this, arguments);
- if ($.isArray(res)) {
- events = res;
- }
- callback(events);
- },
- error: function() {
- applyAll(error, this, arguments);
- callback();
- },
- complete: function() {
- applyAll(complete, this, arguments);
- t.popLoading();
- }
- }));
- }else{
- callback();
- }
- }
- }
-
-
-
- /* Sources
- -----------------------------------------------------------------------------*/
-
- function addEventSource(sourceInput) {
- var source = buildEventSource(sourceInput);
- if (source) {
- sources.push(source);
- pendingSourceCnt++;
- fetchEventSource(source, currentFetchID); // will eventually call reportEvents
- }
- }
- function buildEventSource(sourceInput) { // will return undefined if invalid source
- var normalizers = FC.sourceNormalizers;
- var source;
- var i;
- if ($.isFunction(sourceInput) || $.isArray(sourceInput)) {
- source = { events: sourceInput };
- }
- else if (typeof sourceInput === 'string') {
- source = { url: sourceInput };
- }
- else if (typeof sourceInput === 'object') {
- source = $.extend({}, sourceInput); // shallow copy
- }
- if (source) {
- // TODO: repeat code, same code for event classNames
- if (source.className) {
- if (typeof source.className === 'string') {
- source.className = source.className.split(/\s+/);
- }
- // otherwise, assumed to be an array
- }
- else {
- source.className = [];
- }
- // for array sources, we convert to standard Event Objects up front
- if ($.isArray(source.events)) {
- source.origArray = source.events; // for removeEventSource
- source.events = $.map(source.events, function(eventInput) {
- return buildEventFromInput(eventInput, source);
- });
- }
- for (i=0; i<normalizers.length; i++) {
- normalizers[i].call(t, source);
- }
- return source;
- }
- }
- function removeEventSource(source) {
- sources = $.grep(sources, function(src) {
- return !isSourcesEqual(src, source);
- });
- // remove all client events from that source
- cache = $.grep(cache, function(e) {
- return !isSourcesEqual(e.source, source);
- });
- reportEvents(cache);
- }
- function isSourcesEqual(source1, source2) {
- return source1 && source2 && getSourcePrimitive(source1) == getSourcePrimitive(source2);
- }
- function getSourcePrimitive(source) {
- return (
- (typeof source === 'object') ? // a normalized event source?
- (source.origArray || source.googleCalendarId || source.url || source.events) : // get the primitive
- null
- ) ||
- source; // the given argument *is* the primitive
- }
-
-
-
- /* Manipulation
- -----------------------------------------------------------------------------*/
- // Only ever called from the externally-facing API
- function updateEvent(event) {
- // massage start/end values, even if date string values
- event.start = t.moment(event.start);
- if (event.end) {
- event.end = t.moment(event.end);
- }
- else {
- event.end = null;
- }
- mutateEvent(event, getMiscEventProps(event)); // will handle start/end/allDay normalization
- reportEvents(cache); // reports event modifications (so we can redraw)
- }
- // Returns a hash of misc event properties that should be copied over to related events.
- function getMiscEventProps(event) {
- var props = {};
- $.each(event, function(name, val) {
- if (isMiscEventPropName(name)) {
- if (val !== undefined && isAtomic(val)) { // a defined non-object
- props[name] = val;
- }
- }
- });
- return props;
- }
- // non-date-related, non-id-related, non-secret
- function isMiscEventPropName(name) {
- return !/^_|^(id|allDay|start|end)$/.test(name);
- }
-
- // returns the expanded events that were created
- function renderEvent(eventInput, stick) {
- var abstractEvent = buildEventFromInput(eventInput);
- var events;
- var i, event;
- if (abstractEvent) { // not false (a valid input)
- events = expandEvent(abstractEvent);
- for (i = 0; i < events.length; i++) {
- event = events[i];
- if (!event.source) {
- if (stick) {
- stickySource.events.push(event);
- event.source = stickySource;
- }
- cache.push(event);
- }
- }
- reportEvents(cache);
- return events;
- }
- return [];
- }
-
-
- function removeEvents(filter) {
- var eventID;
- var i;
- if (filter == null) { // null or undefined. remove all events
- filter = function() { return true; }; // will always match
- }
- else if (!$.isFunction(filter)) { // an event ID
- eventID = filter + '';
- filter = function(event) {
- return event._id == eventID;
- };
- }
- // Purge event(s) from our local cache
- cache = $.grep(cache, filter, true); // inverse=true
- // Remove events from array sources.
- // This works because they have been converted to official Event Objects up front.
- // (and as a result, event._id has been calculated).
- for (i=0; i<sources.length; i++) {
- if ($.isArray(sources[i].events)) {
- sources[i].events = $.grep(sources[i].events, filter, true);
- }
- }
- reportEvents(cache);
- }
-
-
- function clientEvents(filter) {
- if ($.isFunction(filter)) {
- return $.grep(cache, filter);
- }
- else if (filter != null) { // not null, not undefined. an event ID
- filter += '';
- return $.grep(cache, function(e) {
- return e._id == filter;
- });
- }
- return cache; // else, return all
- }
-
-
-
- /* Event Normalization
- -----------------------------------------------------------------------------*/
- // Given a raw object with key/value properties, returns an "abstract" Event object.
- // An "abstract" event is an event that, if recurring, will not have been expanded yet.
- // Will return `false` when input is invalid.
- // `source` is optional
- function buildEventFromInput(input, source) {
- var out = {};
- var start, end;
- var allDay;
- if (options.eventDataTransform) {
- input = options.eventDataTransform(input);
- }
- if (source && source.eventDataTransform) {
- input = source.eventDataTransform(input);
- }
- // Copy all properties over to the resulting object.
- // The special-case properties will be copied over afterwards.
- $.extend(out, input);
- if (source) {
- out.source = source;
- }
- out._id = input._id || (input.id === undefined ? '_fc' + eventGUID++ : input.id + '');
- if (input.className) {
- if (typeof input.className == 'string') {
- out.className = input.className.split(/\s+/);
- }
- else { // assumed to be an array
- out.className = input.className;
- }
- }
- else {
- out.className = [];
- }
- start = input.start || input.date; // "date" is an alias for "start"
- end = input.end;
- // parse as a time (Duration) if applicable
- if (isTimeString(start)) {
- start = moment.duration(start);
- }
- if (isTimeString(end)) {
- end = moment.duration(end);
- }
- if (input.dow || moment.isDuration(start) || moment.isDuration(end)) {
- // the event is "abstract" (recurring) so don't calculate exact start/end dates just yet
- out.start = start ? moment.duration(start) : null; // will be a Duration or null
- out.end = end ? moment.duration(end) : null; // will be a Duration or null
- out._recurring = true; // our internal marker
- }
- else {
- if (start) {
- start = t.moment(start);
- if (!start.isValid()) {
- return false;
- }
- }
- if (end) {
- end = t.moment(end);
- if (!end.isValid()) {
- end = null; // let defaults take over
- }
- }
- allDay = input.allDay;
- if (allDay === undefined) { // still undefined? fallback to default
- allDay = firstDefined(
- source ? source.allDayDefault : undefined,
- options.allDayDefault
- );
- // still undefined? normalizeEventDates will calculate it
- }
- assignDatesToEvent(start, end, allDay, out);
- }
- t.normalizeEvent(out); // hook for external use. a prototype method
- return out;
- }
- // Normalizes and assigns the given dates to the given partially-formed event object.
- // NOTE: mutates the given start/end moments. does not make a copy.
- function assignDatesToEvent(start, end, allDay, event) {
- event.start = start;
- event.end = end;
- event.allDay = allDay;
- 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 normalizeEventDates(eventProps) {
- normalizeEventTimes(eventProps);
- if (eventProps.end && !eventProps.end.isAfter(eventProps.start)) {
- eventProps.end = null;
- }
- if (!eventProps.end) {
- if (options.forceEventDuration) {
- eventProps.end = t.getDefaultEventEnd(eventProps.allDay, eventProps.start);
- }
- else {
- eventProps.end = null;
- }
- }
- }
- // Ensures the allDay property exists and the timeliness of the start/end dates are consistent
- function normalizeEventTimes(eventProps) {
- if (eventProps.allDay == null) {
- eventProps.allDay = !(eventProps.start.hasTime() || (eventProps.end && eventProps.end.hasTime()));
- }
- if (eventProps.allDay) {
- eventProps.start.stripTime();
- if (eventProps.end) {
- // TODO: consider nextDayThreshold here? If so, will require a lot of testing and adjustment
- eventProps.end.stripTime();
- }
- }
- else {
- if (!eventProps.start.hasTime()) {
- eventProps.start = t.applyTimezone(eventProps.start.time(0)); // 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 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.
- // HACK: can override the recurring window by providing custom rangeStart/rangeEnd (for businessHours).
- function expandEvent(abstractEvent, _rangeStart, _rangeEnd) {
- var events = [];
- var dowHash;
- var dow;
- var i;
- var date;
- var startTime, endTime;
- var start, end;
- var event;
- _rangeStart = _rangeStart || rangeStart;
- _rangeEnd = _rangeEnd || rangeEnd;
- if (abstractEvent) {
- if (abstractEvent._recurring) {
- // make a boolean hash as to whether the event occurs on each day-of-week
- if ((dow = abstractEvent.dow)) {
- dowHash = {};
- for (i = 0; i < dow.length; i++) {
- dowHash[dow[i]] = true;
- }
- }
- // iterate through every day in the current range
- date = _rangeStart.clone().stripTime(); // holds the date of the current day
- while (date.isBefore(_rangeEnd)) {
- if (!dowHash || dowHash[date.day()]) { // if everyday, or this particular day-of-week
- startTime = abstractEvent.start; // the stored start and end properties are times (Durations)
- endTime = abstractEvent.end; // "
- start = date.clone();
- end = null;
- if (startTime) {
- start = start.time(startTime);
- }
- if (endTime) {
- end = date.clone().time(endTime);
- }
- event = $.extend({}, abstractEvent); // make a copy of the original
- assignDatesToEvent(
- start, end,
- !startTime && !endTime, // allDay?
- event
- );
- events.push(event);
- }
- date.add(1, 'days');
- }
- }
- else {
- events.push(abstractEvent); // return the original event. will be a one-item array
- }
- }
- return events;
- }
- /* Event Modification Math
- -----------------------------------------------------------------------------------------*/
- // Modifies an event and all related events by applying the given properties.
- // Special date-diffing logic is used for manipulation of dates.
- // If `props` does not contain start/end dates, the updated values are assumed to be the event's current start/end.
- // All date comparisons are done against the event's pristine _start and _end dates.
- // Returns an object with delta information and a function to undo all operations.
- // For making computations in a granularity greater than day/time, specify largeUnit.
- // NOTE: The given `newProps` might be mutated for normalization purposes.
- function mutateEvent(event, newProps, largeUnit) {
- var miscProps = {};
- var oldProps;
- var clearEnd;
- var startDelta;
- var endDelta;
- var durationDelta;
- var undoFunc;
- // diffs the dates in the appropriate way, returning a duration
- function diffDates(date1, date0) { // date1 - date0
- if (largeUnit) {
- return diffByUnit(date1, date0, largeUnit);
- }
- else if (newProps.allDay) {
- return diffDay(date1, date0);
- }
- else {
- return diffDayTime(date1, date0);
- }
- }
- newProps = newProps || {};
- // normalize new date-related properties
- if (!newProps.start) {
- newProps.start = event.start.clone();
- }
- if (newProps.end === undefined) {
- newProps.end = event.end ? event.end.clone() : null;
- }
- if (newProps.allDay == null) { // is null or undefined?
- newProps.allDay = event.allDay;
- }
- normalizeEventDates(newProps);
- // create normalized versions of the original props to compare against
- // need a real end value, for diffing
- oldProps = {
- start: event._start.clone(),
- 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
- };
- normalizeEventDates(oldProps);
- // need to clear the end date if explicitly changed to null
- clearEnd = event._end !== null && newProps.end === null;
- // compute the delta for moving the start date
- startDelta = diffDates(newProps.start, oldProps.start);
- // compute the delta for moving the end date
- if (newProps.end) {
- endDelta = diffDates(newProps.end, oldProps.end);
- durationDelta = endDelta.subtract(startDelta);
- }
- else {
- durationDelta = null;
- }
- // gather all non-date-related properties
- $.each(newProps, function(name, val) {
- if (isMiscEventPropName(name)) {
- if (val !== undefined) {
- miscProps[name] = val;
- }
- }
- });
- // apply the operations to the event and all related events
- undoFunc = mutateEvents(
- clientEvents(event._id), // get events with this ID
- clearEnd,
- newProps.allDay,
- startDelta,
- durationDelta,
- miscProps
- );
- return {
- dateDelta: startDelta,
- durationDelta: durationDelta,
- undo: undoFunc
- };
- }
- // Modifies an array of events in the following ways (operations are in order):
- // - clear the event's `end`
- // - convert the event to allDay
- // - add `dateDelta` to the start and end
- // - add `durationDelta` to the event's duration
- // - assign `miscProps` to the event
- //
- // Returns a function that can be called to undo all the operations.
- //
- // TODO: don't use so many closures. possible memory issues when lots of events with same ID.
- //
- function mutateEvents(events, clearEnd, allDay, dateDelta, durationDelta, miscProps) {
- var isAmbigTimezone = t.getIsAmbigTimezone();
- var undoFunctions = [];
- // normalize zero-length deltas to be null
- if (dateDelta && !dateDelta.valueOf()) { dateDelta = null; }
- if (durationDelta && !durationDelta.valueOf()) { durationDelta = null; }
- $.each(events, function(i, event) {
- var oldProps;
- var newProps;
- // build an object holding all the old values, both date-related and misc.
- // for the undo function.
- oldProps = {
- start: event.start.clone(),
- end: event.end ? event.end.clone() : null,
- allDay: event.allDay
- };
- $.each(miscProps, function(name) {
- oldProps[name] = event[name];
- });
- // new date-related properties. work off the original date snapshot.
- // ok to use references because they will be thrown away when backupEventDates is called.
- newProps = {
- start: event._start,
- end: event._end,
- allDay: allDay // normalize the dates in the same regard as the new properties
- };
- normalizeEventDates(newProps); // massages start/end/allDay
- // strip or ensure the end date
- if (clearEnd) {
- newProps.end = null;
- }
- else if (durationDelta && !newProps.end) { // the duration translation requires an end date
- newProps.end = t.getDefaultEventEnd(newProps.allDay, newProps.start);
- }
- if (dateDelta) {
- newProps.start.add(dateDelta);
- if (newProps.end) {
- newProps.end.add(dateDelta);
- }
- }
- if (durationDelta) {
- newProps.end.add(durationDelta); // end already ensured above
- }
- // if the dates have changed, and we know it is impossible to recompute the
- // timezone offsets, strip the zone.
- if (
- isAmbigTimezone &&
- !newProps.allDay &&
- (dateDelta || durationDelta)
- ) {
- newProps.start.stripZone();
- if (newProps.end) {
- newProps.end.stripZone();
- }
- }
- $.extend(event, miscProps, newProps); // copy over misc props, then date-related props
- backupEventDates(event); // regenerate internal _start/_end/_allDay
- undoFunctions.push(function() {
- $.extend(event, oldProps);
- backupEventDates(event); // regenerate internal _start/_end/_allDay
- });
- });
- return function() {
- for (var i = 0; i < undoFunctions.length; i++) {
- undoFunctions[i]();
- }
- };
- }
- /* Business Hours
- -----------------------------------------------------------------------------------------*/
- t.getBusinessHoursEvents = getBusinessHoursEvents;
- // Returns an array of events as to when the business hours occur in the given view.
- // Abuse of our event system :(
- function getBusinessHoursEvents(wholeDay) {
- var optionVal = options.businessHours;
- var defaultVal = {
- className: 'fc-nonbusiness',
- start: '09:00',
- end: '17:00',
- dow: [ 1, 2, 3, 4, 5 ], // monday - friday
- rendering: 'inverse-background'
- };
- var view = t.getView();
- var eventInput;
- if (optionVal) { // `true` (which means "use the defaults") or an override object
- eventInput = $.extend(
- {}, // copy to a new object in either case
- defaultVal,
- typeof optionVal === 'object' ? optionVal : {} // override the defaults
- );
- }
- if (eventInput) {
- // if a whole-day series is requested, clear the start/end times
- if (wholeDay) {
- eventInput.start = null;
- eventInput.end = null;
- }
- return expandEvent(
- buildEventFromInput(eventInput),
- view.start,
- view.end
- );
- }
- return [];
- }
- /* Overlapping / Constraining
- -----------------------------------------------------------------------------------------*/
- t.isEventSpanAllowed = isEventSpanAllowed;
- t.isExternalSpanAllowed = isExternalSpanAllowed;
- t.isSelectionSpanAllowed = isSelectionSpanAllowed;
- // 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,
- source.constraint,
- options.eventConstraint
- );
- var overlap = firstDefined(
- event.overlap,
- source.overlap,
- options.eventOverlap
- );
- return isSpanAllowed(span, constraint, overlap, event);
- }
- // 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, eventLocation);
- event = expandEvent(buildEventFromInput(eventInput))[0];
- }
- if (event) {
- return isEventSpanAllowed(eventSpan, event);
- }
- else { // treat it as a selection
- return isSelectionSpanAllowed(eventSpan);
- }
- }
- // 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 isSpanAllowed(span, constraint, overlap, event) {
- var constraintEvents;
- var anyContainment;
- var peerEvents;
- var i, peerEvent;
- var peerOverlap;
- // the range must be fully contained by at least one of produced constraint events
- if (constraint != null) {
- // not treated as an event! intermediate data structure
- // TODO: use ranges in the future
- constraintEvents = constraintToEvents(constraint);
- anyContainment = false;
- for (i = 0; i < constraintEvents.length; i++) {
- if (eventContainsRange(constraintEvents[i], span)) {
- anyContainment = true;
- break;
- }
- }
- if (!anyContainment) {
- return false;
- }
- }
- 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, span)) {
- // evaluate overlap for the given range and short-circuit if necessary
- if (overlap === false) {
- return false;
- }
- // if the event's overlap is a test function, pass the peer event in question as the first param
- else if (typeof overlap === 'function' && !overlap(peerEvent, event)) {
- return false;
- }
- // if we are computing if the given range is allowable for an event, consider the other event's
- // EventObject-specific or Source-specific `overlap` property
- if (event) {
- peerOverlap = firstDefined(
- peerEvent.overlap,
- (peerEvent.source || {}).overlap
- // we already considered the global `eventOverlap`
- );
- if (peerOverlap === false) {
- return false;
- }
- // if the peer event's overlap is a test function, pass the subject event as the first param
- if (typeof peerOverlap === 'function' && !peerOverlap(event, peerEvent)) {
- return false;
- }
- }
- }
- }
- return true;
- }
- // Given an event input from the API, produces an array of event objects. Possible event inputs:
- // 'businessHours'
- // An event ID (number or string)
- // An object with specific start/end dates or a recurring event (like what businessHours accepts)
- function constraintToEvents(constraintInput) {
- if (constraintInput === 'businessHours') {
- return getBusinessHoursEvents();
- }
- if (typeof constraintInput === 'object') {
- return expandEvent(buildEventFromInput(constraintInput));
- }
- return clientEvents(constraintInput); // probably an ID
- }
- // Does the event's date range fully contain the given range?
- // start/end already assumed to have stripped zones :(
- function eventContainsRange(event, range) {
- var eventStart = event.start.clone().stripZone();
- var eventEnd = t.getEventEnd(event).stripZone();
- return range.start >= eventStart && range.end <= eventEnd;
- }
- // Does the event's date range intersect with the given range?
- // start/end already assumed to have stripped zones :(
- function eventIntersectsRange(event, range) {
- var eventStart = event.start.clone().stripZone();
- var eventEnd = t.getEventEnd(event).stripZone();
- return range.start < eventEnd && range.end > eventStart;
- }
- t.getEventCache = function() {
- return cache;
- };
- }
- // hook for external libs to manipulate event properties upon creation.
- // should manipulate the event in-place.
- Calendar.prototype.normalizeEvent = function(event) {
- };
- // Returns a list of events that the given event should be compared against when being considered for a move to
- // 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;
- for (i = 0; i < cache.length; i++) {
- otherEvent = cache[i];
- if (
- !event ||
- event._id !== otherEvent._id // don't compare the event to itself or other related [repeating] events
- ) {
- peerEvents.push(otherEvent);
- }
- }
- return peerEvents;
- };
- // updates the "backup" properties, which are preserved in order to compute diffs later on.
- function backupEventDates(event) {
- event._allDay = event.allDay;
- event._start = event.start.clone();
- event._end = event.end ? event.end.clone() : null;
- }
|