PageSourceSearch

https://kenyaembassydc.org/wp-content/plugins/bookly-responsive-ap…/resources/js/calendar-common.js?ver=28.0

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