Skip to content
Snippets Groups Projects
availabilities.js 5.96 KiB
Newer Older
  • Learn to ignore specific revisions
  • // Availability editor using fullcalendar v5
    
    // This code was initially based on the availability editor from pretalx (https://github.com/pretalx/pretalx)
    
    // Copyright 2017-2019, Tobias Kunze
    // Original Copyrights licensed under the Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0
    
    // It was significantly changed to deal with the newer fullcalendar version, event specific timezones,
    // to remove the dependency to moments timezone and improve the visualization of deletion
    
    function createAvailabilityEditors(timezone, language, startDate, endDate, slotResolution='00:30:00') {
    
        $("input.availabilities-editor-data").each(function () {
    
            let data_field = $(this);
            let editor = $('<div class="availabilities-editor">');
            editor.attr("data-name", data_field.attr("name"));
            data_field.after(editor);
            data_field.hide();
    
            // Add inputs to add slots without the need to click and drag
            let manualSlotAdderSource = "<form id='formManualAdd'><table class='table table-responsive mb-0'><tr>" +
                "<td style='vertical-align: middle;'><input type='datetime-local' id='inputStart' value='" + startDate + "' min='" + startDate + "' max='" + endDate + "'></td>" +
                "<td style='vertical-align: middle;'><i class=\"fas fa-long-arrow-alt-right\"></i></td>" +
                "<td style='vertical-align: middle;'><input type='datetime-local' id='inputEnd' value='" + endDate + "' min='" + startDate + "' max='" + endDate + "'></td>" +
                "<td><button class='btn btn-primary' type='submit'><i class=\"fas fa-plus\"></i></button></td></tr></table></form>";
            let manualSlotAdder = $(manualSlotAdderSource);
            editor.after(manualSlotAdder);
    
            $('#formManualAdd').submit(function(event) {
                add($('#inputStart').val(), $('#inputEnd').val());
                event.preventDefault();
            });
    
    
            let editable = !Boolean(data_field.attr("disabled"));
            let data = JSON.parse(data_field.attr("value"));
            let events = data.availabilities.map(function (e) {
                start = moment(e.start);
                end = moment(e.end);
                allDay = start.format("HHmmss") === 0 && end.format("HHmmss") === 0;
    
                return {
                    id: e.id,
                    start: start.format(),
                    end: end.format(),
                    allDay: allDay,
                    title: ""
                };
            });
    
            let eventMarkedForDeletion = undefined;
            let eventMarkedForDeletionEl = undefined;
            let newEventsCounter = 0;
    
            let plan = new FullCalendar.Calendar(editor[0], {
                timeZone: timezone,
                themeSystem: 'bootstrap',
                locale: language,
                schedulerLicenseKey: 'GPL-My-Project-Is-Open-Source',
                editable: editable,
                selectable: editable,
                headerToolbar: false,
                initialView: 'timeGridWholeEvent',
    
                    timeGridWholeEvent: {
                        type: 'timeGrid',
                        visibleRange: {
                            start: startDate,
                            end: endDate,
    
                allDaySlot: true,
                events: data.availabilities,
                eventBackgroundColor: eventColor,
                select: function (info) {
    
                },
                eventClick: function (info) {
                    if (eventMarkedForDeletion !== undefined && (eventMarkedForDeletion.id === info.event.id)) {
                        info.event.remove();
                        eventMarkedForDeletion = undefined;
                        eventMarkedForDeletionEl = undefined;
                        save_events();
                    } else {
                        resetDeletionCandidate();
                        makeDeletionCandidate(info.el);
                        eventMarkedForDeletion = info.event;
                        eventMarkedForDeletionEl = info.el;
    
                    }
                },
                selectOverlap: false,
                eventOverlap: false,
    
            function add(start, end) {
                resetDeletionCandidate();
                plan.addEvent({
                    title: "",
                    start: start,
                    end: end,
                    id: 'new' + newEventsCounter
                })
                newEventsCounter++;
                save_events();
            }
    
    
            function makeDeletionCandidate(el) {
                el.classList.add("deleteEvent");
                $(el).find(".fc-event-title").html("<i class='fas fa-trash'></i> <i class='fas fa-question'></i>");
            }
    
            function resetDeletionCandidate() {
                if (eventMarkedForDeletionEl !== undefined) {
                    eventMarkedForDeletionEl.classList.remove("deleteEvent");
                    $(eventMarkedForDeletionEl).find(".fc-event-title").html("");
                }
                eventMarkedForDeletionEl = undefined;
                eventMarkedForDeletion = undefined;
            }
    
            function save_events() {
                data = {
                    availabilities: plan.getEvents().map(function (e) {
                        let id = e.id;
                        if(e.id.startsWith("new"))
                            id = "";
                        return {
                            id: id,
                            // Make sure these timestamps are correctly interpreted as localized ones
                            // by removing the UTC-signaler ("Z" at the end)
                            // A bit dirty, but still more elegant than creating a timestamp with the
                            // required format manually
                            start: e.start.toISOString().replace("Z", ""),
                            end: e.end.toISOString().replace("Z", ""),
                            allDay: e.allDay,
                        }
                    }),
                }
                data_field.attr("value", JSON.stringify(data));
            }
        });
    }