diff --git a/src/Calendar.js b/src/Calendar.js index 5dd47ee..bae1938 100644 --- a/src/Calendar.js +++ b/src/Calendar.js @@ -513,7 +513,6 @@ function Calendar(element, instanceOptions) { function renderEvents(modifiedEventID) { // TODO: remove modifiedEventID hack if (elementVisible()) { - currentView.setEventData(events); // for View.js, TODO: unify with renderEvents currentView.renderEvents(events, modifiedEventID); // actually render the DOM elements currentView.trigger('eventAfterAllRender'); } diff --git a/src/EventManager.js b/src/EventManager.js index 2bdf01d..6267220 100644 --- a/src/EventManager.js +++ b/src/EventManager.js @@ -23,6 +23,7 @@ function EventManager(options) { // assumed to be a calendar t.renderEvent = renderEvent; t.removeEvents = removeEvents; t.clientEvents = clientEvents; + t.mutateEvent = mutateEvent; // imports @@ -30,7 +31,6 @@ function EventManager(options) { // assumed to be a calendar var getView = t.getView; var reportEvents = t.reportEvents; var getEventEnd = t.getEventEnd; - var mutateEvent = t.mutateEvent; // locals @@ -477,6 +477,173 @@ function EventManager(options) { // assumed to be a calendar return out; } + + + + /* Event Modification Math + -----------------------------------------------------------------------------------------*/ + + + // Modify the date(s) of an event and make this change propagate to all other events with + // the same ID (related repeating events). + // + // If `newStart`/`newEnd` are not specified, the "new" dates are assumed to be `event.start` and `event.end`. + // The "old" dates to be compare against are always `event._start` and `event._end` (set by EventManager). + // + // Returns a function that can be called to undo all the operations. + // + function mutateEvent(event, newStart, newEnd) { + var oldAllDay = event._allDay; + var oldStart = event._start; + var oldEnd = event._end; + var clearEnd = false; + var newAllDay; + var dateDelta; + var durationDelta; + + // if no new dates were passed in, compare against the event's existing dates + if (!newStart && !newEnd) { + newStart = event.start; + newEnd = event.end; + } + + // NOTE: throughout this function, the initial values of `newStart` and `newEnd` are + // preserved. These values may be undefined. + + // detect new allDay + if (event.allDay != oldAllDay) { // if value has changed, use it + newAllDay = event.allDay; + } + else { // otherwise, see if any of the new dates are allDay + newAllDay = !(newStart || newEnd).hasTime(); + } + + // normalize the new dates based on allDay + if (newAllDay) { + if (newStart) { + newStart = newStart.clone().stripTime(); + } + if (newEnd) { + newEnd = newEnd.clone().stripTime(); + } + } + + // compute dateDelta + if (newStart) { + if (newAllDay) { + dateDelta = dayishDiff(newStart, oldStart.clone().stripTime()); // treat oldStart as allDay + } + else { + dateDelta = dayishDiff(newStart, oldStart); + } + } + + if (newAllDay != oldAllDay) { + // if allDay has changed, always throw away the end + clearEnd = true; + } + else if (newEnd) { + durationDelta = dayishDiff( + // new duration + newEnd || t.getDefaultEventEnd(newAllDay, newStart || oldStart), + newStart || oldStart + ).subtract(dayishDiff( + // subtract old duration + oldEnd || t.getDefaultEventEnd(oldAllDay, oldStart), + oldStart + )); + } + + return mutateEvents( + clientEvents(event._id), // get events with this ID + clearEnd, + newAllDay, + dateDelta, + durationDelta + ); + } + + + // 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 + // + // Returns a function that can be called to undo all the operations. + // + function mutateEvents(events, clearEnd, forceAllDay, dateDelta, durationDelta) { + var isAmbigTimezone = t.getIsAmbigTimezone(); + var undoFunctions = []; + + $.each(events, function(i, event) { + var oldAllDay = event._allDay; + var oldStart = event._start; + var oldEnd = event._end; + var newAllDay = forceAllDay != null ? forceAllDay : oldAllDay; + var newStart = oldStart.clone(); + var newEnd = (!clearEnd && oldEnd) ? oldEnd.clone() : null; + + // NOTE: this function is responsible for transforming `newStart` and `newEnd`, + // which were initialized to the OLD values first. `newEnd` may be null. + + // normlize newStart/newEnd to be consistent with newAllDay + if (newAllDay) { + newStart.stripTime(); + if (newEnd) { + newEnd.stripTime(); + } + } + else { + if (!newStart.hasTime()) { + newStart = t.rezoneDate(newStart); + } + if (newEnd && !newEnd.hasTime()) { + newEnd = t.rezoneDate(newEnd); + } + } + + // ensure we have an end date if necessary + if (!newEnd && (options.forceEventDuration || +durationDelta)) { + newEnd = t.getDefaultEventEnd(newAllDay, newStart); + } + + // translate the dates + newStart.add(dateDelta); + if (newEnd) { + newEnd.add(dateDelta).add(durationDelta); + } + + // if the dates have changed, and we know it is impossible to recompute the + // timezone offsets, strip the zone. + if (isAmbigTimezone) { + if (+dateDelta) { + newStart.stripZone(); + } + if (newEnd && (+dateDelta || +durationDelta)) { + newEnd.stripZone(); + } + } + + event.allDay = newAllDay; + event.start = newStart; + event.end = newEnd; + backupEventDates(event); + + undoFunctions.push(function() { + event.allDay = oldAllDay; + event.start = oldStart; + event.end = oldEnd; + backupEventDates(event); + }); + }); + + return function() { + for (var i=0; i