mirror of
https://github.com/wassname/fullcalendar.git
synced 2026-07-11 00:40:19 +08:00
[2.0] source file changes
This commit is contained in:
+247
-53
@@ -1,10 +1,38 @@
|
||||
|
||||
|
||||
function Calendar(element, options, eventSources) {
|
||||
function Calendar(element, instanceOptions) {
|
||||
var t = this;
|
||||
|
||||
|
||||
|
||||
// Build options object
|
||||
// -----------------------------------------------------------------------------------
|
||||
// Precedence (lowest to highest): defaults, rtlDefaults, langOptions, instanceOptions
|
||||
|
||||
var options = mergeOptions({}, defaults, instanceOptions);
|
||||
var langOptions;
|
||||
|
||||
// determine language options
|
||||
if (options.lang in langOptionHash) {
|
||||
langOptions = langOptionHash[options.lang];
|
||||
}
|
||||
else {
|
||||
langOptions = langOptionHash[defaults.lang];
|
||||
}
|
||||
|
||||
if (langOptions) { // if language options exist, rebuild...
|
||||
options = mergeOptions({}, defaults, langOptions, instanceOptions);
|
||||
}
|
||||
|
||||
if (options.isRTL) { // is isRTL, rebuild...
|
||||
options = mergeOptions({}, defaults, rtlDefaults, langOptions || {}, instanceOptions);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// exports
|
||||
// Exports
|
||||
// -----------------------------------------------------------------------------------
|
||||
|
||||
t.options = options;
|
||||
t.render = render;
|
||||
t.destroy = destroy;
|
||||
@@ -22,21 +50,186 @@ function Calendar(element, options, eventSources) {
|
||||
t.today = today;
|
||||
t.gotoDate = gotoDate;
|
||||
t.incrementDate = incrementDate;
|
||||
t.formatDate = function(format, date) { return formatDate(format, date, options) };
|
||||
t.formatDates = function(format, date1, date2) { return formatDates(format, date1, date2, options) };
|
||||
t.getDate = getDate;
|
||||
t.getCalendar = getCalendar;
|
||||
t.getView = getView;
|
||||
t.option = option;
|
||||
t.trigger = trigger;
|
||||
|
||||
|
||||
|
||||
// Language-data Internals
|
||||
// -----------------------------------------------------------------------------------
|
||||
// Apply overrides to the current language's data
|
||||
|
||||
var langData = createObject( // make a cheap clone
|
||||
moment.langData(options.lang)
|
||||
);
|
||||
|
||||
if (options.monthNames) {
|
||||
langData._months = options.monthNames;
|
||||
}
|
||||
if (options.monthNamesShort) {
|
||||
langData._monthsShort = options.monthNamesShort;
|
||||
}
|
||||
if (options.dayNames) {
|
||||
langData._weekdays = options.dayNames;
|
||||
}
|
||||
if (options.dayNamesShort) {
|
||||
langData._weekdaysShort = options.dayNamesShort;
|
||||
}
|
||||
if (options.firstDay) {
|
||||
var _week = createObject(langData._week); // _week: { dow: # }
|
||||
_week.dow = options.firstDay;
|
||||
langData._week = _week;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Calendar-specific Date Utilities
|
||||
// -----------------------------------------------------------------------------------
|
||||
|
||||
|
||||
t.defaultAllDayEventDuration = moment.duration(options.defaultAllDayEventDuration);
|
||||
t.defaultTimedEventDuration = moment.duration(options.defaultTimedEventDuration);
|
||||
|
||||
|
||||
// Builds a moment using the settings of the current calendar: timezone and language.
|
||||
// Accepts anything the vanilla moment() constructor accepts.
|
||||
t.moment = function() {
|
||||
var mom;
|
||||
|
||||
if (options.timezone === 'local') {
|
||||
mom = fc.moment.apply(null, arguments);
|
||||
}
|
||||
else if (options.timezone === 'UTC') {
|
||||
mom = fc.moment.utc.apply(null, arguments);
|
||||
}
|
||||
else {
|
||||
mom = fc.moment.parseZone.apply(null, arguments);
|
||||
}
|
||||
|
||||
mom._lang = langData;
|
||||
|
||||
return mom;
|
||||
};
|
||||
|
||||
|
||||
// Returns a boolean about whether or not the calendar knows how to calculate
|
||||
// the timezone offset of arbitrary dates in the current timezone.
|
||||
t.getIsAmbigTimezone = function() {
|
||||
return options.timezone !== 'local' && options.timezone !== 'UTC';
|
||||
};
|
||||
|
||||
|
||||
// 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 moment for the current date, as defined by the client's computer,
|
||||
// or overridden by the `now` option.
|
||||
t.getNow = function() {
|
||||
var now = options.now;
|
||||
if (typeof now === 'function') {
|
||||
now = now();
|
||||
}
|
||||
return t.moment(now);
|
||||
};
|
||||
|
||||
|
||||
// Calculates the week number for a moment according to the calendar's
|
||||
// `weekNumberCalculation` setting.
|
||||
t.calculateWeekNumber = function(mom) {
|
||||
var calc = options.weekNumberCalculation;
|
||||
if (calc == 'local') {
|
||||
return mom.week();
|
||||
}
|
||||
else if (calc.toUpperCase() == 'ISO') {
|
||||
return mom.isoWeek();
|
||||
}
|
||||
else if (typeof calc === 'function') {
|
||||
return calc(mom);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Get an event's normalized end date. If not present, calculate it from the defaults.
|
||||
t.getEventEnd = function(event) {
|
||||
if (event.end) {
|
||||
return event.end.clone();
|
||||
}
|
||||
else {
|
||||
return t.getDefaultEventEnd(event.allDay, event.start);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 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();
|
||||
|
||||
if (allDay) {
|
||||
end.stripTime().add(t.defaultAllDayEventDuration);
|
||||
}
|
||||
else {
|
||||
end.add(t.defaultTimedEventDuration);
|
||||
}
|
||||
|
||||
if (t.getIsAmbigTimezone()) {
|
||||
end.stripZone(); // we don't know what the tzo should be
|
||||
}
|
||||
|
||||
return end;
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Date-formatting Utilities
|
||||
// -----------------------------------------------------------------------------------
|
||||
|
||||
|
||||
// Like the vanilla formatRange, but with calendar-specific settings applied.
|
||||
t.formatRange = function(m1, m2, formatStr) {
|
||||
|
||||
// a function that returns a formatStr // TODO: in future, precompute this
|
||||
if (typeof formatStr === 'function') {
|
||||
formatStr = formatStr.call(t, options, langData);
|
||||
}
|
||||
|
||||
return formatRange(m1, m2, formatStr, null, options.isRTL);
|
||||
};
|
||||
|
||||
|
||||
// Like the vanilla formatDate, but with calendar-specific settings applied.
|
||||
t.formatDate = function(mom, formatStr) {
|
||||
|
||||
// a function that returns a formatStr // TODO: in future, precompute this
|
||||
if (typeof formatStr === 'function') {
|
||||
formatStr = formatStr.call(t, options, langData);
|
||||
}
|
||||
|
||||
return formatDate(mom, formatStr);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
// imports
|
||||
EventManager.call(t, options, eventSources);
|
||||
// Imports
|
||||
// -----------------------------------------------------------------------------------
|
||||
|
||||
|
||||
EventManager.call(t, options);
|
||||
var isFetchNeeded = t.isFetchNeeded;
|
||||
var fetchEvents = t.fetchEvents;
|
||||
|
||||
|
||||
// locals
|
||||
|
||||
|
||||
|
||||
// Locals
|
||||
// -----------------------------------------------------------------------------------
|
||||
|
||||
|
||||
var _element = element[0];
|
||||
var header;
|
||||
var headerElement;
|
||||
@@ -47,17 +240,22 @@ function Calendar(element, options, eventSources) {
|
||||
var suggestedViewHeight;
|
||||
var resizeUID = 0;
|
||||
var ignoreWindowResize = 0;
|
||||
var date = new Date();
|
||||
var date;
|
||||
var events = [];
|
||||
var _dragElement;
|
||||
|
||||
|
||||
|
||||
/* Main Rendering
|
||||
-----------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
setYMD(date, options.year, options.month, options.date);
|
||||
// Main Rendering
|
||||
// -----------------------------------------------------------------------------------
|
||||
|
||||
|
||||
if (options.defaultDate != null) {
|
||||
date = t.moment(options.defaultDate);
|
||||
}
|
||||
else {
|
||||
date = t.getNow();
|
||||
}
|
||||
|
||||
|
||||
function render(inc) {
|
||||
@@ -85,7 +283,7 @@ function Calendar(element, options, eventSources) {
|
||||
element.addClass('ui-widget');
|
||||
}
|
||||
|
||||
content = $("<div class='fc-content' style='position:relative'/>")
|
||||
content = $("<div class='fc-content' />")
|
||||
.prependTo(element);
|
||||
|
||||
header = new Header(t, options);
|
||||
@@ -143,9 +341,9 @@ function Calendar(element, options, eventSources) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* View Rendering
|
||||
-----------------------------------------------------------------------------*/
|
||||
|
||||
// View Rendering
|
||||
// -----------------------------------------------------------------------------------
|
||||
|
||||
|
||||
function changeView(newViewName) {
|
||||
@@ -170,7 +368,7 @@ function Calendar(element, options, eventSources) {
|
||||
header.activateButton(newViewName);
|
||||
|
||||
currentView = new fcViews[newViewName](
|
||||
$("<div class='fc-view fc-view-" + newViewName + "' style='position:relative'/>")
|
||||
$("<div class='fc-view fc-view-" + newViewName + "' />")
|
||||
.appendTo(content),
|
||||
t // the calendar object
|
||||
);
|
||||
@@ -185,7 +383,8 @@ function Calendar(element, options, eventSources) {
|
||||
function renderView(inc) {
|
||||
if (
|
||||
!currentView.start || // never rendered before
|
||||
inc || date < currentView.start || date >= currentView.end // or new date range
|
||||
inc || // explicit date window change
|
||||
!date.isWithin(currentView.intervalStart, currentView.intervalEnd) // implicit date window change
|
||||
) {
|
||||
if (elementVisible()) {
|
||||
_renderView(inc);
|
||||
@@ -204,7 +403,10 @@ function Calendar(element, options, eventSources) {
|
||||
}
|
||||
|
||||
freezeContentHeight();
|
||||
currentView.render(date, inc || 0); // the view's render method ONLY renders the skeleton, nothing else
|
||||
if (inc) {
|
||||
date = currentView.incrementDate(date, inc);
|
||||
}
|
||||
currentView.render(date.clone()); // the view's render method ONLY renders the skeleton, nothing else
|
||||
setSize();
|
||||
unfreezeContentHeight();
|
||||
(currentView.afterRender || noop)();
|
||||
@@ -213,7 +415,6 @@ function Calendar(element, options, eventSources) {
|
||||
updateTodayButton();
|
||||
|
||||
trigger('viewRender', currentView, currentView, currentView.element);
|
||||
currentView.trigger('viewDisplay', _element); // deprecated
|
||||
|
||||
ignoreWindowResize--;
|
||||
|
||||
@@ -222,8 +423,8 @@ function Calendar(element, options, eventSources) {
|
||||
|
||||
|
||||
|
||||
/* Resizing
|
||||
-----------------------------------------------------------------------------*/
|
||||
// Resizing
|
||||
// -----------------------------------------------------------------------------------
|
||||
|
||||
|
||||
function updateSize() {
|
||||
@@ -324,7 +525,7 @@ function Calendar(element, options, eventSources) {
|
||||
|
||||
|
||||
function getAndRenderEvents() {
|
||||
if (!options.lazyFetching || isFetchNeeded(currentView.visStart, currentView.visEnd)) {
|
||||
if (!options.lazyFetching || isFetchNeeded(currentView.start, currentView.end)) {
|
||||
fetchAndRenderEvents();
|
||||
}
|
||||
else {
|
||||
@@ -334,7 +535,7 @@ function Calendar(element, options, eventSources) {
|
||||
|
||||
|
||||
function fetchAndRenderEvents() {
|
||||
fetchEvents(currentView.visStart, currentView.visEnd);
|
||||
fetchEvents(currentView.start, currentView.end);
|
||||
// ... will call reportEvents
|
||||
// ... which will call renderEvents
|
||||
}
|
||||
@@ -364,8 +565,8 @@ function Calendar(element, options, eventSources) {
|
||||
|
||||
|
||||
function updateTodayButton() {
|
||||
var today = new Date();
|
||||
if (today >= currentView.start && today < currentView.end) {
|
||||
var now = t.getNow();
|
||||
if (now.isWithin(currentView.intervalStart, currentView.intervalEnd)) {
|
||||
header.disableButton('today');
|
||||
}
|
||||
else {
|
||||
@@ -379,8 +580,8 @@ function Calendar(element, options, eventSources) {
|
||||
-----------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
function select(start, end, allDay) {
|
||||
currentView.select(start, end, allDay===undefined ? true : allDay);
|
||||
function select(start, end) {
|
||||
currentView.select(start, end);
|
||||
}
|
||||
|
||||
|
||||
@@ -407,49 +608,37 @@ function Calendar(element, options, eventSources) {
|
||||
|
||||
|
||||
function prevYear() {
|
||||
addYears(date, -1);
|
||||
date.add('years', -1);
|
||||
renderView();
|
||||
}
|
||||
|
||||
|
||||
function nextYear() {
|
||||
addYears(date, 1);
|
||||
date.add('years', 1);
|
||||
renderView();
|
||||
}
|
||||
|
||||
|
||||
function today() {
|
||||
date = new Date();
|
||||
date = t.getNow();
|
||||
renderView();
|
||||
}
|
||||
|
||||
|
||||
function gotoDate(year, month, dateOfMonth) {
|
||||
if (year instanceof Date) {
|
||||
date = cloneDate(year); // provided 1 argument, a Date
|
||||
}else{
|
||||
setYMD(date, year, month, dateOfMonth);
|
||||
}
|
||||
function gotoDate(dateInput) {
|
||||
date = t.moment(dateInput);
|
||||
renderView();
|
||||
}
|
||||
|
||||
|
||||
function incrementDate(years, months, days) {
|
||||
if (years !== undefined) {
|
||||
addYears(date, years);
|
||||
}
|
||||
if (months !== undefined) {
|
||||
addMonths(date, months);
|
||||
}
|
||||
if (days !== undefined) {
|
||||
addDays(date, days);
|
||||
}
|
||||
function incrementDate() {
|
||||
date.add.apply(date, arguments);
|
||||
renderView();
|
||||
}
|
||||
|
||||
|
||||
function getDate() {
|
||||
return cloneDate(date);
|
||||
return date.clone();
|
||||
}
|
||||
|
||||
|
||||
@@ -480,6 +669,11 @@ function Calendar(element, options, eventSources) {
|
||||
/* Misc
|
||||
-----------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
function getCalendar() {
|
||||
return t;
|
||||
}
|
||||
|
||||
|
||||
function getView() {
|
||||
return currentView;
|
||||
|
||||
+202
-96
@@ -10,7 +10,7 @@ var ajaxDefaults = {
|
||||
var eventGUID = 1;
|
||||
|
||||
|
||||
function EventManager(options, _sources) {
|
||||
function EventManager(options) { // assumed to be a calendar
|
||||
var t = this;
|
||||
|
||||
|
||||
@@ -23,13 +23,14 @@ function EventManager(options, _sources) {
|
||||
t.renderEvent = renderEvent;
|
||||
t.removeEvents = removeEvents;
|
||||
t.clientEvents = clientEvents;
|
||||
t.normalizeEvent = normalizeEvent;
|
||||
|
||||
|
||||
// imports
|
||||
var trigger = t.trigger;
|
||||
var getView = t.getView;
|
||||
var reportEvents = t.reportEvents;
|
||||
var getEventEnd = t.getEventEnd;
|
||||
var mutateEvent = t.mutateEvent;
|
||||
|
||||
|
||||
// locals
|
||||
@@ -40,7 +41,14 @@ function EventManager(options, _sources) {
|
||||
var pendingSourceCnt = 0;
|
||||
var loadingLevel = 0;
|
||||
var cache = [];
|
||||
|
||||
|
||||
|
||||
|
||||
var _sources = options.eventSources || [];
|
||||
|
||||
if (options.events) {
|
||||
_sources.push(options.events);
|
||||
}
|
||||
|
||||
for (var i=0; i<_sources.length; i++) {
|
||||
_addEventSource(_sources[i]);
|
||||
@@ -53,7 +61,10 @@ function EventManager(options, _sources) {
|
||||
|
||||
|
||||
function isFetchNeeded(start, end) {
|
||||
return !rangeStart || start < rangeStart || end > rangeEnd;
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@@ -73,23 +84,16 @@ function EventManager(options, _sources) {
|
||||
function fetchEventSource(source, fetchID) {
|
||||
_fetchEventSource(source, function(events) {
|
||||
if (fetchID == currentFetchID) {
|
||||
if (events) {
|
||||
|
||||
if (options.eventDataTransform) {
|
||||
events = $.map(events, options.eventDataTransform);
|
||||
}
|
||||
if (source.eventDataTransform) {
|
||||
events = $.map(events, source.eventDataTransform);
|
||||
}
|
||||
// TODO: this technique is not ideal for static array event sources.
|
||||
// For arrays, we'll want to process all events right in the beginning, then never again.
|
||||
|
||||
if (events) {
|
||||
for (var i=0; i<events.length; i++) {
|
||||
events[i].source = source;
|
||||
normalizeEvent(events[i]);
|
||||
var event = buildEvent(events[i], source);
|
||||
if (event) {
|
||||
cache.push(event);
|
||||
}
|
||||
}
|
||||
cache = cache.concat(events);
|
||||
}
|
||||
|
||||
pendingSourceCnt--;
|
||||
if (!pendingSourceCnt) {
|
||||
reportEvents(cache);
|
||||
@@ -103,8 +107,17 @@ function EventManager(options, _sources) {
|
||||
var i;
|
||||
var fetchers = fc.sourceFetchers;
|
||||
var res;
|
||||
|
||||
for (i=0; i<fetchers.length; i++) {
|
||||
res = fetchers[i](source, rangeStart, rangeEnd, callback);
|
||||
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;
|
||||
@@ -115,14 +128,21 @@ function EventManager(options, _sources) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var events = source.events;
|
||||
if (events) {
|
||||
if ($.isFunction(events)) {
|
||||
pushLoading();
|
||||
events(cloneDate(rangeStart), cloneDate(rangeEnd), function(events) {
|
||||
callback(events);
|
||||
popLoading();
|
||||
});
|
||||
events.call(
|
||||
t, // this, the Calendar object
|
||||
rangeStart.clone(),
|
||||
rangeEnd.clone(),
|
||||
options.timezone,
|
||||
function(events) {
|
||||
callback(events);
|
||||
popLoading();
|
||||
}
|
||||
);
|
||||
}
|
||||
else if ($.isArray(events)) {
|
||||
callback(events);
|
||||
@@ -154,11 +174,16 @@ function EventManager(options, _sources) {
|
||||
|
||||
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] = Math.round(+rangeStart / 1000);
|
||||
data[startParam] = rangeStart.format();
|
||||
}
|
||||
if (endParam) {
|
||||
data[endParam] = Math.round(+rangeEnd / 1000);
|
||||
data[endParam] = rangeEnd.format();
|
||||
}
|
||||
if (options.timezone && options.timezone != 'local') {
|
||||
data[timezoneParam] = options.timezone;
|
||||
}
|
||||
|
||||
pushLoading();
|
||||
@@ -232,55 +257,60 @@ function EventManager(options, _sources) {
|
||||
|
||||
/* Manipulation
|
||||
-----------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
function updateEvent(event) { // update an existing event
|
||||
var i, len = cache.length, e,
|
||||
defaultEventEnd = getView().defaultEventEnd, // getView???
|
||||
startDelta = event.start - event._start,
|
||||
endDelta = event.end ?
|
||||
(event.end - (event._end || defaultEventEnd(event))) // event._end would be null if event.end
|
||||
: 0; // was null and event was just resized
|
||||
for (i=0; i<len; i++) {
|
||||
e = cache[i];
|
||||
if (e._id == event._id && e != event) {
|
||||
e.start = new Date(+e.start + startDelta);
|
||||
if (event.end) {
|
||||
if (e.end) {
|
||||
e.end = new Date(+e.end + endDelta);
|
||||
}else{
|
||||
e.end = new Date(+defaultEventEnd(e) + endDelta);
|
||||
}
|
||||
}else{
|
||||
e.end = null;
|
||||
}
|
||||
e.title = event.title;
|
||||
e.url = event.url;
|
||||
e.allDay = event.allDay;
|
||||
e.className = event.className;
|
||||
e.editable = event.editable;
|
||||
e.color = event.color;
|
||||
e.backgroundColor = event.backgroundColor;
|
||||
e.borderColor = event.borderColor;
|
||||
e.textColor = event.textColor;
|
||||
normalizeEvent(e);
|
||||
}
|
||||
}
|
||||
normalizeEvent(event);
|
||||
reportEvents(cache);
|
||||
|
||||
|
||||
function updateEvent(event) {
|
||||
mutateEvent(event);
|
||||
propagateMiscProperties(event);
|
||||
reportEvents(cache); // reports event modifications (so we can redraw)
|
||||
}
|
||||
|
||||
|
||||
function renderEvent(event, stick) {
|
||||
normalizeEvent(event);
|
||||
if (!event.source) {
|
||||
if (stick) {
|
||||
stickySource.events.push(event);
|
||||
event.source = stickySource;
|
||||
|
||||
|
||||
var miscCopyableProps = [
|
||||
'title',
|
||||
'url',
|
||||
'allDay',
|
||||
'className',
|
||||
'editable',
|
||||
'color',
|
||||
'backgroundColor',
|
||||
'borderColor',
|
||||
'textColor'
|
||||
];
|
||||
|
||||
function propagateMiscProperties(event) {
|
||||
var i;
|
||||
var cachedEvent;
|
||||
var j;
|
||||
var prop;
|
||||
|
||||
for (i=0; i<cache.length; i++) {
|
||||
cachedEvent = cache[i];
|
||||
if (cachedEvent._id == event._id && cachedEvent !== event) {
|
||||
for (j=0; j<miscCopyableProps.length; j++) {
|
||||
prop = miscCopyableProps[j];
|
||||
if (event[prop] !== undefined) {
|
||||
cachedEvent[prop] = event[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
cache.push(event);
|
||||
}
|
||||
reportEvents(cache);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function renderEvent(eventData, stick) {
|
||||
var event = buildEvent(eventData);
|
||||
if (event) {
|
||||
if (!event.source) {
|
||||
if (stick) {
|
||||
stickySource.events.push(event);
|
||||
event.source = stickySource;
|
||||
}
|
||||
cache.push(event);
|
||||
}
|
||||
reportEvents(cache);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -348,35 +378,103 @@ function EventManager(options, _sources) {
|
||||
|
||||
/* Event Normalization
|
||||
-----------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
function normalizeEvent(event) {
|
||||
var source = event.source || {};
|
||||
var ignoreTimezone = firstDefined(source.ignoreTimezone, options.ignoreTimezone);
|
||||
event._id = event._id || (event.id === undefined ? '_fc' + eventGUID++ : event.id + '');
|
||||
if (event.date) {
|
||||
if (!event.start) {
|
||||
event.start = event.date;
|
||||
|
||||
function buildEvent(data, source) { // source may be undefined!
|
||||
var out = {};
|
||||
var start;
|
||||
var end;
|
||||
var allDay;
|
||||
var allDayDefault;
|
||||
|
||||
if (options.eventDataTransform) {
|
||||
data = options.eventDataTransform(data);
|
||||
}
|
||||
if (source && source.eventDataTransform) {
|
||||
data = source.eventDataTransform(data);
|
||||
}
|
||||
|
||||
start = t.moment(data.start || data.date); // "date" is an alias for "start"
|
||||
if (!start.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
end = null;
|
||||
if (data.end) {
|
||||
end = t.moment(data.end);
|
||||
if (!end.isValid()) {
|
||||
return;
|
||||
}
|
||||
delete event.date;
|
||||
}
|
||||
event._start = cloneDate(event.start = parseDate(event.start, ignoreTimezone));
|
||||
event.end = parseDate(event.end, ignoreTimezone);
|
||||
if (event.end && event.end <= event.start) {
|
||||
event.end = null;
|
||||
}
|
||||
event._end = event.end ? cloneDate(event.end) : null;
|
||||
if (event.allDay === undefined) {
|
||||
event.allDay = firstDefined(source.allDayDefault, options.allDayDefault);
|
||||
}
|
||||
if (event.className) {
|
||||
if (typeof event.className == 'string') {
|
||||
event.className = event.className.split(/\s+/);
|
||||
|
||||
allDay = data.allDay;
|
||||
if (allDay === undefined) {
|
||||
allDayDefault = firstDefined(
|
||||
source ? source.allDayDefault : undefined,
|
||||
options.allDayDefault
|
||||
);
|
||||
if (allDayDefault !== undefined) {
|
||||
// use the default
|
||||
allDay = allDayDefault;
|
||||
}
|
||||
else {
|
||||
// all dates need to have ambig time for the event to be considered allDay
|
||||
allDay = !start.hasTime() && (!end || !end.hasTime());
|
||||
}
|
||||
}else{
|
||||
event.className = [];
|
||||
}
|
||||
// TODO: if there is no start date, return false to indicate an invalid event
|
||||
|
||||
// normalize the date based on allDay
|
||||
if (allDay) {
|
||||
// neither date should have a time
|
||||
if (start.hasTime()) {
|
||||
start.stripTime();
|
||||
}
|
||||
if (end && end.hasTime()) {
|
||||
end.stripTime();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// force a time/zone up the dates
|
||||
if (!start.hasTime()) {
|
||||
start = t.rezoneDate(start);
|
||||
}
|
||||
if (end && !end.hasTime()) {
|
||||
end = t.rezoneDate(end);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy all properties over to the resulting object.
|
||||
// The special-case properties will be copied over afterwards.
|
||||
$.extend(out, data);
|
||||
|
||||
if (source) {
|
||||
out.source = source;
|
||||
}
|
||||
|
||||
out._id = data._id || (data.id === undefined ? '_fc' + eventGUID++ : data.id + '');
|
||||
|
||||
if (data.className) {
|
||||
if (typeof data.className == 'string') {
|
||||
out.className = data.className.split(/\s+/);
|
||||
}
|
||||
else { // assumed to be an array
|
||||
out.className = data.className;
|
||||
}
|
||||
}
|
||||
else {
|
||||
out.className = [];
|
||||
}
|
||||
|
||||
out.allDay = allDay;
|
||||
out.start = start;
|
||||
out.end = end;
|
||||
|
||||
if (options.forceEventDuration && !out.end) {
|
||||
out.end = getEventEnd(out);
|
||||
}
|
||||
|
||||
backupEventDates(out);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@@ -396,7 +494,7 @@ function EventManager(options, _sources) {
|
||||
}
|
||||
var normalizers = fc.sourceNormalizers;
|
||||
for (var i=0; i<normalizers.length; i++) {
|
||||
normalizers[i](source);
|
||||
normalizers[i].call(t, source);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,3 +510,11 @@ function EventManager(options, _sources) {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
+18
-8
@@ -68,16 +68,26 @@ function Header(calendar, options) {
|
||||
};
|
||||
}
|
||||
if (buttonClick) {
|
||||
var icon = options.theme ? smartProperty(options.buttonIcons, buttonName) : null; // why are we using smartProperty here?
|
||||
var text = smartProperty(options.buttonText, buttonName); // why are we using smartProperty here?
|
||||
|
||||
// smartProperty allows different text per view button (ex: "Agenda Week" vs "Basic Week")
|
||||
var themeIcon = smartProperty(options.themeButtonIcons, buttonName);
|
||||
var normalIcon = smartProperty(options.buttonIcons, buttonName);
|
||||
var text = smartProperty(options.buttonText, buttonName);
|
||||
var html;
|
||||
|
||||
if (themeIcon && options.theme) {
|
||||
html = "<span class='ui-icon ui-icon-" + themeIcon + "'></span>";
|
||||
}
|
||||
else if (normalIcon && !options.theme) {
|
||||
html = "<span class='fc-icon fc-icon-" + normalIcon + "'></span>";
|
||||
}
|
||||
else {
|
||||
html = htmlEscape(text || buttonName);
|
||||
}
|
||||
|
||||
var button = $(
|
||||
"<span class='fc-button fc-button-" + buttonName + " " + tm + "-state-default'>" +
|
||||
(icon ?
|
||||
"<span class='fc-icon-wrap'>" +
|
||||
"<span class='ui-icon ui-icon-" + icon + "'/>" +
|
||||
"</span>" :
|
||||
text
|
||||
) +
|
||||
html +
|
||||
"</span>"
|
||||
)
|
||||
.click(function() {
|
||||
|
||||
+14
-19
@@ -1,39 +1,34 @@
|
||||
|
||||
fcViews.agendaDay = AgendaDayView;
|
||||
|
||||
|
||||
function AgendaDayView(element, calendar) {
|
||||
function AgendaDayView(element, calendar) { // TODO: make a DayView mixin
|
||||
var t = this;
|
||||
|
||||
|
||||
// exports
|
||||
t.incrementDate = incrementDate;
|
||||
t.render = render;
|
||||
|
||||
|
||||
// imports
|
||||
AgendaView.call(t, element, calendar, 'agendaDay');
|
||||
var opt = t.opt;
|
||||
var renderAgenda = t.renderAgenda;
|
||||
var skipHiddenDays = t.skipHiddenDays;
|
||||
var formatDate = calendar.formatDate;
|
||||
|
||||
|
||||
function render(date, delta) {
|
||||
|
||||
if (delta) {
|
||||
addDays(date, delta);
|
||||
}
|
||||
skipHiddenDays(date, delta < 0 ? -1 : 1);
|
||||
|
||||
var start = cloneDate(date, true);
|
||||
var end = addDays(cloneDate(start), 1);
|
||||
function incrementDate(date, delta) {
|
||||
var out = date.clone().stripTime().add('days', delta);
|
||||
out = t.skipHiddenDays(out, delta < 0 ? -1 : 1);
|
||||
return out;
|
||||
}
|
||||
|
||||
t.title = formatDate(date, opt('titleFormat'));
|
||||
|
||||
t.start = t.visStart = start;
|
||||
t.end = t.visEnd = end;
|
||||
function render(date) {
|
||||
|
||||
renderAgenda(1);
|
||||
t.start = t.intervalStart = date.clone().stripTime();
|
||||
t.end = t.intervalEnd = t.start.clone().add('days', 1);
|
||||
|
||||
t.title = calendar.formatDate(t.start, t.opt('titleFormat'));
|
||||
|
||||
t.renderAgenda(1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -15,15 +15,12 @@ function AgendaEventRenderer() {
|
||||
var trigger = t.trigger;
|
||||
var isEventDraggable = t.isEventDraggable;
|
||||
var isEventResizable = t.isEventResizable;
|
||||
var eventEnd = t.eventEnd;
|
||||
var eventElementHandlers = t.eventElementHandlers;
|
||||
var setHeight = t.setHeight;
|
||||
var getDaySegmentContainer = t.getDaySegmentContainer;
|
||||
var getSlotSegmentContainer = t.getSlotSegmentContainer;
|
||||
var getHoverListener = t.getHoverListener;
|
||||
var getMaxMinute = t.getMaxMinute;
|
||||
var getMinMinute = t.getMinMinute;
|
||||
var timePosition = t.timePosition;
|
||||
var computeDateTop = t.computeDateTop;
|
||||
var getIsCellAllDay = t.getIsCellAllDay;
|
||||
var colContentLeft = t.colContentLeft;
|
||||
var colContentRight = t.colContentRight;
|
||||
@@ -31,7 +28,9 @@ function AgendaEventRenderer() {
|
||||
var getColCnt = t.getColCnt;
|
||||
var getColWidth = t.getColWidth;
|
||||
var getSnapHeight = t.getSnapHeight;
|
||||
var getSnapMinutes = t.getSnapMinutes;
|
||||
var getSnapDuration = t.getSnapDuration;
|
||||
var getSlotHeight = t.getSlotHeight;
|
||||
var getSlotDuration = t.getSlotDuration;
|
||||
var getSlotContainer = t.getSlotContainer;
|
||||
var reportEventElement = t.reportEventElement;
|
||||
var showEvents = t.showEvents;
|
||||
@@ -41,9 +40,12 @@ function AgendaEventRenderer() {
|
||||
var renderDayOverlay = t.renderDayOverlay;
|
||||
var clearOverlays = t.clearOverlays;
|
||||
var renderDayEvents = t.renderDayEvents;
|
||||
var getMinTime = t.getMinTime;
|
||||
var getMaxTime = t.getMaxTime;
|
||||
var calendar = t.calendar;
|
||||
var formatDate = calendar.formatDate;
|
||||
var formatDates = calendar.formatDates;
|
||||
var formatRange = calendar.formatRange;
|
||||
var getEventEnd = calendar.getEventEnd;
|
||||
|
||||
|
||||
// overrides
|
||||
@@ -84,25 +86,21 @@ function AgendaEventRenderer() {
|
||||
|
||||
function compileSlotSegs(events) {
|
||||
var colCnt = getColCnt(),
|
||||
minMinute = getMinMinute(),
|
||||
maxMinute = getMaxMinute(),
|
||||
d,
|
||||
visEventEnds = $.map(events, slotEventEnd),
|
||||
minTime = getMinTime(),
|
||||
maxTime = getMaxTime(),
|
||||
cellDate,
|
||||
i,
|
||||
j, seg,
|
||||
colSegs,
|
||||
segs = [];
|
||||
|
||||
for (i=0; i<colCnt; i++) {
|
||||
|
||||
d = cellToDate(0, i);
|
||||
addMinutes(d, minMinute);
|
||||
cellDate = cellToDate(0, i);
|
||||
|
||||
colSegs = sliceSegs(
|
||||
events,
|
||||
visEventEnds,
|
||||
d,
|
||||
addMinutes(cloneDate(d), maxMinute-minMinute)
|
||||
cellDate.clone().time(minTime),
|
||||
cellDate.clone().time(maxTime)
|
||||
);
|
||||
|
||||
colSegs = placeSlotSegs(colSegs); // returns a new order
|
||||
@@ -118,31 +116,45 @@ function AgendaEventRenderer() {
|
||||
}
|
||||
|
||||
|
||||
function sliceSegs(events, visEventEnds, start, end) {
|
||||
function sliceSegs(events, rangeStart, rangeEnd) {
|
||||
|
||||
// normalize, because all dates will be compared w/o zones
|
||||
rangeStart = rangeStart.clone().stripZone();
|
||||
rangeEnd = rangeEnd.clone().stripZone();
|
||||
|
||||
var segs = [],
|
||||
i, len=events.length, event,
|
||||
eventStart, eventEnd,
|
||||
segStart, segEnd,
|
||||
isStart, isEnd;
|
||||
for (i=0; i<len; i++) {
|
||||
|
||||
event = events[i];
|
||||
eventStart = event.start;
|
||||
eventEnd = visEventEnds[i];
|
||||
if (eventEnd > start && eventStart < end) {
|
||||
if (eventStart < start) {
|
||||
segStart = cloneDate(start);
|
||||
|
||||
// get dates, make copies, then strip zone to normalize
|
||||
eventStart = event.start.clone().stripZone();
|
||||
eventEnd = getEventEnd(event).stripZone();
|
||||
|
||||
if (eventEnd > rangeStart && eventStart < rangeEnd) {
|
||||
|
||||
if (eventStart < rangeStart) {
|
||||
segStart = rangeStart.clone();
|
||||
isStart = false;
|
||||
}else{
|
||||
}
|
||||
else {
|
||||
segStart = eventStart;
|
||||
isStart = true;
|
||||
}
|
||||
if (eventEnd > end) {
|
||||
segEnd = cloneDate(end);
|
||||
|
||||
if (eventEnd > rangeEnd) {
|
||||
segEnd = rangeEnd.clone();
|
||||
isEnd = false;
|
||||
}else{
|
||||
}
|
||||
else {
|
||||
segEnd = eventEnd;
|
||||
isEnd = true;
|
||||
}
|
||||
|
||||
segs.push({
|
||||
event: event,
|
||||
start: segStart,
|
||||
@@ -152,17 +164,9 @@ function AgendaEventRenderer() {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return segs.sort(compareSlotSegs);
|
||||
}
|
||||
|
||||
|
||||
function slotEventEnd(event) {
|
||||
if (event.end) {
|
||||
return cloneDate(event.end);
|
||||
}else{
|
||||
return addMinutes(cloneDate(event.start), opt('defaultEventMinutes'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// renders events in the 'time slots' at the bottom
|
||||
@@ -194,8 +198,8 @@ function AgendaEventRenderer() {
|
||||
for (i=0; i<segCnt; i++) {
|
||||
seg = segs[i];
|
||||
event = seg.event;
|
||||
top = timePosition(seg.start, seg.start);
|
||||
bottom = timePosition(seg.start, seg.end);
|
||||
top = computeDateTop(seg.start, seg.start);
|
||||
bottom = computeDateTop(seg.end, seg.start);
|
||||
columnLeft = colContentLeft(seg.col);
|
||||
columnRight = colContentRight(seg.col);
|
||||
columnWidth = columnRight - columnLeft;
|
||||
@@ -296,7 +300,9 @@ function AgendaEventRenderer() {
|
||||
if (seg.contentTop !== undefined && height - seg.contentTop < 10) {
|
||||
// not enough room for title, put it in the time (TODO: maybe make both display:inline instead)
|
||||
eventElement.find('div.fc-event-time')
|
||||
.text(formatDate(event.start, opt('timeFormat')) + ' - ' + event.title);
|
||||
.text(
|
||||
formatDate(event.start, opt('timeFormat')) + ' - ' + event.title
|
||||
);
|
||||
eventElement.find('div.fc-event-title')
|
||||
.remove();
|
||||
}
|
||||
@@ -330,6 +336,7 @@ function AgendaEventRenderer() {
|
||||
}else{
|
||||
html += "div";
|
||||
}
|
||||
|
||||
html +=
|
||||
" class='" + classes.join(' ') + "'" +
|
||||
" style=" +
|
||||
@@ -341,14 +348,22 @@ function AgendaEventRenderer() {
|
||||
"'" +
|
||||
">" +
|
||||
"<div class='fc-event-inner'>" +
|
||||
"<div class='fc-event-time'>" +
|
||||
htmlEscape(formatDates(event.start, event.end, opt('timeFormat'))) +
|
||||
"<div class='fc-event-time'>";
|
||||
|
||||
if (event.end) {
|
||||
html += htmlEscape(formatRange(event.start, event.end, opt('timeFormat')));
|
||||
}else{
|
||||
html += htmlEscape(formatDate(event.start, opt('timeFormat')));
|
||||
}
|
||||
|
||||
html +=
|
||||
"</div>" +
|
||||
"<div class='fc-event-title'>" +
|
||||
htmlEscape(event.title || '') +
|
||||
"</div>" +
|
||||
"</div>" +
|
||||
"<div class='fc-event-bg'></div>";
|
||||
|
||||
if (seg.isEnd && isEventResizable(event)) {
|
||||
html +=
|
||||
"<div class='ui-resizable-handle ui-resizable-s'>=</div>";
|
||||
@@ -386,79 +401,98 @@ function AgendaEventRenderer() {
|
||||
var revert;
|
||||
var allDay = true;
|
||||
var dayDelta;
|
||||
|
||||
var hoverListener = getHoverListener();
|
||||
var colWidth = getColWidth();
|
||||
var minTime = getMinTime();
|
||||
var slotDuration = getSlotDuration();
|
||||
var slotHeight = getSlotHeight();
|
||||
var snapDuration = getSnapDuration();
|
||||
var snapHeight = getSnapHeight();
|
||||
var snapMinutes = getSnapMinutes();
|
||||
var minMinute = getMinMinute();
|
||||
|
||||
eventElement.draggable({
|
||||
opacity: opt('dragOpacity', 'month'), // use whatever the month view was using
|
||||
revertDuration: opt('dragRevertDuration'),
|
||||
start: function(ev, ui) {
|
||||
|
||||
trigger('eventDragStart', eventElement, event, ev, ui);
|
||||
hideEvents(event, eventElement);
|
||||
origWidth = eventElement.width();
|
||||
|
||||
hoverListener.start(function(cell, origCell) {
|
||||
clearOverlays();
|
||||
if (cell) {
|
||||
revert = false;
|
||||
|
||||
var origDate = cellToDate(0, origCell.col);
|
||||
var date = cellToDate(0, cell.col);
|
||||
dayDelta = dayDiff(date, origDate);
|
||||
if (!cell.row) {
|
||||
// on full-days
|
||||
dayDelta = date.diff(origDate, 'days');
|
||||
|
||||
if (!cell.row) { // on full-days
|
||||
|
||||
renderDayOverlay(
|
||||
addDays(cloneDate(event.start), dayDelta),
|
||||
addDays(exclEndDay(event), dayDelta)
|
||||
event.start.clone().add('days', dayDelta),
|
||||
getEventEnd(event).add('days', dayDelta)
|
||||
);
|
||||
|
||||
resetElement();
|
||||
}else{
|
||||
// mouse is over bottom slots
|
||||
}
|
||||
else { // mouse is over bottom slots
|
||||
|
||||
if (isStart) {
|
||||
if (allDay) {
|
||||
// convert event to temporary slot-event
|
||||
eventElement.width(colWidth - 10); // don't use entire width
|
||||
setOuterHeight(
|
||||
eventElement,
|
||||
snapHeight * Math.round(
|
||||
(event.end ? ((event.end - event.start) / MINUTE_MS) : opt('defaultEventMinutes')) /
|
||||
snapMinutes
|
||||
)
|
||||
);
|
||||
eventElement.draggable('option', 'grid', [colWidth, 1]);
|
||||
setOuterHeight(eventElement, calendar.defaultTimedEventDuration / slotDuration * slotHeight); // the default height
|
||||
eventElement.draggable('option', 'grid', [ colWidth, 1 ]);
|
||||
allDay = false;
|
||||
}
|
||||
}else{
|
||||
}
|
||||
else {
|
||||
revert = true;
|
||||
}
|
||||
}
|
||||
|
||||
revert = revert || (allDay && !dayDelta);
|
||||
}else{
|
||||
}
|
||||
else {
|
||||
resetElement();
|
||||
revert = true;
|
||||
}
|
||||
|
||||
eventElement.draggable('option', 'revert', revert);
|
||||
|
||||
}, ev, 'drag');
|
||||
},
|
||||
stop: function(ev, ui) {
|
||||
hoverListener.stop();
|
||||
clearOverlays();
|
||||
trigger('eventDragStop', eventElement, event, ev, ui);
|
||||
if (revert) {
|
||||
// hasn't moved or is out of bounds (draggable has already reverted)
|
||||
|
||||
if (revert) { // hasn't moved or is out of bounds (draggable has already reverted)
|
||||
|
||||
resetElement();
|
||||
eventElement.css('filter', ''); // clear IE opacity side-effects
|
||||
showEvents(event, eventElement);
|
||||
}else{
|
||||
// changed!
|
||||
var minuteDelta = 0;
|
||||
}
|
||||
else { // changed!
|
||||
|
||||
var eventStart = event.start.clone().add('days', dayDelta); // already assumed to have a stripped time
|
||||
var snapTime;
|
||||
var snapIndex;
|
||||
if (!allDay) {
|
||||
minuteDelta = Math.round((eventElement.offset().top - getSlotContainer().offset().top) / snapHeight)
|
||||
* snapMinutes
|
||||
+ minMinute
|
||||
- (event.start.getHours() * 60 + event.start.getMinutes());
|
||||
snapIndex = Math.round((eventElement.offset().top - getSlotContainer().offset().top) / snapHeight); // why not use ui.offset.top?
|
||||
snapTime = moment.duration(minTime + snapIndex * snapDuration);
|
||||
eventStart = calendar.rezoneDate(eventStart.clone().time(snapTime));
|
||||
}
|
||||
eventDrop(this, event, dayDelta, minuteDelta, allDay, ev, ui);
|
||||
|
||||
eventDrop(
|
||||
this, // el
|
||||
event,
|
||||
eventStart,
|
||||
ev,
|
||||
ui
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -481,7 +515,7 @@ function AgendaEventRenderer() {
|
||||
var colCnt = getColCnt();
|
||||
var colWidth = getColWidth();
|
||||
var snapHeight = getSnapHeight();
|
||||
var snapMinutes = getSnapMinutes();
|
||||
var snapDuration = getSnapDuration();
|
||||
|
||||
// states
|
||||
var origPosition; // original position of the element, not the mouse
|
||||
@@ -490,7 +524,10 @@ function AgendaEventRenderer() {
|
||||
var isAllDay, prevIsAllDay;
|
||||
var colDelta, prevColDelta;
|
||||
var dayDelta; // derived from colDelta
|
||||
var minuteDelta, prevMinuteDelta;
|
||||
var snapDelta, prevSnapDelta; // the number of snaps away from the original position
|
||||
|
||||
// newly computed
|
||||
var eventStart, eventEnd;
|
||||
|
||||
eventElement.draggable({
|
||||
scroll: false,
|
||||
@@ -512,8 +549,10 @@ function AgendaEventRenderer() {
|
||||
isAllDay = prevIsAllDay = getIsCellAllDay(origCell);
|
||||
colDelta = prevColDelta = 0;
|
||||
dayDelta = 0;
|
||||
minuteDelta = prevMinuteDelta = 0;
|
||||
snapDelta = prevSnapDelta = 0;
|
||||
|
||||
eventStart = null;
|
||||
eventEnd = null;
|
||||
},
|
||||
drag: function(ev, ui) {
|
||||
|
||||
@@ -539,12 +578,12 @@ function AgendaEventRenderer() {
|
||||
col = Math.max(0, col);
|
||||
col = Math.min(colCnt-1, col);
|
||||
var date = cellToDate(0, col);
|
||||
dayDelta = dayDiff(date, origDate);
|
||||
dayDelta = date.diff(origDate, 'days');
|
||||
}
|
||||
|
||||
// calculate minute delta (only if over slots)
|
||||
if (!isAllDay) {
|
||||
minuteDelta = Math.round((ui.position.top - origPosition.top) / snapHeight) * snapMinutes;
|
||||
snapDelta = Math.round((ui.position.top - origPosition.top) / snapHeight);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -553,16 +592,26 @@ function AgendaEventRenderer() {
|
||||
isInBounds != prevIsInBounds ||
|
||||
isAllDay != prevIsAllDay ||
|
||||
colDelta != prevColDelta ||
|
||||
minuteDelta != prevMinuteDelta
|
||||
snapDelta != prevSnapDelta
|
||||
) {
|
||||
|
||||
// compute new dates
|
||||
if (isAllDay) {
|
||||
eventStart = event.start.clone().stripTime().add('days', dayDelta);
|
||||
eventEnd = eventStart.clone().add(calendar.defaultAllDayEventDuration);
|
||||
}
|
||||
else {
|
||||
eventStart = event.start.clone().add(snapDelta * snapDuration).add('days', dayDelta);
|
||||
eventEnd = getEventEnd(event).add(snapDelta * snapDuration).add('days', dayDelta);
|
||||
}
|
||||
|
||||
updateUI();
|
||||
|
||||
// update previous states for next time
|
||||
prevIsInBounds = isInBounds;
|
||||
prevIsAllDay = isAllDay;
|
||||
prevColDelta = colDelta;
|
||||
prevMinuteDelta = minuteDelta;
|
||||
prevSnapDelta = snapDelta;
|
||||
}
|
||||
|
||||
// if out-of-bounds, revert when done, and vice versa.
|
||||
@@ -574,8 +623,14 @@ function AgendaEventRenderer() {
|
||||
clearOverlays();
|
||||
trigger('eventDragStop', eventElement, event, ev, ui);
|
||||
|
||||
if (isInBounds && (isAllDay || dayDelta || minuteDelta)) { // changed!
|
||||
eventDrop(this, event, dayDelta, isAllDay ? 0 : minuteDelta, isAllDay, ev, ui);
|
||||
if (isInBounds && (isAllDay || dayDelta || snapDelta)) { // changed!
|
||||
eventDrop(
|
||||
this, // el
|
||||
event,
|
||||
eventStart,
|
||||
ev,
|
||||
ui
|
||||
);
|
||||
}
|
||||
else { // either no change or out-of-bounds (draggable has already reverted)
|
||||
|
||||
@@ -584,7 +639,7 @@ function AgendaEventRenderer() {
|
||||
isAllDay = false;
|
||||
colDelta = 0;
|
||||
dayDelta = 0;
|
||||
minuteDelta = 0;
|
||||
snapDelta = 0;
|
||||
|
||||
updateUI();
|
||||
eventElement.css('filter', ''); // clear IE opacity side-effects
|
||||
@@ -605,26 +660,27 @@ function AgendaEventRenderer() {
|
||||
if (isAllDay) {
|
||||
timeElement.hide();
|
||||
eventElement.draggable('option', 'grid', null); // disable grid snapping
|
||||
renderDayOverlay(
|
||||
addDays(cloneDate(event.start), dayDelta),
|
||||
addDays(exclEndDay(event), dayDelta)
|
||||
);
|
||||
renderDayOverlay(eventStart, eventEnd);
|
||||
}
|
||||
else {
|
||||
updateTimeText(minuteDelta);
|
||||
updateTimeText();
|
||||
timeElement.css('display', ''); // show() was causing display=inline
|
||||
eventElement.draggable('option', 'grid', [colWidth, snapHeight]); // re-enable grid snapping
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateTimeText(minuteDelta) {
|
||||
var newStart = addMinutes(cloneDate(event.start), minuteDelta);
|
||||
var newEnd;
|
||||
if (event.end) {
|
||||
newEnd = addMinutes(cloneDate(event.end), minuteDelta);
|
||||
function updateTimeText() {
|
||||
var text;
|
||||
if (eventStart) { // must of had a state change
|
||||
if (event.end) {
|
||||
text = formatRange(eventStart, eventEnd, opt('timeFormat'));
|
||||
}
|
||||
else {
|
||||
text = formatDate(eventStart, opt('timeFormat'));
|
||||
}
|
||||
timeElement.text(text);
|
||||
}
|
||||
timeElement.text(formatDates(newStart, newEnd, opt('timeFormat')));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -638,7 +694,9 @@ function AgendaEventRenderer() {
|
||||
function resizableSlotEvent(event, eventElement, timeElement) {
|
||||
var snapDelta, prevSnapDelta;
|
||||
var snapHeight = getSnapHeight();
|
||||
var snapMinutes = getSnapMinutes();
|
||||
var snapDuration = getSnapDuration();
|
||||
var eventEnd;
|
||||
|
||||
eventElement.resizable({
|
||||
handles: {
|
||||
s: '.ui-resizable-handle'
|
||||
@@ -653,22 +711,34 @@ function AgendaEventRenderer() {
|
||||
// don't rely on ui.size.height, doesn't take grid into account
|
||||
snapDelta = Math.round((Math.max(snapHeight, eventElement.height()) - ui.originalSize.height) / snapHeight);
|
||||
if (snapDelta != prevSnapDelta) {
|
||||
timeElement.text(
|
||||
formatDates(
|
||||
eventEnd = getEventEnd(event).add(snapDuration * snapDelta);
|
||||
var text;
|
||||
if (snapDelta || event.end) {
|
||||
text = formatRange(
|
||||
event.start,
|
||||
(!snapDelta && !event.end) ? null : // no change, so don't display time range
|
||||
addMinutes(eventEnd(event), snapMinutes*snapDelta),
|
||||
eventEnd,
|
||||
opt('timeFormat')
|
||||
)
|
||||
);
|
||||
);
|
||||
}
|
||||
else {
|
||||
text = formatDate(event.start, opt('timeFormat'));
|
||||
}
|
||||
timeElement.text(text);
|
||||
prevSnapDelta = snapDelta;
|
||||
}
|
||||
},
|
||||
stop: function(ev, ui) {
|
||||
trigger('eventResizeStop', this, event, ev, ui);
|
||||
if (snapDelta) {
|
||||
eventResize(this, event, 0, snapMinutes*snapDelta, ev, ui);
|
||||
}else{
|
||||
eventResize(
|
||||
this,
|
||||
event,
|
||||
eventEnd,
|
||||
ev,
|
||||
ui
|
||||
);
|
||||
}
|
||||
else {
|
||||
showEvents(event, eventElement);
|
||||
// BUG: if event was really short, need to put title back in span
|
||||
}
|
||||
|
||||
+193
-132
@@ -2,22 +2,39 @@
|
||||
setDefaults({
|
||||
allDaySlot: true,
|
||||
allDayText: 'all-day',
|
||||
firstHour: 6,
|
||||
slotMinutes: 30,
|
||||
defaultEventMinutes: 120,
|
||||
axisFormat: 'h(:mm)tt',
|
||||
|
||||
scrollTime: '06:00:00',
|
||||
|
||||
slotDuration: '00:30:00',
|
||||
|
||||
axisFormat: generateAgendaAxisFormat,
|
||||
timeFormat: {
|
||||
agenda: 'h:mm{ - h:mm}'
|
||||
agenda: generateAgendaTimeFormat
|
||||
},
|
||||
|
||||
dragOpacity: {
|
||||
agenda: .5
|
||||
},
|
||||
minTime: 0,
|
||||
maxTime: 24,
|
||||
minTime: '00:00:00',
|
||||
maxTime: '24:00:00',
|
||||
slotEventOverlap: true
|
||||
});
|
||||
|
||||
|
||||
function generateAgendaAxisFormat(options, langData) {
|
||||
return langData.longDateFormat('LT')
|
||||
.replace(':mm', '(:mm)')
|
||||
.replace(/(\Wmm)$/, '($1)') // like above, but for foreign langs
|
||||
.replace(/\s*a$/i, 'a'); // convert AM/PM/am/pm to lowercase. remove any spaces beforehand
|
||||
}
|
||||
|
||||
|
||||
function generateAgendaTimeFormat(options, langData) {
|
||||
return langData.longDateFormat('LT')
|
||||
.replace(/\s*a$/i, ''); // remove trailing AM/PM
|
||||
}
|
||||
|
||||
|
||||
// TODO: make it work in quirks mode (event corners, all-day height)
|
||||
// TODO: test liquid width, especially in IE6
|
||||
|
||||
@@ -31,10 +48,9 @@ function AgendaView(element, calendar, viewName) {
|
||||
t.setWidth = setWidth;
|
||||
t.setHeight = setHeight;
|
||||
t.afterRender = afterRender;
|
||||
t.defaultEventEnd = defaultEventEnd;
|
||||
t.timePosition = timePosition;
|
||||
t.computeDateTop = computeDateTop;
|
||||
t.getIsCellAllDay = getIsCellAllDay;
|
||||
t.allDayRow = getAllDayRow;
|
||||
t.allDayRow = function() { return allDayRow }; // badly named
|
||||
t.getCoordinateGrid = function() { return coordinateGrid }; // specifically for AgendaEventRenderer
|
||||
t.getHoverListener = function() { return hoverListener };
|
||||
t.colLeft = colLeft;
|
||||
@@ -43,14 +59,16 @@ function AgendaView(element, calendar, viewName) {
|
||||
t.colContentRight = colContentRight;
|
||||
t.getDaySegmentContainer = function() { return daySegmentContainer };
|
||||
t.getSlotSegmentContainer = function() { return slotSegmentContainer };
|
||||
t.getMinMinute = function() { return minMinute };
|
||||
t.getMaxMinute = function() { return maxMinute };
|
||||
t.getSlotContainer = function() { return slotContainer };
|
||||
t.getRowCnt = function() { return 1 };
|
||||
t.getColCnt = function() { return colCnt };
|
||||
t.getColWidth = function() { return colWidth };
|
||||
t.getSnapHeight = function() { return snapHeight };
|
||||
t.getSnapMinutes = function() { return snapMinutes };
|
||||
t.getSnapDuration = function() { return snapDuration };
|
||||
t.getSlotHeight = function() { return slotHeight };
|
||||
t.getSlotDuration = function() { return slotDuration };
|
||||
t.getMinTime = function() { return minTime };
|
||||
t.getMaxTime = function() { return maxTime };
|
||||
t.defaultSelectionEnd = defaultSelectionEnd;
|
||||
t.renderDayOverlay = renderDayOverlay;
|
||||
t.renderSelection = renderSelection;
|
||||
@@ -76,7 +94,9 @@ function AgendaView(element, calendar, viewName) {
|
||||
var cellToDate = t.cellToDate;
|
||||
var dateToCell = t.dateToCell;
|
||||
var rangeToSegments = t.rangeToSegments;
|
||||
var calendar = t.calendar;
|
||||
var formatDate = calendar.formatDate;
|
||||
var calculateWeekNumber = calendar.calculateWeekNumber;
|
||||
|
||||
|
||||
// locals
|
||||
@@ -105,9 +125,11 @@ function AgendaView(element, calendar, viewName) {
|
||||
var axisWidth;
|
||||
var colWidth;
|
||||
var gutterWidth;
|
||||
|
||||
var slotDuration;
|
||||
var slotHeight; // TODO: what if slotHeight changes? (see issue 650)
|
||||
|
||||
var snapMinutes;
|
||||
var snapDuration;
|
||||
var snapRatio; // ratio of number of "selection" slots to normal slots. (ex: 1, 2, 4)
|
||||
var snapHeight; // holds the pixel hight of a "selection" slot
|
||||
|
||||
@@ -121,11 +143,9 @@ function AgendaView(element, calendar, viewName) {
|
||||
|
||||
var tm;
|
||||
var rtl;
|
||||
var minMinute, maxMinute;
|
||||
var minTime;
|
||||
var maxTime;
|
||||
var colFormat;
|
||||
var showWeekNumbers;
|
||||
var weekNumberTitle;
|
||||
var weekNumberFormat;
|
||||
|
||||
|
||||
|
||||
@@ -153,21 +173,14 @@ function AgendaView(element, calendar, viewName) {
|
||||
|
||||
tm = opt('theme') ? 'ui' : 'fc';
|
||||
rtl = opt('isRTL')
|
||||
minMinute = parseTime(opt('minTime'));
|
||||
maxMinute = parseTime(opt('maxTime'));
|
||||
colFormat = opt('columnFormat');
|
||||
|
||||
// week # options. (TODO: bad, logic also in other views)
|
||||
showWeekNumbers = opt('weekNumbers');
|
||||
weekNumberTitle = opt('weekNumberTitle');
|
||||
if (opt('weekNumberCalculation') != 'iso') {
|
||||
weekNumberFormat = "w";
|
||||
}
|
||||
else {
|
||||
weekNumberFormat = "W";
|
||||
}
|
||||
minTime = moment.duration(opt('minTime'));
|
||||
maxTime = moment.duration(opt('maxTime'));
|
||||
|
||||
snapMinutes = opt('snapMinutes') || opt('slotMinutes');
|
||||
slotDuration = moment.duration(opt('slotDuration'));
|
||||
snapDuration = opt('snapDuration');
|
||||
snapDuration = snapDuration ? moment.duration(snapDuration) : slotDuration;
|
||||
}
|
||||
|
||||
|
||||
@@ -177,14 +190,13 @@ function AgendaView(element, calendar, viewName) {
|
||||
|
||||
|
||||
function buildSkeleton() {
|
||||
var s;
|
||||
var headerClass = tm + "-widget-header";
|
||||
var contentClass = tm + "-widget-content";
|
||||
var s;
|
||||
var d;
|
||||
var i;
|
||||
var maxd;
|
||||
var slotTime;
|
||||
var slotDate;
|
||||
var minutes;
|
||||
var slotNormal = opt('slotMinutes') % 15 == 0;
|
||||
var slotNormal = slotDuration.asMinutes() % 15 == 0;
|
||||
|
||||
buildDayTable();
|
||||
|
||||
@@ -201,7 +213,12 @@ function AgendaView(element, calendar, viewName) {
|
||||
s =
|
||||
"<table style='width:100%' class='fc-agenda-allday' cellspacing='0'>" +
|
||||
"<tr>" +
|
||||
"<th class='" + headerClass + " fc-agenda-axis'>" + opt('allDayText') + "</th>" +
|
||||
"<th class='" + headerClass + " fc-agenda-axis'>" +
|
||||
(
|
||||
opt('allDayHTML') ||
|
||||
htmlEscape(opt('allDayText'))
|
||||
) +
|
||||
"</th>" +
|
||||
"<td>" +
|
||||
"<div class='fc-day-content'><div style='position:relative'/></div>" +
|
||||
"</td>" +
|
||||
@@ -240,27 +257,32 @@ function AgendaView(element, calendar, viewName) {
|
||||
s =
|
||||
"<table class='fc-agenda-slots' style='width:100%' cellspacing='0'>" +
|
||||
"<tbody>";
|
||||
d = zeroDate();
|
||||
maxd = addMinutes(cloneDate(d), maxMinute);
|
||||
addMinutes(d, minMinute);
|
||||
|
||||
slotTime = moment.duration(+minTime); // i wish there was .clone() for durations
|
||||
slotCnt = 0;
|
||||
for (i=0; d < maxd; i++) {
|
||||
minutes = d.getMinutes();
|
||||
while (slotTime < maxTime) {
|
||||
slotDate = t.start.clone().time(slotTime); // will be in UTC but that's good. to avoid DST issues
|
||||
minutes = slotDate.minutes();
|
||||
s +=
|
||||
"<tr class='fc-slot" + i + ' ' + (!minutes ? '' : 'fc-minor') + "'>" +
|
||||
"<tr class='fc-slot" + slotCnt + ' ' + (!minutes ? '' : 'fc-minor') + "'>" +
|
||||
"<th class='fc-agenda-axis " + headerClass + "'>" +
|
||||
((!slotNormal || !minutes) ? formatDate(d, opt('axisFormat')) : ' ') +
|
||||
((!slotNormal || !minutes) ?
|
||||
htmlEscape(formatDate(slotDate, opt('axisFormat'))) :
|
||||
' '
|
||||
) +
|
||||
"</th>" +
|
||||
"<td class='" + contentClass + "'>" +
|
||||
"<div style='position:relative'> </div>" +
|
||||
"</td>" +
|
||||
"</tr>";
|
||||
addMinutes(d, opt('slotMinutes'));
|
||||
slotTime.add(slotDuration);
|
||||
slotCnt++;
|
||||
}
|
||||
|
||||
s +=
|
||||
"</tbody>" +
|
||||
"</table>";
|
||||
|
||||
slotTable = $(s).appendTo(slotContainer);
|
||||
|
||||
slotBind(slotTable.find('td'));
|
||||
@@ -319,14 +341,14 @@ function AgendaView(element, calendar, viewName) {
|
||||
"<thead>" +
|
||||
"<tr>";
|
||||
|
||||
if (showWeekNumbers) {
|
||||
if (opt('weekNumbers')) {
|
||||
date = cellToDate(0, 0);
|
||||
weekText = formatDate(date, weekNumberFormat);
|
||||
weekText = calculateWeekNumber(date);
|
||||
if (rtl) {
|
||||
weekText += weekNumberTitle;
|
||||
weekText += opt('weekNumberTitle');
|
||||
}
|
||||
else {
|
||||
weekText = weekNumberTitle + weekText;
|
||||
weekText = opt('weekNumberTitle') + weekText;
|
||||
}
|
||||
html +=
|
||||
"<th class='fc-agenda-axis fc-week-number " + headerClass + "'>" +
|
||||
@@ -340,7 +362,7 @@ function AgendaView(element, calendar, viewName) {
|
||||
for (col=0; col<colCnt; col++) {
|
||||
date = cellToDate(0, col);
|
||||
html +=
|
||||
"<th class='fc-" + dayIDs[date.getDay()] + " fc-col" + col + ' ' + headerClass + "'>" +
|
||||
"<th class='fc-" + dayIDs[date.day()] + " fc-col" + col + ' ' + headerClass + "'>" +
|
||||
htmlEscape(formatDate(date, colFormat)) +
|
||||
"</th>";
|
||||
}
|
||||
@@ -358,7 +380,7 @@ function AgendaView(element, calendar, viewName) {
|
||||
var headerClass = tm + "-widget-header"; // TODO: make these when updateOptions() called
|
||||
var contentClass = tm + "-widget-content";
|
||||
var date;
|
||||
var today = clearTime(new Date());
|
||||
var today = calendar.getNow().stripTime();
|
||||
var col;
|
||||
var cellsHTML;
|
||||
var cellHTML;
|
||||
@@ -378,10 +400,10 @@ function AgendaView(element, calendar, viewName) {
|
||||
|
||||
classNames = [
|
||||
'fc-col' + col,
|
||||
'fc-' + dayIDs[date.getDay()],
|
||||
'fc-' + dayIDs[date.day()],
|
||||
contentClass
|
||||
];
|
||||
if (+date == +today) {
|
||||
if (date.isSame(today, 'day')) {
|
||||
classNames.push(
|
||||
tm + '-state-highlight',
|
||||
'fc-today'
|
||||
@@ -447,9 +469,12 @@ function AgendaView(element, calendar, viewName) {
|
||||
|
||||
// the stylesheet guarantees that the first row has no border.
|
||||
// this allows .height() to work well cross-browser.
|
||||
slotHeight = slotTable.find('tr:first').height() + 1; // +1 for bottom border
|
||||
var slotHeight0 = slotTable.find('tr:first').height() + 1; // +1 for bottom border
|
||||
var slotHeight1 = slotTable.find('tr:eq(1)').height();
|
||||
// HACK: i forget why we do this, but i think a cross-browser issue
|
||||
slotHeight = (slotHeight0 + slotHeight1) / 2;
|
||||
|
||||
snapRatio = opt('slotMinutes') / snapMinutes;
|
||||
snapRatio = slotDuration / snapDuration;
|
||||
snapHeight = slotHeight / snapRatio;
|
||||
}
|
||||
|
||||
@@ -507,13 +532,14 @@ function AgendaView(element, calendar, viewName) {
|
||||
|
||||
|
||||
function resetScroll() {
|
||||
var d0 = zeroDate();
|
||||
var scrollDate = cloneDate(d0);
|
||||
scrollDate.setHours(opt('firstHour'));
|
||||
var top = timePosition(d0, scrollDate) + 1; // +1 for the border
|
||||
var top = computeTimeTop(
|
||||
moment.duration(opt('scrollTime'))
|
||||
) + 1; // +1 for the border
|
||||
|
||||
function scroll() {
|
||||
slotScroller.scrollTop(top);
|
||||
}
|
||||
|
||||
scroll();
|
||||
setTimeout(scroll, 0); // overrides any previous scroll state made by the browser
|
||||
}
|
||||
@@ -545,15 +571,24 @@ function AgendaView(element, calendar, viewName) {
|
||||
if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick
|
||||
var col = Math.min(colCnt-1, Math.floor((ev.pageX - dayTable.offset().left - axisWidth) / colWidth));
|
||||
var date = cellToDate(0, col);
|
||||
var rowMatch = this.parentNode.className.match(/fc-slot(\d+)/); // TODO: maybe use data
|
||||
if (rowMatch) {
|
||||
var mins = parseInt(rowMatch[1]) * opt('slotMinutes');
|
||||
var hours = Math.floor(mins/60);
|
||||
date.setHours(hours);
|
||||
date.setMinutes(mins%60 + minMinute);
|
||||
trigger('dayClick', dayBodyCells[col], date, false, ev);
|
||||
var match = this.parentNode.className.match(/fc-slot(\d+)/); // TODO: maybe use data
|
||||
if (match) {
|
||||
var slotIndex = parseInt(match[1]);
|
||||
date.add(minTime + slotIndex * slotDuration);
|
||||
date = calendar.rezoneDate(date);
|
||||
trigger(
|
||||
'dayClick',
|
||||
dayBodyCells[col],
|
||||
date,
|
||||
ev
|
||||
);
|
||||
}else{
|
||||
trigger('dayClick', dayBodyCells[col], date, true, ev);
|
||||
trigger(
|
||||
'dayClick',
|
||||
dayBodyCells[col],
|
||||
date,
|
||||
ev
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -594,15 +629,24 @@ function AgendaView(element, calendar, viewName) {
|
||||
|
||||
|
||||
function renderSlotOverlay(overlayStart, overlayEnd) {
|
||||
for (var i=0; i<colCnt; i++) {
|
||||
|
||||
// normalize, because dayStart/dayEnd have stripped time+zone
|
||||
overlayStart = overlayStart.clone().stripZone();
|
||||
overlayEnd = overlayEnd.clone().stripZone();
|
||||
|
||||
for (var i=0; i<colCnt; i++) { // loop through the day columns
|
||||
|
||||
var dayStart = cellToDate(0, i);
|
||||
var dayEnd = addDays(cloneDate(dayStart), 1);
|
||||
var stretchStart = new Date(Math.max(dayStart, overlayStart));
|
||||
var stretchEnd = new Date(Math.min(dayEnd, overlayEnd));
|
||||
var dayEnd = dayStart.clone().add('days', 1);
|
||||
|
||||
var stretchStart = dayStart < overlayStart ? overlayStart : dayStart; // the max of the two
|
||||
var stretchEnd = dayEnd < overlayEnd ? dayEnd : overlayEnd; // the min of the two
|
||||
|
||||
if (stretchStart < stretchEnd) {
|
||||
var rect = coordinateGrid.rect(0, i, 0, i, slotContainer); // only use it for horizontal coords
|
||||
var top = timePosition(dayStart, stretchStart);
|
||||
var bottom = timePosition(dayStart, stretchEnd);
|
||||
var top = computeDateTop(stretchStart, dayStart);
|
||||
var bottom = computeDateTop(stretchEnd, dayStart);
|
||||
|
||||
rect.top = top;
|
||||
rect.height = bottom - top;
|
||||
slotBind(
|
||||
@@ -681,84 +725,92 @@ function AgendaView(element, calendar, viewName) {
|
||||
}
|
||||
|
||||
|
||||
function getIsCellAllDay(cell) {
|
||||
function getIsCellAllDay(cell) { // TODO: remove because mom.hasTime() from realCellToDate() is better
|
||||
return opt('allDaySlot') && !cell.row;
|
||||
}
|
||||
|
||||
|
||||
function realCellToDate(cell) { // ugh "real" ... but blame it on our abuse of the "cell" system
|
||||
var d = cellToDate(0, cell.col);
|
||||
var date = cellToDate(0, cell.col);
|
||||
var slotIndex = cell.row;
|
||||
|
||||
if (opt('allDaySlot')) {
|
||||
slotIndex--;
|
||||
}
|
||||
|
||||
if (slotIndex >= 0) {
|
||||
addMinutes(d, minMinute + slotIndex * snapMinutes);
|
||||
date.time(moment.duration(minTime + slotIndex * slotDuration));
|
||||
date = calendar.rezoneDate(date);
|
||||
}
|
||||
return d;
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
|
||||
// get the Y coordinate of the given time on the given day (both Date objects)
|
||||
function timePosition(day, time) { // both date objects. day holds 00:00 of current day
|
||||
day = cloneDate(day, true);
|
||||
if (time < addMinutes(cloneDate(day), minMinute)) {
|
||||
|
||||
|
||||
function computeDateTop(date, startOfDayDate) {
|
||||
return computeTimeTop(
|
||||
moment.duration(
|
||||
date.clone().stripZone() - startOfDayDate.clone().stripTime()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function computeTimeTop(time) { // time is a duration
|
||||
|
||||
if (time < minTime) {
|
||||
return 0;
|
||||
}
|
||||
if (time >= addMinutes(cloneDate(day), maxMinute)) {
|
||||
if (time >= maxTime) {
|
||||
return slotTable.height();
|
||||
}
|
||||
var slotMinutes = opt('slotMinutes'),
|
||||
minutes = time.getHours()*60 + time.getMinutes() - minMinute,
|
||||
slotI = Math.floor(minutes / slotMinutes),
|
||||
slotTop = slotTopCache[slotI];
|
||||
|
||||
var slots = (time - minTime) / slotDuration;
|
||||
var slotIndex = Math.floor(slots);
|
||||
var slotPartial = slots - slotIndex;
|
||||
var slotTop = slotTopCache[slotIndex];
|
||||
|
||||
// find the position of the corresponding <tr>
|
||||
// need to use this tecnhique because not all rows are rendered at same height sometimes.
|
||||
if (slotTop === undefined) {
|
||||
slotTop = slotTopCache[slotI] =
|
||||
slotTable.find('tr').eq(slotI).find('td div')[0].offsetTop;
|
||||
slotTop = slotTopCache[slotIndex] =
|
||||
slotTable.find('tr').eq(slotIndex).find('td div')[0].offsetTop;
|
||||
// .eq() is faster than ":eq()" selector
|
||||
// [0].offsetTop is faster than .position().top (do we really need this optimization?)
|
||||
// a better optimization would be to cache all these divs
|
||||
}
|
||||
return Math.max(0, Math.round(
|
||||
slotTop - 1 + slotHeight * ((minutes % slotMinutes) / slotMinutes)
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
function getAllDayRow(index) {
|
||||
return allDayRow;
|
||||
}
|
||||
|
||||
|
||||
function defaultEventEnd(event) {
|
||||
var start = cloneDate(event.start);
|
||||
if (event.allDay) {
|
||||
return start;
|
||||
}
|
||||
return addMinutes(start, opt('defaultEventMinutes'));
|
||||
|
||||
var top =
|
||||
slotTop - 1 + // because first row doesn't have a top border
|
||||
slotPartial * slotHeight; // part-way through the row
|
||||
|
||||
top = Math.max(top, 0);
|
||||
|
||||
return top;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Selection
|
||||
---------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
|
||||
function defaultSelectionEnd(startDate, allDay) {
|
||||
if (allDay) {
|
||||
return cloneDate(startDate);
|
||||
function defaultSelectionEnd(start) {
|
||||
if (start.hasTime()) {
|
||||
return start.clone().add(slotDuration);
|
||||
}
|
||||
else {
|
||||
return start.clone().add('days', 1);
|
||||
}
|
||||
return addMinutes(cloneDate(startDate), opt('slotMinutes'));
|
||||
}
|
||||
|
||||
|
||||
function renderSelection(startDate, endDate, allDay) { // only for all-day
|
||||
if (allDay) {
|
||||
if (opt('allDaySlot')) {
|
||||
renderDayOverlay(startDate, addDays(cloneDate(endDate), 1), true);
|
||||
}
|
||||
}else{
|
||||
renderSlotSelection(startDate, endDate);
|
||||
function renderSelection(start, end) {
|
||||
if (start.hasTime() || end.hasTime()) {
|
||||
renderSlotSelection(start, end);
|
||||
}
|
||||
else if (opt('allDaySlot')) {
|
||||
renderDayOverlay(start, end, true); // true for refreshing coordinate grid
|
||||
}
|
||||
}
|
||||
|
||||
@@ -770,8 +822,8 @@ function AgendaView(element, calendar, viewName) {
|
||||
var col = dateToCell(startDate).col;
|
||||
if (col >= 0 && col < colCnt) { // only works when times are on same day
|
||||
var rect = coordinateGrid.rect(0, col, 0, col, slotContainer); // only for horizontal coords
|
||||
var top = timePosition(startDate, startDate);
|
||||
var bottom = timePosition(startDate, endDate);
|
||||
var top = computeDateTop(startDate, startDate);
|
||||
var bottom = computeDateTop(endDate, startDate);
|
||||
if (bottom > top) { // protect against selections that are entirely before or after visible range
|
||||
rect.top = top;
|
||||
rect.height = bottom - top;
|
||||
@@ -834,9 +886,9 @@ function AgendaView(element, calendar, viewName) {
|
||||
var d2 = realCellToDate(cell);
|
||||
dates = [
|
||||
d1,
|
||||
addMinutes(cloneDate(d1), snapMinutes), // calculate minutes depending on selection slot minutes
|
||||
d1.clone().add(snapDuration), // calculate minutes depending on selection slot minutes
|
||||
d2,
|
||||
addMinutes(cloneDate(d2), snapMinutes)
|
||||
d2.clone().add(snapDuration)
|
||||
].sort(dateCompare);
|
||||
renderSlotSelection(dates[0], dates[3]);
|
||||
}else{
|
||||
@@ -847,17 +899,17 @@ function AgendaView(element, calendar, viewName) {
|
||||
hoverListener.stop();
|
||||
if (dates) {
|
||||
if (+dates[0] == +dates[1]) {
|
||||
reportDayClick(dates[0], false, ev);
|
||||
reportDayClick(dates[0], ev);
|
||||
}
|
||||
reportSelection(dates[0], dates[3], false, ev);
|
||||
reportSelection(dates[0], dates[3], ev);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function reportDayClick(date, allDay, ev) {
|
||||
trigger('dayClick', dayBodyCells[dateToCell(date).col], date, allDay, ev);
|
||||
function reportDayClick(date, ev) {
|
||||
trigger('dayClick', dayBodyCells[dateToCell(date).col], date, ev);
|
||||
}
|
||||
|
||||
|
||||
@@ -870,13 +922,16 @@ function AgendaView(element, calendar, viewName) {
|
||||
hoverListener.start(function(cell) {
|
||||
clearOverlays();
|
||||
if (cell) {
|
||||
if (getIsCellAllDay(cell)) {
|
||||
renderCellOverlay(cell.row, cell.col, cell.row, cell.col);
|
||||
}else{
|
||||
var d1 = realCellToDate(cell);
|
||||
var d2 = addMinutes(cloneDate(d1), opt('defaultEventMinutes'));
|
||||
var d1 = realCellToDate(cell);
|
||||
var d2 = d1.clone();
|
||||
if (d1.hasTime()) {
|
||||
d2.add(calendar.defaultTimedEventDuration);
|
||||
renderSlotOverlay(d1, d2);
|
||||
}
|
||||
else {
|
||||
d2.add(calendar.defaultAllDayEventDuration);
|
||||
renderDayOverlay(d1, d2);
|
||||
}
|
||||
}
|
||||
}, ev);
|
||||
}
|
||||
@@ -886,7 +941,13 @@ function AgendaView(element, calendar, viewName) {
|
||||
var cell = hoverListener.stop();
|
||||
clearOverlays();
|
||||
if (cell) {
|
||||
trigger('drop', _dragElement, realCellToDate(cell), getIsCellAllDay(cell), ev, ui);
|
||||
trigger(
|
||||
'drop',
|
||||
_dragElement,
|
||||
realCellToDate(cell),
|
||||
ev,
|
||||
ui
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,52 +1,41 @@
|
||||
|
||||
fcViews.agendaWeek = AgendaWeekView;
|
||||
|
||||
function AgendaWeekView(element, calendar) {
|
||||
function AgendaWeekView(element, calendar) { // TODO: do a WeekView mixin
|
||||
var t = this;
|
||||
|
||||
|
||||
// exports
|
||||
t.incrementDate = incrementDate;
|
||||
t.render = render;
|
||||
|
||||
|
||||
// imports
|
||||
AgendaView.call(t, element, calendar, 'agendaWeek');
|
||||
var opt = t.opt;
|
||||
var renderAgenda = t.renderAgenda;
|
||||
var skipHiddenDays = t.skipHiddenDays;
|
||||
var getCellsPerWeek = t.getCellsPerWeek;
|
||||
var formatDates = calendar.formatDates;
|
||||
|
||||
|
||||
function render(date, delta) {
|
||||
|
||||
if (delta) {
|
||||
addDays(date, delta * 7);
|
||||
}
|
||||
|
||||
var start = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));
|
||||
var end = addDays(cloneDate(start), 7);
|
||||
|
||||
var visStart = cloneDate(start);
|
||||
skipHiddenDays(visStart);
|
||||
|
||||
var visEnd = cloneDate(end);
|
||||
skipHiddenDays(visEnd, -1, true);
|
||||
|
||||
var colCnt = getCellsPerWeek();
|
||||
|
||||
t.title = formatDates(
|
||||
visStart,
|
||||
addDays(cloneDate(visEnd), -1),
|
||||
opt('titleFormat')
|
||||
);
|
||||
|
||||
t.start = start;
|
||||
t.end = end;
|
||||
t.visStart = visStart;
|
||||
t.visEnd = visEnd;
|
||||
|
||||
renderAgenda(colCnt);
|
||||
function incrementDate(date, delta) {
|
||||
return date.clone().stripTime().add('weeks', delta).startOf('week');
|
||||
}
|
||||
|
||||
|
||||
function render(date) {
|
||||
|
||||
t.intervalStart = date.clone().stripTime().startOf('week');
|
||||
t.intervalEnd = t.intervalStart.clone().add('weeks', 1);
|
||||
|
||||
t.start = t.skipHiddenDays(t.intervalStart);
|
||||
t.end = t.skipHiddenDays(t.intervalEnd, -1, true);
|
||||
|
||||
t.title = calendar.formatRange(
|
||||
t.start,
|
||||
t.end.clone().subtract(1), // make inclusive by subtracting 1 ms
|
||||
t.opt('titleFormat'),
|
||||
' \u2014 ' // emphasized dash
|
||||
);
|
||||
|
||||
t.renderAgenda(t.getCellsPerWeek());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -15,10 +15,13 @@
|
||||
padding: 0 4px;
|
||||
vertical-align: middle;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.fc-agenda-slots .fc-agenda-axis {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.fc-agenda .fc-week-number {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
+14
-19
@@ -1,39 +1,34 @@
|
||||
|
||||
fcViews.basicDay = BasicDayView;
|
||||
|
||||
|
||||
function BasicDayView(element, calendar) {
|
||||
function BasicDayView(element, calendar) { // TODO: make a DayView mixin
|
||||
var t = this;
|
||||
|
||||
|
||||
// exports
|
||||
t.incrementDate = incrementDate;
|
||||
t.render = render;
|
||||
|
||||
|
||||
// imports
|
||||
BasicView.call(t, element, calendar, 'basicDay');
|
||||
var opt = t.opt;
|
||||
var renderBasic = t.renderBasic;
|
||||
var skipHiddenDays = t.skipHiddenDays;
|
||||
var formatDate = calendar.formatDate;
|
||||
|
||||
|
||||
function render(date, delta) {
|
||||
|
||||
if (delta) {
|
||||
addDays(date, delta);
|
||||
}
|
||||
skipHiddenDays(date, delta < 0 ? -1 : 1);
|
||||
|
||||
var start = cloneDate(date, true);
|
||||
var end = addDays(cloneDate(start), 1);
|
||||
function incrementDate(date, delta) {
|
||||
var out = date.clone().stripTime().add('days', delta);
|
||||
out = t.skipHiddenDays(out, delta < 0 ? -1 : 1);
|
||||
return out;
|
||||
}
|
||||
|
||||
t.title = formatDate(date, opt('titleFormat'));
|
||||
|
||||
t.start = t.visStart = start;
|
||||
t.end = t.visEnd = end;
|
||||
function render(date) {
|
||||
|
||||
renderBasic(1, 1, false);
|
||||
t.start = t.intervalStart = date.clone().stripTime();
|
||||
t.end = t.intervalEnd = t.start.clone().add('days', 1);
|
||||
|
||||
t.title = calendar.formatDate(t.start, t.opt('titleFormat'));
|
||||
|
||||
t.renderBasic(1, 1, false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+32
-40
@@ -19,7 +19,6 @@ function BasicView(element, calendar, viewName) {
|
||||
t.reportDayClick = reportDayClick; // for selection (kinda hacky)
|
||||
t.dragStart = dragStart;
|
||||
t.dragStop = dragStop;
|
||||
t.defaultEventEnd = defaultEventEnd;
|
||||
t.getHoverListener = function() { return hoverListener };
|
||||
t.colLeft = colLeft;
|
||||
t.colRight = colRight;
|
||||
@@ -46,7 +45,9 @@ function BasicView(element, calendar, viewName) {
|
||||
var cellToDate = t.cellToDate;
|
||||
var dateToCell = t.dateToCell;
|
||||
var rangeToSegments = t.rangeToSegments;
|
||||
var calendar = t.calendar;
|
||||
var formatDate = calendar.formatDate;
|
||||
var calculateWeekNumber = calendar.calculateWeekNumber;
|
||||
|
||||
|
||||
// locals
|
||||
@@ -77,8 +78,6 @@ function BasicView(element, calendar, viewName) {
|
||||
var tm;
|
||||
var colFormat;
|
||||
var showWeekNumbers;
|
||||
var weekNumberTitle;
|
||||
var weekNumberFormat;
|
||||
|
||||
|
||||
|
||||
@@ -106,16 +105,7 @@ function BasicView(element, calendar, viewName) {
|
||||
function updateOptions() {
|
||||
tm = opt('theme') ? 'ui' : 'fc';
|
||||
colFormat = opt('columnFormat');
|
||||
|
||||
// week # options. (TODO: bad, logic also in other views)
|
||||
showWeekNumbers = opt('weekNumbers');
|
||||
weekNumberTitle = opt('weekNumberTitle');
|
||||
if (opt('weekNumberCalculation') != 'iso') {
|
||||
weekNumberFormat = "w";
|
||||
}
|
||||
else {
|
||||
weekNumberFormat = "W";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -188,14 +178,14 @@ function BasicView(element, calendar, viewName) {
|
||||
if (showWeekNumbers) {
|
||||
html +=
|
||||
"<th class='fc-week-number " + headerClass + "'>" +
|
||||
htmlEscape(weekNumberTitle) +
|
||||
htmlEscape(opt('weekNumberTitle')) +
|
||||
"</th>";
|
||||
}
|
||||
|
||||
for (col=0; col<colCnt; col++) {
|
||||
date = cellToDate(0, col);
|
||||
html +=
|
||||
"<th class='fc-day-header fc-" + dayIDs[date.getDay()] + " " + headerClass + "'>" +
|
||||
"<th class='fc-day-header fc-" + dayIDs[date.day()] + " " + headerClass + "'>" +
|
||||
htmlEscape(formatDate(date, colFormat)) +
|
||||
"</th>";
|
||||
}
|
||||
@@ -224,7 +214,7 @@ function BasicView(element, calendar, viewName) {
|
||||
html +=
|
||||
"<td class='fc-week-number " + contentClass + "'>" +
|
||||
"<div>" +
|
||||
htmlEscape(formatDate(date, weekNumberFormat)) +
|
||||
htmlEscape(calculateWeekNumber(date)) +
|
||||
"</div>" +
|
||||
"</td>";
|
||||
}
|
||||
@@ -243,21 +233,21 @@ function BasicView(element, calendar, viewName) {
|
||||
}
|
||||
|
||||
|
||||
function buildCellHTML(date) {
|
||||
var contentClass = tm + "-widget-content";
|
||||
var month = t.start.getMonth();
|
||||
var today = clearTime(new Date());
|
||||
function buildCellHTML(date) { // date assumed to have stripped time
|
||||
var month = t.intervalStart.month();
|
||||
var today = calendar.getNow().stripTime();
|
||||
var html = '';
|
||||
var contentClass = tm + "-widget-content";
|
||||
var classNames = [
|
||||
'fc-day',
|
||||
'fc-' + dayIDs[date.getDay()],
|
||||
'fc-' + dayIDs[date.day()],
|
||||
contentClass
|
||||
];
|
||||
|
||||
if (date.getMonth() != month) {
|
||||
if (date.month() != month) {
|
||||
classNames.push('fc-other-month');
|
||||
}
|
||||
if (+date == +today) {
|
||||
if (date.isSame(today, 'day')) {
|
||||
classNames.push(
|
||||
'fc-today',
|
||||
tm + '-state-highlight'
|
||||
@@ -273,12 +263,12 @@ function BasicView(element, calendar, viewName) {
|
||||
html +=
|
||||
"<td" +
|
||||
" class='" + classNames.join(' ') + "'" +
|
||||
" data-date='" + formatDate(date, 'yyyy-MM-dd') + "'" +
|
||||
" data-date='" + date.format() + "'" +
|
||||
">" +
|
||||
"<div>";
|
||||
|
||||
if (showNumbers) {
|
||||
html += "<div class='fc-day-number'>" + date.getDate() + "</div>";
|
||||
html += "<div class='fc-day-number'>" + date.date() + "</div>";
|
||||
}
|
||||
|
||||
html +=
|
||||
@@ -353,8 +343,8 @@ function BasicView(element, calendar, viewName) {
|
||||
|
||||
function dayClick(ev) {
|
||||
if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick
|
||||
var date = parseISO8601($(this).data('date'));
|
||||
trigger('dayClick', this, date, true, ev);
|
||||
var date = calendar.moment($(this).data('date'));
|
||||
trigger('dayClick', this, date, ev);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,13 +388,13 @@ function BasicView(element, calendar, viewName) {
|
||||
-----------------------------------------------------------------------*/
|
||||
|
||||
|
||||
function defaultSelectionEnd(startDate, allDay) {
|
||||
return cloneDate(startDate);
|
||||
function defaultSelectionEnd(start) {
|
||||
return start.clone().stripTime().add('days', 1);
|
||||
}
|
||||
|
||||
|
||||
function renderSelection(startDate, endDate, allDay) {
|
||||
renderDayOverlay(startDate, addDays(cloneDate(endDate), 1), true); // rebuild every time???
|
||||
function renderSelection(start, end) { // end is exclusive
|
||||
renderDayOverlay(start, end, true); // true = rebuild every time
|
||||
}
|
||||
|
||||
|
||||
@@ -413,10 +403,10 @@ function BasicView(element, calendar, viewName) {
|
||||
}
|
||||
|
||||
|
||||
function reportDayClick(date, allDay, ev) {
|
||||
function reportDayClick(date, ev) {
|
||||
var cell = dateToCell(date);
|
||||
var _element = bodyCells[cell.row*colCnt + cell.col];
|
||||
trigger('dayClick', _element, date, allDay, ev);
|
||||
trigger('dayClick', _element, date, ev);
|
||||
}
|
||||
|
||||
|
||||
@@ -429,7 +419,9 @@ function BasicView(element, calendar, viewName) {
|
||||
hoverListener.start(function(cell) {
|
||||
clearOverlays();
|
||||
if (cell) {
|
||||
renderCellOverlay(cell.row, cell.col, cell.row, cell.col);
|
||||
var d1 = cellToDate(cell);
|
||||
var d2 = d1.clone().add(calendar.defaultAllDayEventDuration);
|
||||
renderDayOverlay(d1, d2);
|
||||
}
|
||||
}, ev);
|
||||
}
|
||||
@@ -439,8 +431,13 @@ function BasicView(element, calendar, viewName) {
|
||||
var cell = hoverListener.stop();
|
||||
clearOverlays();
|
||||
if (cell) {
|
||||
var d = cellToDate(cell);
|
||||
trigger('drop', _dragElement, d, true, ev, ui);
|
||||
trigger(
|
||||
'drop',
|
||||
_dragElement,
|
||||
cellToDate(cell),
|
||||
ev,
|
||||
ui
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,11 +447,6 @@ function BasicView(element, calendar, viewName) {
|
||||
--------------------------------------------------------*/
|
||||
|
||||
|
||||
function defaultEventEnd(event) {
|
||||
return cloneDate(event.start);
|
||||
}
|
||||
|
||||
|
||||
coordinateGrid = new CoordinateGrid(function(rows, cols) {
|
||||
var e, n, p;
|
||||
headCells.each(function(i, _e) {
|
||||
|
||||
+16
-28
@@ -1,52 +1,40 @@
|
||||
|
||||
fcViews.basicWeek = BasicWeekView;
|
||||
|
||||
function BasicWeekView(element, calendar) {
|
||||
function BasicWeekView(element, calendar) { // TODO: do a WeekView mixin
|
||||
var t = this;
|
||||
|
||||
|
||||
// exports
|
||||
t.incrementDate = incrementDate;
|
||||
t.render = render;
|
||||
|
||||
|
||||
// imports
|
||||
BasicView.call(t, element, calendar, 'basicWeek');
|
||||
var opt = t.opt;
|
||||
var renderBasic = t.renderBasic;
|
||||
var skipHiddenDays = t.skipHiddenDays;
|
||||
var getCellsPerWeek = t.getCellsPerWeek;
|
||||
var formatDates = calendar.formatDates;
|
||||
|
||||
|
||||
function render(date, delta) {
|
||||
|
||||
if (delta) {
|
||||
addDays(date, delta * 7);
|
||||
}
|
||||
|
||||
var start = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));
|
||||
var end = addDays(cloneDate(start), 7);
|
||||
function incrementDate(date, delta) {
|
||||
return date.clone().stripTime().add('weeks', delta).startOf('week');
|
||||
}
|
||||
|
||||
var visStart = cloneDate(start);
|
||||
skipHiddenDays(visStart);
|
||||
|
||||
var visEnd = cloneDate(end);
|
||||
skipHiddenDays(visEnd, -1, true);
|
||||
function render(date) {
|
||||
|
||||
var colCnt = getCellsPerWeek();
|
||||
t.intervalStart = date.clone().stripTime().startOf('week');
|
||||
t.intervalEnd = t.intervalStart.clone().add('weeks', 1);
|
||||
|
||||
t.start = start;
|
||||
t.end = end;
|
||||
t.visStart = visStart;
|
||||
t.visEnd = visEnd;
|
||||
t.start = t.skipHiddenDays(t.intervalStart);
|
||||
t.end = t.skipHiddenDays(t.intervalEnd, -1, true);
|
||||
|
||||
t.title = formatDates(
|
||||
visStart,
|
||||
addDays(cloneDate(visEnd), -1),
|
||||
opt('titleFormat')
|
||||
t.title = calendar.formatRange(
|
||||
t.start,
|
||||
t.end.clone().subtract(1), // make inclusive by subtracting 1 ms
|
||||
t.opt('titleFormat'),
|
||||
' \u2014 ' // emphasized dash
|
||||
);
|
||||
|
||||
renderBasic(1, colCnt, false);
|
||||
t.renderBasic(1, t.getCellsPerWeek(), false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+18
-33
@@ -6,56 +6,41 @@ function MonthView(element, calendar) {
|
||||
|
||||
|
||||
// exports
|
||||
t.incrementDate = incrementDate;
|
||||
t.render = render;
|
||||
|
||||
|
||||
// imports
|
||||
BasicView.call(t, element, calendar, 'month');
|
||||
var opt = t.opt;
|
||||
var renderBasic = t.renderBasic;
|
||||
var skipHiddenDays = t.skipHiddenDays;
|
||||
var getCellsPerWeek = t.getCellsPerWeek;
|
||||
var formatDate = calendar.formatDate;
|
||||
|
||||
|
||||
function render(date, delta) {
|
||||
|
||||
if (delta) {
|
||||
addMonths(date, delta);
|
||||
date.setDate(1);
|
||||
}
|
||||
|
||||
var firstDay = opt('firstDay');
|
||||
function incrementDate(date, delta) {
|
||||
return date.clone().stripTime().add('months', delta).startOf('month');
|
||||
}
|
||||
|
||||
var start = cloneDate(date, true);
|
||||
start.setDate(1);
|
||||
|
||||
var end = addMonths(cloneDate(start), 1);
|
||||
function render(date) {
|
||||
|
||||
var visStart = cloneDate(start);
|
||||
addDays(visStart, -((visStart.getDay() - firstDay + 7) % 7));
|
||||
skipHiddenDays(visStart);
|
||||
t.intervalStart = date.clone().stripTime().startOf('month');
|
||||
t.intervalEnd = t.intervalStart.clone().add('months', 1);
|
||||
|
||||
var visEnd = cloneDate(end);
|
||||
addDays(visEnd, (7 - visEnd.getDay() + firstDay) % 7);
|
||||
skipHiddenDays(visEnd, -1, true);
|
||||
t.start = t.intervalStart.clone().startOf('week');
|
||||
t.start = t.skipHiddenDays(t.start);
|
||||
|
||||
var colCnt = getCellsPerWeek();
|
||||
var rowCnt = Math.round(dayDiff(visEnd, visStart) / 7); // should be no need for Math.round
|
||||
t.end = t.intervalEnd.clone().add('days', (7 - t.intervalEnd.weekday()) % 7);
|
||||
t.end = t.skipHiddenDays(t.end, -1, true);
|
||||
|
||||
if (opt('weekMode') == 'fixed') {
|
||||
addDays(visEnd, (6 - rowCnt) * 7); // add weeks to make up for it
|
||||
var rowCnt = Math.ceil( // need to ceil in case there are hidden days
|
||||
t.end.diff(t.start, 'weeks', true) // returnfloat=true
|
||||
);
|
||||
if (t.opt('weekMode') == 'fixed') {
|
||||
t.end.add('weeks', 6 - rowCnt);
|
||||
rowCnt = 6;
|
||||
}
|
||||
|
||||
t.title = formatDate(start, opt('titleFormat'));
|
||||
t.title = calendar.formatDate(t.intervalStart, t.opt('titleFormat'));
|
||||
|
||||
t.start = start;
|
||||
t.end = end;
|
||||
t.visStart = visStart;
|
||||
t.visEnd = visEnd;
|
||||
|
||||
renderBasic(rowCnt, colCnt, true);
|
||||
t.renderBasic(rowCnt, t.getCellsPerWeek(), true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ function DayEventRenderer() {
|
||||
var trigger = t.trigger;
|
||||
var isEventDraggable = t.isEventDraggable;
|
||||
var isEventResizable = t.isEventResizable;
|
||||
var eventEnd = t.eventEnd;
|
||||
var reportEventElement = t.reportEventElement;
|
||||
var eventElementHandlers = t.eventElementHandlers;
|
||||
var showEvents = t.showEvents;
|
||||
@@ -31,7 +30,6 @@ function DayEventRenderer() {
|
||||
var colContentRight = t.colContentRight;
|
||||
var dateToCell = t.dateToCell;
|
||||
var getDaySegmentContainer = t.getDaySegmentContainer;
|
||||
var formatDates = t.calendar.formatDates;
|
||||
var renderDayOverlay = t.renderDayOverlay;
|
||||
var clearOverlays = t.clearOverlays;
|
||||
var clearSelection = t.clearSelection;
|
||||
@@ -42,6 +40,9 @@ function DayEventRenderer() {
|
||||
var cellOffsetToDayOffset = t.cellOffsetToDayOffset;
|
||||
var dateToDayOffset = t.dateToDayOffset;
|
||||
var dayOffsetToCellOffset = t.dayOffsetToCellOffset;
|
||||
var calendar = t.calendar;
|
||||
var getEventEnd = calendar.getEventEnd;
|
||||
var formatDate = calendar.formatDate;
|
||||
|
||||
|
||||
// Render `events` onto the calendar, attach mouse event handlers, and call the `eventAfterRender` callback for each.
|
||||
@@ -181,9 +182,7 @@ function DayEventRenderer() {
|
||||
// A "segment" is the same data structure that View.rangeToSegments produces,
|
||||
// with the addition of the `event` property being set to reference the original event.
|
||||
function buildSegmentsForEvent(event) {
|
||||
var startDate = event.start;
|
||||
var endDate = exclEndDay(event);
|
||||
var segments = rangeToSegments(startDate, endDate);
|
||||
var segments = rangeToSegments(event.start, getEventEnd(event));
|
||||
for (var i=0; i<segments.length; i++) {
|
||||
segments[i].event = event;
|
||||
}
|
||||
@@ -246,7 +245,7 @@ function DayEventRenderer() {
|
||||
classNames.push('fc-event-end');
|
||||
}
|
||||
// use the event's configured classNames
|
||||
// guaranteed to be an array via `normalizeEvent`
|
||||
// guaranteed to be an array via `buildEvent`
|
||||
classNames = classNames.concat(event.className);
|
||||
if (event.source) {
|
||||
// use the event's source's classNames, if specified
|
||||
@@ -276,7 +275,7 @@ function DayEventRenderer() {
|
||||
html +=
|
||||
"<span class='fc-event-time'>" +
|
||||
htmlEscape(
|
||||
formatDates(event.start, event.end, opt('timeFormat'))
|
||||
formatDate(event.start, opt('timeFormat'))
|
||||
) +
|
||||
"</span>";
|
||||
}
|
||||
@@ -285,7 +284,7 @@ function DayEventRenderer() {
|
||||
htmlEscape(event.title || '') +
|
||||
"</span>" +
|
||||
"</div>";
|
||||
if (segment.isEnd && isEventResizable(event)) {
|
||||
if (event.allDay && segment.isEnd && isEventResizable(event)) {
|
||||
html +=
|
||||
"<div class='ui-resizable-handle ui-resizable-" + (isRTL ? 'w' : 'e') + "'>" +
|
||||
" " + // makes hit area a lot better for IE6/7
|
||||
@@ -554,6 +553,7 @@ function DayEventRenderer() {
|
||||
}
|
||||
|
||||
if (
|
||||
event.allDay &&
|
||||
segment.isEnd && // only allow resizing on the final segment for an event
|
||||
isEventResizable(event)
|
||||
) {
|
||||
@@ -565,10 +565,11 @@ function DayEventRenderer() {
|
||||
eventElementHandlers(event, eventElement);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function draggableDayEvent(event, eventElement) {
|
||||
var hoverListener = getHoverListener();
|
||||
var dayDelta;
|
||||
var eventStart;
|
||||
eventElement.draggable({
|
||||
delay: 50,
|
||||
opacity: opt('dragOpacity'),
|
||||
@@ -580,14 +581,16 @@ function DayEventRenderer() {
|
||||
eventElement.draggable('option', 'revert', !cell || !rowDelta && !colDelta);
|
||||
clearOverlays();
|
||||
if (cell) {
|
||||
var origDate = cellToDate(origCell);
|
||||
var date = cellToDate(cell);
|
||||
dayDelta = dayDiff(date, origDate);
|
||||
var origCellDate = cellToDate(origCell);
|
||||
var cellDate = cellToDate(cell);
|
||||
dayDelta = cellDate.diff(origCellDate, 'days');
|
||||
eventStart = event.start.clone().add('days', dayDelta);
|
||||
renderDayOverlay(
|
||||
addDays(cloneDate(event.start), dayDelta),
|
||||
addDays(exclEndDay(event), dayDelta)
|
||||
eventStart,
|
||||
getEventEnd(event).add('days', dayDelta)
|
||||
);
|
||||
}else{
|
||||
}
|
||||
else {
|
||||
dayDelta = 0;
|
||||
}
|
||||
}, ev, 'drag');
|
||||
@@ -597,8 +600,15 @@ function DayEventRenderer() {
|
||||
clearOverlays();
|
||||
trigger('eventDragStop', eventElement, event, ev, ui);
|
||||
if (dayDelta) {
|
||||
eventDrop(this, event, dayDelta, 0, event.allDay, ev, ui);
|
||||
}else{
|
||||
eventDrop(
|
||||
this, // el
|
||||
event,
|
||||
eventStart,
|
||||
ev,
|
||||
ui
|
||||
);
|
||||
}
|
||||
else {
|
||||
eventElement.css('filter', ''); // clear IE opacity side-effects
|
||||
showEvents(event, eventElement);
|
||||
}
|
||||
@@ -637,6 +647,7 @@ function DayEventRenderer() {
|
||||
var colCnt = getColCnt();
|
||||
var elementTop = element.css('top');
|
||||
var dayDelta;
|
||||
var eventEnd;
|
||||
var helpers;
|
||||
var eventCopy = $.extend({}, event);
|
||||
var minCellOffset = dayOffsetToCellOffset( dateToDayOffset(event.start) );
|
||||
@@ -658,18 +669,17 @@ function DayEventRenderer() {
|
||||
cellOffsetToDayOffset(cellOffset) -
|
||||
cellOffsetToDayOffset(origCellOffset);
|
||||
|
||||
if (dayDelta) {
|
||||
eventCopy.end = addDays(eventEnd(event), dayDelta, true);
|
||||
var oldHelpers = helpers;
|
||||
eventEnd = getEventEnd(event).add('days', dayDelta); // assumed to already have a stripped time
|
||||
|
||||
if (dayDelta) {
|
||||
eventCopy.end = eventEnd;
|
||||
var oldHelpers = helpers;
|
||||
helpers = renderTempDayEvent(eventCopy, segment.row, elementTop);
|
||||
helpers = $(helpers); // turn array into a jQuery object
|
||||
|
||||
helpers.find('*').css('cursor', direction + '-resize');
|
||||
if (oldHelpers) {
|
||||
oldHelpers.remove();
|
||||
}
|
||||
|
||||
hideEvents(event);
|
||||
}
|
||||
else {
|
||||
@@ -679,10 +689,11 @@ function DayEventRenderer() {
|
||||
helpers = null;
|
||||
}
|
||||
}
|
||||
|
||||
clearOverlays();
|
||||
renderDayOverlay( // coordinate grid already rebuilt with hoverListener.start()
|
||||
event.start,
|
||||
addDays( exclEndDay(event), dayDelta )
|
||||
eventEnd
|
||||
// TODO: instead of calling renderDayOverlay() with dates,
|
||||
// call _renderDayOverlay (or whatever) with cell offsets.
|
||||
);
|
||||
@@ -694,8 +705,14 @@ function DayEventRenderer() {
|
||||
$('body').css('cursor', '');
|
||||
hoverListener.stop();
|
||||
clearOverlays();
|
||||
|
||||
if (dayDelta) {
|
||||
eventResize(this, event, dayDelta, 0, ev);
|
||||
eventResize(
|
||||
this, // el
|
||||
event,
|
||||
eventEnd,
|
||||
ev
|
||||
);
|
||||
// event redraw will clear helpers
|
||||
}
|
||||
// otherwise, the drag handler already restored the old events
|
||||
|
||||
@@ -13,6 +13,7 @@ function SelectionManager() {
|
||||
|
||||
|
||||
// imports
|
||||
var calendar = t.calendar;
|
||||
var opt = t.opt;
|
||||
var trigger = t.trigger;
|
||||
var defaultSelectionEnd = t.defaultSelectionEnd;
|
||||
@@ -39,13 +40,19 @@ function SelectionManager() {
|
||||
}
|
||||
|
||||
|
||||
function select(startDate, endDate, allDay) {
|
||||
function select(start, end) {
|
||||
unselect();
|
||||
if (!endDate) {
|
||||
endDate = defaultSelectionEnd(startDate, allDay);
|
||||
|
||||
start = calendar.moment(start);
|
||||
if (end) {
|
||||
end = calendar.moment(end);
|
||||
}
|
||||
renderSelection(startDate, endDate, allDay);
|
||||
reportSelection(startDate, endDate, allDay);
|
||||
else {
|
||||
end = defaultSelectionEnd(start);
|
||||
}
|
||||
|
||||
renderSelection(start, end);
|
||||
reportSelection(start, end);
|
||||
}
|
||||
|
||||
|
||||
@@ -58,9 +65,9 @@ function SelectionManager() {
|
||||
}
|
||||
|
||||
|
||||
function reportSelection(startDate, endDate, allDay, ev) {
|
||||
function reportSelection(start, end, ev) {
|
||||
selected = true;
|
||||
trigger('select', null, startDate, endDate, allDay, ev);
|
||||
trigger('select', null, start, end, ev);
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +76,7 @@ function SelectionManager() {
|
||||
var getIsCellAllDay = t.getIsCellAllDay;
|
||||
var hoverListener = t.getHoverListener();
|
||||
var reportDayClick = t.reportDayClick; // this is hacky and sort of weird
|
||||
|
||||
if (ev.which == 1 && opt('selectable')) { // which==1 means left mouse button
|
||||
unselect(ev);
|
||||
var _mousedownElement = this;
|
||||
@@ -77,7 +85,10 @@ function SelectionManager() {
|
||||
clearSelection();
|
||||
if (cell && getIsCellAllDay(cell)) {
|
||||
dates = [ cellToDate(origCell), cellToDate(cell) ].sort(dateCompare);
|
||||
renderSelection(dates[0], dates[1], true);
|
||||
renderSelection(
|
||||
dates[0],
|
||||
dates[1].clone().add('days', 1) // make exclusive
|
||||
);
|
||||
}else{
|
||||
dates = null;
|
||||
}
|
||||
@@ -86,9 +97,13 @@ function SelectionManager() {
|
||||
hoverListener.stop();
|
||||
if (dates) {
|
||||
if (+dates[0] == +dates[1]) {
|
||||
reportDayClick(dates[0], true, ev);
|
||||
reportDayClick(dates[0], ev);
|
||||
}
|
||||
reportSelection(dates[0], dates[1], true, ev);
|
||||
reportSelection(
|
||||
dates[0],
|
||||
dates[1].clone().add('days', 1), // make exclusive
|
||||
ev
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+203
-83
@@ -14,7 +14,6 @@ function View(element, calendar, viewName) {
|
||||
t.isEventResizable = isEventResizable;
|
||||
t.setEventData = setEventData;
|
||||
t.clearEventData = clearEventData;
|
||||
t.eventEnd = eventEnd;
|
||||
t.reportEventElement = reportEventElement;
|
||||
t.triggerEventDestroy = triggerEventDestroy;
|
||||
t.eventElementHandlers = eventElementHandlers;
|
||||
@@ -22,15 +21,14 @@ function View(element, calendar, viewName) {
|
||||
t.hideEvents = hideEvents;
|
||||
t.eventDrop = eventDrop;
|
||||
t.eventResize = eventResize;
|
||||
// t.title
|
||||
// t.start, t.end
|
||||
// t.visStart, t.visEnd
|
||||
t.mutateEvent = mutateEvent;
|
||||
// t.start, t.end // moments with ambiguous-time
|
||||
// t.intervalStart, t.intervalEnd // moments with ambiguous-time
|
||||
|
||||
|
||||
// imports
|
||||
var defaultEventEnd = t.defaultEventEnd;
|
||||
var normalizeEvent = calendar.normalizeEvent; // in EventManager
|
||||
var reportEventChange = calendar.reportEventChange;
|
||||
var getEventEnd = calendar.getEventEnd;
|
||||
|
||||
|
||||
// locals
|
||||
@@ -38,12 +36,14 @@ function View(element, calendar, viewName) {
|
||||
var eventElementsByID = {}; // eventID mapped to array of jQuery elements
|
||||
var eventElementCouples = []; // array of objects, { event, element } // TODO: unify with segment system
|
||||
var options = calendar.options;
|
||||
var nextDayThreshold = moment.duration(options.nextDayThreshold);
|
||||
|
||||
|
||||
|
||||
|
||||
function opt(name, viewNameOverride) {
|
||||
var v = options[name];
|
||||
if ($.isPlainObject(v)) {
|
||||
if ($.isPlainObject(v) && !isForcedAtomicOption(name)) {
|
||||
return smartProperty(v, viewNameOverride || viewName);
|
||||
}
|
||||
return v;
|
||||
@@ -72,8 +72,7 @@ function View(element, calendar, viewName) {
|
||||
event.editable,
|
||||
source.editable,
|
||||
opt('editable')
|
||||
)
|
||||
&& !opt('disableDragging'); // deprecated
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -86,8 +85,7 @@ function View(element, calendar, viewName) {
|
||||
event.editable,
|
||||
source.editable,
|
||||
opt('editable')
|
||||
)
|
||||
&& !opt('disableResizing'); // deprecated
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -117,12 +115,6 @@ function View(element, calendar, viewName) {
|
||||
}
|
||||
|
||||
|
||||
// returns a Date object for an event's end
|
||||
function eventEnd(event) {
|
||||
return event.end ? cloneDate(event.end) : defaultEventEnd(event);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Event Elements
|
||||
------------------------------------------------------------------------------*/
|
||||
@@ -194,83 +186,206 @@ function View(element, calendar, viewName) {
|
||||
|
||||
/* Event Modification Reporting
|
||||
---------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
|
||||
function eventDrop(e, event, dayDelta, minuteDelta, allDay, ev, ui) {
|
||||
var oldAllDay = event.allDay;
|
||||
var eventId = event._id;
|
||||
moveEvents(eventsByID[eventId], dayDelta, minuteDelta, allDay);
|
||||
function eventDrop(el, event, newStart, ev, ui) {
|
||||
var undoMutation = mutateEvent(event, newStart, null);
|
||||
|
||||
trigger(
|
||||
'eventDrop',
|
||||
e,
|
||||
el,
|
||||
event,
|
||||
dayDelta,
|
||||
minuteDelta,
|
||||
allDay,
|
||||
function() {
|
||||
// TODO: investigate cases where this inverse technique might not work
|
||||
moveEvents(eventsByID[eventId], -dayDelta, -minuteDelta, oldAllDay);
|
||||
reportEventChange(eventId);
|
||||
undoMutation();
|
||||
reportEventChange(event._id);
|
||||
},
|
||||
ev,
|
||||
ui
|
||||
);
|
||||
reportEventChange(eventId);
|
||||
|
||||
reportEventChange(event._id);
|
||||
}
|
||||
|
||||
|
||||
function eventResize(e, event, dayDelta, minuteDelta, ev, ui) {
|
||||
var eventId = event._id;
|
||||
elongateEvents(eventsByID[eventId], dayDelta, minuteDelta);
|
||||
|
||||
|
||||
function eventResize(el, event, newEnd, ev, ui) {
|
||||
var undoMutation = mutateEvent(event, null, newEnd);
|
||||
|
||||
trigger(
|
||||
'eventResize',
|
||||
e,
|
||||
el,
|
||||
event,
|
||||
dayDelta,
|
||||
minuteDelta,
|
||||
function() {
|
||||
// TODO: investigate cases where this inverse technique might not work
|
||||
elongateEvents(eventsByID[eventId], -dayDelta, -minuteDelta);
|
||||
reportEventChange(eventId);
|
||||
undoMutation();
|
||||
reportEventChange(event._id);
|
||||
},
|
||||
ev,
|
||||
ui
|
||||
);
|
||||
reportEventChange(eventId);
|
||||
|
||||
reportEventChange(event._id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Event Modification Math
|
||||
---------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
function moveEvents(events, dayDelta, minuteDelta, allDay) {
|
||||
minuteDelta = minuteDelta || 0;
|
||||
for (var e, len=events.length, i=0; i<len; i++) {
|
||||
e = events[i];
|
||||
if (allDay !== undefined) {
|
||||
e.allDay = allDay;
|
||||
}
|
||||
addMinutes(addDays(e.start, dayDelta, true), minuteDelta);
|
||||
if (e.end) {
|
||||
e.end = addMinutes(addDays(e.end, dayDelta, true), minuteDelta);
|
||||
}
|
||||
normalizeEvent(e, options);
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function elongateEvents(events, dayDelta, minuteDelta) {
|
||||
minuteDelta = minuteDelta || 0;
|
||||
for (var e, len=events.length, i=0; i<len; i++) {
|
||||
e = events[i];
|
||||
e.end = addMinutes(addDays(eventEnd(e), dayDelta, true), minuteDelta);
|
||||
normalizeEvent(e, options);
|
||||
|
||||
// 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 || calendar.getDefaultEventEnd(newAllDay, newStart || oldStart),
|
||||
newStart || oldStart
|
||||
).subtract(dayishDiff(
|
||||
// subtract old duration
|
||||
oldEnd || calendar.getDefaultEventEnd(oldAllDay, oldStart),
|
||||
oldStart
|
||||
));
|
||||
}
|
||||
|
||||
return mutateEvents(eventsByID[event._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 = calendar.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 = calendar.rezoneDate(newStart);
|
||||
}
|
||||
if (newEnd && !newEnd.hasTime()) {
|
||||
newEnd = calendar.rezoneDate(newEnd);
|
||||
}
|
||||
}
|
||||
|
||||
// ensure we have an end date if necessary
|
||||
if (!newEnd && (options.forceEventDuration || +durationDelta)) {
|
||||
newEnd = calendar.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<undoFunctions.length; i++) {
|
||||
undoFunctions[i]();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// ====================================================================================================
|
||||
// Utilities for day "cells"
|
||||
@@ -289,7 +404,7 @@ function View(element, calendar, viewName) {
|
||||
//
|
||||
// 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 JavaScript Date object).
|
||||
// 3. Convert the "day offset" into a "date" (a Moment).
|
||||
//
|
||||
// The reverse transformation happens when transforming a date into a cell.
|
||||
|
||||
@@ -345,10 +460,10 @@ function View(element, calendar, viewName) {
|
||||
|
||||
|
||||
// Is the current day hidden?
|
||||
// `day` is a day-of-week index (0-6), or a Date object
|
||||
// `day` is a day-of-week index (0-6), or a Moment
|
||||
function isHiddenDay(day) {
|
||||
if (typeof day == 'object') {
|
||||
day = day.getDay();
|
||||
if (moment.isMoment(day)) {
|
||||
day = day.day();
|
||||
}
|
||||
return isHiddenDayHash[day];
|
||||
}
|
||||
@@ -359,17 +474,19 @@ function View(element, calendar, viewName) {
|
||||
}
|
||||
|
||||
|
||||
// Keep incrementing the current day until it is no longer a hidden day.
|
||||
// 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[ ( date.getDay() + (isExclusive ? inc : 0) + 7 ) % 7 ]
|
||||
isHiddenDayHash[ ( out.day() + (isExclusive ? inc : 0) + 7 ) % 7 ]
|
||||
) {
|
||||
addDays(date, inc);
|
||||
out.add('days', inc);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@@ -410,7 +527,7 @@ function View(element, calendar, viewName) {
|
||||
|
||||
// cell offset -> day offset
|
||||
function cellOffsetToDayOffset(cellOffset) {
|
||||
var day0 = t.visStart.getDay(); // first date's day of week
|
||||
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
|
||||
@@ -419,11 +536,9 @@ function View(element, calendar, viewName) {
|
||||
- day0; // adjustment for beginning-of-week normalization
|
||||
}
|
||||
|
||||
// day offset -> date (JavaScript Date object)
|
||||
// day offset -> date
|
||||
function dayOffsetToDate(dayOffset) {
|
||||
var date = cloneDate(t.visStart);
|
||||
addDays(date, dayOffset);
|
||||
return date;
|
||||
return t.start.clone().add('days', dayOffset);
|
||||
}
|
||||
|
||||
|
||||
@@ -441,12 +556,12 @@ function View(element, calendar, viewName) {
|
||||
|
||||
// date -> day offset
|
||||
function dateToDayOffset(date) {
|
||||
return dayDiff(date, t.visStart);
|
||||
return date.clone().stripTime().diff(t.start, 'days');
|
||||
}
|
||||
|
||||
// day offset -> cell offset
|
||||
function dayOffsetToCellOffset(dayOffset) {
|
||||
var day0 = t.visStart.getDay(); // first date's day of week
|
||||
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
|
||||
@@ -481,14 +596,19 @@ function View(element, calendar, viewName) {
|
||||
// - isStart
|
||||
// - isEnd
|
||||
//
|
||||
function rangeToSegments(startDate, endDate) {
|
||||
function rangeToSegments(start, end) {
|
||||
|
||||
var rowCnt = t.getRowCnt();
|
||||
var colCnt = t.getColCnt();
|
||||
var segments = []; // array of segments to return
|
||||
|
||||
// day offset for given date range
|
||||
var rangeDayOffsetStart = dateToDayOffset(startDate);
|
||||
var rangeDayOffsetEnd = dateToDayOffset(endDate); // exclusive
|
||||
var rangeDayOffsetStart = dateToDayOffset(start);
|
||||
var rangeDayOffsetEnd = dateToDayOffset(end); // an exclusive value
|
||||
if (end.time() > nextDayThreshold) {
|
||||
rangeDayOffsetEnd++;
|
||||
}
|
||||
rangeDayOffsetEnd = Math.max(rangeDayOffsetEnd, rangeDayOffsetStart + 1);
|
||||
|
||||
// first and last cell offset for the given date range
|
||||
// "last" implies inclusivity
|
||||
|
||||
+19
-13
@@ -52,32 +52,38 @@
|
||||
and we'll try to make them look good cross-browser.
|
||||
*/
|
||||
|
||||
.fc-text-arrow {
|
||||
.fc-button .fc-icon {
|
||||
margin: 0 .1em;
|
||||
font-size: 2em;
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
vertical-align: baseline; /* for IE7 */
|
||||
}
|
||||
|
||||
.fc-button-prev .fc-text-arrow,
|
||||
.fc-button-next .fc-text-arrow { /* for ‹ › */
|
||||
.fc-icon-left-single-arrow:after {
|
||||
content: "\02039";
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.fc-icon-right-single-arrow:after {
|
||||
content: "\0203A";
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.fc-icon-left-double-arrow:after {
|
||||
content: "\000AB";
|
||||
}
|
||||
|
||||
.fc-icon-right-double-arrow:after {
|
||||
content: "\000BB";
|
||||
}
|
||||
|
||||
/* icon (for jquery ui) */
|
||||
|
||||
.fc-button .fc-icon-wrap {
|
||||
position: relative;
|
||||
float: left;
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
|
||||
.fc-button .ui-icon {
|
||||
position: relative;
|
||||
top: 50%;
|
||||
float: left;
|
||||
margin-top: -50%;
|
||||
*margin-top: 0;
|
||||
*top: -50%;
|
||||
margin-top: -8px; /* we know jqui icons are always 16px tall */
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
+421
-335
@@ -1,376 +1,462 @@
|
||||
|
||||
var dayIDs = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
|
||||
var ambigTimeRegex = /^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))$/;
|
||||
var ambigZoneRegex = /^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?)?$/;
|
||||
var momentFormatMethod = moment.fn.format;
|
||||
var momentToISOStringMethod = moment.fn.toISOString;
|
||||
|
||||
|
||||
fc.addDays = addDays;
|
||||
fc.cloneDate = cloneDate;
|
||||
fc.parseDate = parseDate;
|
||||
fc.parseISO8601 = parseISO8601;
|
||||
fc.parseTime = parseTime;
|
||||
fc.formatDate = formatDate;
|
||||
fc.formatDates = formatDates;
|
||||
// diffs the two moments into a Duration where full-days are recorded first,
|
||||
// then the remaining time.
|
||||
function dayishDiff(d1, d0) {
|
||||
return moment.duration({
|
||||
days: d1.clone().stripTime().diff(d0.clone().stripTime(), 'days'),
|
||||
ms: d1.time() - d0.time()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Date Math
|
||||
-----------------------------------------------------------------------------*/
|
||||
|
||||
var dayIDs = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'],
|
||||
DAY_MS = 86400000,
|
||||
HOUR_MS = 3600000,
|
||||
MINUTE_MS = 60000;
|
||||
|
||||
|
||||
function addYears(d, n, keepTime) {
|
||||
d.setFullYear(d.getFullYear() + n);
|
||||
if (!keepTime) {
|
||||
clearTime(d);
|
||||
}
|
||||
return d;
|
||||
function isNativeDate(input) {
|
||||
return Object.prototype.toString.call(input) === '[object Date]' ||
|
||||
input instanceof Date;
|
||||
}
|
||||
|
||||
|
||||
function addMonths(d, n, keepTime) { // prevents day overflow/underflow
|
||||
if (+d) { // prevent infinite looping on invalid dates
|
||||
var m = d.getMonth() + n,
|
||||
check = cloneDate(d);
|
||||
check.setDate(1);
|
||||
check.setMonth(m);
|
||||
d.setMonth(m);
|
||||
if (!keepTime) {
|
||||
clearTime(d);
|
||||
// MOMENT: creating
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
// Creates a moment in the local timezone, similar to the vanilla moment(...) constructor,
|
||||
// but with extra features:
|
||||
// - ambiguous times
|
||||
// - enhanced formatting (TODO)
|
||||
fc.moment = function() {
|
||||
return buildMoment(arguments);
|
||||
};
|
||||
|
||||
// Sames as fc.moment, but creates a moment in the UTC timezone.
|
||||
fc.moment.utc = function() {
|
||||
return buildMoment(arguments, true);
|
||||
};
|
||||
|
||||
// Creates a moment and preserves the timezone offset of the ISO8601 string,
|
||||
// allowing for ambigous timezones. If the string is not an ISO8601 string,
|
||||
// the moment is processed in UTC-mode (a departure from moment's method).
|
||||
fc.moment.parseZone = function() {
|
||||
return buildMoment(arguments, true, true);
|
||||
};
|
||||
|
||||
// when parseZone==true, if can't figure it out, fall back to parseUTC
|
||||
function buildMoment(args, parseUTC, parseZone) {
|
||||
var isSingleArg = args.length == 1;
|
||||
var isSingleString = isSingleArg && typeof args[0] === 'string';
|
||||
var isSingleArray = isSingleArg && $.isArray(args[0]);
|
||||
var isSingleNativeDate = isSingleArg && isNativeDate(args[0]);
|
||||
var isAmbigTime = isSingleString && ambigTimeRegex.test(args[0]);
|
||||
var isAmbigZone = isAmbigTime || isSingleArray || isSingleString && ambigZoneRegex.test(args[0]);
|
||||
var mom;
|
||||
|
||||
if (parseUTC || parseZone || isAmbigTime) {
|
||||
mom = moment.utc.apply(moment, args);
|
||||
}
|
||||
else {
|
||||
mom = moment.apply(null, args);
|
||||
}
|
||||
|
||||
if (isAmbigTime) {
|
||||
mom._ambigTime = true;
|
||||
mom._ambigZone = true; // if ambiguous time, also ambiguous timezone offset
|
||||
}
|
||||
|
||||
if (parseZone) {
|
||||
if (isAmbigZone) {
|
||||
mom._ambigZone = true;
|
||||
}
|
||||
while (d.getMonth() != check.getMonth()) {
|
||||
d.setDate(d.getDate() + (d < check ? 1 : -1));
|
||||
else if (isSingleString) {
|
||||
mom.zone(args[0]); // if fails, will set it to 0, which it already was
|
||||
}
|
||||
else if (isSingleNativeDate || args[0] === undefined) {
|
||||
// native Date object?
|
||||
// specified with no arguments?
|
||||
// then consider the moment to be local
|
||||
mom.local();
|
||||
}
|
||||
}
|
||||
return d;
|
||||
|
||||
mom._fc = true; // flag for use other extended functionality (only formatting at this point)
|
||||
|
||||
return mom;
|
||||
}
|
||||
|
||||
|
||||
function addDays(d, n, keepTime) { // deals with daylight savings
|
||||
if (+d) {
|
||||
var dd = d.getDate() + n,
|
||||
check = cloneDate(d);
|
||||
check.setHours(9); // set to middle of day
|
||||
check.setDate(dd);
|
||||
d.setDate(dd);
|
||||
if (!keepTime) {
|
||||
clearTime(d);
|
||||
}
|
||||
fixDate(d, check);
|
||||
// MOMENT: time-of-day
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
// GETTER
|
||||
// Returns a Duration with the hours/minutes/seconds/ms values of the moment.
|
||||
// If the moment has an ambiguous time, a duration of 00:00 will be returned.
|
||||
//
|
||||
// SETTER
|
||||
// You can supply a Duration, a Moment, or a Duration-like argument.
|
||||
// When setting the time, and the moment has an ambiguous time, it then becomes unambiguous.
|
||||
moment.fn.time = function(time) {
|
||||
if (time === undefined) { // getter
|
||||
return moment.duration({
|
||||
hours: this.hours(),
|
||||
minutes: this.minutes(),
|
||||
seconds: this.seconds(),
|
||||
milliseconds: this.milliseconds()
|
||||
});
|
||||
}
|
||||
return d;
|
||||
}
|
||||
else { // setter
|
||||
|
||||
delete this._ambigTime; // mark that the moment now has a time
|
||||
|
||||
function fixDate(d, check) { // force d to be on check's YMD, for daylight savings purposes
|
||||
if (+d) { // prevent infinite looping on invalid dates
|
||||
while (d.getDate() != check.getDate()) {
|
||||
d.setTime(+d + (d < check ? 1 : -1) * HOUR_MS);
|
||||
if (!moment.isDuration(time) && !moment.isMoment(time)) {
|
||||
time = moment.duration(time);
|
||||
}
|
||||
|
||||
return this.hours(time.hours() + time.days() * 24) // day value will cause overflow (so 24 hours becomes 00:00:00 of next day)
|
||||
.minutes(time.minutes())
|
||||
.seconds(time.seconds())
|
||||
.milliseconds(time.milliseconds());
|
||||
}
|
||||
};
|
||||
|
||||
// Converts the moment to UTC, stripping out its time-of-day and timezone offset,
|
||||
// but preserving its YMD. A moment with a stripped time will display no time
|
||||
// nor timezone offset when .format() is called.
|
||||
moment.fn.stripTime = function() {
|
||||
var a = this.toArray(); // year,month,date,hours,minutes,seconds as an array
|
||||
|
||||
this._ambigTime = true;
|
||||
this._ambigZone = true; // if ambiguous time, also ambiguous timezone offset
|
||||
|
||||
return this.utc()
|
||||
.year(a[0])
|
||||
.month(a[1])
|
||||
.date(a[2])
|
||||
.hours(0)
|
||||
.minutes(0)
|
||||
.seconds(0)
|
||||
.milliseconds(0);
|
||||
};
|
||||
|
||||
// Returns if the moment has a non-ambiguous time (boolean)
|
||||
moment.fn.hasTime = function() {
|
||||
return !this._ambigTime;
|
||||
};
|
||||
|
||||
|
||||
// MOMENT: timezone offset
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
// Converts the moment to UTC, stripping out its timezone offset, but preserving its
|
||||
// YMD and time-of-day. A moment with a stripped timezone offset will display no
|
||||
// timezone offset when .format() is called.
|
||||
moment.fn.stripZone = function() {
|
||||
var a = this.toArray(); // year,month,date,hours,minutes,seconds as an array
|
||||
|
||||
this._ambigZone = true;
|
||||
|
||||
return this.utc()
|
||||
.year(a[0])
|
||||
.month(a[1])
|
||||
.date(a[2])
|
||||
.hours(a[3])
|
||||
.minutes(a[4])
|
||||
.seconds(a[5])
|
||||
.milliseconds(a[6]);
|
||||
};
|
||||
|
||||
// Returns of the moment has a non-ambiguous timezone offset (boolean)
|
||||
moment.fn.hasZone = function() {
|
||||
return !this._ambigZone;
|
||||
};
|
||||
|
||||
|
||||
// MOMENT: formatting mods
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
moment.fn.format = function() {
|
||||
if (!arguments[0]) {
|
||||
if (this._ambigTime) {
|
||||
return momentFormat(this, 'YYYY-MM-DD');
|
||||
}
|
||||
if (this._ambigZone) {
|
||||
return momentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss');
|
||||
}
|
||||
}
|
||||
if (this._fc) {
|
||||
return formatDate(this, arguments[0]); // our extended formatting
|
||||
}
|
||||
else {
|
||||
return momentFormatMethod.apply(this, arguments); // pass along all arguments
|
||||
}
|
||||
}
|
||||
|
||||
moment.fn.toISOString = function() {
|
||||
if (this._ambigTime) {
|
||||
return momentFormat(this, 'YYYY-MM-DD');
|
||||
}
|
||||
if (this._ambigZone) {
|
||||
return momentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss');
|
||||
}
|
||||
return momentToISOStringMethod.apply(this, arguments); // pass along all arguments
|
||||
};
|
||||
|
||||
function addMinutes(d, n) {
|
||||
d.setMinutes(d.getMinutes() + n);
|
||||
return d;
|
||||
// call this if you want Moment's original format method to be used
|
||||
function momentFormat(moment, formatStr) {
|
||||
return momentFormatMethod.call(moment, formatStr);
|
||||
}
|
||||
|
||||
|
||||
function clearTime(d) {
|
||||
d.setHours(0);
|
||||
d.setMinutes(0);
|
||||
d.setSeconds(0);
|
||||
d.setMilliseconds(0);
|
||||
return d;
|
||||
}
|
||||
// MOMENT: misc utils
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
// Is the moment within the specified range? `end` is exclusive.
|
||||
// TODO: rename for collision reasons?
|
||||
moment.fn.isWithin = function(start, end) {
|
||||
return this >= moment(start) && this < moment(end);
|
||||
};
|
||||
|
||||
function cloneDate(d, dontKeepTime) {
|
||||
if (dontKeepTime) {
|
||||
return clearTime(new Date(+d));
|
||||
}
|
||||
return new Date(+d);
|
||||
}
|
||||
$.each([
|
||||
'isBefore',
|
||||
'isAfter',
|
||||
//'isSame', // nevermind. moment handles normalization to UTC
|
||||
'isWithin'
|
||||
], function(i, methodName) {
|
||||
var origMethod = moment.fn[methodName];
|
||||
var momentCount = methodName == 'isWithin' ? 2 : 1;
|
||||
|
||||
moment.fn[methodName] = function() {
|
||||
var newThis;
|
||||
var i;
|
||||
|
||||
function zeroDate() { // returns a Date with time 00:00:00 and dateOfMonth=1
|
||||
var i=0, d;
|
||||
do {
|
||||
d = new Date(1970, i++, 1);
|
||||
} while (d.getHours()); // != 0
|
||||
return d;
|
||||
}
|
||||
|
||||
|
||||
function dayDiff(d1, d2) { // d1 - d2
|
||||
return Math.round((cloneDate(d1, true) - cloneDate(d2, true)) / DAY_MS);
|
||||
}
|
||||
|
||||
|
||||
function setYMD(date, y, m, d) {
|
||||
if (y !== undefined && y != date.getFullYear()) {
|
||||
date.setDate(1);
|
||||
date.setMonth(0);
|
||||
date.setFullYear(y);
|
||||
}
|
||||
if (m !== undefined && m != date.getMonth()) {
|
||||
date.setDate(1);
|
||||
date.setMonth(m);
|
||||
}
|
||||
if (d !== undefined) {
|
||||
date.setDate(d);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Date Parsing
|
||||
-----------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
function parseDate(s, ignoreTimezone) { // ignoreTimezone defaults to true
|
||||
if (typeof s == 'object') { // already a Date object
|
||||
return s;
|
||||
}
|
||||
if (typeof s == 'number') { // a UNIX timestamp
|
||||
return new Date(s * 1000);
|
||||
}
|
||||
if (typeof s == 'string') {
|
||||
if (s.match(/^\d+(\.\d+)?$/)) { // a UNIX timestamp
|
||||
return new Date(parseFloat(s) * 1000);
|
||||
}
|
||||
if (ignoreTimezone === undefined) {
|
||||
ignoreTimezone = true;
|
||||
}
|
||||
return parseISO8601(s, ignoreTimezone) || (s ? new Date(s) : null);
|
||||
}
|
||||
// TODO: never return invalid dates (like from new Date(<string>)), return null instead
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
function parseISO8601(s, ignoreTimezone) { // ignoreTimezone defaults to false
|
||||
// derived from http://delete.me.uk/2005/03/iso8601.html
|
||||
// TODO: for a know glitch/feature, read tests/issue_206_parseDate_dst.html
|
||||
var m = s.match(/^([0-9]{4})(-([0-9]{2})(-([0-9]{2})([T ]([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2})(:?([0-9]{2}))?))?)?)?)?$/);
|
||||
if (!m) {
|
||||
return null;
|
||||
}
|
||||
var date = new Date(m[1], 0, 1);
|
||||
if (ignoreTimezone || !m[13]) {
|
||||
var check = new Date(m[1], 0, 1, 9, 0);
|
||||
if (m[3]) {
|
||||
date.setMonth(m[3] - 1);
|
||||
check.setMonth(m[3] - 1);
|
||||
}
|
||||
if (m[5]) {
|
||||
date.setDate(m[5]);
|
||||
check.setDate(m[5]);
|
||||
}
|
||||
fixDate(date, check);
|
||||
if (m[7]) {
|
||||
date.setHours(m[7]);
|
||||
}
|
||||
if (m[8]) {
|
||||
date.setMinutes(m[8]);
|
||||
}
|
||||
if (m[10]) {
|
||||
date.setSeconds(m[10]);
|
||||
}
|
||||
if (m[12]) {
|
||||
date.setMilliseconds(Number("0." + m[12]) * 1000);
|
||||
}
|
||||
fixDate(date, check);
|
||||
}else{
|
||||
date.setUTCFullYear(
|
||||
m[1],
|
||||
m[3] ? m[3] - 1 : 0,
|
||||
m[5] || 1
|
||||
);
|
||||
date.setUTCHours(
|
||||
m[7] || 0,
|
||||
m[8] || 0,
|
||||
m[10] || 0,
|
||||
m[12] ? Number("0." + m[12]) * 1000 : 0
|
||||
);
|
||||
if (m[14]) {
|
||||
var offset = Number(m[16]) * 60 + (m[18] ? Number(m[18]) : 0);
|
||||
offset *= m[15] == '-' ? 1 : -1;
|
||||
date = new Date(+date + (offset * 60 * 1000));
|
||||
}
|
||||
}
|
||||
return date;
|
||||
}
|
||||
|
||||
|
||||
function parseTime(s) { // returns minutes since start of day
|
||||
if (typeof s == 'number') { // an hour
|
||||
return s * 60;
|
||||
}
|
||||
if (typeof s == 'object') { // a Date object
|
||||
return s.getHours() * 60 + s.getMinutes();
|
||||
}
|
||||
var m = s.match(/(\d+)(?::(\d+))?\s*(\w+)?/);
|
||||
if (m) {
|
||||
var h = parseInt(m[1], 10);
|
||||
if (m[3]) {
|
||||
h %= 12;
|
||||
if (m[3].toLowerCase().charAt(0) == 'p') {
|
||||
h += 12;
|
||||
}
|
||||
}
|
||||
return h * 60 + (m[2] ? parseInt(m[2], 10) : 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Date Formatting
|
||||
-----------------------------------------------------------------------------*/
|
||||
// TODO: use same function formatDate(date, [date2], format, [options])
|
||||
|
||||
|
||||
function formatDate(date, format, options) {
|
||||
return formatDates(date, null, format, options);
|
||||
}
|
||||
|
||||
|
||||
function formatDates(date1, date2, format, options) {
|
||||
options = options || defaults;
|
||||
var date = date1,
|
||||
otherDate = date2,
|
||||
i, len = format.length, c,
|
||||
i2, formatter,
|
||||
res = '';
|
||||
for (i=0; i<len; i++) {
|
||||
c = format.charAt(i);
|
||||
if (c == "'") {
|
||||
for (i2=i+1; i2<len; i2++) {
|
||||
if (format.charAt(i2) == "'") {
|
||||
if (date) {
|
||||
if (i2 == i+1) {
|
||||
res += "'";
|
||||
}else{
|
||||
res += format.substring(i+1, i2);
|
||||
}
|
||||
i = i2;
|
||||
}
|
||||
break;
|
||||
if (this._ambigZone) {
|
||||
for (i=0; i<momentCount; i++) {
|
||||
if (typeof arguments[i] === 'string') {
|
||||
arguments[i] = fc.moment.parseZone(arguments[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (c == '(') {
|
||||
for (i2=i+1; i2<len; i2++) {
|
||||
if (format.charAt(i2) == ')') {
|
||||
var subres = formatDate(date, format.substring(i+1, i2), options);
|
||||
if (parseInt(subres.replace(/\D/, ''), 10)) {
|
||||
res += subres;
|
||||
}
|
||||
i = i2;
|
||||
break;
|
||||
}
|
||||
|
||||
for (i=0; i<momentCount; i++) {
|
||||
if (moment.isMoment(arguments[i]) && arguments[i]._ambigZone !== this._ambigZone) {
|
||||
newThis = newThis || this.clone().stripZone();
|
||||
arguments[i] = arguments[i].clone().stripZone();
|
||||
}
|
||||
}
|
||||
else if (c == '[') {
|
||||
for (i2=i+1; i2<len; i2++) {
|
||||
if (format.charAt(i2) == ']') {
|
||||
var subformat = format.substring(i+1, i2);
|
||||
var subres = formatDate(date, subformat, options);
|
||||
if (subres != formatDate(otherDate, subformat, options)) {
|
||||
res += subres;
|
||||
}
|
||||
i = i2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return origMethod.apply(newThis || this, arguments);
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
// Single Date Formatting
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
// Formats `date` with a Moment formatting string, but allow our non-zero areas and
|
||||
// additional token.
|
||||
function formatDate(date, formatStr) {
|
||||
return formatDateWithChunks(date, getFormatStringChunks(formatStr));
|
||||
}
|
||||
|
||||
|
||||
function formatDateWithChunks(date, chunks) {
|
||||
var s = '';
|
||||
var i;
|
||||
|
||||
for (i=0; i<chunks.length; i++) {
|
||||
s += formatDateWithChunk(date, chunks[i]);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
// addition formatting tokens we want recognized
|
||||
var tokenOverrides = {
|
||||
t: function(date) { // "a" or "p"
|
||||
return momentFormat(date, 'a').charAt(0);
|
||||
},
|
||||
T: function(date) { // "A" or "P"
|
||||
return momentFormat(date, 'A').charAt(0);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function formatDateWithChunk(date, chunk) {
|
||||
var token;
|
||||
var maybeStr;
|
||||
|
||||
if (typeof chunk === 'string') { // a literal string
|
||||
return chunk;
|
||||
}
|
||||
else if (token = chunk.token) { // a token, like "YYYY"
|
||||
if (tokenOverrides[token]) {
|
||||
return tokenOverrides[token](date); // use our custom token
|
||||
}
|
||||
else if (c == '{') {
|
||||
date = date2;
|
||||
otherDate = date1;
|
||||
return momentFormat(date, token);
|
||||
}
|
||||
else if (chunk.maybe) { // a grouping of other chunks that must be non-zero
|
||||
maybeStr = formatDateWithChunks(date, chunk.maybe);
|
||||
if (maybeStr.match(/[1-9]/)) {
|
||||
return maybeStr;
|
||||
}
|
||||
else if (c == '}') {
|
||||
date = date1;
|
||||
otherDate = date2;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
// Date Range Formatting
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// TODO: make it work with timezone offset
|
||||
|
||||
// Using a formatting string meant for a single date, generate a range string, like
|
||||
// "Sep 2 - 9 2013", that intelligently inserts a separator where the dates differ.
|
||||
// If the dates are the same as far as the format string is concerned, just return a single
|
||||
// rendering of one date, without any separator.
|
||||
function formatRange(date1, date2, formatStr, separator, isRTL) {
|
||||
|
||||
// Expand localized format strings, like "LL" -> "MMMM D YYYY"
|
||||
formatStr = date1.lang().longDateFormat(formatStr) || formatStr;
|
||||
// BTW, this is not important for `formatDate` because it is impossible to put custom tokens
|
||||
// or non-zero areas in Moment's localized format strings.
|
||||
|
||||
separator = separator || ' - ';
|
||||
|
||||
return formatRangeWithChunks(
|
||||
date1,
|
||||
date2,
|
||||
getFormatStringChunks(formatStr),
|
||||
separator,
|
||||
isRTL
|
||||
);
|
||||
}
|
||||
fc.formatRange = formatRange; // expose
|
||||
|
||||
|
||||
function formatRangeWithChunks(date1, date2, chunks, separator, isRTL) {
|
||||
var chunkStr; // the rendering of the chunk
|
||||
var leftI;
|
||||
var leftStr = '';
|
||||
var rightI;
|
||||
var rightStr = '';
|
||||
var middleI;
|
||||
var middleStr1 = '';
|
||||
var middleStr2 = '';
|
||||
var middleStr = '';
|
||||
|
||||
// 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]);
|
||||
if (chunkStr === false) {
|
||||
break;
|
||||
}
|
||||
leftStr += chunkStr;
|
||||
}
|
||||
|
||||
// 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]);
|
||||
if (chunkStr === false) {
|
||||
break;
|
||||
}
|
||||
rightStr = chunkStr + rightStr;
|
||||
}
|
||||
|
||||
// The area in the middle is different for both of the dates.
|
||||
// Collect them distinctly so we can jam them together later.
|
||||
for (middleI=leftI; middleI<=rightI; middleI++) {
|
||||
middleStr1 += formatDateWithChunk(date1, chunks[middleI]);
|
||||
middleStr2 += formatDateWithChunk(date2, chunks[middleI]);
|
||||
}
|
||||
|
||||
if (middleStr1 || middleStr2) {
|
||||
if (isRTL) {
|
||||
middleStr = middleStr2 + separator + middleStr1;
|
||||
}
|
||||
else {
|
||||
for (i2=len; i2>i; i2--) {
|
||||
if (formatter = dateFormatters[format.substring(i, i2)]) {
|
||||
if (date) {
|
||||
res += formatter(date, options);
|
||||
}
|
||||
i = i2 - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i2 == i) {
|
||||
if (date) {
|
||||
res += c;
|
||||
}
|
||||
}
|
||||
middleStr = middleStr1 + separator + middleStr2;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
|
||||
var dateFormatters = {
|
||||
s : function(d) { return d.getSeconds() },
|
||||
ss : function(d) { return zeroPad(d.getSeconds()) },
|
||||
m : function(d) { return d.getMinutes() },
|
||||
mm : function(d) { return zeroPad(d.getMinutes()) },
|
||||
h : function(d) { return d.getHours() % 12 || 12 },
|
||||
hh : function(d) { return zeroPad(d.getHours() % 12 || 12) },
|
||||
H : function(d) { return d.getHours() },
|
||||
HH : function(d) { return zeroPad(d.getHours()) },
|
||||
d : function(d) { return d.getDate() },
|
||||
dd : function(d) { return zeroPad(d.getDate()) },
|
||||
ddd : function(d,o) { return o.dayNamesShort[d.getDay()] },
|
||||
dddd: function(d,o) { return o.dayNames[d.getDay()] },
|
||||
M : function(d) { return d.getMonth() + 1 },
|
||||
MM : function(d) { return zeroPad(d.getMonth() + 1) },
|
||||
MMM : function(d,o) { return o.monthNamesShort[d.getMonth()] },
|
||||
MMMM: function(d,o) { return o.monthNames[d.getMonth()] },
|
||||
yy : function(d) { return (d.getFullYear()+'').substring(2) },
|
||||
yyyy: function(d) { return d.getFullYear() },
|
||||
t : function(d) { return d.getHours() < 12 ? 'a' : 'p' },
|
||||
tt : function(d) { return d.getHours() < 12 ? 'am' : 'pm' },
|
||||
T : function(d) { return d.getHours() < 12 ? 'A' : 'P' },
|
||||
TT : function(d) { return d.getHours() < 12 ? 'AM' : 'PM' },
|
||||
u : function(d) { return formatDate(d, "yyyy-MM-dd'T'HH:mm:ss'Z'") },
|
||||
S : function(d) {
|
||||
var date = d.getDate();
|
||||
if (date > 10 && date < 20) {
|
||||
return 'th';
|
||||
}
|
||||
return ['st', 'nd', 'rd'][date%10-1] || 'th';
|
||||
},
|
||||
w : function(d, o) { // local
|
||||
return o.weekNumberCalculation(d);
|
||||
},
|
||||
W : function(d) { // ISO
|
||||
return iso8601Week(d);
|
||||
}
|
||||
};
|
||||
fc.dateFormatters = dateFormatters;
|
||||
|
||||
|
||||
/* thanks jQuery UI (https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.datepicker.js)
|
||||
*
|
||||
* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition.
|
||||
* `date` - the date to get the week for
|
||||
* `number` - the number of the week within the year that contains this date
|
||||
*/
|
||||
function iso8601Week(date) {
|
||||
var time;
|
||||
var checkDate = new Date(date.getTime());
|
||||
|
||||
// Find Thursday of this week starting on Monday
|
||||
checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7));
|
||||
|
||||
time = checkDate.getTime();
|
||||
checkDate.setMonth(0); // Compare with Jan 1
|
||||
checkDate.setDate(1);
|
||||
return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
|
||||
return leftStr + middleStr + rightStr;
|
||||
}
|
||||
|
||||
|
||||
var similarUnitMap = {
|
||||
Y: 'year',
|
||||
M: 'month',
|
||||
D: 'day', // day of month
|
||||
d: 'day' // day of week
|
||||
};
|
||||
// don't go any further than day, because we don't want to break apart times like "12:30:00"
|
||||
// TODO: week maybe?
|
||||
|
||||
|
||||
// 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) {
|
||||
var token;
|
||||
var unit;
|
||||
|
||||
if (typeof chunk === 'string') { // a literal string
|
||||
return 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)) {
|
||||
return momentFormat(date1, token); // would be the same if we used `date2`
|
||||
// BTW, don't support custom tokens
|
||||
}
|
||||
}
|
||||
|
||||
return false; // the chunk is NOT the same for the two dates
|
||||
// BTW, don't support splitting on non-zero areas
|
||||
}
|
||||
|
||||
|
||||
// Chunking Utils
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
var formatStringChunkCache = {};
|
||||
|
||||
|
||||
function getFormatStringChunks(formatStr) {
|
||||
if (formatStr in formatStringChunkCache) {
|
||||
return formatStringChunkCache[formatStr];
|
||||
}
|
||||
return formatStringChunkCache[formatStr] = chunkFormatString(formatStr);
|
||||
}
|
||||
|
||||
|
||||
// Break the formatting string into an array of chunks
|
||||
function chunkFormatString(formatStr) {
|
||||
var chunks = [];
|
||||
var chunker = /\[([^\]]*)\]|\(([^\)]*)\)|((\w)\4*o?T?)|([^\w\[\(]+)/g; // TODO: more descrimination
|
||||
var match;
|
||||
|
||||
while (match = chunker.exec(formatStr)) {
|
||||
if (match[1]) { // a literal string instead [ ... ]
|
||||
chunks.push(match[1]);
|
||||
}
|
||||
else if (match[2]) { // non-zero formatting inside ( ... )
|
||||
chunks.push({ maybe: chunkFormatString(match[2]) });
|
||||
}
|
||||
else if (match[3]) { // a formatting token
|
||||
chunks.push({ token: match[3] });
|
||||
}
|
||||
else if (match[5]) { // an unenclosed literal string
|
||||
chunks.push(match[5]);
|
||||
}
|
||||
}
|
||||
|
||||
return chunks;
|
||||
}
|
||||
|
||||
|
||||
+74
-32
@@ -1,6 +1,13 @@
|
||||
|
||||
var defaults = {
|
||||
|
||||
lang: 'en',
|
||||
|
||||
defaultTimedEventDuration: '02:00:00',
|
||||
defaultAllDayEventDuration: { days: 1 },
|
||||
forceEventDuration: false,
|
||||
nextDayThreshold: '09:00:00', // 9am
|
||||
|
||||
// display
|
||||
defaultView: 'month',
|
||||
aspectRatio: 1.35,
|
||||
@@ -11,60 +18,62 @@ var defaults = {
|
||||
},
|
||||
weekends: true,
|
||||
weekNumbers: false,
|
||||
weekNumberCalculation: 'iso',
|
||||
|
||||
weekNumberTitle: 'W',
|
||||
weekNumberCalculation: 'local',
|
||||
|
||||
// editing
|
||||
//editable: false,
|
||||
//disableDragging: false,
|
||||
//disableResizing: false,
|
||||
|
||||
allDayDefault: true,
|
||||
ignoreTimezone: true,
|
||||
|
||||
// event ajax
|
||||
lazyFetching: true,
|
||||
startParam: 'start',
|
||||
endParam: 'end',
|
||||
timezoneParam: 'timezone',
|
||||
|
||||
//allDayDefault: undefined,
|
||||
|
||||
// time formats
|
||||
titleFormat: {
|
||||
month: 'MMMM yyyy',
|
||||
week: "MMM d[ yyyy]{ '—'[ MMM] d yyyy}",
|
||||
day: 'dddd, MMM d, yyyy'
|
||||
month: 'MMMM YYYY', // like "September 1986". each language will override this
|
||||
week: 'll', // like "Sep 4 1986"
|
||||
day: 'LL' // like "September 4 1986"
|
||||
},
|
||||
columnFormat: {
|
||||
month: 'ddd',
|
||||
week: 'ddd M/d',
|
||||
day: 'dddd M/d'
|
||||
month: 'ddd', // like "Sat"
|
||||
week: generateWeekColumnFormat,
|
||||
day: 'dddd' // like "Saturday"
|
||||
},
|
||||
timeFormat: { // for event elements
|
||||
'': 'h(:mm)t' // default
|
||||
'default': generateShortTimeFormat
|
||||
},
|
||||
|
||||
// locale
|
||||
isRTL: false,
|
||||
firstDay: 0,
|
||||
monthNames: ['January','February','March','April','May','June','July','August','September','October','November','December'],
|
||||
monthNamesShort: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
|
||||
dayNames: ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
|
||||
dayNamesShort: ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],
|
||||
buttonText: {
|
||||
prev: "<span class='fc-text-arrow'>‹</span>",
|
||||
next: "<span class='fc-text-arrow'>›</span>",
|
||||
prevYear: "<span class='fc-text-arrow'>«</span>",
|
||||
nextYear: "<span class='fc-text-arrow'>»</span>",
|
||||
prev: "prev",
|
||||
next: "next",
|
||||
prevYear: "prev year",
|
||||
nextYear: "next year",
|
||||
today: 'today',
|
||||
month: 'month',
|
||||
week: 'week',
|
||||
day: 'day'
|
||||
},
|
||||
|
||||
buttonIcons: {
|
||||
prev: 'left-single-arrow',
|
||||
next: 'right-single-arrow',
|
||||
prevYear: 'left-double-arrow',
|
||||
nextYear: 'right-double-arrow'
|
||||
},
|
||||
|
||||
// jquery-ui theming
|
||||
theme: false,
|
||||
buttonIcons: {
|
||||
themeButtonIcons: {
|
||||
prev: 'circle-triangle-w',
|
||||
next: 'circle-triangle-e'
|
||||
next: 'circle-triangle-e',
|
||||
prevYear: 'seek-prev',
|
||||
nextYear: 'seek-next'
|
||||
},
|
||||
|
||||
//selectable: false,
|
||||
@@ -76,6 +85,37 @@ var defaults = {
|
||||
|
||||
};
|
||||
|
||||
|
||||
function generateShortTimeFormat(options, langData) {
|
||||
return langData.longDateFormat('LT')
|
||||
.replace(':mm', '(:mm)')
|
||||
.replace(/(\Wmm)$/, '($1)') // like above, but for foreign langs
|
||||
.replace(/\s*a$/i, 't'); // convert to AM/PM/am/pm to lowercase one-letter. remove any spaces beforehand
|
||||
}
|
||||
|
||||
|
||||
function generateWeekColumnFormat(options, langData) {
|
||||
var format = langData.longDateFormat('L'); // for the format like "MM/DD/YYYY"
|
||||
format = format.replace(/^Y+[^\w\s]*|[^\w\s]*Y+$/g, ''); // strip the year off the edge, as well as other misc non-whitespace chars
|
||||
if (options.isRTL) {
|
||||
format += ' ddd'; // for RTL, add day-of-week to end
|
||||
}
|
||||
else {
|
||||
format = 'ddd ' + format; // for LTR, add day-of-week to beginning
|
||||
}
|
||||
return format;
|
||||
}
|
||||
|
||||
|
||||
var langOptionHash = {
|
||||
en: {
|
||||
columnFormat: {
|
||||
week: 'ddd M/D' // override for english. different from the generated default, which is MM/DD
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// right-to-left defaults
|
||||
var rtlDefaults = {
|
||||
header: {
|
||||
@@ -83,15 +123,17 @@ var rtlDefaults = {
|
||||
center: '',
|
||||
right: 'title'
|
||||
},
|
||||
buttonText: {
|
||||
prev: "<span class='fc-text-arrow'>›</span>",
|
||||
next: "<span class='fc-text-arrow'>‹</span>",
|
||||
prevYear: "<span class='fc-text-arrow'>»</span>",
|
||||
nextYear: "<span class='fc-text-arrow'>«</span>"
|
||||
},
|
||||
buttonIcons: {
|
||||
prev: 'right-single-arrow',
|
||||
next: 'left-single-arrow',
|
||||
prevYear: 'right-double-arrow',
|
||||
nextYear: 'left-double-arrow'
|
||||
},
|
||||
themeButtonIcons: {
|
||||
prev: 'circle-triangle-e',
|
||||
next: 'circle-triangle-w'
|
||||
next: 'circle-triangle-w',
|
||||
nextYear: 'seek-prev',
|
||||
prevYear: 'seek-next'
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+22
-29
@@ -4,13 +4,17 @@
|
||||
* (c) <%= meta.copyright %>
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
(function(factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define([ 'jquery' ], factory);
|
||||
}
|
||||
else {
|
||||
factory(jQuery);
|
||||
}
|
||||
})(function($) {
|
||||
|
||||
|
||||
var fc = $.fullCalendar;
|
||||
var formatDate = fc.formatDate;
|
||||
var parseISO8601 = fc.parseISO8601;
|
||||
var addDays = fc.addDays;
|
||||
var applyAll = fc.applyAll;
|
||||
|
||||
|
||||
@@ -26,64 +30,53 @@ fc.sourceNormalizers.push(function(sourceOptions) {
|
||||
});
|
||||
|
||||
|
||||
fc.sourceFetchers.push(function(sourceOptions, start, end) {
|
||||
fc.sourceFetchers.push(function(sourceOptions, start, end, timezone) {
|
||||
if (sourceOptions.dataType == 'gcal') {
|
||||
return transformOptions(sourceOptions, start, end);
|
||||
return transformOptions(sourceOptions, start, end, timezone);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function transformOptions(sourceOptions, start, end) {
|
||||
function transformOptions(sourceOptions, start, end, timezone) {
|
||||
|
||||
var success = sourceOptions.success;
|
||||
var data = $.extend({}, sourceOptions.data || {}, {
|
||||
'start-min': formatDate(start, 'u'),
|
||||
'start-max': formatDate(end, 'u'),
|
||||
'singleevents': true,
|
||||
'max-results': 9999
|
||||
});
|
||||
|
||||
var ctz = sourceOptions.currentTimezone;
|
||||
if (ctz) {
|
||||
data.ctz = ctz = ctz.replace(' ', '_');
|
||||
}
|
||||
|
||||
return $.extend({}, sourceOptions, {
|
||||
url: sourceOptions.url.replace(/\/basic$/, '/full') + '?alt=json-in-script&callback=?',
|
||||
dataType: 'jsonp',
|
||||
data: data,
|
||||
startParam: false,
|
||||
endParam: false,
|
||||
timezoneParam: 'ctz',
|
||||
startParam: 'start-min',
|
||||
endParam: 'start-max',
|
||||
success: function(data) {
|
||||
var events = [];
|
||||
if (data.feed.entry) {
|
||||
$.each(data.feed.entry, function(i, entry) {
|
||||
var startStr = entry['gd$when'][0]['startTime'];
|
||||
var start = parseISO8601(startStr, true);
|
||||
var end = parseISO8601(entry['gd$when'][0]['endTime'], true);
|
||||
var allDay = startStr.indexOf('T') == -1;
|
||||
|
||||
var url;
|
||||
$.each(entry.link, function(i, link) {
|
||||
if (link.type == 'text/html') {
|
||||
url = link.href;
|
||||
if (ctz) {
|
||||
url += (url.indexOf('?') == -1 ? '?' : '&') + 'ctz=' + ctz;
|
||||
if (timezone && timezone != 'local') {
|
||||
url += (url.indexOf('?') == -1 ? '?' : '&') + 'ctz=' + encodeURIComponent(timezone);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (allDay) {
|
||||
addDays(end, -1); // make inclusive
|
||||
}
|
||||
|
||||
events.push({
|
||||
id: entry['gCal$uid']['value'],
|
||||
title: entry['title']['$t'],
|
||||
start: entry['gd$when'][0]['startTime'],
|
||||
end: entry['gd$when'][0]['endTime'],
|
||||
url: url,
|
||||
start: start,
|
||||
end: end,
|
||||
allDay: allDay,
|
||||
location: entry['gd$where'][0]['valueString'],
|
||||
description: entry['content']['$t']
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
var args = [events].concat(Array.prototype.slice.call(arguments, 1));
|
||||
@@ -104,4 +97,4 @@ fc.gcalFeed = function(url, sourceOptions) {
|
||||
};
|
||||
|
||||
|
||||
})(jQuery);
|
||||
});
|
||||
|
||||
+8
-8
@@ -4,11 +4,11 @@
|
||||
* (c) <%= meta.copyright %>
|
||||
*/
|
||||
|
||||
/*
|
||||
* Use fullcalendar.css for basic styling.
|
||||
* For event drag & drop, requires jQuery UI draggable.
|
||||
* For event resizing, requires jQuery UI resizable.
|
||||
*/
|
||||
|
||||
(function($, undefined) {
|
||||
|
||||
(function(factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define([ 'jquery', 'moment' ], factory);
|
||||
}
|
||||
else {
|
||||
factory(jQuery, moment);
|
||||
}
|
||||
})(function($, moment) {
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
|
||||
//var langOptionHash = {}; // initialized in defaults.js
|
||||
fc.langs = langOptionHash; // expose
|
||||
|
||||
|
||||
// Initialize jQuery UI Datepicker translations while using some of the translations
|
||||
// for our own purposes. Will set this as the default language for datepicker.
|
||||
// Called from a translation file.
|
||||
fc.datepickerLang = function(langCode, datepickerLangCode, options) {
|
||||
var langOptions = langOptionHash[langCode];
|
||||
|
||||
// initialize FullCalendar's lang hash for this language
|
||||
if (!langOptions) {
|
||||
langOptions = langOptionHash[langCode] = {};
|
||||
}
|
||||
|
||||
// merge certain Datepicker options into FullCalendar's options
|
||||
mergeOptions(langOptions, {
|
||||
isRTL: options.isRTL,
|
||||
weekNumberTitle: options.weekHeader,
|
||||
titleFormat: {
|
||||
month: options.showMonthAfterYear ?
|
||||
'YYYY[' + options.yearSuffix + '] MMMM' :
|
||||
'MMMM YYYY[' + options.yearSuffix + ']'
|
||||
},
|
||||
buttonText: {
|
||||
// the translations sometimes wrongly contain HTML entities
|
||||
prev: stripHTMLEntities(options.prevText),
|
||||
next: stripHTMLEntities(options.nextText),
|
||||
today: stripHTMLEntities(options.currentText)
|
||||
}
|
||||
});
|
||||
|
||||
// is jQuery UI Datepicker is on the page?
|
||||
if ($.datepicker) {
|
||||
|
||||
// Register the language data.
|
||||
// FullCalendar and MomentJS use language codes like "pt-br" but Datepicker
|
||||
// does it like "pt-BR" or if it doesn't have the language, maybe just "pt".
|
||||
// Make an alias so the language can be referenced either way.
|
||||
$.datepicker.regional[datepickerLangCode] =
|
||||
$.datepicker.regional[langCode] = // alias
|
||||
options;
|
||||
|
||||
// Alias 'en' to the default language data. Do this every time.
|
||||
$.datepicker.regional['en'] = $.datepicker.regional[''];
|
||||
|
||||
// Set as Datepicker's global defaults.
|
||||
$.datepicker.setDefaults(options);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Sets FullCalendar-specific translations. Also sets the language as the global default.
|
||||
// Called from a translation file.
|
||||
fc.lang = function(langCode, options) {
|
||||
var langOptions;
|
||||
|
||||
if (options) {
|
||||
langOptions = langOptionHash[langCode];
|
||||
|
||||
// initialize the hash for this language
|
||||
if (!langOptions) {
|
||||
langOptions = langOptionHash[langCode] = {};
|
||||
}
|
||||
|
||||
mergeOptions(langOptions, options || {});
|
||||
}
|
||||
|
||||
// set it as the default language for FullCalendar
|
||||
defaults.lang = langCode;
|
||||
};
|
||||
@@ -101,11 +101,14 @@ html .fc,
|
||||
------------------------------------------------------------------------*/
|
||||
|
||||
.fc-content {
|
||||
position: relative;
|
||||
z-index: 1; /* scopes all other z-index's to be inside this container */
|
||||
clear: both;
|
||||
zoom: 1; /* for IE7, gives accurate coordinates for [un]freezeContentHeight */
|
||||
}
|
||||
|
||||
.fc-view {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
+33
-24
@@ -5,7 +5,6 @@ var fcViews = fc.views = {};
|
||||
|
||||
$.fn.fullCalendar = function(options) {
|
||||
|
||||
|
||||
// method calling
|
||||
if (typeof options == 'string') {
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
@@ -27,41 +26,51 @@ $.fn.fullCalendar = function(options) {
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
|
||||
// would like to have this logic in EventManager, but needs to happen before options are recursively extended
|
||||
var eventSources = options.eventSources || [];
|
||||
delete options.eventSources;
|
||||
if (options.events) {
|
||||
eventSources.push(options.events);
|
||||
delete options.events;
|
||||
}
|
||||
|
||||
|
||||
options = $.extend(true, {},
|
||||
defaults,
|
||||
(options.isRTL || options.isRTL===undefined && defaults.isRTL) ? rtlDefaults : {},
|
||||
options
|
||||
);
|
||||
|
||||
|
||||
this.each(function(i, _element) {
|
||||
var element = $(_element);
|
||||
var calendar = new Calendar(element, options, eventSources);
|
||||
element.data('fullCalendar', calendar); // TODO: look into memory leak implications
|
||||
var calendar = new Calendar(element, options);
|
||||
element.data('fullCalendar', calendar);
|
||||
calendar.render();
|
||||
});
|
||||
|
||||
|
||||
return this;
|
||||
|
||||
};
|
||||
|
||||
|
||||
// function for adding/overriding defaults
|
||||
function setDefaults(d) {
|
||||
$.extend(true, defaults, d);
|
||||
mergeOptions(defaults, d);
|
||||
}
|
||||
|
||||
|
||||
// Recursively combines option hash-objects.
|
||||
// Better than `$.extend(true, ...)` because arrays are not traversed/copied.
|
||||
//
|
||||
// called like:
|
||||
// mergeOptions(target, obj1, obj2, ...)
|
||||
//
|
||||
function mergeOptions(target) {
|
||||
for (var i=1; i<arguments.length; i++) {
|
||||
$.each(arguments[i], function(name, value) {
|
||||
if ($.isPlainObject(value) && $.isPlainObject(target[name]) && !isForcedAtomicOption(name)) {
|
||||
// merge into a new object to avoid destruction
|
||||
target[name] = mergeOptions({}, target[name], value); // combine. `value` object takes precedence
|
||||
}
|
||||
else if (value !== undefined) { // only use values that are set and not undefined
|
||||
target[name] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
|
||||
// overcome sucky view-option-hash and option-merging behavior messing with options it shouldn't
|
||||
function isForcedAtomicOption(name) {
|
||||
// Any option that ends in "Time" or "Duration" is probably a Duration,
|
||||
// and these will commonly be specified as plain objects, which we don't want to mess up.
|
||||
return /(Time|Duration)$/.test(name);
|
||||
}
|
||||
// FIX: find a different solution for view-option-hashes and have a whitelist
|
||||
// for options that can be recursively merged.
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
|
||||
})(jQuery);
|
||||
});
|
||||
+13
-32
@@ -2,25 +2,14 @@
|
||||
fc.applyAll = applyAll;
|
||||
|
||||
|
||||
/* Event Date Math
|
||||
-----------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
function exclEndDay(event) {
|
||||
if (event.end) {
|
||||
return _exclEndDay(event.end, event.allDay);
|
||||
}else{
|
||||
return addDays(cloneDate(event.start), 1);
|
||||
}
|
||||
function createObject(proto) { // like Object.create
|
||||
var f = function() {};
|
||||
f.prototype = proto;
|
||||
return new f();
|
||||
}
|
||||
|
||||
|
||||
function _exclEndDay(end, allDay) {
|
||||
end = cloneDate(end);
|
||||
return allDay || end.getHours() || end.getMinutes() ? addDays(end, 1) : clearTime(end);
|
||||
// why don't we check for seconds/ms too?
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Event Element Binding
|
||||
@@ -125,7 +114,7 @@ function vborders(element) {
|
||||
function noop() { }
|
||||
|
||||
|
||||
function dateCompare(a, b) {
|
||||
function dateCompare(a, b) { // works with moments too
|
||||
return a - b;
|
||||
}
|
||||
|
||||
@@ -135,11 +124,6 @@ function arrayMax(a) {
|
||||
}
|
||||
|
||||
|
||||
function zeroPad(n) {
|
||||
return (n < 10 ? '0' : '') + n;
|
||||
}
|
||||
|
||||
|
||||
function smartProperty(obj, name) { // get a camel-cased/namespaced property of an object
|
||||
if (obj[name] !== undefined) {
|
||||
return obj[name];
|
||||
@@ -152,12 +136,12 @@ function smartProperty(obj, name) { // get a camel-cased/namespaced property of
|
||||
return res;
|
||||
}
|
||||
}
|
||||
return obj[''];
|
||||
return obj['default'];
|
||||
}
|
||||
|
||||
|
||||
function htmlEscape(s) {
|
||||
return s.replace(/&/g, '&')
|
||||
return (s + '').replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/'/g, ''')
|
||||
@@ -166,6 +150,11 @@ function htmlEscape(s) {
|
||||
}
|
||||
|
||||
|
||||
function stripHTMLEntities(text) {
|
||||
return text.replace(/&.*?;/g, '');
|
||||
}
|
||||
|
||||
|
||||
function disableTextSelection(element) {
|
||||
element
|
||||
.attr('unselectable', 'on')
|
||||
@@ -184,7 +173,7 @@ function enableTextSelection(element) {
|
||||
*/
|
||||
|
||||
|
||||
function markFirstLast(e) {
|
||||
function markFirstLast(e) { // TODO: use CSS selectors instead
|
||||
e.children()
|
||||
.removeClass('fc-first fc-last')
|
||||
.filter(':first-child')
|
||||
@@ -195,14 +184,6 @@ function markFirstLast(e) {
|
||||
}
|
||||
|
||||
|
||||
function setDayID(cell, date) {
|
||||
cell.each(function(i, _cell) {
|
||||
_cell.className = _cell.className.replace(/^fc-\w*/, 'fc-' + dayIDs[date.getDay()]);
|
||||
// TODO: make a way that doesn't rely on order of classes
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function getSkinCss(event, opt) {
|
||||
var source = event.source || {};
|
||||
var eventColor = event.color;
|
||||
|
||||
Reference in New Issue
Block a user