/** * App Calendar */ /** * ! If both start and end dates are same Full calendar will nullify the end date value. * ! Full calendar will end the event on a day before at 12:00:00AM thus, event won't extend to the end date. * ! We are getting events from a separate file named app-calendar-events.js. You can add or remove events from there. * **/ 'use strict'; let direction = 'ltr'; if (isRtl) { direction = 'rtl'; } document.addEventListener('DOMContentLoaded', function () { (function () { const calendarEl = document.getElementById('calendar'), appCalendarSidebar = document.querySelector('.app-calendar-sidebar'), addEventSidebar = document.getElementById('addEventSidebar'), appOverlay = document.querySelector('.app-overlay'), calendarsColor = { Business: 'primary', Holiday: 'success', Personal: 'danger', Family: 'warning', ETC: 'info' }, offcanvasTitle = document.querySelector('.offcanvas-title'), btnToggleSidebar = document.querySelector('.btn-toggle-sidebar'), btnSubmit = document.querySelector('button[type="submit"]'), btnDeleteEvent = document.querySelector('.btn-delete-event'), btnCancel = document.querySelector('.btn-cancel'), eventTitle = document.querySelector('#eventTitle'), eventStartDate = document.querySelector('#eventStartDate'), eventEndDate = document.querySelector('#eventEndDate'), eventUrl = document.querySelector('#eventURL'), eventLabel = $('#eventLabel'), // ! Using jquery vars due to select2 jQuery dependency eventGuests = $('#eventGuests'), // ! Using jquery vars due to select2 jQuery dependency eventLocation = document.querySelector('#eventLocation'), eventDescription = document.querySelector('#eventDescription'), allDaySwitch = document.querySelector('.allDay-switch'), selectAll = document.querySelector('.select-all'), filterInput = [].slice.call(document.querySelectorAll('.input-filter')), inlineCalendar = document.querySelector('.inline-calendar'); let eventToUpdate, currentEvents = events, // Assign app-calendar-events.js file events (assume events from API) to currentEvents (browser store/object) to manage and update calender events isFormValid = false, inlineCalInstance; // Init event Offcanvas const bsAddEventSidebar = new bootstrap.Offcanvas(addEventSidebar); //! TODO: Update Event label and guest code to JS once select removes jQuery dependency // Event Label (select2) if (eventLabel.length) { function renderBadges(option) { if (!option.id) { return option.text; } var $badge = " " + '' + option.text; return $badge; } eventLabel.wrap('
').select2({ placeholder: 'Select value', dropdownParent: eventLabel.parent(), templateResult: renderBadges, templateSelection: renderBadges, minimumResultsForSearch: -1, escapeMarkup: function (es) { return es; } }); } // Event Guests (select2) if (eventGuests.length) { function renderGuestAvatar(option) { if (!option.id) { return option.text; } var $avatar = "
" + "
" + "avatar" + '
' + option.text + '
'; return $avatar; } eventGuests.wrap('
').select2({ placeholder: 'Select value', dropdownParent: eventGuests.parent(), closeOnSelect: false, templateResult: renderGuestAvatar, templateSelection: renderGuestAvatar, escapeMarkup: function (es) { return es; } }); } // Event start (flatpicker) if (eventStartDate) { var start = eventStartDate.flatpickr({ enableTime: true, altFormat: 'Y-m-dTH:i:S', onReady: function (selectedDates, dateStr, instance) { if (instance.isMobile) { instance.mobileInput.setAttribute('step', null); } } }); } // Event end (flatpicker) if (eventEndDate) { var end = eventEndDate.flatpickr({ enableTime: true, altFormat: 'Y-m-dTH:i:S', onReady: function (selectedDates, dateStr, instance) { if (instance.isMobile) { instance.mobileInput.setAttribute('step', null); } } }); } // Inline sidebar calendar (flatpicker) if (inlineCalendar) { inlineCalInstance = inlineCalendar.flatpickr({ monthSelectorType: 'static', inline: true }); } // Event click function function eventClick(info) { eventToUpdate = info.event; if (eventToUpdate.url) { info.jsEvent.preventDefault(); window.open(eventToUpdate.url, '_blank'); } bsAddEventSidebar.show(); // For update event set offcanvas title text: Update Event if (offcanvasTitle) { offcanvasTitle.innerHTML = 'Update Event'; } btnSubmit.innerHTML = 'Update'; btnSubmit.classList.add('btn-update-event'); btnSubmit.classList.remove('btn-add-event'); btnDeleteEvent.classList.remove('d-none'); eventTitle.value = eventToUpdate.title; start.setDate(eventToUpdate.start, true, 'Y-m-d'); eventToUpdate.allDay === true ? (allDaySwitch.checked = true) : (allDaySwitch.checked = false); eventToUpdate.end !== null ? end.setDate(eventToUpdate.end, true, 'Y-m-d') : end.setDate(eventToUpdate.start, true, 'Y-m-d'); eventLabel.val(eventToUpdate.extendedProps.calendar).trigger('change'); eventToUpdate.extendedProps.location !== undefined ? (eventLocation.value = eventToUpdate.extendedProps.location) : null; eventToUpdate.extendedProps.guests !== undefined ? eventGuests.val(eventToUpdate.extendedProps.guests).trigger('change') : null; eventToUpdate.extendedProps.description !== undefined ? (eventDescription.value = eventToUpdate.extendedProps.description) : null; // // Call removeEvent function // btnDeleteEvent.addEventListener('click', e => { // removeEvent(parseInt(eventToUpdate.id)); // // eventToUpdate.remove(); // bsAddEventSidebar.hide(); // }); } // Modify sidebar toggler function modifyToggler() { const fcSidebarToggleButton = document.querySelector('.fc-sidebarToggle-button'); fcSidebarToggleButton.classList.remove('fc-button-primary'); fcSidebarToggleButton.classList.add('d-lg-none', 'd-inline-block', 'ps-0'); while (fcSidebarToggleButton.firstChild) { fcSidebarToggleButton.firstChild.remove(); } fcSidebarToggleButton.setAttribute('data-bs-toggle', 'sidebar'); fcSidebarToggleButton.setAttribute('data-overlay', ''); fcSidebarToggleButton.setAttribute('data-target', '#app-calendar-sidebar'); fcSidebarToggleButton.insertAdjacentHTML('beforeend', ''); } // Filter events by calender function selectedCalendars() { let selected = [], filterInputChecked = [].slice.call(document.querySelectorAll('.input-filter:checked')); filterInputChecked.forEach(item => { selected.push(item.getAttribute('data-value')); }); return selected; } // -------------------------------------------------------------------------------------------------- // AXIOS: fetchEvents // * This will be called by fullCalendar to fetch events. Also this can be used to refetch events. // -------------------------------------------------------------------------------------------------- function fetchEvents(info, successCallback) { // Fetch Events from API endpoint reference /* $.ajax( { url: '../../../app-assets/data/app-calendar-events.js', type: 'GET', success: function (result) { // Get requested calendars as Array var calendars = selectedCalendars(); return [result.events.filter(event => calendars.includes(event.extendedProps.calendar))]; }, error: function (error) { console.log(error); } } ); */ let calendars = selectedCalendars(); // We are reading event object from app-calendar-events.js file directly by including that file above app-calendar file. // You should make an API call, look into above commented API call for reference let selectedEvents = currentEvents.filter(function (event) { // console.log(event.extendedProps.calendar.toLowerCase()); return calendars.includes(event.extendedProps.calendar.toLowerCase()); }); // if (selectedEvents.length > 0) { successCallback(selectedEvents); // } } // Init FullCalendar // ------------------------------------------------ let calendar = new Calendar(calendarEl, { initialView: 'dayGridMonth', events: fetchEvents, plugins: [dayGridPlugin, interactionPlugin, listPlugin, timegridPlugin], editable: true, dragScroll: true, dayMaxEvents: 2, eventResizableFromStart: true, customButtons: { sidebarToggle: { text: 'Sidebar' } }, headerToolbar: { start: 'sidebarToggle, prev,next, title', end: 'dayGridMonth,timeGridWeek,timeGridDay,listMonth' }, direction: direction, initialDate: new Date(), navLinks: true, // can click day/week names to navigate views eventClassNames: function ({ event: calendarEvent }) { const colorName = calendarsColor[calendarEvent._def.extendedProps.calendar]; // Background Color return ['fc-event-' + colorName]; }, dateClick: function (info) { let date = moment(info.date).format('YYYY-MM-DD'); resetValues(); bsAddEventSidebar.show(); // For new event set offcanvas title text: Add Event if (offcanvasTitle) { offcanvasTitle.innerHTML = 'Add Event'; } btnSubmit.innerHTML = 'Add'; btnSubmit.classList.remove('btn-update-event'); btnSubmit.classList.add('btn-add-event'); btnDeleteEvent.classList.add('d-none'); eventStartDate.value = date; eventEndDate.value = date; }, eventClick: function (info) { eventClick(info); }, datesSet: function () { modifyToggler(); }, viewDidMount: function () { modifyToggler(); } }); // Render calendar calendar.render(); // Modify sidebar toggler modifyToggler(); const eventForm = document.getElementById('eventForm'); const fv = FormValidation.formValidation(eventForm, { fields: { eventTitle: { validators: { notEmpty: { message: 'Please enter event title ' } } }, eventStartDate: { validators: { notEmpty: { message: 'Please enter start date ' } } }, eventEndDate: { validators: { notEmpty: { message: 'Please enter end date ' } } } }, plugins: { trigger: new FormValidation.plugins.Trigger(), bootstrap5: new FormValidation.plugins.Bootstrap5({ // Use this for enabling/changing valid/invalid class eleValidClass: '', rowSelector: function (field, ele) { // field is the field name & ele is the field element return '.mb-3'; } }), submitButton: new FormValidation.plugins.SubmitButton(), // Submit the form when all fields are valid // defaultSubmit: new FormValidation.plugins.DefaultSubmit(), autoFocus: new FormValidation.plugins.AutoFocus() } }) .on('core.form.valid', function () { // Jump to the next step when all fields in the current step are valid isFormValid = true; }) .on('core.form.invalid', function () { // if fields are invalid isFormValid = false; }); // Sidebar Toggle Btn if (btnToggleSidebar) { btnToggleSidebar.addEventListener('click', e => { btnCancel.classList.remove('d-none'); }); } // Add Event // ------------------------------------------------ function addEvent(eventData) { // ? Add new event data to current events object and refetch it to display on calender // ? You can write below code to AJAX call success response currentEvents.push(eventData); calendar.refetchEvents(); // ? To add event directly to calender (won't update currentEvents object) // calendar.addEvent(eventData); } // Update Event // ------------------------------------------------ function updateEvent(eventData) { // ? Update existing event data to current events object and refetch it to display on calender // ? You can write below code to AJAX call success response eventData.id = parseInt(eventData.id); currentEvents[currentEvents.findIndex(el => el.id === eventData.id)] = eventData; // Update event by id calendar.refetchEvents(); // ? To update event directly to calender (won't update currentEvents object) // let propsToUpdate = ['id', 'title', 'url']; // let extendedPropsToUpdate = ['calendar', 'guests', 'location', 'description']; // updateEventInCalendar(eventData, propsToUpdate, extendedPropsToUpdate); } // Remove Event // ------------------------------------------------ function removeEvent(eventId) { // ? Delete existing event data to current events object and refetch it to display on calender // ? You can write below code to AJAX call success response currentEvents = currentEvents.filter(function (event) { return event.id != eventId; }); calendar.refetchEvents(); // ? To delete event directly to calender (won't update currentEvents object) // removeEventInCalendar(eventId); } // (Update Event In Calendar (UI Only) // ------------------------------------------------ const updateEventInCalendar = (updatedEventData, propsToUpdate, extendedPropsToUpdate) => { const existingEvent = calendar.getEventById(updatedEventData.id); // --- Set event properties except date related ----- // // ? Docs: https://fullcalendar.io/docs/Event-setProp // dateRelatedProps => ['start', 'end', 'allDay'] // eslint-disable-next-line no-plusplus for (var index = 0; index < propsToUpdate.length; index++) { var propName = propsToUpdate[index]; existingEvent.setProp(propName, updatedEventData[propName]); } // --- Set date related props ----- // // ? Docs: https://fullcalendar.io/docs/Event-setDates existingEvent.setDates(updatedEventData.start, updatedEventData.end, { allDay: updatedEventData.allDay }); // --- Set event's extendedProps ----- // // ? Docs: https://fullcalendar.io/docs/Event-setExtendedProp // eslint-disable-next-line no-plusplus for (var index = 0; index < extendedPropsToUpdate.length; index++) { var propName = extendedPropsToUpdate[index]; existingEvent.setExtendedProp(propName, updatedEventData.extendedProps[propName]); } }; // Remove Event In Calendar (UI Only) // ------------------------------------------------ function removeEventInCalendar(eventId) { calendar.getEventById(eventId).remove(); } // Add new event // ------------------------------------------------ btnSubmit.addEventListener('click', e => { if (btnSubmit.classList.contains('btn-add-event')) { if (isFormValid) { let newEvent = { id: calendar.getEvents().length + 1, title: eventTitle.value, start: eventStartDate.value, end: eventEndDate.value, startStr: eventStartDate.value, endStr: eventEndDate.value, display: 'block', extendedProps: { location: eventLocation.value, guests: eventGuests.val(), calendar: eventLabel.val(), description: eventDescription.value } }; if (eventUrl.value) { newEvent.url = eventUrl.value; } if (allDaySwitch.checked) { newEvent.allDay = true; } addEvent(newEvent); bsAddEventSidebar.hide(); } } else { // Update event // ------------------------------------------------ if (isFormValid) { let eventData = { id: eventToUpdate.id, title: eventTitle.value, start: eventStartDate.value, end: eventEndDate.value, url: eventUrl.value, extendedProps: { location: eventLocation.value, guests: eventGuests.val(), calendar: eventLabel.val(), description: eventDescription.value }, display: 'block', allDay: allDaySwitch.checked ? true : false }; updateEvent(eventData); bsAddEventSidebar.hide(); } } }); // Call removeEvent function btnDeleteEvent.addEventListener('click', e => { removeEvent(parseInt(eventToUpdate.id)); // eventToUpdate.remove(); bsAddEventSidebar.hide(); }); // Reset event form inputs values // ------------------------------------------------ function resetValues() { eventEndDate.value = ''; eventUrl.value = ''; eventStartDate.value = ''; eventTitle.value = ''; eventLocation.value = ''; allDaySwitch.checked = false; eventGuests.val('').trigger('change'); eventDescription.value = ''; } // When modal hides reset input values addEventSidebar.addEventListener('hidden.bs.offcanvas', function () { resetValues(); }); // Hide left sidebar if the right sidebar is open btnToggleSidebar.addEventListener('click', e => { if (offcanvasTitle) { offcanvasTitle.innerHTML = 'Add Event'; } btnSubmit.innerHTML = 'Add'; btnSubmit.classList.remove('btn-update-event'); btnSubmit.classList.add('btn-add-event'); btnDeleteEvent.classList.add('d-none'); appCalendarSidebar.classList.remove('show'); appOverlay.classList.remove('show'); }); // Calender filter functionality // ------------------------------------------------ if (selectAll) { selectAll.addEventListener('click', e => { if (e.currentTarget.checked) { document.querySelectorAll('.input-filter').forEach(c => (c.checked = 1)); } else { document.querySelectorAll('.input-filter').forEach(c => (c.checked = 0)); } calendar.refetchEvents(); }); } if (filterInput) { filterInput.forEach(item => { item.addEventListener('click', () => { document.querySelectorAll('.input-filter:checked').length < document.querySelectorAll('.input-filter').length ? (selectAll.checked = false) : (selectAll.checked = true); calendar.refetchEvents(); }); }); } // Jump to date on sidebar(inline) calendar change inlineCalInstance.config.onChange.push(function (date) { calendar.changeView(calendar.view.type, moment(date[0]).format('YYYY-MM-DD')); modifyToggler(); appCalendarSidebar.classList.remove('show'); appOverlay.classList.remove('show'); }); })(); });