PageSourceSearch

https://holtriem.de/wp-content/plugins/bookly-responsive-appointme…/resources/js/calendar-common.js?ver=25.9

js holtriem.de collected 2026-10-02 18:21:08 UTC 25,207 bytes, 520 lines download raw bytes

1(function ($) {
2
3    let calendar;
4
5    let Calendar = function ($container, options) {
6        let obj = this;
7        jQuery.extend(obj.options, options);
8
9        // Special locale for moment
10        moment.locale('bookly', {
11            months: obj.options.l10n.datePicker.monthNames,
12            monthsShort: obj.options.l10n.datePicker.monthNamesShort,
13            weekdays: obj.options.l10n.datePicker.dayNames,
14            weekdaysShort: obj.options.l10n.datePicker.dayNamesShort,
15            meridiem: function (hours, minutes, isLower) {
16                return hours < 12
17                    ? obj.options.l10n.datePicker.meridiem[isLower ? 'am' : 'AM']
18                    : obj.options.l10n.datePicker.meridiem[isLower ? 'pm' : 'PM'];
19            },
20        });
21
22        let allowEditAppointment = typeof BooklyAppointmentDialog !== 'undefined' && (!options.hasOwnProperty('disable_editor') || !options.disable_editor);
23
24        let viewTypes = {
25            dayGridMonth: 'resourceTimelineMonth',
26            timeGridWeek: 'resourceTimelineWeek',
27            resourceTimeGridDay: 'resourceTimelineDay'
28        }
29        // Settings for Event Calendar
30        let settings = {
31            view: 'timeGridWeek',
32            customButtons: {
33                timeline: {
34                    active: true,
35                    text: {html: '<i class="far fa-fw fa-check-square"></i> ' + obj.options.l10n.timeline},
36                    click: function (e) {
37                        let headerToolbar = calendar.getOption('headerToolbar'),
38                            view = calendar.getOption('view');
39                        headerToolbar.end = 'dayGridMonth,timeGridWeek,resourceTimeGridDay,grid listWeek'
40                        calendar.setOption('headerToolbar', headerToolbar);
41                        let _viewTypes = Object.values(viewTypes);
42                        if (_viewTypes.includes(view)) {
43                            // Change view
44                            Object.keys(viewTypes).forEach(function (key) {
45                                if (viewTypes[key] === view) {
46                                    calendar.setOption('view', key);
47                                }
48                            });
49                        } else if (view === 'listWeek') {
50                            setCookie('bookly_cal_view', 'listWeek');
51                        }
52                    }
53                },
54                grid: {
55                    text: {html: '<i class="far fa-fw fa-square"></i> ' + obj.options.l10n.timeline},
56                    click: function () {
57                        let headerToolbar = calendar.getOption('headerToolbar'),
58                            view = calendar.getOption('view');
59                        headerToolbar.end = 'resourceTimelineMonth,resourceTimelineWeek,resourceTimelineDay,timeline listWeek'
60                        calendar.setOption('headerToolbar', headerToolbar);
61                        if (viewTypes.hasOwnProperty(view)) {
62                            // Change view
63                            calendar.setOption('view', viewTypes[view]);
64                        } else if (view === 'listWeek') {
65                            setCookie('bookly_cal_view', 'listWeekTimeline');
66                        }
67                    }
68                },
69            },
70            views: {
71                dayGridMonth: {
72                    dayHeaderFormat: function (date) {
73                        return moment(date).locale('bookly').format('ddd');
74                    },
75                    displayEventEnd: true,
76                    dayMaxEvents: obj.options.l10n.monthDayMaxEvents === '1',
77                    theme: function (theme) {
78                        if (obj.options.l10n.monthDayMaxEvents === '1') {
79                            theme.view += ' ec-minimalistic';
80                        }
81                        return theme;
82                    }
83                },
84                timeGridDay: {
85                    dayHeaderFormat: function (date) {
86                        return moment(date).locale('bookly').format('dddd');
87                    },
88                    pointer: true
89                },
90                timeGridWeek: {pointer: true},
91                resourceTimeGridDay: {pointer: true},
92                resourceTimelineDay: {pointer: true, displayEventEnd: true, slotWidth: 180},
93                resourceTimelineWeek: {pointer: true, displayEventEnd: true, slotWidth: 180},
94                resourceTimelineMonth: {pointer: true, displayEventEnd: true, slotWidth: 180}
95            },
96            nowIndicator: true,
97            hiddenDays: obj.options.l10n.hiddenDays,
98            slotDuration: obj.options.l10n.slotDuration,
99            slotMinTime: obj.options.l10n.slotMinTime,
100            slotMaxTime: obj.options.l10n.slotMaxTime,
101            scrollTime: obj.options.l10n.scrollTime,
102            moreLinkContent: function (arg) {
103                return obj.options.l10n.more.replace('%d', arg.num)
104            },
105            flexibleSlotTimeLimits: true,
106            eventStartEditable: false,
107            eventDurationEditable: false,
108            allDaySlot: false,
109            allDayContent: obj.options.l10n.allDay,
110
111            slotLabelFormat: function (date) {
112                return moment(date).locale('bookly').format(obj.options.l10n.mjsTimeFormat);
113            },
114            eventTimeFormat: function (start, end) {
115                if (start.getTime() === end.getTime()) {
116                    return moment(start).locale('bookly').format(obj.options.l10n.mjsTimeFormat);
117                } else {
118                    return moment(start).locale('bookly').format(obj.options.l10n.mjsTimeFormat) + ' - ' + moment(end).locale('bookly').format(obj.options.l10n.mjsTimeFormat);
119                }
120            },
121            dayHeaderFormat: function (date) {
122                return moment(date).locale('bookly').format('ddd, D');
123            },
124            listDayFormat: function (date) {
125                return moment(date).locale('bookly').format('dddd');
126            },
127            firstDay: obj.options.l10n.datePicker.firstDay,
128            locale: obj.options.l10n.locale.replace('_', '-'),
129            buttonText: {
130                today: obj.options.l10n.today,
131                dayGridMonth: obj.options.l10n.month,
132                timeGridWeek: obj.options.l10n.week,
133                timeGridDay: obj.options.l10n.day,
134                resourceTimeGridDay: obj.options.l10n.day,
135                resourceTimelineMonth: obj.options.l10n.month,
136                resourceTimelineWeek: obj.options.l10n.week,
137                resourceTimelineDay: obj.options.l10n.day,
138                listWeek: obj.options.l10n.list
139            },
140            noEventsContent: obj.options.l10n.noEvents,
141            eventSources: [{
142                url: ajaxurl,
143                method: 'POST',
144                extraParams: function () {
145                    return {
146                        action: 'bookly_get_staff_appointments',
147                        csrf_token: BooklyL10nGlobal.csrf_token,
148                        staff_ids: obj.options.getStaffMemberIds(),
149                        location_ids: obj.options.getLocationIds(),
150                        service_ids: obj.options.getServiceIds()
151                    };
152                }
153            }],
154            eventFilter: function (info) {
155                return !['dayGridMonth', 'timeGridWeek', 'listWeek'].includes(info.view.type) || !info.event.extendedProps.resources_only;
156            },
157            eventBackgroundColor: '#ccc',
158            eventMouseEnter: function (arg) {
159                arg.jsEvent.stopPropagation();
160                if (arg.event.display === 'background') {
161                    return '';
162                }
163                let $event = $(arg.el);
164                if (arg.event.display === 'auto' && arg.view.type !== 'listWeek') {
165                    let $existing_popover = $event.find('.bookly-ec-popover')
166                    if (!$existing_popover.length) {
167                        let $popover = $('<div class="bookly-popover bs-popover-top bookly-ec-popover">')
168                        let $arrow = $('<div class="arrow"></div><div class="bookly-arrow-background"></div>');
169                        let $body = $('<div class="popover-body">');
170                        let $buttons = allowEditAppointment ? popoverButtons(arg) : '';
171
172                        $body.append(arg.event.extendedProps.tooltip).append($buttons).css({minWidth: '200px'});
173                        $popover.append($arrow).append($body);
174                        $event.append($popover);
175                    }
176
177                    let $popover = $event.find('.bookly-ec-popover'),
178                        $arrow = $popover.find('.arrow'),
179                        offset = $event.offset(),
180                        popover_height = $popover.outerHeight(),
181                        $calendar_container = $event.closest('.ec-body').length ? $event.closest('.ec-body') : $event.closest('.ec-all-day'),
182                        container_top = $calendar_container.offset().top,
183                        event_width = $event.outerWidth(),
184                        popover_right = offset.left > window.innerWidth / 2;
185
186                    $popover.css('min-width', (Math.min(400, event_width - 2)) + 'px');
187
188                    if (container_top > offset.top - popover_height) {
189                        // Popover onside of the event
190                        $popover.css('top', (Math.max(container_top, offset.top) - $(document).scrollTop()) + 'px');
191                        if (popover_right) {
192                            $popover.removeClass('bs-popover-top').removeClass('bs-popover-right').addClass('bs-popover-left');
193                            $popover.css('left', (offset.left - $popover.outerWidth()) + 'px');
194                            $arrow.css('right', '-8px');
195                        } else {
196                            $popover.removeClass('bs-popover-top').removeClass('bs-popover-left').addClass('bs-popover-right');
197                            $popover.css('left', Math.min(offset.left - 7 + event_width, $calendar_container.offset().left + $calendar_container.outerWidth() - $popover.outerWidth() - 32) + 'px');
198                            $arrow.css('left', '-8px');
199                        }
200                    } else {
201                        // Popover on top of the event
202                        let top = Math.max(popover_height + 40, Math.max(container_top, offset.top) - $(document).scrollTop());
203
204                        $popover.css('top', (top - popover_height - 5) + 'px')
205                        $popover.removeClass('bs-popover-left').removeClass('bs-popover-right').addClass('bs-popover-top');
206                        if (popover_right) {
207                            $popover.css('left', (offset.left + event_width - $popover.outerWidth()) + 'px');
208                            $arrow.css('right', '8px');
209                        } else {
210                            $popover.css('left', (offset.left + 2) + 'px');
211                            $arrow.css('left', '8px');
212                        }
213                    }
214                }
215            },
216            eventContent: function (arg) {
217                if (arg.event.display === 'background') {
218                    return '';
219                }
220                let event = arg.event;
221                let props = event.extendedProps;
222                let nodes = [];
223                let $time = $('<div class="ec-event-time"/>');
224                let $title = $('<div class="ec-event-title"/>');
225
226                $time.append(props.header_text || arg.timeText);
227                nodes.push($time.get(0));
228                if (arg.view.type === 'listWeek') {
229                    let dot = $('<div class="ec-event-dot"></div>').css('border-color', event.backgroundColor);
230                    nodes.push($('<div/>').append(dot).get(0));
231                }
232                $title.append(props.desc || '');
233                nodes.push($title.get(0));
234
235                switch (props.overall_status) {
236                    case 'pending':
237                        $time.addClass('text-muted');
238                        $title.addClass('text-muted');
239                        break;
240                    case 'rejected':
241                    case 'cancelled':
242                        $time.addClass('text-muted').wrapInner('<s>');
243                        $title.addClass('text-muted');
244                        break;
245                }
246
247                if (arg.view.type === 'listWeek' && allowEditAppointment) {
248                    $title.append(popoverButtons(arg));
249                }
250
251                return {domNodes: nodes};
252            },
253            eventClick: function (arg) {
254                if (arg.event.display === 'background') {
255                    return;
256                }
257                arg.jsEvent.stopPropagation();
258
259                if (allowEditAppointment) {
260                    if (arg.event.extendedProps.type === 'event') {
261                        BooklyAttendeesDialog.showDialog({id: arg.event.extendedProps.id}, function () { calendar.refetchEvents(); })
262                    } else {
263                        let visible_staff_id;
264                        if (arg.view.type === 'resourceTimeGridDay') {
265                            visible_staff_id = 0;
266                        } else {
267                            visible_staff_id = obj.options.getCurrentStaffId();
268                        }
269                        BooklyAppointmentDialog.showDialog(
270                            arg.event.id,
271                            null,
272                            null,
273                            function (event) {
274                                if (event == 'refresh') {
275                                    calendar.refetchEvents();
276                                } else {
277                                    if (event.start === null) {
278                                        // Task
279                                        calendar.removeEventById(event.id);
280                                    } else {
281                                        if (visible_staff_id == event.resourceId || visible_staff_id == 0) {
282                                            // Update event in calendar.
283                                            calendar.removeEventById(event.id);
284                                            calendar.addEvent(event);
285                                        } else {
286                                            // Switch to the event owner tab.
287                                            jQuery('li > a[data-staff_id=' + event.resourceId + ']').click();
288                                        }
289                                    }
290                                }
291
292                                if (locationChanged) {
293                                    calendar.refetchEvents();
294                                    locationChanged = false;
295                                }
296                            }
297                        );
298                    }
299                }
300            },
301            dateClick: function (arg) {
302                let staff_id, visible_staff_id;
303                if (['resourceTimeGridDay', 'resourceTimelineMonth', 'resourceTimelineWeek', 'resourceTimelineDay'].includes(arg.view.type)) {
304                    staff_id = arg.resource.id;
305                    visible_staff_id = 0;
306                } else {
307                    staff_id = visible_staff_id = obj.options.getCurrentStaffId();
308                }
309                addAppointmentDialog(arg.date, staff_id, visible_staff_id);
310            },
311            noEventsClick: function (arg) {
312                let staffId = obj.options.getCurrentStaffId();
313                addAppointmentDialog(arg.view.activeStart, staffId, staffId);
314            },
315            loading: function (isLoading) {
316                if (!calendar) {
317                    return;
318                }
319                if (isLoading) {
320                    if (allowEditAppointment) {
321                        BooklyL10nAppDialog.refreshed = true;
322                    }
323                    if (dateSetFromDatePicker) {
324                        dateSetFromDatePicker = false;
325                    } else {
326                        calendar.setOption('highlightedDates', []);
327                    }
328                    $('.bookly-ec-loading').show();
329                } else {
330                    let allDay = false;
331                    if (calendar.getEvents().length) {
332                        calendar.getEvents().forEach(function (event) {
333                            if (event.allDay) {
334                                allDay = true;
335                            }
336                        })
337                    }
338                    calendar.setOption('allDaySlot', allDay);
339                    $('.bookly-ec-loading').hide();
340                    obj.options.refresh();
341                }
342            },
343            viewDidMount: function (view) {
344                calendar.setOption('highlightedDates', []);
345                obj.options.viewChanged(view);
346            },
347            theme: function (theme) {
348                theme.button = 'btn btn-default';
349                theme.buttonGroup = 'btn-group';
350                theme.active = 'active';
351                return theme;
352            }
353        };
354
355        function popoverButtons(arg) {
356            const $buttons = arg.view.type === 'listWeek' ? $('<div class="mt-2 d-flex"></div>') : $('<div class="mt-2 d-flex justify-content-end border-top pt-2"></div>');
357            let props = arg.event.extendedProps;
358            if (props.hasOwnProperty('type') && props.type === 'event') {
359                $buttons.append(
360                    $('<a class="btn btn-success btn-sm mr-1">').append('<i class="fas fa-fw fa-users">')
361                        .attr('title', obj.options.l10n.events.attendees)
362                        .on('click', function (e) {
363                            e.stopPropagation();
364                            BooklyAttendeesDialog.showDialog({id: arg.event.extendedProps.id}, function () { calendar.refetchEvents(); })
365                        })
366                );
367            } else {
368                $buttons.append($('<button class="btn btn-success btn-sm mr-1">').append('<i class="far fa-fw fa-edit">'));
369                if (obj.options.l10n.recurring_appointments.active == '1' && props.series_id) {
370                    $buttons.append(
371                        $('<a class="btn btn-default btn-sm mr-1">').append('<i class="fas fa-fw fa-link">')
372                            .attr('title', obj.options.l10n.recurring_appointments.title)
373                            .on('click', function (e) {
374                                e.stopPropagation();
375                                BooklySeriesDialog.showDialog({
376                                    series_id: props.series_id,
377                                    done: function () {
378                                        calendar.refetchEvents();
379                                    }
380                                });
381                            })
382                    );
383                }
384                if (obj.options.l10n.waiting_list.active == '1' && props.waitlisted > 0) {
385                    $buttons.append(
386                        $('<a class="btn btn-default btn-sm mr-1">').append('<i class="far fa-fw fa-list-alt">')
387                            .attr('title', obj.options.l10n.waiting_list.title)
388                    );
389                }
390                if (obj.options.l10n.packages.active == '1' && props.package_id > 0) {
391                    $buttons.append(
392                        $('<a class="btn btn-default btn-sm mr-1">').append('<i class="far fa-fw fa-calendar-alt">')
393                            .attr('title', obj.options.l10n.packages.title)
394                            .on('click', function (e) {
395                                e.stopPropagation();
396                                if (obj.options.l10n.packages.active == '1' && props.package_id) {
397                                    $(document.body).trigger('bookly_packages.schedule_dialog', [props.package_id, function () {
398                                        calendar.refetchEvents();
399                                    }]);
400                                }
401                            })
402                    );
403                }
404                $buttons.append(
405                    $('<a class="btn btn-danger btn-sm text-white">
405').append('<i class="far fa-fw fa-trash-alt">')
406                        .attr('title', obj.options.l10n.delete)
407                        .on('click', function (e) {
408                            e.stopPropagation();
409                            // Localize contains only string values
410                            if (obj.options.l10n.recurring_appointments.active == '1' && props.series_id) {
411                                $(document.body).trigger('recurring_appointments.delete_dialog', [calendar, arg.event]);
412                            } else {
413                                new BooklyConfirmDeletingAppointment({
414                                        action: 'bookly_delete_appointment',
415                                        appointment_id: arg.event.id,
416                                        csrf_token: BooklyL10nGlobal.csrf_token
417                                    },
418                                    function (response) {
419                                        calendar.removeEventById(arg.event.id);
420                                    }
421                                );
422                            }
423                        })
424                );
425            }
426
427            return $buttons;
428        }
429
430        function addAppointmentDialog(date, staffId, visibleStaffId) {
431            if (allowEditAppointment) {
432                BooklyAppointmentDialog.showDialog(
433                    null,
434                    parseInt(staffId),
435                    moment(date),
436                    function (event) {
437                        if (event == 'refresh') {
438                            calendar.refetchEvents();
439                        } else {
440                            if (visibleStaffId == event.resourceId || visibleStaffId == 0) {
441                                if (event.start !== null) {
442                                    if (event.id) {
443                                        // Create event in calendar.
444                                        calendar.addEvent(event);
445                                    } else {
446                                        calendar.refetchEvents();
447                                    }
448                                }
449                            } else {
450                                // Switch to the event owner tab.
451                                jQuery('li[data-staff_id=' + event.resourceId + ']').click();
452                            }
453                        }
454
455                        if (locationChanged) {
456                            calendar.refetchEvents();
457                            locationChanged = false;
458                        }
459                    }
460                );
461            }
462        }
463
464
465        /**
466         * Set cookie.
467         *
468         * @param key
469         * @param value
470         */
471        function setCookie(key, value) {
472            var expires = new Date();
473            expires.setFullYear(expires.getFullYear() + 3);
474            document.cookie = key + '=' + value + ';expires=' + expires.toUTCString();
475        }
476
477        let dateSetFromDatePicker = false;
478
479        calendar = EventCalendar.create($container.get(0), $.extend(true, {}, settings, obj.options.calendar));
480
481        $('.ec-toolbar .ec-title', $container).on('click', function () {
482            let picker = $(this).data('daterangepicker');
483            picker.setStartDate(calendar.getOption('date'));
484            picker.setEndDate(calendar.getOption('date'));
485        });
486        // Init date picker for fast navigation in Event Calendar.
487        $('.ec-toolbar .ec-title', $container).daterangepicker({
488            parentEl: '.bookly-js-calendar',
489            singleDatePicker: true,
490            showDropdowns: true,
491            autoUpdateInput: false,
492            locale: obj.options.l10n.datePicker
493        }, function (start) {
494            dateSetFromDatePicker = true;
495            calendar.setOption('highlightedDates', [start.toDate()]);
496            calendar.setOption('date', start.toDate());
497        });
498
499        // Export calendar
500        this.ec = calendar;
501    };
502
503    var locationChanged = false;
504    $('body').on('change', '#bookly-appointment-location', function () {
505        locationChanged = true;
506    });
507
508    Calendar.prototype.options = {
509        calendar: {},
510        getCurrentStaffId: function () { return -1; },
511        getStaffMemberIds: function () { return [this.getCurrentStaffId()]; },
512        getServiceIds: function () { return ['all']; },
513        getLocationIds: function () { return ['all']; },
514        refresh: function () {},
515        viewChanged: function () {},
516        l10n: {}
517    };
518
519    window.BooklyCalendar = Calendar;
520})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.