1jQuery(function ($) { 2 class DatePickerDialog { 3 constructor(cdp) { 4 this.buttonLabelChoose = "Choose Date"; 5 this.buttonLabelChange = "Change Date"; 6 this.dayLabels = [ 7 "Sunday", 8 "Monday", 9 "Tuesday", 10 "Wednesday", 11 "Thursday", 12 "Friday", 13 "Saturday", 14 ]; 15 this.monthLabels = [ 16 "January", 17 "February", 18 "March", 19 "April", 20 "May", 21 "June", 22 "July", 23 "August", 24 "September", 25 "October", 26 "November", 27 "December", 28 ]; 29 30 this.messageCursorKeys = "Cursor keys can navigate dates"; 31 this.lastMessage = ""; 32 33 // this.textboxNode = cdp.querySelector('input[type="text"'); 34 // this.buttonNode = cdp.querySelector(".group button"); 35 this.dialogNode = cdp.querySelector('.datepicker-dialog'); 36 // this.messageNode = this.dialogNode.querySelector(".dialog-message"); 37 38 this.monthYearNode = this.dialogNode.querySelector(".month-year"); 39 40 this.prevYearNode = this.dialogNode.querySelector(".prev-year"); 41 this.prevMonthNode = this.dialogNode.querySelector(".prev-month"); 42 this.nextMonthNode = this.dialogNode.querySelector(".next-month"); 43 this.nextYearNode = this.dialogNode.querySelector(".next-year"); 44 45 // this.okButtonNode = this.dialogNode.querySelector('button[value="ok"]'); 46 // this.cancelButtonNode = this.dialogNode.querySelector('button[value="cancel"]'); 47 48 this.tbodyNode = this.dialogNode.querySelector("table.dates tbody"); 49 50 this.lastRowNode = null; 51 52 this.days = []; 53 this.dayButtons = []; // Store references to the button elements 54 55 this.focusDay = new Date(); 56 this.selectedDay = new Date(0, 0, 1); 57 58 this.lastDate = -1; 59 60 this.isMouseDownOnBackground = false; 61 62 // this.textboxNode.addEventListener("blur", this.setDateForButtonLabel.bind(this)); 63 64 // this.buttonNode.addEventListener("keydown", this.handleButtonKeydown.bind(this)); 65 // this.buttonNode.addEventListener("click", this.handleButtonClick.bind(this)); 66 67 // this.okButtonNode.addEventListener("click", this.handleOkButton.bind(this)); 68 // this.okButtonNode.addEventListener("keydown", this.handleOkButton.bind(this)); 69 70 // this.cancelButtonNode.addEventListener("click", this.handleCancelButton.bind(this)); 71 // this.cancelButtonNode.addEventListener("keydown", this.handleCancelButton.bind(this)); 72 73 this.prevMonthNode.addEventListener("click", this.handlePreviousMonthButton.bind(this)); 74 this.nextMonthNode.addEventListener("click", this.handleNextMonthButton.bind(this)); 75 this.prevYearNode.addEventListener("click", this.handlePreviousYearButton.bind(this)); 76 this.nextYearNode.addEventListener("click", this.handleNextYearButton.bind(this)); 77 78 this.prevMonthNode.addEventListener("keydown", this.handlePreviousMonthButton.bind(this)); 79 this.nextMonthNode.addEventListener("keydown", this.handleNextMonthButton.bind(this)); 80 this.prevYearNode.addEventListener("keydown", this.handlePreviousYearButton.bind(this)); 81 this.nextYearNode.addEventListener("keydown", this.handleNextYearButton.bind(this)); 82 83 // document.body.addEventListener("pointerup", this.handleBackgroundMouseUp.bind(this), true); 84 85 // Create Grid of Dates 86 87 this.tbodyNode.innerHTML = ""; 88 for (let i = 0; i < 6; i++) { 89 const row = this.tbodyNode.insertRow(i); 90 this.lastRowNode = row; 91 for (let j = 0; j < 7; j++) { 92 const cell = document.createElement("td"); 93 const button = document.createElement("button"); 94 95 // Set button properties instead of cell properties 96 button.tabIndex = -1; 97 button.addEventListener("click", this.handleDayClick.bind(this)); 98 button.addEventListener("keydown", this.handleDayKeyDown.bind(this)); 99 button.addEventListener("focus", this.handleDayFocus.bind(this)); 100 101 button.textContent = ""; 102 button.type = "button"; // Ensure it's a button type 103 button.className = "calendar-day-button disabled"; // Add a class for styling 104 button.setAttribute("aria-hidden", "true"); 105 106 // Add button to cell 107 cell.appendChild(button); 108 row.appendChild(cell); 109 110 this.days.push(cell); 111 this.dayButtons.push(button); // Store button reference 112 } 113 } 114 115 this.updateGrid(); 116 // this.setDateForButtonLabel(); 117 } 118 119 isSameDay(day1, day2) { 120 return ( 121 day1.getFullYear() == day2.getFullYear() && 122 day1.getMonth() == day2.getMonth() && 123 day1.getDate() == day2.getDate() 124 ); 125 } 126 127 isNotSameMonth(day1, day2) { 128 return day1.getFullYear() != day2.getFullYear() || day1.getMonth() != day2.getMonth(); 129 } 130 131 updateGrid() { 132 const fd = this.focusDay; 133 134 this.monthYearNode.textContent = this.monthLabels[fd.getMonth()] + " " + fd.getFullYear(); 135 136 let firstDayOfMonth = new Date(fd.getFullYear(), fd.getMonth(), 1); 137 let dayOfWeek = firstDayOfMonth.getDay(); 138 139 firstDayOfMonth.setDate(firstDayOfMonth.getDate() - dayOfWeek); 140 141 const d = new Date(firstDayOfMonth); 142 143 for (let i = 0; i < this.days.length; i++) { 144 const flag = d.getMonth() != fd.getMonth(); 145 this.updateDate(this.days[i], flag, d, this.isSameDay(d, this.selectedDay)); 146 d.setDate(d.getDate() + 1); 147 148 // Hide last row if all dates are disabled (e.g. in next month) 149 if (i === 35) { 150 if (flag) { 151 this.lastRowNode.style.visibility = "hidden"; 152 } else { 153 this.lastRowNode.style.visibility = "visible"; 154 } 155 } 156 } 157 } 158 159 updateDate(domNode, disable, day, selected) { 160 let d = day.getDate().toString(); 161 if (day.getDate() <= 9) { 162 d = "0" + d; 163 } 164 165 let m = day.getMonth() + 1; 166 if (day.getMonth() < 9) { 167 m = "0" + m; 168 } 169 170 // Get the button inside the td 171 const button = domNode.querySelector("button"); 172 if (!button) return; 173 174 // Set properties on the button instead of the td 175 button.tabIndex = -1; 176 button.removeAttribute("aria-selected"); 177 button.setAttribute("data-date", day.getFullYear() + "-" + m + "-" + d); 178 179 if (disable) { 180 domNode.classList.add("disabled"); 181 domNode.setAttribute("aria-hidden", "true"); 182 button.classList.add("disabled"); 183 button.disabled = true; 184 button.tabIndex = -1; 185 button.textContent = ""; 186 button.removeAttribute("aria-label"); 187 button.setAttribute("aria-hidden", "true"); 188 } else { 189 domNode.classList.remove("disabled"); 190 domNode.removeAttribute("aria-hidden"); 191 button.classList.remove("disabled"); 192 button.disabled = false;
193 button.removeAttribute("aria-hidden"); 194 button.textContent = day.getDate(); 195 196 // Name starts with the visible day number (2.5.3) and keeps the full date 197 const dayName = this.dayLabels[day.getDay()]; 198 const monthName = this.monthLabels[day.getMonth()]; 199 button.setAttribute( 200 "aria-label", 201 `${day.getDate()}, ${dayName}, ${monthName} ${day.getDate()}, ${day.getFullYear()}` 202 ); 203 204 if (selected) { 205 button.setAttribute("aria-selected", "true"); 206 button.tabIndex = 0; 207 } else if (domNode.classList.contains("highlight")) { 208 // Keep tabindex="0" for highlighted dates (dates with events) 209 button.tabIndex = 0; 210 } 211 } 212 } 213 214 moveFocusToDay(day) { 215 const d = this.focusDay; 216 217 this.focusDay = day; 218 219 if ( 220 d.getMonth() != this.focusDay.getMonth() || 221 d.getFullYear() != this.focusDay.getFullYear() 222 ) { 223 this.updateGrid(); 224 } 225 this.setFocusDay(); 226 } 227 228 setFocusDay(flag) { 229 if (typeof flag !== "boolean") { 230 flag = true; 231 } 232 233 for (let i = 0; i < this.days.length; i++) { 234 const dayNode = this.days[i]; 235 const button = dayNode.querySelector("button"); 236 if (!button) continue; 237 238 const day = this.getDayFromDataDateAttribute(button); 239 240 button.tabIndex = -1; 241 if (this.isSameDay(day, this.focusDay)) { 242 button.tabIndex = 0; 243 if (flag) { 244 button.focus(); 245 } 246 } 247 } 248 } 249 250 open() { 251 this.dialogNode.style.display = "block"; 252 this.dialogNode.style.zIndex = 2; 253 254 // Initialize with current date if no date is set 255 if (!this.focusDay || this.focusDay.getTime() === new Date(0, 0, 1).getTime()) { 256 this.focusDay = new Date(); 257 } 258 this.updateGrid(); 259 this.lastDate = this.focusDay.getDate(); 260 } 261 262 close() { 263 if (this.dialogNode) { 264 // this.dialogNode.style.display = "none"; 265 } 266 } 267 268 // Implement the missing setTextboxDate method 269 setTextboxDate(buttonNode) { 270 let d = this.focusDay; 271 272 if (buttonNode) { 273 d = this.getDayFromDataDateAttribute(buttonNode); 274 // Update selected day 275 this.selectedDay = new Date(d); 276 277 // Remove selected class from all buttons and add to current selection 278 this.dayButtons.forEach((button) => { 279 const parentCell = button.parentNode; 280 if (parentCell) { 281 parentCell.classList.remove("selected"); 282 } 283 button.removeAttribute("aria-selected"); 284 285 if (button === buttonNode) { 286 button.setAttribute("aria-selected", "true"); 287 if (parentCell) { 288 parentCell.classList.add("selected"); 289 } 290 } 291 }); 292 } 293 294 // Trigger custom event for date selection 295 const event = new CustomEvent("dateSelected", { 296 detail: { date: d }, 297 }); 298 this.dialogNode.dispatchEvent(event); 299 } 300 301 // Implement the missing setMessage method 302 setMessage(str) {} 303 304 changeMonth(currentDate, numMonths) { 305 const getDays = (year, month) => new Date(year, month, 0).getDate(); 306 307 const isPrev = numMonths < 0; 308 const numYears = Math.trunc(Math.abs(numMonths) / 12); 309 numMonths = Math.abs(numMonths) % 12; 310 311 const newYear = isPrev 312 ? currentDate.getFullYear() - numYears 313 : currentDate.getFullYear() + numYears; 314 315 const newMonth = isPrev 316 ? currentDate.getMonth() - numMonths 317 : currentDate.getMonth() + numMonths; 318 319 const newDate = new Date(newYear, newMonth, 1); 320 321 const daysInMonth = getDays(newDate.getFullYear(), newDate.getMonth() + 1); 322 323 // If lastDat is not initialized set to current date 324 this.lastDate = this.lastDate ? this.lastDate : currentDate.getDate(); 325 326 if (this.lastDate > daysInMonth) { 327 newDate.setDate(daysInMonth); 328 } else { 329 newDate.setDate(this.lastDate); 330 } 331 332 return newDate; 333 } 334 335 moveToNextYear() { 336 this.focusDay = this.changeMonth(this.focusDay, 13); 337 this.updateGrid(); 338 } 339 340 moveToPreviousYear() { 341 this.focusDay = this.changeMonth(this.focusDay, -11); 342 this.updateGrid(); 343 } 344 345 moveToNextMonth() { 346 this.focusDay = this.changeMonth(this.focusDay, 2); 347 this.updateGrid(); 348 } 349 350 moveToPreviousMonth() { 351 this.focusDay = this.changeMonth(this.focusDay, -0); 352 this.updateGrid(); 353 } 354 355 moveFocusToNextDay() { 356 const d = new Date(this.focusDay); 357 d.setDate(d.getDate() + 1); 358 this.lastDate = d.getDate(); 359 this.moveFocusToDay(d); 360 } 361 362 moveFocusToNextWeek() { 363 const d = new Date(this.focusDay); 364 d.setDate(d.getDate() + 7); 365 this.lastDate = d.getDate(); 366 this.moveFocusToDay(d); 367 } 368 369 moveFocusToPreviousDay() { 370 const d = new Date(this.focusDay); 371 d.setDate(d.getDate() - 1); 372 this.lastDate = d.getDate(); 373 this.moveFocusToDay(d); 374 } 375 376 moveFocusToPreviousWeek() { 377 const d = new Date(this.focusDay); 378 d.setDate(d.getDate() - 7); 379 this.lastDate = d.getDate(); 380 this.moveFocusToDay(d); 381 } 382 383 moveFocusToFirstDayOfWeek() { 384 const d = new Date(this.focusDay); 385 d.setDate(d.getDate() - d.getDay()); 386 this.lastDate = d.getDate(); 387 this.moveFocusToDay(d); 388 } 389 390 moveFocusToLastDayOfWeek() { 391 const d = new Date(this.focusDay); 392 d.setDate(d.getDate() + (6 - d.getDay())); 393 this.lastDate = d.getDate(); 394 this.moveFocusToDay(d); 395 } 396 397 // Day methods 398 399 isDayDisabled(buttonNode) { 400 return buttonNode.disabled || buttonNode.parentNode.classList.contains("disabled"); 401 } 402 403 getDayFromDataDateAttribute(buttonNode) { 404 const parts = buttonNode.getAttribute("data-date").split("-"); 405 return new Date(parts[0], parseInt(parts[1]) - 1, parts[2]); 406 } 407 408 // Textbox methods 409 410 // setTextboxDate(domNode) { 411 // let d = this.focusDay; 412 413 // if (domNode) { 414 // d = this.getDayFromDataDateAttribute(domNode); 415 // // updated aria-selected 416 // this.days.forEach((day) => 417 // day === domNode 418 // ? day.setAttribute("aria-selected", "true") 419 // : day.removeAttribute("aria-selected") 420 // ); 421 // } 422 423 // this.textboxNode.value = d.getMonth() + 1 + "/" + d.getDate() + "/" + d.getFullYear(); 424 // this.setDateForButtonLabel(); 425 // } 426 427 // getDateFromTextbox() { 428 // const parts = this.textboxNode.value.split("/");
429 // const month = parseInt(parts[0]); 430 // const day = parseInt(parts[1]); 431 // let year = parseInt(parts[2]); 432 433 // if ( 434 // parts.length === 3 && 435 // Number.isInteger(month) && 436 // Number.isInteger(day) && 437 // Number.isInteger(year) 438 // ) { 439 // if (year < 100) { 440 // year = 2000 + year; 441 // } 442 // this.focusDay = new Date(year, month - 1, day); 443 // this.selectedDay = new Date(this.focusDay); 444 // } else { 445 // // If not a valid date (MM/DD/YY) initialize with todays date 446 // this.focusDay = new Date(); 447 // this.selectedDay = new Date(0, 0, 1); 448 // } 449 // } 450 451 // setDateForButtonLabel() { 452 // const parts = this.textboxNode.value.split("/"); 453 454 // if ( 455 // parts.length === 3 && 456 // Number.isInteger(parseInt(parts[0])) && 457 // Number.isInteger(parseInt(parts[1])) && 458 // Number.isInteger(parseInt(parts[2])) 459 // ) { 460 // const day = new Date(parseInt(parts[2]), parseInt(parts[0]) - 1, parseInt(parts[1])); 461 462 // let label = this.buttonLabelChange; 463 // label += ", " + this.dayLabels[day.getDay()]; 464 // label += " " + this.monthLabels[day.getMonth()]; 465 // label += " " + day.getDate(); 466 // label += ", " + day.getFullYear(); 467 // this.buttonNode.setAttribute("aria-label", label); 468 // } else { 469 // // If not a valid date, initialize with "Choose Date" 470 // this.buttonNode.setAttribute("aria-label", this.buttonLabelChoose); 471 // } 472 // } 473 474 // setMessage(str) { 475 // function setMessageDelayed() { 476 // this.messageNode.textContent = str; 477 // } 478 479 // if (str !== this.lastMessage) { 480 // setTimeout(setMessageDelayed.bind(this), 200); 481 // this.lastMessage = str; 482 // } 483 // } 484 485 // Event handlers 486 487 // handleOkButton(event) { 488 // let flag = false; 489 490 // switch (event.type) { 491 // case "keydown": 492 // switch (event.key) { 493 // case "Tab": 494 // if (!event.shiftKey) { 495 // this.prevYearNode.focus(); 496 // flag = true; 497 // } 498 // break; 499 500 // case "Esc": 501 // case "Escape": 502 // this.close(); 503 // flag = true; 504 // break; 505 506 // default: 507 // break; 508 // } 509 // break; 510 511 // case "click": 512 // this.setTextboxDate(); 513 // this.close(); 514 // flag = true; 515 // break; 516 517 // default: 518 // break; 519 // } 520 521 // if (flag) { 522 // event.stopPropagation(); 523 // event.preventDefault(); 524 // } 525 // } 526 527 // handleCancelButton(event) { 528 // let flag = false; 529 530 // switch (event.type) { 531 // case "keydown": 532 // switch (event.key) { 533 // case "Esc": 534 // case "Escape": 535 // this.close(); 536 // flag = true; 537 // break; 538 539 // default: 540 // break; 541 // } 542 // break; 543 544 // case "click": 545 // this.close(); 546 // flag = true; 547 // break; 548 549 // default: 550 // break; 551 // } 552 553 // if (flag) { 554 // event.stopPropagation(); 555 // event.preventDefault(); 556 // } 557 // } 558 559 handleNextYearButton(event) { 560 let flag = false; 561 562 switch (event.type) { 563 case "keydown": 564 switch (event.key) { 565 case "Esc": 566 case "Escape": 567 this.close(); 568 flag = true; 569 break; 570 571 case "Enter": 572 this.moveToNextYear(); 573 this.setFocusDay(false); 574 flag = true; 575 break; 576 } 577 578 break; 579 580 case "click": 581 this.moveToNextYear(); 582 this.setFocusDay(false); 583 break; 584 585 default: 586 break; 587 } 588 589 if (flag) { 590 event.stopPropagation(); 591 event.preventDefault(); 592 } 593 } 594 595 handlePreviousYearButton(event) { 596 let flag = false;
597 598 switch (event.type) { 599 case "keydown": 600 switch (event.key) { 601 case "Enter": 602 this.moveToPreviousYear(); 603 this.setFocusDay(false); 604 flag = true; 605 break; 606 607 case "Tab": 608 if (event.shiftKey) { 609 // this.okButtonNode.focus(); 610 flag = true; 611 } 612 break; 613 614 case "Esc": 615 case "Escape": 616 this.close(); 617 flag = true; 618 break; 619 620 default: 621 break; 622 } 623 624 break; 625 626 case "click": 627 this.moveToPreviousYear(); 628 this.setFocusDay(false); 629 break; 630 631 default: 632 break; 633 } 634 635 if (flag) { 636 event.stopPropagation(); 637 event.preventDefault(); 638 } 639 } 640 641 handleNextMonthButton(event) { 642 let flag = false; 643 644 switch (event.type) { 645 case "keydown": 646 switch (event.key) { 647 case "Esc": 648 case "Escape": 649 this.close(); 650 flag = true; 651 break; 652 653 case "Enter": 654 this.moveToNextMonth(); 655 this.setFocusDay(false); 656 flag = true; 657 break; 658 } 659 660 break; 661 662 case "click": 663 this.moveToNextMonth(); 664 this.setFocusDay(false); 665 break; 666 667 default: 668 break; 669 } 670 671 if (flag) { 672 event.stopPropagation(); 673 event.preventDefault(); 674 } 675 } 676 677 handlePreviousMonthButton(event) { 678 let flag = false; 679 680 switch (event.type) { 681 case "keydown": 682 switch (event.key) { 683 case "Esc": 684 case "Escape": 685 this.close(); 686 flag = true; 687 break; 688 689 case "Enter": 690 this.moveToPreviousMonth(); 691 this.setFocusDay(false); 692 flag = true; 693 break; 694 } 695 696 break; 697 698 case "click": 699 this.moveToPreviousMonth(); 700 this.setFocusDay(false); 701 flag = true; 702 break; 703 704 default: 705 break; 706 } 707 708 if (flag) { 709 event.stopPropagation(); 710 event.preventDefault(); 711 } 712 } 713 714 handleDayKeyDown(event) { 715 let flag = false; 716 717 switch (event.key) { 718 case "Esc": 719 case "Escape": 720 this.close(); 721 break; 722 723 case " ": 724 this.setTextboxDate(event.currentTarget); 725 flag = true; 726 break; 727 728 case "Enter": 729 this.setTextboxDate(event.currentTarget); 730 this.close(); 731 flag = true; 732 break; 733 734 case "Tab": 735 // this.cancelButtonNode.focus(); 736 if (event.shiftKey) { 737 this.nextYearNode.focus(); 738 } 739 this.setMessage(""); 740 flag = true; 741 break; 742 743 case "Right": 744 case "ArrowRight": 745 this.moveFocusToNextDay(); 746 flag = true; 747 break; 748 749 case "Left": 750 case "ArrowLeft": 751 this.moveFocusToPreviousDay(); 752 flag = true; 753 break; 754 755 case "Down": 756 case "ArrowDown": 757 this.moveFocusToNextWeek(); 758 flag = true; 759 break; 760 761 case "Up": 762 case "ArrowUp": 763 this.moveFocusToPreviousWeek(); 764 flag = true; 765 break; 766 767 case "PageUp": 768 if (event.shiftKey) { 769 this.moveToPreviousYear(); 770 } else { 771 this.moveToPreviousMonth(); 772 } 773 this.setFocusDay(); 774 flag = true; 775 break; 776 777 case "PageDown": 778 if (event.shiftKey) { 779 this.moveToNextYear(); 780 } else { 781 this.moveToNextMonth(); 782 } 783 this.setFocusDay(); 784 flag = true; 785 break; 786 787 case "Home": 788 this.moveFocusToFirstDayOfWeek(); 789 flag = true; 790 break; 791 792 case "End": 793 this.moveFocusToLastDayOfWeek(); 794 flag = true; 795 break; 796 } 797 798 if (flag) { 799 event.stopPropagation(); 800 event.preventDefault(); 801 } 802 } 803 804 handleDayClick(event) { 805 if (!this.isDayDisabled(event.currentTarget) && event.which !== 3) { 806 this.setTextboxDate(event.currentTarget); 807 this.close(); 808 } 809 810 event.stopPropagation(); 811 event.preventDefault(); 812 } 813 814 handleDayFocus() { 815 this.setMessage(this.messageCursorKeys); 816 } 817 818 handleButtonKeydown(event) { 819 if (event.key === "Enter" || event.key === " ") { 820 this.open(); 821 this.setFocusDay(); 822 823 event.stopPropagation(); 824 event.preventDefault(); 825 } 826 } 827 828 handleButtonClick(event) { 829 this.setFocusDay(); 830 831 event.stopPropagation(); 832 event.preventDefault(); 833 } 834
835 handleBackgroundMouseUp(event) { 836 if (!this.dialogNode.contains(event.target)) { 837 event.stopPropagation(); 838 event.preventDefault(); 839 } 840 } 841 } 842 843 const PerformanceCalendar = (function () { 844 const today = dayjs(); 845 const state = { 846 datepicker: null, 847 performances: [], 848 selectedDay: today.date(), 849 selectedMonth: today.month(), 850 startOfMonth: today.startOf("month"), 851 prevHighlights: [], 852 hasUserSelectedDate: false, 853 }; 854 855 // Adapt getHTMLForDay to accept a Date object 856 function getCurrentPerformanceListForDate(dateObj, performances) { 857 if (!dateObj || !performances) { 858 return []; 859 } 860 var month = dateObj.getMonth(); 861 var day = dateObj.getDate(); 862 863 var filteredEvents = performances.filter(function (event) { 864 var hasMatchingDate = (event.dates || []).some(function (d) { 865 var dateStr = d.date.split("@")[0]; 866 var dObj = new Date(dateStr); 867 var matches = dObj.getMonth() === month && dObj.getDate() === day; 868 return matches; 869 }); 870 return hasMatchingDate; 871 }); 872 873 return filteredEvents; 874 } 875 876 function getHTMLForDay(dateObj) { 877 var list = getCurrentPerformanceListForDate(dateObj, state.performances); 878 879 // Find the event titled "KRAVIS CENTER BLOCK PARTY" and separate it 880 var kravisEvent = null; 881 var otherEvents = []; 882 883 list.forEach(function (event) { 884 if (event.title.includes("KRAVIS CENTER BLOCK PARTY")) { 885 kravisEvent = event; 886 } else { 887 otherEvents.push(event); 888 } 889 }); 890 891 // If the KRAVIS CENTER BLOCK PARTY event exists, put it first 892 if (kravisEvent) { 893 otherEvents.unshift(kravisEvent); 894 } 895 896 // Now, map through the adjusted event list to generate HTML 897 var newHTML; 898 var html = otherEvents.map(function (event) { 899 var html = ""; 900 901 // HARDCODED - hotfix 902 if (event.title.includes("KRAVIS CENTER BLOCK PARTY")) { 903 newHTML = ", 1pm - 8pm"; 904 } else { 905 newHTML = ""; 906 } 907 908 event.thumbnail = 909 event.thumbnail || "/wp-content/themes/kravis/dist/img/event-placeholder.png"; 910 html += "<div class='vc_row'><a href='" + event.link + "'>"; 911 html += "<img class='vc_col-xs-3' src='" + event.thumbnail + "' alt='" + event.title + "'>"; 912 html += "<div class='vc_col-xs-8'>"; 913 html += "<h2>" + event.title + "</h2>"; 914 html += "<span>" + event.location + newHTML + "</span>"; 915 html += "</div>"; 916 html += "</a></div>"; 917 return html; 918 }); 919 920 return html.join(""); 921 } 922 923 function setLoading($value) { 924 $("#cal-loading-div").css("display", $value ? "flex" : "none"); 925 } 926 927 async function getEvents() { 928 var startOfMonth = state.startOfMonth.toString(); 929 930 // Use REST API endpoint (avoids admin-ajax.php rate limiting) 931 var apiUrl = (ajax_object.rest_url || ajax_object.ajax_url.replace('admin-ajax.php', 'wp-json/kravis/v1/')); 932 933 return await $.ajax({ 934 url: apiUrl + "performances-by-month", 935 method: "GET", 936 data: { 937 date: startOfMonth, 938 }, 939 }) 940 .done(function (data) { 941 state.performances = data && R.values(data.data.performances); 942 943 // Update the event list for the currently selected date after loading events 944 updateEventListForCurrentDate(); 945 946 // Apply highlights to the calendar after events are loaded 947 updateCalendarWithEvents(); 948 }) 949 .fail(function (e) { 950 console.error("Failed to load events:", e); 951 }); 952 } 953 954 // Helper function to update event list for current selected date 955 function updateEventListForCurrentDate() { 956 var currentDate = new Date(); 957 currentDate.setDate(state.selectedDay); 958 currentDate.setMonth(state.selectedMonth); 959 960 var html = getHTMLForDay(currentDate) || "<p>No showtimes available.</p>"; 961 $("#event-cal-datepicker-list").html(html); 962 } 963 964 function setHighlights() { 965 // Calculate all dates that should be highlighted 966 var highlightDates = []; 967
968 state.performances.forEach(function (performance) { 969 if (performance.dates && performance.dates.length > 0) { 970 performance.dates.forEach(function (dateInfo) { 971 var dateStr = dateInfo.date.split("@")[0]; // Remove time portion 972 var startDate = new Date(dateStr); 973 974 // Check if this event has an end date (for multi-day events) 975 if (dateInfo.end_date) { 976 var endDateStr = dateInfo.end_date.split("@")[0]; 977 var endDate = new Date(endDateStr); 978 979 // Add all dates from start to end (inclusive) 980 var currentDate = new Date(startDate); 981 while (currentDate <= endDate) { 982 highlightDates.push(formatDateForDataAttribute(currentDate)); 983 currentDate.setDate(currentDate.getDate() + 1); 984 } 985 } else { 986 // Single day event 987 highlightDates.push(formatDateForDataAttribute(startDate)); 988 } 989 }); 990 } 991 }); 992 993 // Remove duplicates 994 highlightDates = [...new Set(highlightDates)]; 995 996 // Apply highlights to the DOM 997 applyHighlightsToCalendar(highlightDates); 998 } 999 1000 // Helper function to format date as YYYY-MM-DD for data-date attribute 1001 function formatDateForDataAttribute(date) { 1002 var year = date.getFullYear(); 1003 var month = (date.getMonth() + 1).toString().padStart(2, "0"); 1004 var day = date.getDate().toString().padStart(2, "0"); 1005 return year + "-" + month + "-" + day; 1006 } 1007 1008 // Function to apply highlight class to calendar cells 1009 function applyHighlightsToCalendar(highlightDates) { 1010 // First, remove all existing highlight classes (but preserve selected class) 1011 $("#event-cal-datepicker td").removeClass("highlight"); 1012 1013 // Then add highlight class to cells with matching dates, but only if they're not disabled 1014 highlightDates.forEach(function (dateStr) { 1015 var $button = $("#event-cal-datepicker button[data-date='" + dateStr + "']"); 1016 var $cell = $button.parent("td"); 1017 // Only highlight if the button exists and is not disabled 1018 if ($button.length && !$button.prop("disabled")) { 1019 $cell.addClass("highlight"); 1020 // Set tabindex="0" for highlighted buttons to make them focusable 1021 $button.attr("tabindex", "0"); 1022 } 1023 }); 1024 1025 // After adding highlight classes, update the grid to apply correct tabindex values 1026 if (state.datepicker) { 1027 state.datepicker.updateGrid(); 1028 1029 // Only reapply selected class if we're in the same month as the selected date 1030 // and if there's actually a selected date that's not just the default 1031 if ( 1032 state.datepicker.selectedDay && 1033 state.datepicker.selectedDay.getTime() !== new Date(0, 0, 1).getTime() 1034 ) { 1035 var currentMonth = state.datepicker.focusDay.getMonth(); 1036 var currentYear = state.datepicker.focusDay.getFullYear(); 1037 var selectedMonth = state.datepicker.selectedDay.getMonth(); 1038 var selectedYear = state.datepicker.selectedDay.getFullYear(); 1039 1040 // Only apply selected class if we're viewing the same month as the selected date 1041 if (currentMonth === selectedMonth && currentYear === selectedYear) { 1042 var selectedDateStr = formatDateForDataAttribute(state.datepicker.selectedDay); 1043 $("#event-cal-datepicker td").removeClass("selected"); 1044 var $selectedButton = $( 1045 "#event-cal-datepicker button[data-date='" + selectedDateStr + "']" 1046 ); 1047 $selectedButton.parent("td").addClass("selected"); 1048 } 1049 } 1050 } 1051 } 1052 1053 // Helper: update event list for selected date 1054 function updateEventListForDate(dateObj) { 1055 // Adapt getHTMLForDay to accept a Date object 1056 var html = getHTMLForDay(dateObj) || "<p>No showtimes available.</p>"; 1057 $("#event-cal-datepicker-list").html(html); 1058 } 1059 1060 async function init() { 1061 var trigger = $("#cal-trigger"), 1062 calendar = $(".event-calendar-widget"); 1063 var $widgetOuter = $("#cal-widget-outer"); 1064 1065 function setCalendarOpen(isOpen) { 1066 if (isOpen) { 1067 calendar.addClass("open"); 1068 trigger.attr("aria-expanded", "true"); 1069 trigger.attr("aria-label", "See calendar, close performance calendar"); 1070 $widgetOuter.attr("aria-hidden", "false"); 1071 $widgetOuter.removeAttr("inert"); 1072 $widgetOuter.prop("inert", false); 1073 $(".content.row").attr("aria-hidden", "true"); 1074 $(".banner").attr("aria-hidden", "true"); 1075 } else { 1076 calendar.removeClass("open"); 1077 trigger.attr("aria-expanded", "false"); 1078 trigger.attr("aria-label", "See calendar, open performance calendar"); 1079 $widgetOuter.attr("aria-hidden", "true"); 1080 $widgetOuter.attr("inert", ""); 1081 $widgetOuter.prop("inert", true); 1082 $(".content.row").removeAttr("aria-hidden"); 1083 $(".banner").removeAttr("aria-hidden"); 1084 } 1085 } 1086 1087 // Initialize the DatePickerDialog instance 1088 const datePickerElement = document.getElementById("event-cal-datepicker"); 1089 if (datePickerElement) { 1090 state.datepicker = new DatePickerDialog(datePickerElement); 1091 } 1092 1093 setCalendarOpen(false); 1094 1095 // Completely rewrite the trigger click handler 1096 trigger.on("click", function (e) { 1097 e.preventDefault(); 1098 e.stopPropagation(); 1099 1100 if (calendar.hasClass("open")) { 1101 setCalendarOpen(false); 1102 } else { 1103 setCalendarOpen(true); 1104 $("#event-cal-datepicker").attr("tabindex", "-1").focus(); 1105 } 1106 }); 1107 1108 // Add keyboard support - close calendar with ESC key 1109 $(document).on("keydown", function (e) { 1110 if (e.key === "Escape" && calendar.hasClass("open")) { 1111 setCalendarOpen(false); 1112 trigger.focus(); 1113 } 1114 }); 1115 1116 // Add click handler for the close button 1117 $("#cal-trigger .fa-close") 1118 .parent() 1119 .on("click", function (e) { 1120 e.preventDefault(); 1121 e.stopPropagation(); 1122 setCalendarOpen(false); 1123 trigger.focus(); 1124 }); 1125 1126 // Trap focus within the calendar widget when it's open 1127 $("#cal-widget-outer").on("keydown", function (e) { 1128 if (e.key === "Tab") { 1129 var focusableElements = $("#cal-widget-outer").find(
1130 'a[href], button:not(:disabled), [tabindex]:not([tabindex="-1"])' 1131 ).filter(function () { 1132 return $(this).is(':visible') && !$(this).closest('[aria-hidden="true"]').length; 1133 }); 1134 1135 if (focusableElements.length === 0) return; 1136 1137 var firstFocusableElement = focusableElements[0]; 1138 var lastFocusableElement = focusableElements[focusableElements.length - 1]; 1139 1140 if (e.shiftKey && document.activeElement === firstFocusableElement) { 1141 e.preventDefault(); 1142 lastFocusableElement.focus(); 1143 } else if (!e.shiftKey && document.activeElement === lastFocusableElement) { 1144 e.preventDefault(); 1145 firstFocusableElement.focus(); 1146 } 1147 } 1148 }); 1149 1150 // Note: The new DatePickerDialog class doesn't support onSelect and onNavigation methods 1151 // Event handling is done through custom events and direct DOM event listeners below 1152 1153 setLoading(true); 1154 await getEvents(); 1155 setTimeout(setInitialDate, 500); 1156 setLoading(false); 1157 // Only set up event handlers if datepicker was successfully created 1158 if (state.datepicker) { 1159 // Override navigation methods to trigger event updates 1160 const originalMoveToNextMonth = state.datepicker.moveToNextMonth; 1161 const originalMoveToPreviousMonth = state.datepicker.moveToPreviousMonth; 1162 const originalMoveToNextYear = state.datepicker.moveToNextYear; 1163 const originalMoveToPreviousYear = state.datepicker.moveToPreviousYear; 1164 1165 state.datepicker.moveToNextMonth = async function () { 1166 const oldMonth = this.focusDay.getMonth(); 1167 const oldYear = this.focusDay.getFullYear(); 1168 1169 // Call original method 1170 originalMoveToNextMonth.call(this); 1171 1172 // Check if month/year changed and trigger updates 1173 const newMonth = this.focusDay.getMonth(); 1174 const newYear = this.focusDay.getFullYear(); 1175 1176 if (oldMonth !== newMonth || oldYear !== newYear) { 1177 setLoading(true); 1178 1179 $("#event-cal-datepicker td").removeClass("selected"); 1180 state.selectedMonth = newMonth; 1181 state.startOfMonth = dayjs(state.datepicker.focusDay).startOf("month"); 1182 await getEvents(); 1183 setLoading(false); 1184 } 1185 }; 1186 1187 state.datepicker.moveToPreviousMonth = async function () { 1188 const oldMonth = this.focusDay.getMonth(); 1189 const oldYear = this.focusDay.getFullYear(); 1190 1191 // Call original method 1192 originalMoveToPreviousMonth.call(this); 1193 1194 // Check if month/year changed and trigger updates 1195 const newMonth = this.focusDay.getMonth(); 1196 const newYear = this.focusDay.getFullYear(); 1197 1198 if (oldMonth !== newMonth || oldYear !== newYear) { 1199 setLoading(true); 1200 1201 $("#event-cal-datepicker td").removeClass("selected"); 1202 state.selectedMonth = newMonth; 1203 state.startOfMonth = dayjs(state.datepicker.focusDay).startOf("month"); 1204 await getEvents(); 1205 setLoading(false); 1206 } 1207 }; 1208 1209 state.datepicker.moveToNextYear = async function () { 1210 const oldMonth = this.focusDay.getMonth(); 1211 const oldYear = this.focusDay.getFullYear(); 1212 1213 // Call original method 1214 originalMoveToNextYear.call(this); 1215 1216 // Check if month/year changed and trigger updates 1217 const newMonth = this.focusDay.getMonth(); 1218 const newYear = this.focusDay.getFullYear(); 1219 1220 if (oldMonth !== newMonth || oldYear !== newYear) { 1221 setLoading(true); 1222 1223 $("#event-cal-datepicker td").removeClass("selected"); 1224 state.selectedMonth = newMonth; 1225 state.startOfMonth = dayjs(state.datepicker.focusDay).startOf("month"); 1226 await getEvents(); 1227 setLoading(false); 1228 } 1229 }; 1230 1231 state.datepicker.moveToPreviousYear = async function () { 1232 const oldMonth = this.focusDay.getMonth(); 1233 const oldYear = this.focusDay.getFullYear(); 1234 1235 // Call original method 1236 originalMoveToPreviousYear.call(this); 1237 1238 // Check if month/year changed and trigger updates 1239 const newMonth = this.focusDay.getMonth(); 1240 const newYear = this.focusDay.getFullYear(); 1241 1242 if (oldMonth !== newMonth || oldYear !== newYear) {
1243 setLoading(true); 1244 1245 $("#event-cal-datepicker td").removeClass("selected"); 1246 state.selectedMonth = newMonth; 1247 state.startOfMonth = dayjs(state.datepicker.focusDay).startOf("month"); 1248 await getEvents(); 1249 setLoading(false); 1250 } 1251 }; 1252 1253 // Listen for custom date selection events 1254 $(state.datepicker.dialogNode).on("dateSelected", function (e) { 1255 var selectedDate = e.originalEvent.detail.date; 1256 1257 // Mark that user has made a selection 1258 state.hasUserSelectedDate = true; 1259 1260 state.selectedDay = selectedDate.getDate(); 1261 state.selectedMonth = selectedDate.getMonth(); 1262 updateEventListForDate(selectedDate); 1263 }); 1264 1265 // Set up date selection handler for direct clicks 1266 $(document).on("click", "#event-cal-datepicker button:not(:disabled)", function () { 1267 var dateStr = $(this).attr("data-date"); 1268 if (dateStr) { 1269 // Mark that user has made a selection 1270 state.hasUserSelectedDate = true; 1271 1272 // Remove selected class from all cells 1273 $("#event-cal-datepicker td").removeClass("selected"); 1274 1275 // Add selected class to clicked button's parent cell 1276 $(this).parent("td").addClass("selected"); 1277 1278 var dateParts = dateStr.split("-"); 1279 var selectedDate = new Date(dateParts[0], dateParts[1] - 1, dateParts[2]); 1280 state.selectedDay = selectedDate.getDate(); 1281 state.selectedMonth = selectedDate.getMonth(); 1282 updateEventListForDate(selectedDate); 1283 } 1284 }); 1285 } 1286 } 1287 1288 function setInitialDate() { 1289 if (state.performances.length === 0) { 1290 setTimeout(setInitialDate, 500); 1291 } else if (state.datepicker) { 1292 // Set initial focus to today's date 1293 var today = new Date(); 1294 state.datepicker.focusDay = today; 1295 state.datepicker.selectedDay = today; // Also set selectedDay 1296 state.datepicker.updateGrid(); 1297 state.selectedDay = today.getDate(); 1298 state.selectedMonth = today.getMonth(); 1299 1300 // Only add selected class to today's date on initial load 1301 // Check if this is the initial load by seeing if no date has been explicitly selected 1302 if (!state.hasUserSelectedDate) { 1303 var todayStr = formatDateForDataAttribute(today); 1304 setTimeout(function () { 1305 $("#event-cal-datepicker td").removeClass("selected"); 1306 var $todayButton = $("#event-cal-datepicker button[data-date='" + todayStr + "']"); 1307 $todayButton.parent("td").addClass("selected"); 1308 }, 100); 1309 } 1310 1311 // Update the event list for today's date 1312 updateEventListForCurrentDate(); 1313 } 1314 } 1315 1316 function updateCalendarWithEvents() { 1317 // This function can be used to highlight dates with events 1318 // You can implement highlighting logic here if needed 1319 if (state.datepicker && state.performances.length > 0) { 1320 // Update the calendar display 1321 state.datepicker.updateGrid(); 1322 1323 setHighlights(); 1324 } 1325 } 1326 1327 return { 1328 init: init, 1329 }; 1330 })(); 1331 1332 $(document).ready(function () { 1333 PerformanceCalendar.init(); 1334 }); 1335});
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.