I'm stuck on a calendar problem. I use FullCalendar to select a range of dates. Example when I select from 11/20/23 to 11/22/23.
Fullcalendar adds 1 more day (see screen).
this is the result when I selected 20 to 22.
I don't understand where the problem comes from.
Here is the code I already made.
var CalendarProduct = function () {
var calendarUnvailable;
var data = {
id: '',
eventName: '',
startDate: '',
endDate: '',
allDay: false
};
// Add event variables
var eventName;
var eventDescription;
var eventLocation;
var startDatepicker;
var startFlatpickr;
var endDatepicker;
var endFlatpickr;
var startTimepicker;
var startTimeFlatpickr;
var endTimepicker
var endTimeFlatpickr;
var modal;
var modalTitle;
var form;
var validator;
var addButton;
var submitButton;
var cancelButton;
var closeButton;
var viewEventName;
var viewAllDay;
var viewEventDescription;
var viewEventLocation;
var viewStartDate;
var viewEndDate;
var viewModal;
var viewEditButton;
var viewDeleteButton;
var initCalendarApp = function () {
moment.locale('fr');
let calendarElt = document.getElementById('date_unvailable');
var todayDate = moment().startOf('day');
var TODAY = todayDate.format('YYYY-MM-DD');
calendarUnvailable = new FullCalendar.Calendar(calendarElt, {
plugins: [dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin],
events: JSON.parse(unvailable),
initialView: 'dayGridWeek',
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay'
},
locales: [frLocale],
locale: 'fr',
initialDate: TODAY,
themeSystem: 'bootstrap',
navLinks: true,
selectable: true,
selectMirror: true,
dayHeaderContent: (args) => {
return moment(args.date).format('dddd')
},
eventResize: function (info) {
console.log(info);
},
// Select dates action --- more info: https://fullcalendar.io/docs/select-callback
select: function (arg) {
arg.allDay = false;
formatArgs(arg);
handleNewEvent();
},
eventDrop: function (info) {
$.post(calendarUnvailableEditRoute, {
'datas': {
title: info.event.title,
id: info.event.id,
start: info.event.start.toISOString(),
end: info.event.end.toISOString()
}
})
.done(function () {
Swal.fire(
"Enregistré !",
"Date déplacée",
"success"
)
})
.fail(function () {
Swal.fire(
"Internal Error",
"Votre date n'a pas été déplacée, contacter un administrateur",
"error"
)
})
},
eventClick: function (arg) {
formatArgs({
id: arg.event.id,
title: arg.event.title,
startStr: arg.event.start,
endStr: arg.event.end,
allDay: arg.event.allDay
});
handleViewEvent();
},
editable: true,
dayMaxEvents: true
});
calendarUnvailable.render();
}
const handleNewEvent = () => {
// Update modal title
modalTitle.innerText = "Ajouter une indisponibilité";
modal.show();
// Select datepicker wrapper elements
const datepickerWrappers = form.querySelectorAll('[data-kt-calendar="datepicker"]');
// Handle all day toggle
const allDayToggle = form.querySelector('#kt_calendar_datepicker_allday');
allDayToggle.addEventListener('click', e => {
if (e.target.checked) {
datepickerWrappers.forEach(dw => {
dw.classList.add('d-none');
});
} else {
endFlatpickr.setDate(data.endDate, true, 'Y-m-d H:i');
datepickerWrappers.forEach(dw => {
dw.classList.remove('d-none');
});
}
});
populateForm(data);
// Handle submit form
submitButton.addEventListener('click', function (e) {
// Prevent default button action
e.preventDefault();
// Validate form before submit
if (validator) {
validator.validate().then(function (status) {
if (status == 'Valid') {
// Show loading indication
submitButton.setAttribute('data-kt-indicator', 'on');
// Disable submit button whilst loading
submitButton.disabled = true;
// Simulate form submission
setTimeout(function () {
// Simulate form submission
submitButton.removeAttribute('data-kt-indicator');
// Show popup confirmation
Swal.fire({
text: "Nouvelle disponibilité ajouté avec succès !",
icon: "success",
buttonsStyling: false,
confirmButtonText: "D'accord, j'ai compris !",
customClass: {
confirmButton: "btn btn-primary"
}
}).then(function (result) {
if (result.isConfirmed) {
modal.hide();
// Enable submit button after loading
submitButton.disabled = false;
// Detect if is all day event
let allDayEvent = false;
if (allDayToggle.checked) {
allDayEvent = true;
}
if (startTimeFlatpickr.selectedDates.length === 0) {
allDayEvent = true;
}
// Merge date & time
var startDateTime = moment(startFlatpickr.selectedDates[0]).format();
var endDateTime = moment(endFlatpickr.selectedDates[endFlatpickr.selectedDates.length - 1]).format();
if (!allDayEvent) {
const startDate = moment(startFlatpickr.selectedDates[0]).format('YYYY-MM-DD');
const endDate = startDate;
const startTime = moment(startTimeFlatpickr.selectedDates[0]).format('HH:mm:ss');
const endTime = moment(endTimeFlatpickr.selectedDates[0]).format('HH:mm:ss');
startDateTime = startDate + 'T' + startTime;
endDateTime = endDate + 'T' + endTime;
}
// Add new event to calendar
calendarUnvailable.addEvent({
id: uid(),
title: eventName.value,
start: startDateTime,
end: endDateTime,
className: 'fc-event-success',
allDay: allDayEvent
});
calendarUnvailable.render();
// Save in database
$.post(calendarUnvailableAddRoute, {
'datas': {
title: eventName.value,
start: startDateTime,
end: endDateTime,
allDay: allDayEvent
}
})
.fail(function () {
Swal.fire(
"Internal Error",
"Votre disponibilité n'a pas été enregistré, contacter un administrateur",
"error"
)
})
// Reset form for demo purposes only
form.reset();
}
});
//form.submit(); // Submit form
}, 2000);
} else {
// Show popup warning
Swal.fire({
text: "Désolé, il semble qu'il y ait des erreurs détectées, veuillez réessayer.",
icon: "error",
buttonsStyling: false,
confirmButtonText: "D'accord, j'ai compris !",
customClass: {
confirmButton: "btn btn-primary"
}
});
}
});
}
});
}
const initDatepickers = () => {
startFlatpickr = flatpickr(startDatepicker, {
enableTime: false,
dateFormat: "d/m/Y",
locale: French,
static: true
});
endFlatpickr = flatpickr(endDatepicker, {
enableTime: false,
dateFormat: "d/m/Y",
locale: French,
static: true
});
startTimeFlatpickr = flatpickr(startTimepicker, {
enableTime: true,
noCalendar: true,
dateFormat: "H:i",
locale: French,
static: true
});
endTimeFlatpickr = flatpickr(endTimepicker, {
enableTime: true,
noCalendar: true,
dateFormat: "H:i",
locale: French,
static: true
});
}
// Init validator
const initValidator = () => {
// Init form validation rules. For more info check the FormValidation plugin's official documentation:https://formvalidation.io/
validator = FormValidation.formValidation(
form,
{
fields: {
'calendar_event_name': {
validators: {
notEmpty: {
message: 'Le titre est requis'
}
}
},
'calendar_event_start_date': {
validators: {
notEmpty: {
message: 'La date de début est requise'
}
}
},
'calendar_event_end_date': {
validators: {
notEmpty: {
message: 'La date de fin est requise'
}
}
}
},
plugins: {
trigger: new FormValidation.plugins.Trigger(),
bootstrap: new FormValidation.plugins.Bootstrap5({
rowSelector: '.fv-row',
eleInvalidClass: '',
eleValidClass: ''
})
},
}
);
}
// Handle add button
const handleAddButton = () => {
addButton.addEventListener('click', e => {
// Reset form data
data = {
id: '',
startDate: new Date(),
endDate: new Date(),
allDay: false
};
handleNewEvent();
});
}
const resetFormValidator = (element) => {
// Target modal hidden event --- For more info: https://getbootstrap.com/docs/5.0/components/modal/#events
element.addEventListener('hidden.bs.modal', e => {
if (validator) {
// Reset form validator. For more info: https://formvalidation.io/guide/api/reset-form
validator.resetForm(true);
}
});
}
// Populate form
const populateForm = () => {
eventName.value = data.title ? data.title : '';
startFlatpickr.setDate(data.startDate, true, 'Y-m-d');
// Handle null end dates
const endDate = data.endDate ? data.endDate : moment(data.startDate).format();
endFlatpickr.setDate(endDate, true, 'Y-m-d');
const allDayToggle = form.querySelector('#kt_calendar_datepicker_allday');
const datepickerWrappers = form.querySelectorAll('[data-kt-calendar="datepicker"]');
if (data.allDay) {
allDayToggle.checked = true;
datepickerWrappers.forEach(dw => {
dw.classList.add('d-none');
});
} else {
startTimeFlatpickr.setDate(data.startDate, true, 'Y-m-d H:i');
endTimeFlatpickr.setDate(data.endDate, true, 'Y-m-d H:i');
endFlatpickr.setDate(data.endDate, true, 'Y-m-d');
allDayToggle.checked = false;
datepickerWrappers.forEach(dw => {
dw.classList.remove('d-none');
});
}
}
const formatArgs = (res) => {
console.log(res);
data.id = res.id;
data.title = res.title;
data.startDate = res.startStr;
data.endDate = res.endStr;
data.allDay = res.allDay;
}
const uid = () => {
return Date.now().toString() + Math.floor(Math.random() * 1000).toString();
}
return {
init: function () {
const element = document.getElementById('kt_modal_add_event');
if (element != undefined) {
form = element.querySelector('#kt_modal_add_event_form');
eventName = form.querySelector('[name="calendar_event_name"]');
eventDescription = form.querySelector('[name="calendar_event_description"]');
eventLocation = form.querySelector('[name="calendar_event_location"]');
startDatepicker = form.querySelector('#kt_calendar_datepicker_start_date');
endDatepicker = form.querySelector('#kt_calendar_datepicker_end_date');
startTimepicker = form.querySelector('#kt_calendar_datepicker_start_time');
endTimepicker = form.querySelector('#kt_calendar_datepicker_end_time');
addButton = document.querySelector('[data-kt-calendar="add"]');
submitButton = form.querySelector('#kt_modal_add_event_submit');
cancelButton = form.querySelector('#kt_modal_add_event_cancel');
closeButton = element.querySelector('#kt_modal_add_event_close');
modalTitle = form.querySelector('[data-kt-calendar="title"]');
modal = new bootstrap.Modal(element);
const viewElement = document.getElementById('kt_modal_view_event');
viewModal = new bootstrap.Modal(viewElement);
viewEventName = viewElement.querySelector('[data-kt-calendar="event_name"]');
viewAllDay = viewElement.querySelector('[data-kt-calendar="all_day"]');
viewEventDescription = viewElement.querySelector('[data-kt-calendar="event_description"]');
viewEventLocation = viewElement.querySelector('[data-kt-calendar="event_location"]');
viewStartDate = viewElement.querySelector('[data-kt-calendar="event_start_date"]');
viewEndDate = viewElement.querySelector('[data-kt-calendar="event_end_date"]');
viewEditButton = viewElement.querySelector('#kt_modal_view_event_edit');
viewDeleteButton = viewElement.querySelector('#kt_modal_view_event_delete');
initCalendarApp();
initValidator();
initDatepickers();
handleAddButton();
resetFormValidator(element);
}
}
}
}();
Exemple:
Do you have any idea where this could come from? Thanks for your help.
As per the documentation of the "select" callback, fullcalendar treats end dates as exclusive. The end date you see is the first moment after the event finishes.
Therefore if you select the entire day of the 22nd, the first moment after the end of the 22nd is midnight on the 23rd.
For ease of reference, here is the relevant quote from the documentation of the "select" callback: