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.