1import {startCartTimer} from "/assets/js/cart-timer.js" 2 3const CONFIG = 4{ 5 MAX_TICKETS: 50, 6 BULK_MIN_TICKETS: 50, 7 BULK_HH_MIN_TICKETS: 20, 8 BULK_MAX_TICKETS: 500, 9 FREE_DAY_MAX: 7, 10 MAX_CHILDREN: 10, 11 MEMBERSHIP_UPSELL_BREAKPOINT: 225, 12 MEMBERSHIP_UPSELL_OFFSET: 100, 13 MOBILE_BREAKPOINT: 1200, 14 FOOTER_STICKY_GAP: 15, 15 PATHS_WITH_EXPIRATION: ["admission", "ticketedEvent", "hauntedHouse", "bulkTickets", "bulkHauntedHouse"], 16 URLS: { 17 zipCode: "/ajax/modules/tnew/zip-code", 18 stepSave: "/modules/tnew/purchase-path-step-save", 19 stepGet: "/modules/tnew/purchase-path-step", 20 cart: "/modules/tnew/purchase-path-cart", 21 promo: "/modules/tnew/purchase-path-promo", 22 clearPromo: "/modules/tnew/purchase-path-clear-promo", 23 giftCert: "/modules/tnew/purchase-path-gift-cert", 24 removeGiftCert: "/modules/tnew/purchase-path-remove-gift-cert", 25 removeItem: "/modules/tnew/purchase-path-remove-item", 26 removeDate: "/modules/tnew/purchase-path-remove-date", 27 donation: "/modules/tnew/purchase-path-donation", 28 switchView: "/modules/tnew/purchase-path-switch-view", 29 membershipUpsell: "/cart/membership?group=classic&selected=", 30 thankYou: "/cart/thank-you", 31 payment: "/cart/payment", 32 } 33} 34 35const analytics = 36{ 37 _send(event, params) 38 { 39 if (typeof gtag === "function") gtag("event", event, params) 40 }, 41 addToCart(items, value) 42 { 43 this._send("add_to_cart", { currency: "USD", value, items }) 44 }, 45 removeFromCart(items) 46 { 47 this._send("remove_from_cart", { items }) 48 }, 49 viewItem(items, value) 50 { 51 this._send("view_item", { currency: "USD", value, items }) 52 }, 53 beginCheckout() 54 { 55 this._send("begin_checkout", {}) 56 }, 57 viewPromotion(name) 58 { 59 this._send("view_promotion", { promotion_name: name }) 60 }, 61 selectPromotion(name, items) 62 { 63 this._send("select_promotion", { promotion_name: name, items }) 64 }, 65} 66 67const purchasePath = $("#purchasePath").html() 68 69let purchasePathStep = $("#purchasePathStep").html() 70 71let calendarView = $("#purchasePathCalendarView").html() 72 73let scrollPosition = 0 74 75let adjustCalendarGridRows = () => 76{ 77 if($(".purchase-path-ticket-calendar").length) 78 { 79 const $days = $(".purchase-path-ticket-calendar .day") 80 if ($days.length <= 35) 81 {
82 $(".purchase-path-ticket-calendar").css("grid-template-rows", "repeat(5, 1fr)") 83 } 84 else 85 { 86 $(".purchase-path-ticket-calendar").css("grid-template-rows", "repeat(6, 1fr)") 87 } 88 } 89} 90 91let setupMobileSelectToggle = ($selectButton, calendarSelector, deselectOtherDaysValue = false) => 92{ 93 $selectButton.off("click") 94 95 if($(calendarSelector).filter(".selected").length > 0) 96 { 97 $selectButton.html("selected").addClass("selected") 98 } 99 else 100 { 101 $selectButton.html("select").removeClass("selected") 102 } 103 104 $selectButton.on("click", function() 105 { 106 const $calBtns = $(calendarSelector) 107 if($(this).hasClass("selected")) 108 { 109 $(this).removeClass("selected").html("select") 110 $calBtns.removeClass("selected").attr("aria-checked", "false") 111 } 112 else 113 { 114 $(this).addClass("selected").html("selected") 115 $calBtns.addClass("selected").attr("aria-checked", "true") 116 if(deselectOtherDaysValue !== false) 117 { 118 const $otherBtns = $(`.calendar-day-button:not([data-day="${deselectOtherDaysValue}"])`) 119 $otherBtns.removeClass("selected").attr("aria-checked", "false") 120 } 121 } 122 }) 123} 124 125let handleMobileHauntedHouseClick = ($clicked) => 126{ 127 let friendly = $clicked 128 if ($clicked.data("subType") === "fright") 129 { 130 const $friendlyForDay = $(`.calendar-day-button[data-day="${$clicked.data("day")}"][data-sub-type="friendly"]`) 131 friendly = $friendlyForDay.length ? $friendlyForDay : friendly 132 } 133 134 const $partOne = $(".mobile-select-container .day-part.one") 135 if(friendly.hasClass("times-over")) 136 { 137 $partOne.hide() 138 } 139 else 140 { 141 $partOne.show() 142 $partOne.find(".day-time").html(friendly.data("time")) 143 $partOne.find(".day-title").html(friendly.data("title")) 144 if(friendly.data("name")) 145 { 146 $partOne.find(".day-name").html(friendly.data("name")) 147 } 148 else 149 { 150 $partOne.find(".day-name").hide() 151 } 152 } 153 154 const friendlyDay = friendly.data("day") 155 setupMobileSelectToggle( 156 $partOne.find("button"), 157 `.calendar-day-button[data-day="${friendlyDay}"][data-sub-type="friendly"]`, 158 friendlyDay 159 ) 160 161 const $frightEl = $(`.half.second[data-day="${$clicked.data("day")}"]`) 162 const $partTwo = $(".mobile-select-container .day-part.two") 163 if($frightEl.length > 0) 164 { 165 if($frightEl.data("name")) 166 { 167 $partTwo.find(".day-name").html($frightEl.data("name")) 168 } 169 else 170 { 171 $partTwo.find(".day-name").hide() 172 } 173 $partTwo.find(".day-time").html($frightEl.data("time")) 174 $partTwo.find(".day-title").html($frightEl.data("title")) 175 176 setupMobileSelectToggle( 177 $partTwo.find("button"), 178 `.calendar-day-button[data-day="${$frightEl.data("day")}"][data-sub-type="${$frightEl.data("subType")}"]`, 179 $frightEl.data("day") 180 ) 181 $partTwo.show() 182 } 183 else 184 { 185 $partTwo.hide() 186 } 187 188 if($clicked.data("price-adult")) 189 { 190 $("#mobile-price-adult").html(`$${$clicked.data("price-adult")} Adult`) 191 } 192 if($clicked.data("price-youth")) 193 { 194 $("#mobile-price-youth").html(`$${$clicked.data("price-youth")} Youth`) 195 } 196 if($clicked.data("price")) 197 { 198 $(".mobile-price-guest").html(`$${$clicked.data("price")} Guest`) 199 } 200} 201 202let handleMobileStandardClick = ($clicked) => 203{ 204 const $partOne = $(".mobile-select-container .day-part.one") 205 const $partTwo = $(".mobile-select-container .day-part.two") 206 $partTwo.hide() 207 208 $partOne.find(".day-time").html($clicked.data("time")) 209 $partOne.find(".day-title").html($clicked.data("title")) 210 if($clicked.data("name")) 211 { 212 $partOne.find(".day-name").html($clicked.data("name")) 213 } 214 else 215 { 216 $partOne.find(".day-name").hide() 217 } 218 219 if($clicked.data("special-event") != "0") 220 { 221 $("#mobile-calendar-special-event").find(".body-copy-small").html($clicked.data("special-event")) 222 $("#mobile-calendar-special-event").show() 223 } 224 else 225 { 226 $("#mobile-calendar-special-event").hide() 227 } 228 229 const dayValue = $clicked.data("day") 230 231 if($clicked.data("firstThursday")) 232 { 233 const $firstThursday = $(`.half.second[data-day="${dayValue}"]`) 234 $partTwo.find(".day-time").html($firstThursday.data("time")) 235 $partTwo.find(".day-title").html($firstThursday.data("name")) 236 237 $("#mobile-price-adult-2").html(`$${$firstThursday.data("price")} Adult`) 238 $("#mobile-price-youth-2").html(`$${$firstThursday.data("price")} Youth`) 239 240 setupMobileSelectToggle( 241 $partTwo.find("button"), 242 `.calendar-day-button[data-day="${dayValue}"][data-sub-type="firstThursday"]` 243 ) 244 $partTwo.show() 245 } 246 247 if($clicked.data("price-adult") !== undefined) 248 { 249 $("#mobile-price-adult").html(`$${$clicked.data("price-adult")} Adult`) 250 } 251 if($clicked.data("price-youth") !== undefined) 252 { 253 $("#mobile-price-youth").html(`$${$clicked.data("price-youth")} Youth`) 254 } 255 if($clicked.data("price")) 256 { 257 $("#mobile-price-guest").html(`$${$clicked.data("price")} Guest`) 258 } 259 260 setupMobileSelectToggle( 261 $partOne.find("button"), 262 `.calendar-day-button[data-day="${dayValue}"][data-type="admission"]:not([data-sub-type="firstThursday"])` 263 ) 264} 265 266let handleDesktopCalendarClick = ($clicked) => 267{ 268 if($clicked.hasClass("selected")) 269 { 270 $clicked.removeClass("selected") 271 $clicked.attr("aria-checked", "false") 272 } 273 else if(!$clicked.hasClass("closed")) 274 { 275 if(purchasePath == "hauntedHouse") 276 { 277 const $otherBtns = $(`.calendar-day-button:not([data-day="${$clicked.data("day")}"])`) 278 $otherBtns.removeClass("selected") 279 $otherBtns.attr("aria-checked", "false") 280 } 281 $clicked.addClass("selected") 282 $clicked.attr("aria-checked", "true") 283 } 284} 285 286let setupCalendarMonthSelect = () => 287{ 288 $("#purchase-path-calendar-month-select").on("change", async function() 289 { 290 if($(".calendar-day-button.selected").length > 0) 291 { 292 let days = collectSelectedDays() 293 if(days && days["selectedDays"] && Object.keys(days["selectedDays"]).length > 0) 294 { 295 await sendStepData("selectDays", days, false) 296 const data = await fetchNextStep() 297 applyStep(data) 298 } 299 } 300 else 301 { 302 const data = await fetchNextStep() 303 applyStep(data) 304 } 305 }) 306} 307 308let setupCalendarViewSwitch = () => 309{ 310 $("#calendar-switch-view").on("click", function() 311 { 312 calendarSwitchView() 313 }) 314} 315 316let initMobileCalendarState = () => 317{ 318 if ($(window).width() < CONFIG.MOBILE_BREAKPOINT) 319 { 320 $(".purchase-path-ticket-calendar .day .calendar-day-button").addClass("mobile") 321 } 322 else 323 { 324 $(".purchase-path-ticket-calendar .day .calendar-day-button").removeClass("mobile") 325 } 326 let selectedMonth = 0; 327 if($("#purchase-path-calendar-month-select").length) 328 { 329 selectedMonth = $("#purchase-path-calendar-month-select").val().split("-")[0] 330 } 331 if($(".calendar-day-button.mobile").length && $(`.calendar-day-button.selected[data-month="${selectedMonth}"]`).length == 0) 332 { 333 $(".calendar-day-button:not(.closed)").first().focus() 334 $(".calendar-day-button:not(.closed)").first().trigger("click") 335 } 336 else if($(".calendar-day-button.mobile.selected").length > 0) 337 { 338 if($(".calendar-day-button.selected:not(.closed)").first().data("subType") == "firstThursday") 339 { 340 setTimeout(function() 341 { 342 $(".calendar-day-button.selected:not(.closed)").first().parent().find(".calendar-day-button").first().focus() 343 $(".calendar-day-button.selected:not(.closed)").first().parent().find(".calendar-day-button").first().trigger("click") 344 }, 200) 345 } 346 else 347 { 348 $(".calendar-day-button.selected:not(.closed)").first().focus() 349 $(".calendar-day-button.selected:not(.closed)").first().trigger("click") 350 } 351 } 352} 353 354let applyCalendarClickEvents = () => 355{ 356 adjustCalendarGridRows() 357 358 $(".calendar-day-button").on("click", function() 359 { 360 if($(this).hasClass("mobile")) 361 { 362 if(purchasePath == "hauntedHouse") 363 { 364 handleMobileHauntedHouseClick($(this)) 365 } 366 else 367 { 368 handleMobileStandardClick($(this)) 369 } 370 } 371 else 372 { 373 handleDesktopCalendarClick($(this)) 374 } 375 }) 376 377 setupCalendarMonthSelect() 378 setupCalendarViewSwitch() 379 initMobileCalendarState() 380 resize() 381} 382 383let applyExtraCollapseEvents = () => 384{ 385 $(".purchase-path-ticket-rolling-week.add-extras .day").on("click keydown", function(event) 386 { 387 if(event.type === 'click' || (event.type === 'keydown' && event.keyCode === 13)) 388 { 389 let groups = $(`.ticket-group[data-day="${$(this).data("day")}"], .ticket-group-header[data-day="${$(this).data("day")}"]`) 390 if($(this).hasClass("uncollapsed")) 391 { 392 $(this).removeClass("uncollapsed") 393 groups.attr("hidden", true) 394 } 395 else 396 { 397 $(this).addClass("uncollapsed") 398 if($("#accessPass").length) 399 { 400 groups = groups.not(".access-pass-additional-tickets:not
400(.unlocked)") 401 } 402 groups.removeAttr("hidden") 403 } 404 } 405 }) 406 $(".purchase-path-extras-event-header").on("click keydown", function(event) 407 { 408 if(event.type === 'click' || (event.type === 'keydown' && event.keyCode === 13)) 409 { 410 let event = $(this).parent() 411 if(event.hasClass("uncollapsed")) 412 { 413 event.removeClass("uncollapsed") 414 } 415 else 416 { 417 event.addClass("uncollapsed") 418 } 419 } 420 }) 421} 422 423let unlockAdditionalTickets = () => 424{ 425 $(".ticket-group.access-pass-additional-tickets").addClass("unlocked").each(function() 426 { 427 let day = $(this).data("day") 428 if(!day || $(`.purchase-path-ticket-rolling-week.add-extras .day[data-day="${day}"]:not(.ticket-group-header)`).hasClass("uncollapsed")) 429 { 430 $(this).removeAttr("hidden") 431 } 432 }) 433} 434 435let applyTicketSelectEvents = () => 436{ 437 $(".ticket-container input").on("input", function() 438 { 439 let ticketContainer = $(this).parent().parent() 440 ticketContainer.data("qty", $(this).val()) 441 }) 442 $(".ticket-container button").on("click", function() 443 { 444 let ticketContainer = $(this).parent().parent() 445 if($(this).data("action") == "plus") 446 { 447 if(ticketContainer.data("accessPass")) 448 { 449 let accessPassMax = parseInt($("#accessPassMax").html()) 450 if(purchasePathStep == "reviewCart") 451 { 452 if(countTicketsByAttribute("access-pass", $(this).data("performanceId")) >= accessPassMax) 453 { 454 displayStepError(`You are limited to ${accessPassMax} Access Pass tickets per event.`) 455 } 456 else if(ticketContainer.data("qty") >= CONFIG.MAX_TICKETS) 457 { 458 displayStepError("Max ticket count reached.") 459 } 460 else 461 { 462 ticketContainer.data("qty", ticketContainer.data("qty") + 1) 463 } 464 } 465 else 466 { 467 if(countTicketsByAttribute("access-pass") >= accessPassMax) 468 { 469 displayStepError("Too many access pass tickets selected. Additional admission tickets are offered at regular price.") 470 unlockAdditionalTickets() 471 } 472 else if(ticketContainer.data("qty") >= CONFIG.MAX_TICKETS) 473 { 474 displayStepError("Max ticket count reached.") 475 } 476 else 477 { 478 ticketContainer.data("qty", ticketContainer.data("qty") + 1) 479 } 480 } 481 } 482 else if(ticketContainer.data("freeDay")) 483 { 484 let remaining = ticketContainer.data("freeDayRemaining") !== undefined ? parseInt(ticketContainer.data("freeDayRemaining")) : CONFIG.FREE_DAY_MAX 485 if(countTicketsByAttribute("free-day", ticketContainer.data("performanceId")) >= remaining) 486 { 487 displayStepError(`You are limited to ${CONFIG.FREE_DAY_MAX} free tickets per free admission day. You have already claimed ${CONFIG.FREE_DAY_MAX - remaining}.`) 488 } 489 else if(ticketContainer.data("qty") >= CONFIG.MAX_TICKETS) 490 { 491 displayStepError("Max ticket count reached.") 492 } 493 else 494 { 495 ticketContainer.data("qty", ticketContainer.data("qty") + 1) 496 } 497 } 498 else 499 { 500 if(ticketContainer.data("qty") < CONFIG.MAX_TICKETS) 501 { 502 ticketContainer.data("qty", ticketContainer.data("qty") + 1) 503 } 504 else 505 { 506 displayStepError("Max ticket count reached.") 507 } 508 if($("#membership-upsell").length && totalAdmissionPrice() + CONFIG.MEMBERSHIP_UPSELL_OFFSET > CONFIG.MEMBERSHIP_UPSELL_BREAKPOINT) 509 { 510 $("#save-message").html($("#save-message").html().replace("%AMOUNT%", CONFIG.MEMBERSHIP_UPSELL_BREAKPOINT - totalAdmissionPrice())) 511 $("#default-message").attr("hidden", true) 512 $("#save-message").removeAttr("hidden") 513 } 514 } 515 } 516 else if($(this).data("action") == "minus") 517 { 518 if(ticketContainer.data("qty") > 0) 519 { 520 ticketContainer.data("qty", ticketContainer.data("qty") - 1) 521 } 522 } 523 $(this).parent().find(".ticket-qty-value").html(ticketContainer.data("qty")) 524 let ticketLabel = ticketContainer.find(".ticket-info .h5").first().text() 525 || ticketContainer.find(".ticket-type span").first().text() 526 || "Ticket" 527 announceQuantityChange(ticketLabel.trim(), ticketContainer.data("qty")) 528 }) 529 $("#zip-code-enter").on("click", async function() 530 { 531 if($("#zip-code").val().length == 5 && !isNaN($("#zip-code").val())) 532 { 533 let fd = new FormData(); 534 fd.append('zipCode', $("#zip-code").val()) 535 try 536 { 537 const data = await $.ajax({ 538 method: "POST", 539 url: CONFIG.URLS.zipCode, 540 processData: false, 541 contentType: false, 542 dataType: "json", 543 data: fd 544 }) 545 if(data.isResident) 546 { 547 $(`.ticket-container`).each(function() 548 { 549 if($(this).is(":visible") && !$(this).data("freeDay") && $(this).data("inStatePriceTypeId")) 550 { 551 $(this).data("priceTypeId", $(this).data("inStatePriceTypeId")) 552 let oldPrice = parseFloat($(this).data("price")) 553 let newPrice = parseFloat($(this).data("inStatePrice")) 554 let $priceEl = $(this).find(".ticket-info .ticket-price") 555 $({val: oldPrice}).animate({val: newPrice}, { 556 duration: 600,
557 easing: "swing", 558 step: function(now) 559 { 560 $priceEl.html(`$${now.toFixed(2)}`) 561 }, 562 complete: function() 563 { 564 $priceEl.html(`$${newPrice.toFixed(2)}`) 565 $priceEl.addClass("price-updated") 566 setTimeout(() => $priceEl.removeClass("price-updated"), 1000) 567 } 568 }) 569 } 570 }) 571 } 572 else 573 { 574 // zip code not eligible — no discount applied 575 } 576 $("#ticket-zip-code").slideUp() 577 } 578 catch (err) 579 { 580 displayStepError("There was a problem checking your zip code. Please try again later."); 581 } 582 } 583 else 584 { 585 displayStepError("Invalid zip code."); 586 } 587 }) 588 $(".ticket-group-remove-date").on("click", async function() 589 { 590 let fd = new FormData(); 591 fd.append('date', $(this).data("date")) 592 try 593 { 594 await $.ajax({ 595 method: "POST", 596 url: CONFIG.URLS.removeDate, 597 processData: false, 598 contentType: false, 599 data: fd 600 }) 601 const data = await fetchNextStep() 602 applyStep(data) 603 } 604 catch (err) 605 { 606 displayStepError("There was a problem removing this date. Please try again later."); 607 } 608 }) 609 $("#upsell-uncollapse-button").on("click", function(e) 610 { 611 e.preventDefault() 612 $(".membership-upsell").removeAttr("hidden") 613 $(this).hide() 614 analytics.viewPromotion("Membership Upsell") 615 }) 616 $(".membership-upsell-select button").on("click", function(e) 617 { 618 e.preventDefault() 619 analytics.selectPromotion("Membership Upsell", [{ 620 item_id: $(this).data("id"), 621 item_category: $(this).data("group"), 622 item_name: $(this).data("name"), 623 }]) 624 window.location = `${CONFIG.URLS.membershipUpsell}${$(this).data("id")}` 625 }) 626 $(".ticket-container").each(function() 627 { 628 if($(this).is(":visible")) 629 { 630 analytics.viewItem([{ 631 item_id: $(this).data("performanceId"), 632 item_name: "Performance", 633 item_category: "Performance", 634 price: $(this).data("price") 635 }], $(this).data("price")) 636 } 637 }) 638} 639 640let countTicketsByAttribute = (attrName, performanceId = false) => 641{ 642 let count = 0 643 let selectorString = `.ticket-container[data-${attrName}="1"]` 644 if(performanceId) 645 { 646 selectorString = `${selectorString}[data-performance-id="${performanceId}"]` 647 } 648 $(selectorString).each(function() 649 { 650 if ($(this).is(":visible")) 651 { 652 count += $(this).data("qty") 653 } 654 }) 655 return count 656} 657 658let totalAdmissionPrice = () => 659{ 660 let price = 0 661 $(`.ticket-container[data-type="admission"`).each(function() 662 { 663 price += parseFloat($(this).data("price")) * parseInt($(this).data("qty")) 664 }) 665 return price 666} 667 668let applyFeeSelectEvents = () => 669{ 670 $(".extras-fee button").on("click", function() 671 { 672 let feeContainer = $(this).parent().parent() 673 if($(this).data("action") == "plus") 674 { 675 feeContainer.data("qty", feeContainer.data("qty") + 1) 676 } 677 else if($(this).data("action") == "minus") 678 { 679 if(feeContainer.data("qty") > 0) 680 { 681 feeContainer.data("qty", feeContainer.data("qty") - 1) 682 } 683 } 684 $(this).parent().find(".fee-qty-display h4").html(feeContainer.data("qty")) 685 let feeLabel = feeContainer.find(".extras-fee-info h5").first().text() || "Item" 686 announceQuantityChange(feeLabel.trim(), feeContainer.data("qty")) 687 }) 688} 689 690let applyFormEvents = () => 691{ 692 $(".form-group input, .form-group select").off() 693 $(".form-group input, .form-group select").each(function() 694 { 695 if($(this).val().length != 0) 696 { 697 $(this).closest(".form-group").addClass("filled") 698 } 699 }) 700 $(".form-group input, .form-group select").on("change", function() 701 { 702 if($(this).val() != "") 703 { 704 $(this).closest(".form-group").addClass("filled") 705 } 706 else 707 { 708 $(this).closest(".form-group").removeClass("filled") 709 } 710 }) 711} 712 713let applyConfirmMembershipClickEvents = () => 714{ 715 $(".purchase-path-membership-choice input[type='radio']").on("change", function() 716 { 717 $(".purchase-path-membership-choice input[type='radio']").not($(this)).removeAttr("checked") 718 }) 719 $("#membership-show-all").on("click", function (e) 720 { 721 e.preventDefault() 722 $(".purchase-path-membership").removeAttr("hidden") 723 $("#membership-show-all").attr("hidden", "") 724 }) 725} 726 727let applyMemberInfoFormEvents = () => 728{ 729 applyRemoveChildEvents() 730 if($("#add-a2-button").length > 0) 731 { 732 applyRemoveAdultEvents("a2") 733 } 734 applyRemoveAdultEvents("a3") 735 $("#add-child-button").on('click keydown', function(event) 736 { 737 if(event.type === 'click' || (event.type === 'keydown' && event.keyCode === 13)) 738 { 739 if(countChildren() < CONFIG.MAX_CHILDREN) 740 { 741 let nextChildNum = 1 742 if($(".membership-child").last().length > 0) 743 { 744 //nextChildNum = $(".membership-child").last().data("child") + 1 745 for(let i = 1;i <= CONFIG.MAX_CHILDREN;i++) 746 { 747 if(!$(`.membership-child[data-child="${i}"]`).length) 748 { 749 nextChildNum = i 750 break 751 } 752 } 753 } 754 $("#child-container").append( 755 `
756 <div class="uk-grid membership-child new-child" data-child="${nextChildNum}" uk-grid> 757 <div class="uk-width-1-1 uk-width-1-4@l"> 758 <div class="form-group"> 759 <label class="form-label" for="child-${nextChildNum}-first-name">First Name*</label> 760 <input id="child-${nextChildNum}-first-name" name="child-${nextChildNum}-first-name" class="form-input" type="text" required> 761 <div class="form-input-helper-text">Enter the first name of your child.</div> 762 </div> 763 </div> 764 <div class="uk-width-1-1 uk-width-1-4@l"> 765 <div class="form-group"> 766 <label class="form-label" for="child--last-name">Last Name*</label> 767 <input id="child-${nextChildNum}-last-name" name="child-${nextChildNum}-last-name" class="form-input" type="text" required> 768 <div class="form-input-helper-text">Enter the last name of your child.</div> 769 </div> 770 </div> 771 <div class="uk-width-1-1 uk-width-1-4@l"> 772 <div class="form-group "> 773 <label class="form-label" for="child-${nextChildNum}-birthdate">Date of Birth*</label> 774 <input id="child-${nextChildNum}-birthdate" name="child-${nextChildNum}-birthdate" class="form-input" type="date" required> 775 <div class="form-input-helper-text">MM / DD / YYYY</div> 776 </div> 777 </div> 778 <input type="hidden" name="child-${nextChildNum}-affiliation-id" value="0"> 779 <input type="hidden" name="child-${nextChildNum}-type" value="new"> 780 <input type="hidden" name="child-${nextChildNum}-removed" value="0"> 781 <input type="hidden" name="child-${nextChildNum}-id" value="0"> 782 <input type="hidden" name="child-${nextChildNum}-birthdate-exists" value="0"> 783 <div class="uk-width-1-1 uk-width-1-4@l uk-flex uk-flex-middle"> 784 <a class="membership-remove-button remove-child-button body-copy-small" data-child="${nextChildNum}">Remove Child</a> 785 </div> 786 </div> 787 `) 788 applyRemoveChildEvents() 789 } 790 else 791 { 792 displayStepError(`You can have a max of ${CONFIG.MAX_CHILDREN} children on your membership.`); 793 } 794 } 795 }) 796 if($("#add-a2-button").length > 0) 797 { 798 $("#add-a2-button").on('click keydown', function(event) 799 { 800 if(event.type === 'click' || (event.type === 'keydown' && event.keyCode === 13)) 801 { 802 $("#new-a2-container").removeAttr("hidden") 803 $("#add-a2-button-container").attr("hidden", "") 804 $("#remove-a2-button-container").removeAttr("hidden") 805 } 806 }) 807 } 808 $("#add-a3-button").on('click keydown', function(event) 809 { 810 if(event.type === 'click' || (event.type === 'keydown' && event.keyCode === 13)) 811 { 812 $("#new-a3-container").removeAttr("hidden") 813 $("#add-a3-button-container").attr("hidden", "") 814 $("#remove-a3-button-container").removeAttr("hidden") 815 } 816 }) 817 $(".preCheckoutFileUpload").on("change", function() 818 { 819 let fileName = $(this).val().split('\\').pop() 820 $('label[for="document"]').find("span").text(fileName) 821 $('label[for="document"]').addClass("complete") 822 }) 823} 824 825let applyRemoveAdultEvents = (key) => 826{ 827 $(`.remove-${key}-button`).off() 828 $(`.remove-${key}-button`).on("click keydown", function(event) 829 { 830 if(event.type === 'click' || (event.type === 'keydown' && event.keyCode === 13)) 831 { 832 if($(`#existing-${key}-container`).length > 0) 833 { 834 $(`#existing-${key}-container`).attr("hidden", "") 835 $(`#removed-${key}-container`).removeAttr("hidden") 836 } 837 $(`#new-${key}-container`).attr("hidden", "") 838 $(`#remove-${key}-button-container`).attr("hidden", "") 839 $(`#add-${key}-button-container`).removeAttr("hidden") 840 } 841 }) 842} 843 844let applyRemoveChildEvents = () => 845{ 846 $(`.remove-child-button`).off() 847 $(`.remove-child-button`).on("click keydown", function(event) 848 { 849 if(event.type === 'click' || (event.type === 'keydown' && event.keyCode === 13)) 850 { 851 let currentChild = $(this).closest(".membership-child") 852 if(currentChild.hasClass("new-child")) 853 { 854 // if it's a new child, just remove the entire thing 855 currentChild.remove() 856 } 857 else 858 { 859 currentChild.addClass("removed-child") 860 let childNum = currentChild.data("child") 861 let childFirstName = $(this).parent().parent().find("input[data-input-type='first']").val() 862 let childLastName = $(this).parent().parent().find("input[data-input-type='last']").val() 863 let affiliationId = $(this).parent().parent().find("input[data-input-type='affiliation']").val() 864 let id = $(this).parent().parent().find("input[data-input-type='id']").val() 865 // if it's an existing child, display the "reason" input 866 currentChild.html(` 867 <div class="uk-width-1-1"> 868 <div class="form-group"> 869 <label class="form-label" for="child-${childNum}-reason">Child Removal Reason (optional)</label> 870 <input id="child-${childNum}-reason" name="child-${childNum}-reason" class="form-input" type="text"> 871 <div class="form-input-helper-text">Child removal reason for ${childFirstName} ${childLastName}</div> 872 </div> 873 </div> 874 <input type="hidden" name="child-${childNum}-first-name" value="${childFirstName}"> 875 <input type="hidden" name="child-${childNum}-last-name" value="${childLastName}"> 876 <input type="hidden" name="child-${childNum}-affiliation-id" value="${affiliationId}"> 877 <input type="hidden" name="child-${childNum}-id" value="${id}"> 878 <input type="hidden" name="child-${childNum}-type" value="existing"> 879 <input type="hidden" name="child-${childNum}-removed" value="1"> 880 `) 881 applyFormEvents() 882 } 883 } 884 }) 885 applyFormEvents() 886} 887 888let countChildren = () => 889{ 890 return $(".membership-child:not(.removed-child)").length 891} 892 893let announceQuantityChange = (label, qty) => 894{ 895 const el = $("#purchase-path-qty-status") 896 el.text("") 897 setTimeout(() => el.text(`${label}, quantity ${qty}`), 100) 898} 899 900let displayStepError = (text) => 901{ 902 $(".next-step").removeClass("loading") 903 const errorContainer = $(`.error-message[data-step="${purchasePathStep}"]`) 904 errorContainer.find("span").html(text) 905 errorContainer.removeAttr("hidden") 906 const alert = $("#purchase-path-alert") 907 alert.text("") 908 setTimeout(() => alert.text(text), 100) 909} 910 911let clearStepError = () => 912{ 913 $(`.error-message[data-step="${purchasePathStep}"]`).attr("hidden", true) 914 $("#purchase-path-alert").text(""); 915} 916 917let applyGiftCertRecipientEvents = () => 918{ 919 $("#gift-cert-method").on("click keydown", function(event) 920 { 921 if(event.type === 'click' || (event.type === 'keydown' && event.keyCode === 13)) 922 { 923 let method = $(this).val() 924 $(`div[data-input-category='gift-cert-method']`).attr("hidden", "") 925 $(`div[data-section='${method}']`).removeAttr("hidden") 926 } 927 }) 928} 929 930// for select inputs that show/hide other fields/elements 931let applyDataToggle = () => 932{ 933 $(document).on("change click keydown", "[data-toggle='fields']", function(event) 934 { 935 if (event.type !== "change" && !(event.type === "click" || (event.type === "keydown" && event.keyCode === 13))) 936 { 937 return 938 } 939 let $ctrl = $(this) 940 let current = String($ctrl.val()) 941 942 let group = $ctrl.data("toggleGroup") 943 let groupAttr = $ctrl.data("targetGroupAttr") || "data-input-category" 944 let valueAttr = $ctrl.data("targetValueAttr") || "data-section" 945 946 if (!group) return 947 948 let $all = $(`[${groupAttr}='${group}']`) 949 // Hide everything in the group 950 $all.attr("hidden", "") 951 952 // Show items whose valueAttr matches the current select value 953 let $show = $all.filter(function() 954 { 955 return $(this).attr(valueAttr) === current 956 }) 957 $show.removeAttr("hidden") 958 }) 959 960 $("[data-toggle='fields']").each(function() 961 { 962 $(this).trigger("change") 963 }) 964} 965 966let applyDataCopy = () => 967{ 968 let doCopy = (group, sourcePrefix, targetPrefix, suffixes) => 969 {
970 suffixes.forEach(suffix => 971 { 972 let sourceName = `${sourcePrefix}${suffix}` 973 let targetName = `${targetPrefix}${suffix}` 974 975 let $source = $(`[name='${sourceName}']`) 976 let $target = $(`[name='${targetName}']`) 977 978 if ($source.length && $target.length) 979 { 980 if ($source.is(":checkbox")) 981 { 982 $target.prop("checked", $source.prop("checked")).trigger("change") 983 } 984 else if ($source.is(":radio")) 985 { 986 let val = $source.filter(":checked").val() 987 $target.filter(`[value='${val ?? ""}']`).prop("checked", true).trigger("change") 988 } 989 else 990 { 991 $target.val($source.val()).trigger("change") 992 } 993 } 994 }) 995 } 996 997 let unbindLive = group => 998 { 999 $(`[data-copy-source-listener='${group}']`).each(function() 1000 { 1001 $(this).off(".copy_" + group).removeAttr("data-copy-source-listener") 1002 }) 1003 } 1004 1005 let bindLive = (group, sourcePrefix, targetPrefix, suffixes) => 1006 { 1007 suffixes.forEach(suffix => 1008 { 1009 let sourceName = `${sourcePrefix}${suffix}` 1010 let $source = $(`[name='${sourceName}']`) 1011 if ($source.length) 1012 { 1013 $source.attr("data-copy-source-listener", group) 1014 .on(`input.copy_${group} change.copy_${group}`, function() 1015 { 1016 // Mirror only while the checkbox is still checked 1017 let $toggle = $(`[data-copy='fields'][data-copy-group='${group}']`) 1018 if (!$toggle.is(":checked")) return 1019 1020 let targetName = `${targetPrefix}${suffix}` 1021 let $target = $(`[name='${targetName}']`) 1022 1023 if ($(this).is(":checkbox")) 1024 { 1025 $target.prop("checked", $(this).prop("checked")).trigger("change") 1026 } 1027 else if ($(this).is(":radio")) 1028 { 1029 let val = $source.filter(":checked").val() 1030 $target.filter(`[value='${val ?? ""}']`).prop("checked", true).trigger("change") 1031 } 1032 else 1033 { 1034 $target.val($(this).val()).trigger("change") 1035 } 1036 }) 1037 } 1038 }) 1039 } 1040 1041 let setLocked = (targetPrefix, suffixes, locked) => 1042 { 1043 suffixes.forEach(suffix => 1044 { 1045 let targetName = `${targetPrefix}${suffix}` 1046 let $target = $(`[name='${targetName}']`) 1047 if ($target.is(":checkbox,:radio")) 1048 { 1049 $target.prop("disabled", locked) 1050 } 1051 else 1052 { 1053 $target.prop("readonly", locked) 1054 } 1055 }) 1056 } 1057 1058 $(document).on("change", "[data-copy='fields']", function() 1059 { 1060 let $cb = $(this) 1061 let group = $cb.data("copyGroup") 1062 if (!group) return 1063 1064 let sourcePrefix = $cb.data("copySourcePrefix") || "" 1065 let targetPrefix = $cb.data("copyTargetPrefix") || "" 1066 let suffixes = String($cb.data("copyFields") || "") 1067 .split(",") 1068 .map(s => s.trim()) 1069 .filter(Boolean) 1070 1071 unbindLive(group) 1072 1073 if ($cb.is(":checked")) 1074 { 1075 doCopy(group, sourcePrefix, targetPrefix, suffixes) 1076 bindLive(group, sourcePrefix, targetPrefix, suffixes) 1077 setLocked(targetPrefix, suffixes, true) 1078 } 1079 else 1080 { 1081 setLocked(targetPrefix, suffixes, false) 1082 } 1083 }) 1084 1085 $("[data-copy='fields']").each(function() 1086 { 1087 $(this).trigger("change") 1088 }) 1089} 1090 1091let applyAttendeeEvents = () => 1092{ 1093 $(".attendee-select").on("change", function() 1094 { 1095 let attendeeId = $(this).data("attendee") 1096 if($(this).val() == "other") 1097 { 1098 $(`.form-group[data-attendee="${attendeeId}"]`).removeAttr("hidden") 1099 } 1100 else 1101 { 1102 $(`.form-group[data-attendee="${attendeeId}"]`).attr("hidden", true) 1103 } 1104 }) 1105} 1106 1107let applyClearPromoCodeEvents = () => 1108{ 1109 $(".clear-promo").on("click", async function() 1110 { 1111 try 1112 { 1113 await $.ajax({ 1114 method: "GET", 1115 url: CONFIG.URLS.clearPromo, 1116 dataType: "json", 1117 }) 1118 getCartSidebar() 1119 $(".promo-applied").html("") 1120 } 1121 catch (err) 1122 { 1123 displayStepError("There was a problem clearing your promo code. Please try again later."); 1124 } 1125 }) 1126} 1127 1128 1129let applyClearGiftCertEvents = () => 1130{ 1131 $(".clear-gift-cert").on("click", async function() 1132 { 1133 let giftCertCode = $(this).data("giftCertCode") 1134 let formData = new FormData() 1135 formData.append("code", giftCertCode) 1136 try 1137 { 1138 await $.ajax({ 1139 method: "POST", 1140 url: CONFIG.URLS.removeGiftCert, 1141 data: formData, 1142 processData: false, 1143 contentType: false, 1144 }) 1145 getCartSidebar() 1146 $(".gift-cert-applied").html("") 1147 } 1148 catch (err) 1149 { 1150 displayStepError("There was a problem removing your gift certificate. Please try again later."); 1151 } 1152 }) 1153} 1154 1155let applyDonationSelectEvents = () => 1156{ 1157 $('input[type="radio"][name="checkout-donate"]').on("change", async function(e) 1158 { 1159 let formData = new FormData() 1160 let amount = $(this).val() 1161 formData.append("donation", amount) 1162 try 1163 { 1164 const data = await $.ajax({ 1165 method: "POST", 1166 url: CONFIG.URLS.donation, 1167 data: formData, 1168 processData: false, 1169 contentType: false, 1170 dataType: "json", 1171 }) 1172 if(data.nextStep) 1173 { 1174 getCartSidebar() 1175 analytics.addToCart([{ item_name: "Checkout Donation" }], amount) 1176 } 1177 } 1178 catch (err) 1179 { 1180 displayStepError("There was a problem saving your donation selection. Please try again later."); 1181 } 1182 }) 1183} 1184 1185let applyPromoCodeEvents = () => 1186{ 1187 $(".submit-promo").on("click", async function(e) 1188 { 1189 e.preventDefault() 1190 const button = $(this) 1191 button.addClass("loading") 1192 let formGroup = button.closest(".form-group") 1193 let input = formGroup.find(".promo-code-input") 1194 if(input.val().length > 0) 1195 { 1196 let fd = new FormData(); 1197 fd.append('promo', input.val()) 1198 try 1199 { 1200 const data = await $.ajax({ 1201 method: "POST", 1202 url: CONFIG.URLS.promo, 1203 processData: false, 1204 contentType: false, 1205 dataType: "json", 1206 data: fd 1207 }) 1208 if(data.successPromoCode) 1209 { 1210 getCartSidebar() 1211 let html = $("#clear-promo-html").html() 1212 $(".promo-applied").html(` 1213 <div class="promo-code-tag button">
1214 <span>${input.val()}</span> 1215 ${html} 1216 </div> 1217 `) 1218 applyClearPromoCodeEvents() 1219 button.removeClass("loading") 1220 } 1221 else if(data.error) 1222 { 1223 formGroup.find(".form-input-helper-text").html(data.error) 1224 formGroup.addClass("error") 1225 } 1226 } 1227 catch (err) 1228 { 1229 button.removeClass("loading") 1230 displayStepError("There was a problem applying your promo code. Please try again later"); 1231 } 1232 } 1233 else 1234 { 1235 button.removeClass("loading") 1236 displayStepError("Invalid promo code."); 1237 } 1238 }) 1239 applyClearPromoCodeEvents() 1240} 1241 1242let applyGiftCertCodeEvents = () => 1243{ 1244 $(".submit-gift-cert").on("click", async function(e) 1245 { 1246 e.preventDefault() 1247 const button = $(this) 1248 let formGroup = button.closest(".form-group") 1249 let input = formGroup.find(".gift-cert-input").val() 1250 if(input.includes("#")) 1251 { 1252 input = input.replace("#", "") 1253 } 1254 if(input.length > 0) 1255 { 1256 button.addClass("loading") 1257 let fd = new FormData(); 1258 fd.append('gift-cert', input) 1259 try 1260 { 1261 const data = await $.ajax({ 1262 method: "POST", 1263 url: CONFIG.URLS.giftCert, 1264 processData: false, 1265 contentType: false, 1266 dataType: "json", 1267 data: fd 1268 }) 1269 if(data.successGiftCert) 1270 { 1271 getCartSidebar() 1272 if(data.giftCerts) 1273 { 1274 $(".gift-cert-applied").html("") 1275 for (const [key, value] of Object.entries(data.giftCerts)) 1276 { 1277 $(".gift-cert-applied").append(` 1278 <div class="gift-cert-tag button"> 1279 <span>#${value.GiftCertificateNumber} - $${value.Amount}</span> 1280 <span class="clear-gift-cert" data-gift-cert-code="${value.GiftCertificateNumber}"> 1281 <svg width="10" height="11" viewB
1281ox="0 0 10 11" fill="none" xmlns="http://www.w3.org/2000/svg"> 1282 <path d="M8.07129 1.96484L1.00022 9.03591" stroke="#08245E" style="stroke:#08245E;stroke:color(display-p3 0.0314 0.1412 0.3686);stroke-opacity:1;" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> 1283 <path d="M1.00098 1.96484L8.07204 9.03591" stroke="#08245E" style="stroke:#08245E;stroke:color(display-p3 0.0314 0.1412 0.3686);stroke-opacity:1;" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> 1284 </svg> 1285 </span> 1286 </div> 1287 `) 1288 } 1289 } 1290 applyClearGiftCertEvents() 1291 button.removeClass("loading") 1292 } 1293 else if(data.error) 1294 { 1295 button.removeClass("loading") 1296 displayStepError(data.error); 1297 } 1298 } 1299 catch (err) 1300 { 1301 button.removeClass("loading") 1302 displayStepError("There was a problem applying your gift certificate code. Please try again later"); 1303 } 1304 } 1305 }) 1306 applyClearGiftCertEvents() 1307} 1308 1309let applyTicketCalendar = html => 1310{ 1311 $("#ticket-calendar-container").html(html) 1312 $("#ticket-calendar-container").fadeTo(1000, 100, function(){}) 1313 applyCalendarClickEvents() 1314 if ($(window).width() < CONFIG.MOBILE_BREAKPOINT) 1315 { 1316 $(".purchase-path-ticket-calendar .day .calendar-day-button").addClass("mobile") 1317 } 1318 else 1319 { 1320 $(".purchase-path-ticket-calendar .day .calendar-day-button").removeClass("mobile") 1321 } 1322 if($(".calendar-day-button.mobile").length > 0 && $(".calendar-day-button.selected").length == 0) 1323 { 1324 $(".calendar-day-button").first().focus() 1325 $(".calendar-day-button").first().trigger("click") 1326 } 1327 else if($(".calendar-day-button.mobile").length > 0 && $(".calendar-day-button.selected").length > 0) 1328 { 1329 $(".calendar-day-button.selected").first().focus() 1330 $(".calendar-day-button.selected").first().trigger("click") 1331 } 1332} 1333 1334let applyReviewAmountEvents = () => 1335{ 1336 $(".gift-cert-amount-button").on("click", function(e) 1337 { 1338 e.preventDefault() 1339 $("#amount").val($(this).data("amount")) 1340 $("#amount").parent().addClass("filled") 1341 }) 1342} 1343 1344// MARK: step setups 1345 1346const STEP_SETUP = { 1347 selectDays: () => 1348 { 1349 applyCalendarClickEvents() 1350 applyNextStepButtonClickEvents() 1351 1352 // selecting date automatically if in url params 1353 const urlParams = new URLSearchParams(window.location.search) 1354 const dateParam = urlParams.get('date') 1355 if(dateParam) 1356 { 1357 const parts = dateParam.split('-') 1358 if(parts.length === 3) 1359 { 1360 const targetMonth = parseInt(parts[0], 10) 1361 const targetDay = parseInt(parts[1], 10) 1362 const targetYear = parseInt(parts[2], 10) 1363 1364 const $dayBtn = $(".calendar-day-button").filter(function() 1365 { 1366 return $(this).data("day") == targetDay && 1367 $(this).data("month") == targetMonth && 1368 $(this).data("year") == targetYear 1369 }) 1370 1371 if($dayBtn.length && !$dayBtn.hasClass("closed") && !$dayBtn.hasClass("selected")) 1372 { 1373 $dayBtn.trigger("click") 1374 if($dayBtn.hasClass("selected")) 1375 { 1376 $(".next-step").trigger("click") 1377 } 1378 } 1379 } 1380 } 1381 }, 1382 reviewAmount: () => 1383 { 1384 applyFormEvents() 1385 applyNextStepButtonClickEvents() 1386 applyReviewAmountEvents() 1387 }, 1388 recipientInfo: () => 1389 { 1390 applyFormEvents() 1391 applyGiftCertRecipientEvents() 1392 applyNextStepButtonClickEvents() 1393 }, 1394 selectTickets: () => 1395 { 1396 applyTicketSelectEvents() 1397 applyNextStepButtonClickEvents() 1398 }, 1399 bulkTicketsSelect: () => 1400 { 1401 applyTicketSelectEvents() 1402 applyNextStepButtonClickEvents() 1403 }, 1404 addExtras: () => 1405 { 1406 applyTicketSelectEvents() 1407 applyExtraCollapseEvents() 1408 applyFeeSelectEvents() 1409 applyNextStepButtonClickEvents() 1410 }, 1411 confirmMembership: () => 1412 { 1413 applyConfirmMembershipClickEvents() 1414 applyNextStepButtonClickEvents() 1415 }, 1416 memberInfo: () => 1417 { 1418 applyMemberInfoFormEvents() 1419 applyFormEvents() 1420 applyNextStepButtonClickEvents() 1421 }, 1422 checkout: () => 1423 { 1424 applyFormEvents() 1425 applyNextStepButtonClickEvents() 1426 applyPromoCodeEvents() 1427 applyGiftCertCodeEvents() 1428 applyDonationSelectEvents() 1429 applyAttendeeEvents() 1430 analytics.beginCheckout() 1431 }, 1432 reviewCart: () => 1433 { 1434 applyFeeSelectEvents() 1435 applyTicketSelectEvents() 1436 applyNextStepButtonClickEvents() 1437 }, 1438} 1439 1440const STEP_ADVANCE = { 1441 selectDays: () => 1442 { 1443 let days = collectSelectedDays() 1444 if(days && days["selectedDays"]) 1445 { 1446 advanceStep("selectDays", days) 1447 } 1448 else 1449 { 1450 displayStepError("No days selected.") 1451 } 1452 }, 1453 selectTickets: () => 1454 { 1455 let tickets = collectSelectedTickets() 1456 if(tickets) 1457 { 1458 for (const [key, value] of Object.entries(tickets.selectedTickets)) 1459 { 1460 analytics.addToCart([{ 1461 item_id: value.performanceId, 1462 item_category: "Performance", 1463 item_category2: "Admission", 1464 quantity: value.qty 1465 }], value.price) 1466 } 1467 advanceStep("selectTickets", tickets) 1468 } 1469 else 1470 { 1471 displayStepError("No tickets selected.") 1472 } 1473 }, 1474 bulkTicketsSelect: () => 1475 { 1476 let tickets = collectSelectedTickets() 1477 if(tickets) 1478 { 1479 for (const [key, value] of Object.entries(tickets.selectedTickets)) 1480 { 1481 analytics.addToCart([{ 1482 item_id: value.performanceId, 1483 item_category: "Performance", 1484 item_category2: "Admission - Bulk", 1485 quantity: value.qty 1486 }], value.price) 1487 } 1488 advanceStep("bulkTicketsSelect", tickets) 1489 } 1490 }, 1491 addExtras: () => 1492 { 1493 let extras = collectSelectedExtras() 1494 if(extras) 1495 { 1496 for (const [key, value] of Object.entries(extras.selectedExtras)) 1497 { 1498 analytics.addToCart([{ 1499 item_id: value.performanceId, 1500 item_category: "Performance", 1501 item_category2: "Event", 1502 quantity: value.qty 1503 }], value.price) 1504 } 1505 for (const [key, value] of Object.entries(extras.selectedFees)) 1506 { 1507 if(!value.preselected) 1508 { 1509 analytics.addToCart([{ 1510 item_id: value.feeId, 1511 item_category: "Fee", 1512 item_name: value.description, 1513 quantity: value.qty 1514 }], value.price * value.qty) 1515 } 1516 } 1517 advanceStep("addExtras", extras) 1518 } 1519 }, 1520 reviewAmount: () => 1521 { 1522 let amount = false 1523 if($("#amount").val().length > 0 && !isNaN($("#amount").val())) 1524 { 1525 amount = $("#amount").val() 1526 } 1527 else 1528 { 1529 displayStepError("Amount is required.") 1530 } 1531 if(amount) 1532 { 1533 analytics.addToCart([{ 1534 item_category: "Gift Certificate", 1535 item_name: "Gift Certificate" 1536 }], amount) 1537 advanceStep("reviewAmount", {amount: amount}) 1538 } 1539 }, 1540 confirmMembership: () => 1541 { 1542 let membership = collectSelectedMembership() 1543 analytics.addToCart([{ 1544 item_id: membership.membershipId, 1545 item_category: "Membership", 1546 item_name: membership.description 1547 }], membership.price) 1548 if(membership) 1549 { 1550 advanceStep("confirmMembership", membership) 1551 } 1552 else 1553 { 1554 displayStepError("Select a membership to continue.") 1555 } 1556 }, 1557 memberInfo: () => 1558 { 1559 let file = false 1560 let memberInfoForm = collectMemberInfoForm() 1561 if($(".preCheckoutFileUpload").length) 1562 { 1563 const files = $(".preCheckoutFileUpload")[0].files 1564 if (files.length > 0) 1565 { 1566 file = files[0] 1567 } 1568 else 1569 { 1570 displayStepError("File upload is required.") 1571 return 1572 } 1573 } 1574 if(memberInfoForm) 1575 { 1576 advanceStep("memberInfo", memberInfoForm, { 1577 file, 1578 onErrors(errors) { 1579 processFormErrors(errors) 1580 if (errors.document) displayStepError(errors.document) 1581 } 1582 }) 1583 } 1584 else 1585 { 1586 displayStepError("Correct the errors above before continuing.") 1587 } 1588 }, 1589 recipientInfo: () => 1590 { 1591 let giftCertForm = collectGiftCertForm() 1592 if(giftCertForm) 1593 { 1594 advanceStep("recipientInfo", giftCertForm, {
1595 onErrors: processFormErrors 1596 }) 1597 } 1598 else 1599 { 1600 displayStepError("Please correct the errors above.") 1601 } 1602 }, 1603 checkout: async (nextButton) => 1604 { 1605 let checkoutForm = collectCheckoutForm() 1606 if(checkoutForm) 1607 { 1608 const data = await sendStepData("checkout", checkoutForm) 1609 if (!data) return 1610 if(data.giftCertApplied) 1611 { 1612 $("#gift-cert-group").addClass("valid") 1613 $("#gift-cert-group input").prop("disabled", true) 1614 $("#gift-cert-group .form-input-helper-text").html("Gift certificate applied") 1615 } 1616 if(data.checkoutOk) 1617 { 1618 if(data.invoice && 1619 (purchasePath == "bulkTickets" || purchasePath == "bulkHauntedHouse")) 1620 { 1621 window.location = CONFIG.URLS.thankYou; 1622 } 1623 else 1624 { 1625 window.location = CONFIG.URLS.payment; 1626 } 1627 } 1628 else if(data.errors) 1629 { 1630 processFormErrors(data.errors) 1631 if(data.errors.mustReload) 1632 { 1633 nextButton.addClass("disabled") 1634 nextButton.attr("disabled", true) 1635 } 1636 $(".next-step").removeClass("loading") 1637 } 1638 } 1639 else 1640 { 1641 $(".next-step").removeClass("loading") 1642 } 1643 }, 1644} 1645STEP_ADVANCE.reviewCart = STEP_ADVANCE.addExtras 1646 1647// MARK: applyStep 1648 1649let applyStep = data => 1650{ 1651 $(`.step-container[data-step="${data.step}"]`).html(data.html) 1652 const setup = STEP_SETUP[purchasePathStep] 1653 if (setup) setup() 1654 if(CONFIG.PATHS_WITH_EXPIRATION.includes(purchasePath)) 1655 { 1656 startCartTimer({ 1657 container: "#purchase-path-container", 1658 onError: displayStepError, 1659 beforeExpire: () => 1660 { 1661 if($("#cart-container").hasClass("open")) 1662 { 1663 toggleMobileSummary() 1664 } 1665 }, 1666 }) 1667 applyDataToggle() 1668 applyDataCopy() 1669 } 1670 getCartSidebar() 1671} 1672 1673let smoothScrollToElement = element => 1674{ 1675 $("html, body").animate( 1676 { 1677 scrollTop: $(element).offset().top - 200 1678 }, 500) 1679} 1680 1681let updateStep = step => 1682{ 1683 purchasePathStep = step 1684 const activeStep = $(`.purchase-path-step[data-step="${step}"]`) 1685 $(`.purchase-path-step:not([data-step="${step}"])`).addClass("collapsed") 1686 activeStep.removeClass("collapsed") 1687 $(`.purchase-path-step`).removeClass("completed") 1688 $(`.purchase-path-step[data-step="${step}"]`).prevAll('.purchase-path-step').addClass('completed') 1689 $(`.purchase-path-step`).removeClass("locked").removeClass("show-message") 1690 $(`.purchase-path-step[data-step="${step}"]`).nextAll('.purchase-path-step').addClass('locked') 1691 $(`.purchase-path-step .purchase-path-step-header`).removeAttr("role tabindex") 1692 $(`.purchase-path-step.locked .purchase-path-step-header`).attr({"role": "button", "tabindex": 0}) 1693 $(`.stepper-item`).removeClass("completed").removeClass("active").removeAttr("aria-current").removeAttr("role tabindex") 1694 $(`.stepper-item[data-step="${step}"]`).addClass("active").attr("aria-current", "step") 1695 $(`.stepper-item[data-step="${step}"]`).prevAll('.stepper-item').addClass('completed').attr({"role": "button", "tabindex": 0}) 1696 smoothScrollToElement(activeStep) 1697 const stepTitle = activeStep.find(".purchase-path-step-header h2").text() 1698 if (stepTitle) 1699 { 1700 const status = $("#purchase-path-status") 1701 status.text("") 1702 setTimeout(() => status.text(`Now on step: ${stepTitle}`), 100) 1703 } 1704} 1705 1706let jumpToStepInFlight = false 1707 1708let jumpToStep = async (targetStep) => 1709{ 1710 if (!targetStep || targetStep === purchasePathStep || jumpToStepInFlight) return 1711 jumpToStepInFlight = true 1712 let formData = new FormData() 1713 formData.append("targetStep", targetStep) 1714 try 1715 { 1716 const data = await $.ajax({ 1717 method: "POST", 1718 url: CONFIG.URLS.stepSave, 1719 data: formData, 1720 processData: false, 1721 contentType: false, 1722 dataType: "json", 1723 }) 1724 if (data && data.nextStep) 1725 { 1726 updateStep(data.nextStep) 1727 const stepData = await fetchNextStep() 1728 applyStep(stepData) 1729 const $activeStep = $(`.purchase-path-step[data-step="${data.nextStep}"]`) 1730 const $heading = $activeStep.find(".purchase-path-step-header h2").first() 1731 if ($heading.length) 1732 { 1733 $heading.attr("tabindex", "-1").focus() 1734 } 1735 } 1736 } 1737 catch (err) 1738 { 1739 displayStepError("There was a problem processing your request. Please try again later.") 1740 } 1741 finally 1742 { 1743 jumpToStepInFlight = false 1744 } 1745} 1746 1747let calendarSwitchView = async () => 1748{ 1749 try 1750 { 1751 const data = await $.ajax({ 1752 method: "GET", 1753 url: CONFIG.URLS.switchView, 1754 dataType: "json", 1755 }) 1756 calendarView = data.view 1757 if($(".calendar-day-button.selected").length > 0) 1758 { 1759 let days = collectSelectedDays() 1760 if(days && days["selectedDays"]) 1761 { 1762 await sendStepData("selectDays", days, false) 1763 const data = await fetchNextStep() 1764 applyStep(data) 1765 } 1766 } 1767 else 1768 { 1769 const data = await fetchNextStep() 1770 applyStep(data) 1771 } 1772 } 1773 catch (err) 1774 { 1775 displayStepError("There was a problem switching the calendar view. Please try again later."); 1776 } 1777} 1778 1779// MARK: collect funcs 1780 1781let collectSelectedDays = () => 1782{ 1783 let data = {} 1784 if($(".calendar-day-button.selected").length > 0) 1785 { 1786 data.selectedDays = {} 1787 $(`.calendar-day-button.selected`).each(function() 1788 { 1789 if(!(`${$(this).data("month")}-${$(this).data("day")}-${$(this).data("year")}` in data.selectedDays)) 1790 { 1791 data.selectedDays[`${$(this).data("month")}-${$(this).data("day")}-${$(this).data("year")}`] = [] 1792 } 1793 data.selectedDays[`${$(this).data("month")}-${$(this).data("day")}-${$(this).data("year")}`].push({ 1794 day: $(this).data("day"), 1795 month: $(this).data("month"), 1796 year: $(this).data("year"), 1797 subType: $(this).data("subType") ? $(this).data("subType") : false, 1798 performanceId: $(this).data("performanceId"), 1799 freeDay: $(this).data("freeDay"), 1800 start: $(this).data("start"), 1801 end: $(this).data("end") 1802 }) 1803 }) 1804 return data 1805 } 1806 return false 1807} 1808 1809let collectSelectedTickets = () => 1810{ 1811 let data = {} 1812 data["selectedTickets"] = [] 1813 let ticketQty = 0 1814 $(".ticket-container").each(function() 1815 { 1816 if($(this).data("qty") > 0 && $(this).is(":visible")) 1817 { 1818 data["selectedTickets"].push({ 1819 priceTypeId: $(this).data("priceTypeId"), 1820 performanceId: $(this).data("performanceId"), 1821 qty: $(this).data("qty"), 1822 price: $(this).data("price") * $(this).data("qty") 1823 }) 1824 ticketQty += parseInt($(this).data("qty")) 1825 } 1826 }) 1827 if(ticketQty > CONFIG.BULK_MAX_TICKETS && (purchasePath == "bulkTickets" || purchasePath == "bulkHauntedHouse")) 1828 { 1829 displayStepError(`You can purchase a maximum of ${CONFIG.BULK_MAX_TICKETS} tickets.`); 1830 } 1831 else if(ticketQty < CONFIG.BULK_MIN_TICKETS && purchasePath == "bulkTickets") 1832 { 1833 displayStepError(`You must purchase at least ${CONFIG.BULK_MIN_TICKETS} tickets.`); 1834 } 1835 else if(ticketQty < CONFIG.BULK_HH_MIN_TICKETS && purchasePath == "bulkHauntedHouse") 1836 { 1837 displayStepError(`You must purchase at least ${CONFIG.BULK_HH_MIN_TICKETS} tickets.`); 1838 } 1839 else if(ticketQty > 0) 1840 { 1841 return data 1842 } 1843 return false 1844} 1845 1846let collectSelectedExtras = () => 1847{ 1848 let data = {} 1849 data["selectedExtras"] = [] 1850 data["selectedFees"] = [] 1851 let ticketQty = 0 1852 $(`.ticket-container`).each(function() 1853 { 1854 data["selectedExtras"].push({ 1855 priceTypeId: $(this).data("priceTypeId"), 1856 performanceId: $(this).data("performanceId"), 1857 qty: $(this).data("qty"), 1858 price: $(this).data("price") * $(this).data("qty") 1859 }) 1860 ticketQty++ 1861 }) 1862 $(`.extras-fee`).each(function() 1863 { 1864 data["selectedFees"].push({ 1865 associatedPerf: $(this).data("associatedPerf"), 1866 feeId: $(this).data("feeId"), 1867 qty: $(this).data("qty"), 1868 date: $(this).data("date"), 1869 description: $(this).data("description"), 1870 price: $(this).data("price"), 1871 preselected: $(this).data("preselected") ? true : false 1872 }) 1873 }) 1874 return data 1875} 1876 1877let collectSelectedMembership = () => 1878{ 1879 let membership = {} 1880 $(".purchase-path-membership input[type='radio']").each(function() 1881 { 1882 if($(this).is(":checked") && Object.keys(membership).length == 0) 1883 { 1884 membership.fundId = $(this).data("fundId") 1885 membership.membershipId = $(this).data("membershipId") 1886 membership.description = $(this).data("membershipDescription") 1887 membership.price = $(this).data("price") 1888 if($(`input[type="number"][data-membership-id="${$(this).data("membershipId")}"]`).length) 1889 { 1890 membership.guildCustomPrice = parseInt($(`input[type="number"][data-membership-id="${$(this).data("membershipId")}"]`).val()) 1891 } 1892 else if($(`input[type="number"][data-membership-id-2="${$(this).data("membershipId")}"]`).length) 1893 { 1894 membership.guildCustomPrice = parseInt($(`input[type="number"][data-membership-id-2="${$(this).data("membershipId")}"]`).val()) 1895 } 1896 } 1897 }) 1898 return membership 1899} 1900 1901let collectFormInputs = (containerSelector, inputsKey, options = {}) => 1902{ 1903 let inputs = {} 1904 let errors = 0 1905 inputs[inputsKey] = {} 1906 const exclusions = options.checkboxExclusions || [] 1907 1908 $(`${containerSelector} input, ${containerSelector} select`).each(function() 1909 { 1910 const type = $(this).attr("type") 1911 1912 if(options.skipFileInputs && type == "file") 1913 { 1914 return 1915 } 1916 1917 if(type == "checkbox" && !exclusions.includes($(this).attr("id"))) 1918 { 1919 if($(this).is(":checked")) 1920 { 1921 if(typeof inputs[inputsKey][$(this).attr("name")] == "undefined") 1922 { 1923 inputs[inputsKey][$(this).attr("name")] = [] 1924 } 1925 inputs[inputsKey][$(this).attr("name")].push($(this).val()) 1926 } 1927 } 1928 else 1929 { 1930 if($(this).attr("required") && $(this).val().trim().length == 0 && $(this).is(":visible")) 1931 { 1932 $(this).parent().addClass("error") 1933 if(options.scrollToFirstError && errors == 0) 1934 { 1935 smoothScrollToElement($(this).parent()) 1936 } 1937 if(!$(this).parent().find(".form-input-helper-text").data("helperText")) 1938 { 1939 $(this).parent().find(".form-input-helper-text").data("helperText", $(this).parent().find(".form-input-helper-text").html()) 1940 } 1941 $(this).parent().find(".form-input-helper-text").html("This field cannot be blank") 1942 errors++ 1943 } 1944 else if($(this).is(":visible") || 1945 (type == "hidden" && (options.strictVisibility ? $(this).parent().is(":visible") : true)) || 1946 (options.strictVisibility && $(this).data("inputForce"))) 1947 { 1948 inputs[inputsKey][$(this).attr("name")] = $(this).val() 1949 } 1950 if($(this).parent().find(".form-input-helper-text").data("helperText") && $(this).val().trim().length > 0 && type != "hidden") 1951 { 1952 $(this).parent().removeClass("error") 1953 $(this).parent().find(".form-input-helper-text").html($(this).parent().find(".form-input-helper-text").data("helperText")) 1954 } 1955 } 1956 }) 1957 if(errors == 0) 1958 {
1959 return inputs 1960 } 1961 return false 1962} 1963 1964let collectMemberInfoForm = () => collectFormInputs("#member-info", "member-info-inputs", { 1965 skipFileInputs: true, 1966 checkboxExclusions: ["member-email-opt-in"], 1967 scrollToFirstError: true, 1968 strictVisibility: true 1969}) 1970 1971let collectGiftCertForm = () => collectFormInputs("#gift-cert", "gift-cert-inputs") 1972 1973let collectCheckoutForm = () => 1974{ 1975 let inputs = {} 1976 let errors = 0 1977 inputs["checkout-inputs"] = {} 1978 1979 $("#checkout input, #checkout select, #checkout textarea").each(function() 1980 { 1981 const element = $(this) 1982 const name = element.attr("name") 1983 if (!name) return 1984 1985 const type = (element.attr("type") || "").toLowerCase() 1986 const visible = element.is(":visible") 1987 const required = !!element.prop("required") 1988 1989 let raw = element.val() 1990 let value = Array.isArray(raw) ? raw : (raw == null ? "" : String(raw).trim()) 1991 const isEmpty = Array.isArray(value) ? value.length === 0 : value === "" 1992 1993 if (required && isEmpty && visible) 1994 { 1995 const parent = element.parent() 1996 parent.addClass("error") 1997 1998 const helper = parent.find(".form-input-helper-text") 1999 if (!helper.data("helperText")) 2000 { 2001 helper.data("helperText", helper.html()) 2002 } 2003 helper.html("This field cannot be blank") 2004 errors++ 2005 } 2006 else if (visible || type === "hidden") 2007 { 2008 if (element.is(":checkbox") || element.is(":radio")) 2009 { 2010 if (this.checked) 2011 { 2012 inputs["checkout-inputs"][name] = element.val() 2013 } 2014 } 2015 else 2016 { 2017 inputs["checkout-inputs"][name] = Array.isArray(raw) ? raw : element.val() 2018 } 2019 } 2020 if (element.parent().find(".form-input-helper-text").data("helperText") && !isEmpty && type !== "hidden") 2021 { 2022 const parent = element.parent() 2023 const helper = parent.find(".form-input-helper-text") 2024 parent.removeClass("error") 2025 helper.html(helper.data("helperText")) 2026 } 2027 }) 2028 if (errors === 0) 2029 { 2030 return inputs 2031 } 2032 2033 displayStepError("Correct the errors above before continuing.") 2034 return false 2035} 2036 2037 2038let processFormErrors = errors => 2039{ 2040 let errorCount = 0 2041 for (const [key, value] of Object.entries(errors)) 2042 { 2043 if(key == "step") 2044 { 2045 displayStepError(value) 2046 continue 2047 } 2048 let input = $(`input[name="${key}"]`) 2049 if(input.length == 0 && $(`select[name="${key}"]`).length > 0) 2050 { 2051 input = $(`select[name="${key}"]`) 2052 } 2053 if(input.length > 0) 2054 { 2055 input.parent().addClass("error") 2056 input.parent().find(".form-input-helper-text").html(value) 2057 if(errorCount == 0) 2058 { 2059 smoothScrollToElement(input.parent()) 2060 } 2061 errorCount++ 2062 } 2063 } 2064} 2065 2066let applySidebarClickEvents = () => 2067{ 2068 $(".item .handle").on("click keydown", function(event) 2069 { 2070 if(event.type === 'click' || (event.type === 'keydown' && event.keyCode === 13)) 2071 { 2072 if($(this).parent().hasClass("collapsed")) 2073 { 2074 $(this).parent().removeClass("collapsed") 2075 } 2076 else 2077 { 2078 $(this).parent().addClass("collapsed") 2079 } 2080 } 2081 }) 2082 $(".actions .remove, .purchase-path-review-cart-event .remove").on("click keydown", async function(event) 2083 { 2084 if(event.type === 'click' || (event.type === 'keydown' && event.keyCode === 13)) 2085 { 2086 showSpinner($(".item-container")) 2087 let item = {} 2088 let formData = new FormData() 2089 for (const [key, value] of Object.entries($(this).data())) 2090 { 2091 formData.append(key, value) 2092 item[key] = value 2093 } 2094 try 2095 { 2096 const data = await $.ajax({ 2097 method: "POST", 2098 url: CONFIG.URLS.removeItem, 2099 data: formData, 2100 processData: false, 2101 contentType: false, 2102 dataType: "json", 2103 }) 2104 analytics.removeFromCart([{ 2105 item_id: item.itemId, 2106 item_category: item.itemType, 2107 quantity: item.qty 2108 }]) 2109 if(data.nextStep) 2110 { 2111 updateStep(data.nextStep) 2112 const stepData = await fetchNextStep() 2113 applyStep(stepData) 2114 } 2115 } 2116 catch (err) 2117 { 2118 displayStepError("There was a problem removing this item. Please try again later."); 2119 } 2120 } 2121 }) 2122} 2123 2124let lastCartHtml = null 2125 2126let getCartSidebar = async () => 2127{ 2128 $(".cart-sticky").addClass("loading") 2129 try 2130 { 2131 const data = await $.ajax({ 2132 method: "GET", 2133 url: CONFIG.URLS.cart, 2134 dataType: "json", 2135 }) 2136 const cartChanged = lastCartHtml !== null && lastCartHtml !== data.html 2137 lastCartHtml = data.html
2138 $("#cart-container").html(data.html) 2139 if (cartChanged) 2140 { 2141 $("#cart-container .order-summary").addClass("cart-updated") 2142 } 2143 $("#cart-container").toggleClass("empty", !!data.isEmpty) 2144 $(".checkout-dino").toggleClass("small", !data.isEmpty) 2145 applySidebarClickEvents() 2146 clearStepError() 2147 resizeSidebar() 2148 updateOrderSummaryToggleState() 2149 $("#cart-container .order-summary, #cart-container .close").on("click keydown", function(e) 2150 { 2151 const isClick = e.type === "click" 2152 const isEnterOrSpace = e.type === "keydown" && (e.key === "Enter" || e.key === " ") 2153 if (!isClick && !isEnterOrSpace) return 2154 if ($(window).width() >= CONFIG.MOBILE_BREAKPOINT) return 2155 const isOpen = $("#cart-container").hasClass("open") 2156 if ($(this).hasClass("close")) 2157 { 2158 e.preventDefault() 2159 e.stopPropagation() 2160 if (isOpen) toggleMobileSummary() 2161 } 2162 else if (!isOpen) 2163 { 2164 e.preventDefault() 2165 toggleMobileSummary() 2166 } 2167 }) 2168 return data 2169 } 2170 catch (err) 2171 { 2172 displayStepError("There was a problem loading your cart. Please try again later."); 2173 } 2174} 2175 2176let fetchNextStep = async () => 2177{ 2178 let stepData = {} 2179 if(purchasePathStep == "selectDays") 2180 { 2181 const urlParams = new URLSearchParams(window.location.search) 2182 const dateParam = urlParams.get('date') 2183 2184 if($("#purchase-path-calendar-month-select").length > 0 && calendarView != "rolling") 2185 { 2186 stepData.month = $("#purchase-path-calendar-month-select").val().split("-")[0] 2187 stepData.year = $("#purchase-path-calendar-month-select").val().split("-")[1] 2188 } 2189 else if(dateParam) 2190 { 2191 const parts = dateParam.split('-') 2192 if(parts.length === 3) 2193 { 2194 stepData.month = parts[0] 2195 stepData.year = parts[2] 2196 } 2197 } 2198 stepData.template = `calendar-${calendarView}` 2199 } 2200 showSpinner($(`.step-container[data-step="${purchasePathStep}"]`)) 2201 2202 try 2203 { 2204 const data = await $.ajax({ 2205 method: "GET", 2206 url: `${CONFIG.URLS.stepGet}?${getFormattedUrlParams()}`, 2207 data: stepData, 2208 dataType: "json", 2209 }) 2210 if(data.redirect) 2211 { 2212 window.location = `${window.location.origin}/${data.redirect}` 2213 } 2214 else if(data.goBackStep) 2215 { 2216 updateStep(data.step) 2217 const nextData = await fetchNextStep() 2218 applyStep(nextData) 2219 } 2220 return data 2221 } 2222 catch (err) 2223 { 2224 displayStepError("There was a problem loading this step. Please try again later."); 2225 } 2226} 2227 2228let sendStepData = async (step, data, completeStep = true, file = false) => 2229{ 2230 $(".next-step").addClass("loading") 2231 let formData = new FormData() 2232 formData.append("step", step) 2233 if (completeStep) 2234 { 2235 formData.append("completeCurrentStep", completeStep); 2236 } 2237 for (const [key, value] of Object.entries(data)) 2238 { 2239 formData.append(key, JSON.stringify(value)) 2240 } 2241 if (file) 2242 { 2243 formData.append("document", file) 2244 } 2245 try 2246 { 2247 return await $.ajax({ 2248 method: "POST", 2249 url: CONFIG.URLS.stepSave, 2250 data: formData, 2251 processData: false, 2252 contentType: false, 2253 dataType: "json", 2254 }) 2255 } 2256 catch (err) 2257 { 2258 let parsed = err && err.responseJSON ? err.responseJSON : null 2259 if (!parsed && err && err.responseText) 2260 { 2261 try { parsed = JSON.parse(err.responseText) } catch (_) {} 2262 } 2263 if (parsed && parsed.errors) 2264 { 2265 return parsed 2266 } 2267 displayStepError("There was a problem saving your information. Please try again later."); 2268 } 2269} 2270 2271async function advanceStep(stepName, data, options = {}) 2272{ 2273 const parsed = await sendStepData(stepName, data, options.completeStep ?? true, options.file) 2274 $(".next-step").removeClass("loading") 2275 if (!parsed) return 2276 if (parsed.errors) 2277 { 2278 if (options.onErrors) options.onErrors(parsed.errors) 2279 else displayStepError(parsed.errors[0] || "An error occurred.") 2280 return parsed 2281 } 2282 if (parsed.nextStep) 2283 { 2284 updateStep(parsed.nextStep) 2285 const stepData = await fetchNextStep() 2286 applyStep(stepData) 2287 2288 // Move focus to the heading of the step 2289 const $activeStep = $(`.purchase-path-step[data-step="${parsed.nextStep}"]`) 2290 const $heading = $activeStep.find(".purchase-path-step-header h2").first() 2291 if ($heading.length) 2292 { 2293 $heading.attr("tabindex", "-1").focus() 2294 } 2295 } 2296 return parsed 2297} 2298 2299let showSpinner = target => 2300{ 2301 target.html( 2302 ` 2303 <div class="loading-spinner"> 2304 <div class="spinner"></div> 2305 </div> 2306 `) 2307} 2308 2309let applyNextStepButtonClickEvents = () => 2310{ 2311 $(".next-step").on("click", function(event) 2312 { 2313 event.preventDefault() 2314 const nextButton = $(this) 2315 if(nextButton.hasClass("loading")) 2316 { 2317 return 2318 } 2319 const step = nextButton.data("step") 2320 const handler = STEP_ADVANCE[step] 2321 if (handler) handler(nextButton) 2322 }) 2323 $(".back-step-button").on("click", async function(event) 2324 { 2325 event.preventDefault() 2326 let formData = new FormData() 2327 formData.append("goBackStep", true) 2328 try 2329 { 2330 const data = await $.ajax({ 2331 method: "POST", 2332 url: CONFIG.URLS.stepSave, 2333 data: formData, 2334 processData: false, 2335 contentType: false, 2336 dataType: "json", 2337 }) 2338 updateStep(data.nextStep) 2339 const stepData = await fetchNextStep() 2340 applyStep(stepData) 2341 2342 // Move focus to the heading of step 2343 const $activeStep = $(`.purchase-path-step[data-step="${data.nextStep}"]`) 2344 const $heading = $activeStep.find(".purchase-path-step-header h2").first() 2345 if ($heading.length) 2346 { 2347 $heading.attr("tabindex", "-1").focus() 2348 } 2349 } 2350 catch (err) 2351 { 2352 displayStepError("There was a problem processing your request. Please try again later."); 2353 } 2354 }) 2355} 2356 2357let getFormattedUrlParams = () => 2358{
2359 const urlParams = new URLSearchParams(window.location.search) 2360 const formattedParams = [] 2361 urlParams.forEach((value, key) => 2362 { 2363 formattedParams.push(`${key}=${encodeURIComponent(value)}`) 2364 }) 2365 return formattedParams.join('&') 2366} 2367 2368let resizeSidebar = () => 2369{ 2370 const cartEmpty = $("#cart-container").hasClass("empty") 2371 const vh = (window.visualViewport && window.visualViewport.height) || $(window).height() 2372 if ($(window).width() < CONFIG.MOBILE_BREAKPOINT) 2373 { 2374 $('.cart-content').css("max-height", "initial") 2375 $('.cart-content').css("height", "initial") 2376 $("#cart-container").css("height", "") 2377 if (cartEmpty) 2378 { 2379 $("#cart-container .item-container").css("height", "initial") 2380 return 2381 } 2382 const headerHeight = $(".order-summary").outerHeight(true) 2383 const totalDivHeight = $(".cart-total").outerHeight(true) 2384 $("#cart-container .item-container").css("height", vh - headerHeight - totalDivHeight) 2385 $("#cart-container .cart-content").css("height", vh - totalDivHeight) 2386 //$("#cart-container .item-container").css("margin-bottom", totalDivHeight) 2387 } 2388 else 2389 { 2390 if($("#cart-container").hasClass("open")) 2391 { 2392 toggleMobileSummary() 2393 } 2394 let height = 0 2395 $(".purchase-path-step").each(function() 2396 { 2397 height += $(this).outerHeight() 2398 }) 2399 const sidebarOffset = 200 2400 if (!cartEmpty) 2401 { 2402 const totalHeight = $('.cart-total').outerHeight() 2403 const adjustedHeight = vh - totalHeight - sidebarOffset 2404 $('.cart-content').css('max-height', adjustedHeight + 'px') 2405 $('.cart-content').css("overflow-y", "scroll") 2406 } 2407 let containerHeight = height + $(".cart-sticky").height() + sidebarOffset 2408 const $footer = $("#footer") 2409 if ($footer.length) 2410 { 2411 $("#cart-container").css("height", "") 2412 const maxHeight = $footer.offset().top - CONFIG.FOOTER_STICKY_GAP - $("#cart-container").offset().top 2413 containerHeight = Math.min(containerHeight, maxHeight) 2414 } 2415 $("#cart-container").css("height", containerHeight) 2416 } 2417} 2418 2419let resize = () => 2420{ 2421 if ($(window).width() < CONFIG.MOBILE_BREAKPOINT) 2422 { 2423 $(".purchase-path-ticket-calendar .day .calendar-day-button").addClass("mobile") 2424 } 2425 else 2426 { 2427 $(".purchase-path-ticket-calendar .day .calendar-day-button").removeClass("mobile") 2428 } 2429 if($(".calendar-day-button.mobile").length > 0 && $(".calendar-day-button.selected").length == 0) 2430 { 2431 $(".calendar-day-button:not(.closed)").first().focus() 2432 $(".calendar-day-button:not(.closed)").first().trigger("click") 2433 } 2434 else if($(".calendar-day-button.mobile").length > 0 && $(".calendar-day-button.selected").length > 0) 2435 { 2436 $(".calendar-day-button:not(.closed).selected").first().focus() 2437 $(".calendar-day-button:not(.closed).selected").first().trigger("click") 2438 } 2439 resizeSidebar() 2440 updateOrderSummaryToggleState() 2441} 2442 2443let toggleMobileSummary = () => 2444{ 2445
2446 const $container = $("#cart-container") 2447 const isOpen = $container.hasClass("open") 2448 const $close = $container.find(".close") 2449 2450 if (!isOpen) 2451 { 2452 scrollPosition = window.scrollY || window.pageYOffset 2453 lockScroll() 2454 $container.addClass("open") 2455 $close.attr("aria-expanded", "true").removeAttr("hidden").trigger("focus") 2456 updateOrderSummaryToggleState() 2457 updateMobileCartHeights() 2458 } 2459 else 2460 { 2461 $container.removeClass("open") 2462 $close.attr("aria-expanded", "false").attr("hidden", true) 2463 updateOrderSummaryToggleState() 2464 updateMobileCartHeights() 2465 unlockScroll() 2466 $container.find(".order-summary").trigger("focus") 2467 } 2468} 2469 2470let updateOrderSummaryToggleState = () => 2471{ 2472 const $container = $("#cart-container") 2473 const $summary = $container.find(".order-summary") 2474 if ($(window).width() < CONFIG.MOBILE_BREAKPOINT && !$container.hasClass("open")) 2475 { 2476 $summary.attr({ 2477 "role": "button", 2478 "tabindex": 0, 2479 "aria-label": "open order summary", 2480 "aria-controls": "cart-container", 2481 "aria-expanded": "false" 2482 }) 2483 } 2484 else 2485 { 2486 $summary.removeAttr("role tabindex aria-label aria-controls aria-expanded") 2487 } 2488} 2489 2490let lockScroll = () => 2491{ 2492 $("body").addClass("no-scroll").css({ 2493 position: "fixed", 2494 left: 0, 2495 right: 0, 2496 width: "100%" 2497 }) 2498 document.body.style.setProperty("top", `-${scrollPosition}px`, "important") 2499 $("html").addClass("no-scroll") 2500} 2501 2502let unlockScroll = () => 2503{ 2504 $("body").removeClass("no-scroll").attr("style", "") 2505 $("html").removeClass("no-scroll") 2506 window.scrollTo(0, scrollPosition) 2507} 2508 2509 2510function updateMobileCartHeights() 2511{ 2512 if ($(window).width() < CONFIG.MOBILE_BREAKPOINT) 2513 {
2514 const $cart = $("#cart-container") 2515 if (!$cart.hasClass("open")) 2516 { 2517 $cart.css("height", "") 2518 $('.cart-content').css("max-height", "initial") 2519 $('.cart-content').css("height", "initial") 2520 $("#cart-container .item-container").css("height", "initial") 2521 $("#cart-container .item-container").css("margin-bottom", "initial") 2522 } 2523 else 2524 { 2525 const vh = window.visualViewport.height 2526 const $nav = $(".navbar-mobile") 2527 const headerBottom = $nav.length ? Math.max(0, Math.round($nav[0].getBoundingClientRect().bottom)) : 0 2528 const drawerHeight = vh - headerBottom 2529 const headerHeight = $(".order-summary").outerHeight() 2530 const totalDivHeight = $(".cart-total").outerHeight() 2531 const usableHeight = Math.floor(drawerHeight - headerHeight - totalDivHeight) 2532 2533 $cart.css("height", drawerHeight) 2534 $(".cart-content").css("height", usableHeight) 2535 $(".item-container").css({ 2536 height: usableHeight, 2537 "margin-bottom": totalDivHeight 2538 }) 2539 } 2540 } 2541 2542} 2543 2544 2545$(document).ready(async function() 2546{ 2547 resize() 2548 const data = await fetchNextStep() 2549 applyStep(data) 2550 2551 $(document).on("click", ".edit-selection-button", function(event) 2552 { 2553 event.preventDefault() 2554 const targetStep = $(this).data("step") 2555 jumpToStep(targetStep) 2556 }) 2557 2558 $(document).on("click keydown", ".stepper-item.completed", function(event) 2559 { 2560 if(event.type === "click" || (event.type === "keydown" && (event.key === "Enter" || event.key === " "))) 2561 { 2562 event.preventDefault() 2563 const targetStep = $(this).data("step") 2564 jumpToStep(targetStep) 2565 } 2566 }) 2567 2568 // future steps can't be skipped to. Clicking them briefly reveals 2569 // a "Please complete the previous section." message on the step 2570 $(document).on("click keydown", ".purchase-path-step.locked .purchase-path-step-header", function(event) 2571 { 2572 if(event.type === "click" || (event.type === "keydown" && (event.key === "Enter" || event.key === " "))) 2573 { 2574 event.preventDefault() 2575 const $step = $(this).closest(".purchase-path-step.locked") 2576 if (!$step.length) return 2577 if ($step.data("lockedMsgTimeout")) 2578 { 2579 clearTimeout($step.data("lockedMsgTimeout")) 2580 } 2581 $step.addClass("show-message") 2582 const alert = $("#purchase-path-alert") 2583 alert.text("") 2584 setTimeout(() => alert.text("Please complete the previous section."), 100) 2585 const timeout = setTimeout(() => 2586 { 2587 $step.removeClass("show-message") 2588 $step.removeData("lockedMsgTimeout") 2589 }, 2000) 2590 $step.data("lockedMsgTimeout", timeout) 2591 } 2592 }) 2593 2594 $(window).resize(function() 2595 { 2596 resize() 2597 }) 2598 $(window).on("scroll resize", updateMobileCartHeights) 2599 if (window.visualViewport) 2600 { 2601 window.visualViewport.addEventListener("resize", function() 2602 { 2603 resize() 2604 updateMobileCartHeights() 2605 }) 2606 } 2607})
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.