PageSourceSearch

https://www.diocesisgetafe.es/wp-content/themes/hello-elementor-ch…/assets/js/eventos-calendario.js?ver=1.0.1

js diocesisgetafe.es collected 2026-10-02 07:19:59 UTC 21,878 bytes, 679 lines download raw bytes

1/**
2 * Calendario de eventos.
3 *
4 * Sustituye el datepicker nativo del filtro "Date Period" de JetSmartFilters por
5 * un panel propio que permite: filtrar por día, filtrar por mes completo, ver
6 * destacados los días que tienen eventos y limpiar la selección. Todo sigue
7 * pasando por JetSmartFilters, así que el AJAX y la URL no cambian.
8 */
9(function () {
10    'use strict';
11
12    var CFG = window.diocesisEventosCal || {};
13    var DAYS = CFG.dates || {};
14    var TXT = CFG.l10n || {};
15    var WEEK_START = typeof CFG.weekStart === 'number' ? CFG.weekStart : 1;
16
17    var MONTH_NAMES = TXT.months || ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'];
18    var MONTH_SHORT = TXT.monthsShort || MONTH_NAMES;
19    var DOW_NAMES = TXT.daysMin || ['D', 'L', 'M', 'X', 'J', 'V', 'S'];
20
21    var MONTHS = {};
22    var SORTED_DAYS = Object.keys(DAYS).sort();
23
24    SORTED_DAYS.forEach(function (day) {
25        var month = day.slice(0, 7);
26        MONTHS[month] = (MONTHS[month] || 0) + DAYS[day];
27    });
28
29    // ------------------------------------------------------------- utilidades
30
31    function pad(value) {
32        return value < 10 ? '0' + value : '' + value;
33    }
34
35    function dayKey(year, month, day) {
36        return year + '-' + pad(month + 1) + '-' + pad(day);
37    }
38
39    function monthKey(year, month) {
40        return year + '-' + pad(month + 1);
41    }
42
43    function serial(date) {
44        return date.y * 10000 + date.m * 100 + date.d;
45    }
46
47    function daysInMonth(year, month) {
48        return new Date(year, month + 1, 0).getDate();
49    }
50
51    /** Fecha en el formato que espera JetSmartFilters: "2026.9.23". */
52    function toJsf(date) {
53        return date.y + '.' + (date.m + 1) + '.' + date.d;
54    }
55
56    function fromJsf(value) {
57        var parts = String(value).split('.');
58
59        if (parts.length !== 3) {
60            return null;
61        }
62
63        var date = { y: parseInt(parts[0], 10), m: parseInt(parts[1], 10) - 1, d: parseInt(parts[2], 10) };
64
65        return isNaN(date.y) || isNaN(date.m) || isNaN(date.d) ? null : date;
66    }
67
68    function isFullMonth(range) {
69        return range.start.d === 1 &&
70            range.start.y === range.end.y &&
71            range.start.m === range.end.m &&
72            range.end.d === daysInMonth(range.end.y, range.end.m);
73    }
74
75    function isSameDay(range) {
76        return serial(range.start) === serial(range.end);
77    }
78
79    function label(range) {
80        if (isSameDay(range)) {
81            return range.start.d + ' ' + MONTH_SHORT[range.start.m] + ' ' + range.start.y;
82        }
83
84        if (isFullMonth(range)) {
85            return MONTH_NAMES[range.start.m] + ' ' + range.start.y;
86        }
87
88        return range.start.d + ' ' + MONTH_SHORT[range.start.m] + ' – ' +
89            range.end.d + ' ' + MONTH_SHORT[range.end.m] + ' ' + range.end.y;
90    }
91
92    function el(tag, className, text) {
93        var node = document.createElement(tag);
94
95        if (className) {
96            node.className = className;
97        }
98
99        if (text != null) {
100            node.textContent = text;
101        }
102
103        if (tag === 'button') {
104            node.type = 'button';
105        }
106
107        return node;
108    }
109
110    // ------------------------------------------------- puente a JetSmartFilters
111
112    function filterGroup(provider, queryId) {
113        var groups = window.JetSmartFilters && window.JetSmartFilters.filterGroups;
114
115        return (groups && groups[provider + '/' + queryId]) || null;
116    }
117
118    function groupFilter(group, method) {
119        var filters = (group && group.filters) || [];
120
121        for (var i = 0; i < filters.length; i++) {
122            if (filters[i] && typeof filters[i][method] === 'function') {
123                return filters[i];
124            }
125        }
126
127        return null;
128    }
129
130    function hasActiveFilters(group) {
131        return ((group && group.filters) || []).some(function (filter) {
132            return !!filter.data;
133        });
134    }
135
136    function triggerChange(input) {
137        if (window.jQuery) {
138            window.jQuery(input).trigger('change');
139        } else {
140            input.dispatchEvent(new Event('change', { bubbles: true }));
141        }
142    }
143
144    // ------------------------------------------------------------- calendario
145
146    function Calendar(root) {
147        this.root = root;
148        this.provider = root.getAttribute('data-content-provider') || 'jet-engine';
149        this.queryId = root.getAttribute('data-query-id') || 'default';
150        this.input = root.querySelector('.jet-date-period__datepicker-input');
151        this.button = root.querySelector('.jet-date-period__datepicker-button');
152
153        if (!this.input || !this.button) {
154            return;
155        }
156
157        this.view = 'days';
158        this.cursor = { y: 0, m: 0 };
159
160        this.disableNativeDatepicker();
161        this.build();
162        this.bind();
163        this.sync();
164
165        root.classList.add('dc-cal-ready');
166    }
167
168    Calendar.prototype.disableNativeDatepicker = function () {
169        var instance = window.jQuery && window.jQuery(this.input).data('datepicker');
170
171        if (instance) {
172            instance.show = function () {};
173            instance.hide();
174        }
175
176        if (window.jQuery) {
177            // JetSmartFilters abre su propio datepicker desde estos tres botones.
178            window.jQuery(this.button).off('click');
179            window.jQuery(this.root.querySelectorAll('.jet-date-period__prev, .jet-date-period__next')).off('click');
180        }
181    };
182
183    Calendar.prototype.build = function () {
184        var panel = el('div', 'dc-cal');
185        panel.hidden = true;
186
187        var head = el('div', 'dc-cal__head');
188        this.prevBtn = el('button', 'dc-cal__nav', '‹');
189        this.prevBtn.setAttribute('aria-label', TXT.prev || 'Anterior');
190        this.titleBtn = el('button', 'dc-cal__title');
191        this.titleBtn.setAttribute('aria-label', TXT.pickMonth || 'Elegir mes');
192        this.nextBtn = el('button', 'dc-cal__nav', '›');
193        this.nextBtn.setAttribute('aria-label', TXT.next || 'Siguiente');
194        head.appendChild(this.prevBtn);
195        head.appendChild(this.titleBtn);
196        head.appendChild(this.nextBtn);
197
198        this.dows = el('div', 'dc-cal__dows');
199
200        for (var i = 0; i < 7; i++) {
201            this.dows.appendChild(el('span', null, DOW_NAMES[(WEEK_START + i) % 7]));
202        }
203
204        this.grid = el('div', 'dc-cal__grid');
205        this.months = el('div', 'dc-cal__months');
206        this.months.hidden = true;
207        this.hint = el('div', 'dc-cal__hint');
208
209        var foot = el('div', 'dc-cal__foot');
210        this.monthBtn = el('button', 'dc-cal__action', TXT.wholeMonth || 'Ver todo el mes');
211        this.clearBtn = el('button', 'dc-cal__action dc-cal__action--ghost', TXT.clearDate || 'Quitar fecha');
212        foot.appendChild(this.monthBtn);
213        foot.appendChild(this.clearBtn);
214
215        panel.appendChild(head);
216        panel.appendChild(this.dows);
217        panel.appendChild(this.grid);
218        panel.appendChild(this.months);
219        panel.appendChild(this.hint);
220        panel.appendChild(foot);
221
222        this.panel = panel;
223        this.root.appendChild(panel);
224    };
225
226    Calendar.prototype.bind = function () {
227        var self = this;
228
229        this.button.addEventListener('click', function (event) {
230            if (event.target.closest('.dc-cal-btn-clear')) {
231                return;
232            }
233
234            event.preventDefault();
235            self.toggle();
236        });
237
238        this.prevBtn.addEventListener('click', function () {
239            self.step(-1);
240        });
241
242        this.nextBtn.addEventListener('click', function () {
243            self.step(1);
244        });
245
246        this.titleBtn.addEventListener('click', function () {
247            self.view = self.view === 'days' ? 'months' : 'days';
248            self.render();
249        });
250
251        this.monthBtn.addEventListener('click', function () {
252            self.selectMonth(self.cursor.y, self.cursor.m);
253        });
254
255        this.clearBtn.addEventListener('click', function () {
256            self.clear();
257        });
258
259        this.root.querySelectorAll('.jet-date-period__prev, .jet-date-period__next').forEach(function (node) {
260            node.addEventListener('click', function (event) {
261                event.preventDefault();
262                event.stopPropagation();
263                self.shiftSelection(node.classList.contains('jet-date-period__next') ? 1 : -1);
264            });
265        });
266
267        document.addEventListener('click', function (event) {
268            if (!self.panel.hidden && !self.root.contains(event.target)) {
269                self.close();
270            }
271        });
272
273        document.addEventListener('keydown', function (event) {
274            if (event.key === 'Escape') {
275                self.close();
276            }
277        });
278
279        // Cuando JetSmartFilters repinta (AJAX, back del navegador, "quitar filtros")
280        // el valor del input puede haber cambiado por debajo.
281        if (window.jQuery) {
282            window.jQuery(document).on('jet-filter-content-rendered', function () {
283                self.sync();
284            });
285        }
286    };
287
288    // ------------------------------------------------------------- selección
289
290    Calendar.prototype.selection = function () {
291        var raw = (this.input.value || '').trim();
292
293        if (!raw) {
294            return null;
295        }
296
297        var parts = raw.split('-');
298        var start = fromJsf(parts[0]);
299        var end = parts.length > 1 ? fromJsf(parts[1]) : start;
300
301        return start && end ? { start: start, end: end } : null;
302    };
303
304    Calendar.prototype.setRange = function (start, end) {
305        this.input.value = toJsf(start) + '-' + toJsf(end);
306        triggerChange(this.input);
307        this.applyFilters();
308        this.close();
309        this.sync();
310    };
311
312    Calendar.prototype.selectDay = function (year, month, day) {
313        var date = { y: year, m: month, d: day };
314        this.setRange(date, date);
315    };
316
317    Calendar.prototype.selectMonth = function (year, month) {
318        this.setRange({ y: year, m: month, d: 1 }, { y: year, m: month, d: daysInMonth(year, month) });
319    };
320
321    Calendar.prototype.clear = function () {
322        this.input.value = '';
323        triggerChange(this.input);
324        this.applyFilters();
325        this.close();
326        this.sync();
327    };
328
329    /** Salta al día (o mes) con eventos anterior o siguiente. */
330    Calendar.prototype.shiftSelection = function (direction) {
331        var current = this.selection();
332
333        if (!current) {
334            this.open();
335            return;
336        }
337
338        if (isFullMonth(current)) {
339            var months = Object.keys(MONTHS).sort();
340            var currentMonth = monthKey(current.start.y, current.start.m);
341            var monthIndex = months.indexOf(currentMonth);
342            var nextMonth = months[monthIndex + direction];
343
344            if (monthIndex === -1 || !nextMonth) {
345                return;
346            }
347
348            this.selectMonth(parseInt(nextMonth.slice(0, 4), 10), parseInt(nextMonth.slice(5, 7), 10) - 1);
349            return;
350        }
351
352        var reference = direction > 0 ? current.end : current.start;
353        var referenceKey = dayKey(reference.y, reference.m, reference.d);
354        var target = null;
355
356        for (var i = 0; i < SORTED_DAYS.length; i++) {
357            var candidate = SORTED_DAYS[direction > 0 ? i : SORTED_DAYS.length - 1 - i];
358
359            if (direction > 0 ? candidate > referenceKey : candidate < referenceKey) {
360                target = candidate;
361                break;
362            }
363        }
364
365        if (!target) {
366            return;
367        }
368
369        this.selectDay(parseInt(target.slice(0, 4), 10), parseInt(target.slice(5, 7), 10) - 1, parseInt(target.slice(8, 10), 10));
370    };
371
372    // --------------------------------------------------------------- pintado
373
374    Calendar.prototype.sync = function () {
375        var current = this.selection();
376
377        if (current) {
378            this.cursor = { y: current.start.y, m: current.start.m };
379        } else if (!this.cursor.y || this.panel.hidden) {
380            // Sin selección y con el panel cerrado, volvemos al mes con eventos
381            // más próximo en lugar de dejar el último mes visitado.
382            this.cursor = this.defaultCursor();
383        }
384
385        this.renderButton(current);
386        this.render();
387    };
388
389    Calendar.prototype.defaultCursor = function () {
390        var today = new Date();
391        var todayKey = dayKey(today.getFullYear(), today.getMonth(), today.getDate());
392
393        if (MONTHS[monthKey(today.getFullYear(), today.getMonth())]) {
394            return { y: today.getFullYear(), m: today.getMonth() };
395        }
396
397        var upcoming = SORTED_DAYS.find(function (day) {
398            return day >= todayKey;
399        }) || SORTED_DAYS[SORTED_DAYS.length - 1];
400
401        if (!upcoming) {
402            return { y: today.getFullYear(), m: today.getMonth() };
403        }
404
405        return { y: parseInt(upcoming.slice(0, 4), 10), m: parseInt(upcoming.slice(5, 7), 10) - 1 };
406    };
407
408    Calendar.prototype.renderButton = function (current) {
409        if (!current) {
410            return;
411        }
412
413        var self = this;
414        var wrap = el('span', 'dc-cal-btn-label');
415        wrap.appendChild(el('span', null, label(current)));
416
417        var clear = el('button', 'dc-cal-btn-clear', '✕');
418        clear.setAttribute('aria-label', TXT.clearDate || 'Quitar fecha');
419        clear.addEventListener('click', function (event) {
420            event.preventDefault();
421            event.stopPropagation();
422            self.clear();
423        });
424
425        wrap.appendChild(clear);
426        this.button.textContent = '';
427        this.button.appendChild(wrap);
428    };
429
430    Calendar.prototype.render = function () {
431        var current = this.selection();
432
433        this.titleBtn.textContent = this.view === 'days'
434            ? MONTH_NAMES[this.cursor.m] + ' ' + this.cursor.y
435            : this.cursor.y;
436
437        this.dows.hidden = this.view !== 'days';
438        this.grid.hidden = this.view !== 'days';
439        this.months.hidden = this.view !== 'months';
440        this.monthBtn.disabled = !MONTHS[monthKey(this.cursor.y, this.cursor.m)];
441        this.clearBtn.disabled = !current;
442
443        if (this.view === 'days') {
444            this.renderDays(current);
445        } else {
446            this.renderMonths(current);
447        }
448    };
449
450    Calendar.prototype.renderDays = function (current) {
451        var self = this;
452        var year = this.cursor.y;
453        var month = this.cursor.m;
454        var total = daysInMonth(year, month);
455        var offset = (new Date(year, month, 1).getDay() - WEEK_START + 7) % 7;
456        var today = new Date();
457        var todayKey = dayKey(today.getFullYear(), today.getMonth(), today.getDate());
458        var count = MONTHS[monthKey(year, month)] || 0;
459
460        this.grid.textContent = '';
461
462        for (var blank = 0; blank < offset; blank++) {
463            this.grid.appendChild(el('span', 'dc-cal__day dc-cal__day--empty'));
464        }
465
466        for (var day = 1; day <= total; day++) {
467            var key = dayKey(year, month, day);
468            var events = DAYS[key] || 0;
469            var classes = ['dc-cal__day'];
470
471            if (events) {
472                classes.push('dc-cal__day--has');
473            }
474
475            if (key === todayKey) {
476                classes.push('dc-cal__day--today');
477            }
478
479            if (current) {
480                var value = serial({ y: year, m: month, d: day });
481
482                if (value >= serial(current.start) && value <= serial(current.end)) {
483                    classes.push('dc-cal__day--selected');
484                }
485            }
486
487            var cell = el(events ? 'button' : 'span', classes.join(' '), String(day));
488
489            if (events) {
490                cell.title = events + ' ' + (events === 1 ? (TXT.event || 'evento') : (TXT.events || 'eventos'));
491                cell.dataset.day = String(day);
492                cell.addEventListener('click', function () {
493                    self.selectDay(year, month, parseInt(this.dataset.day, 10));
494                });
495            }
496
497            this.grid.appendChild(cell);
498        }
499
500        this.hint.textContent = count
501            ? count + ' ' + (count === 1 ? (TXT.event || 'evento') : (TXT.events || 'eventos'))
502            : (TXT.noEventsMonth || 'No hay eventos este mes');
503    };
504
505    Calendar.prototype.renderMonths = function (current) {
506        var self = this;
507        var year = this.cursor.y;
508
509        this.months.textContent = '';
510        this.hint.textContent = TXT.pickMonthHint || 'Elige un mes para filtrar';
511
512        for (var month = 0; month < 12; month++) {
513            var classes = ['dc-cal__month'];
514
515            if (MONTHS[monthKey(year, month)]) {
516                classes.push('dc-cal__month--has');
517            }
518
519            if (current && isFullMonth(current) && current.start.y === year && current.start.m === month) {
520                classes.push('dc-cal__month--selected');
521            }
522
523            var cell = el('button', classes.join(' '), MONTH_SHORT[month]);
524            cell.dataset.month = String(month);
525            cell.addEventListener('click', function () {
526                self.selectMonth(year, parseInt(this.dataset.month, 10));
527            });
528
529            this.months.appendChild(cell);
530        }
531    };
532
533    // ------------------------------------------------------------ navegación
534
535    Calendar.prototype.step = function (direction) {
536        if (this.view === 'months') {
537            this.cursor = { y: this.cursor.y + direction, m: this.cursor.m };
538        } else {
539            var next = new Date(this.cursor.y, this.cursor.m + direction, 1);
540            this.cursor = { y: next.getFullYear(), m: next.getMonth() };
541        }
542
543        this.render();
544    };
545
546    Calendar.prototype.open = function () {
547        this.panel.hidden = false;
548        this.render();
549    };
550
551    Calendar.prototype.close = function () {
552        this.panel.hidden = true;
553        this.view = 'days';
554    };
555
556    Calendar.prototype.toggle = function () {
557        if (this.panel.hidden) {
558            this.open();
559        } else {
560            this.close();
561        }
562    };
563
564    Calendar.prototype.applyFilters = function () {
565        var group = filterGroup(this.provider, this.queryId);
566        var filter = groupFilter(group, 'emitFitersApply');
567
568        if (filter) {
569            filter.emitFitersApply();
570            return;
571        }
572
573        var button = document.querySelector('.apply-filters[data-query-id="' + this.queryId + '"] .apply-filters__button');
574
575        if (button) {
576            button.click();
577        }
578    };
579
580    // ---------------------------------------------------- estado "sin eventos"
581
582    function clearAllFilters(provider, queryId) {
583        var group = filterGroup(provider, queryId);
584        var filter = groupFilter(group, 'emitFitersRemove');
585
586        if (filter) {
587            filter.emitFitersRemove();
588            return;
589        }
590
591        window.location.href = window.location.pathname;
592    }
593
594    function renderEmptyState() {
595        document.querySelectorAll('.jet-listing-not-found').forEach(function (node) {
596            if (node.parentNode.querySelector('.dc-empty')) {
597                return;
598            }
599
600            var period = document.querySelector('.jet-date-period');
601            var provider = (period && period.getAttribute('data-content-provider')) || 'jet-engine';
602            var queryId = (period && period.getAttribute('data-query-id')) ||
603                node.getAttribute('data-query-id') || 'default';
604
605            if (!hasActiveFilters(filterGroup(provider, queryId))) {
606                return;
607            }
608
609            var box = el('div', 'dc-empty');
610            box.appendChild(el('p', 'dc-empty__text', TXT.emptyText || 'Prueba con otra fecha o quita los filtros.'));
611
612            var button = el('button', 'dc-empty__button', TXT.emptyButton || 'Volver a ver todos los eventos');
613            button.addEventListener('click', function () {
614                clearAllFilters(provider, queryId);
615            });
616
617            box.appendChild(button);
618            node.parentNode.insertBefore(box, node.nextSibling);
619        });
620    }
621
622    // ------------------------------------------------------------------ init
623
624    function init() {
625        document.querySelectorAll('.jet-date-period:not(.dc-cal-ready)').forEach(function (root) {
626            new Calendar(root);
627        });
628
629        renderEmptyState();
630    }
631
632    /** JetSmartFilters debe haber montado su datepicker antes de reemplazarlo. */
633    function ready() {
634        if (!window.JetSmartFilters || !window.jQuery) {
635            return false;
636        }
637
638        var pending = document.querySelectorAll('.jet-date-period:not(.dc-cal-ready)');
639
640        for (var i = 0; i < pending.length; i++) {
641            var input = pending[i].querySelector('.jet-date-period__datepicker-input');
642
643            if (input && !window.jQuery(input).data('datepicker')) {
644                return false;
645            }
646        }
647
648        return true;
649    }
650
651    function waitAndInit() {
652        var attempts = 0;
653        var timer = setInterval(function () {
654            attempts++;
655
656            if (ready() || attempts > 40) {
657                clearInterval(timer);
658                init();
659            }
660        }, 150);
661
662        if (ready()) {
663            clearInterval(timer);
664            init();
665        }
666    }
667
668    document.addEventListener('jet-smart-filters/inited', init);
669
670    if (window.jQuery) {
671        window.jQuery(document).on('jet-filter-content-rendered', renderEmptyState);
672    }
673
674    if (document.readyState === 'loading') {
675        document.addEventListener('DOMContentLoaded', waitAndInit);
676    } else {
677        waitAndInit();
678    }
679})();

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.