1/* globals Stripe */ 2'use strict'; 3window.jQuery( document ).ready( function ($) { 4 5 var currentDonationAmount = 0; 6 7 /** 8 * Page/functionality setup. 9 */ 10 toggleNavAriaHidden( false ); 11 toggleNavTabindexes( window.innerWidth >= 1024 ); 12 toggleNavSubmenuAriaExpanded(); 13 14 /** 15 * Global keypress listeners. 16 */ 17 $(document).on( 'keypress', function(e) { 18 if ( 13 === e.which ) { 19 handleEnterKeypress(e); 20 } 21 } ); 22 23 $(window).on( 'resize', function(e) { 24 toggleNavTabindexes( window.innerWidth >= 1024 ); 25 }); 26 27 $(document).on( 'click', '[role="checkbox"]', function(e) { 28 handleCheckboxRoleToggle(e); 29 } ); 30 31 /** 32 * Returns currency symbol. 33 */ 34 function getCurrencySymbol() { 35 var defaultSymbol = '$'; 36 var currencySymbolHolder = $( '#currency-symbol' ); 37 if ( currencySymbolHolder.length ) { 38 var currencySymbol = currencySymbolHolder.text(); 39 return currencySymbol ? currencySymbol : defaultSymbol; 40 } 41 return defaultSymbol; 42 } 43 44 function moneyFormat( value ) { 45 return parseFloat( value ).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); 46 } 47 48 /** 49 * When keypressing Enter on elements with role="link", "checkbox", or "button", trigger a click. 50 * 51 * @param {object} e Original keypress event. 52 */ 53 function handleEnterKeypress(e) { 54 var $el = $(e.target); 55 var role = $el.attr( 'role' ); 56 57 if ( 'link' === role || 'button' === role || 'checkbox' === role ) { 58 $el.trigger( 'click' ); 59 } 60 } 61 62 /** 63 * Mobile menu toggle. 64 */ 65 $( '.menu-toggle' ).click( function () { 66 const $this = $( this ); 67 const $body = $( 'body' ); 68 const $siteNav = $this.parent( '#site-navigation' ); 69 70 $siteNav.toggleClass( 'menu-open' ); 71 $body.toggleClass( 'modal-open' ); 72 73 // Next calls must be after toggleClass calls, so that e.g. when menu is modal-open, aria-expanded is true, and vice versa. 74 const menuOpen = $siteNav.is( '.menu-open' ); 75 76 $this.attr( 'aria-expanded', menuOpen ); 77 78 toggleNavAriaHidden( menuOpen ); 79 toggleNavTabindexes( menuOpen ); 80 } ); 81 82 /** 83 * Toggles ARIA attributes on nav elements. 84 * 85 * @param {boolean} menuOpen True if mobile menu is open/expanded, false otherwise. 86 */ 87 function toggleNavAriaHidden( menuOpen ) { 88 const $siteNav = $( document.getElementById( 'site-navigation' ) ); 89 90 $siteNav.find( '.main-nav-container' ).attr( 'aria-hidden', ! menuOpen ); 91 $siteNav.find( '#menu-toggle__open' ).attr( 'aria-hidden', menuOpen ); 92 $siteNav.find( '#menu-toggle__close' ).attr( 'aria-hidden', ! menuOpen ); 93 } 94 95 /** 96 * Adjusts nav element tabindexes, so they're included or excluded from tab order when appropriate. 97 * 98 * @param {boolean} menuOpen True if mobile menu is open/expanded, false otherwise. 99 */ 100 function toggleNavTabindexes( menuOpen ) { 101 const $primaryMenu = $( document.getElementById( 'primary-menu' ) ); 102 const primarySearch = document.getElementById( 'primary-nav-search' ); 103 const auxiliarySearch = document.getElementById( 'auxiliary-nav-search' ); 104 const tabindex = menuOpen ? '0' : '-1'; 105 106 $primaryMenu.find( 'li a' ).each( function( i, el ) { 107 el.setAttribute( 'tabindex', tabindex ); 108 } ); 109 110 primarySearch && primarySearch.setAttribute( 'tabindex', menuOpen ? '-1' : '0' ); 111 auxiliarySearch && auxiliarySearch.setAttribute( 'tabindex', tabindex ); 112 } 113 114 /** 115 * Ensures parent menu items' `aria-expanded` attrs are toggled correctly as their submenus are opened/closed. 116 */ 117 function toggleNavSubmenuAriaExpanded() { 118 $(document).on( 'mouseenter focus', '.menu-item-has-children', function(e) { 119 $(e.currentTarget).find( '> a' ).attr( 'aria-expanded', 'true' ); 120 } ); 121 122 $(document).on( 'mouseleave blur', '.menu-item-has-children', function(e) { 123 $(e.currentTarget).find( '> a' ).attr( 'aria-expanded', 'false' ); 124 } ); 125 } 126 127 /** 128 * Toggles aria-checked attr on items using "checkbox" role. 129 * 130 * @param {object} e Original click event. 131 * 132 * @return {string} 133 */ 134 function handleCheckboxRoleToggle(e) { 135 // `prevAttrValue` is always a string or undefined. 136 $(e.target).attr( 'aria-checked', function( i, prevAttrValue ) { 137 if ( 'undefined' === typeof prevAttrValue || 'false' === prevAttrValue ) { 138 return 'true'; 139 } 140
141 return 'false'; 142 }); 143 } 144 145 /** 146 * Updates donation amount. 147 */ 148 function setDonationAmount( amount ) { 149 var $donateAmount = $( '.donate-amount' ); 150 if ( !$donateAmount.length ) { 151 return; 152 } 153 154 const $purchasePrice = $( '.total_purchase_price' ); 155 let orderTotal = parseFloat( $purchasePrice.first().text().replace( /[^0-9.-]+/g, '' ) ); 156 157 // Remove the current donation amount from the total. 158 orderTotal -= currentDonationAmount; 159 160 amount = parseFloat( amount ); 161 162 currentDonationAmount = amount; 163 var $donationRow = $( '.donate' ); 164 var isDonationVisible = $donationRow.is( ':visible' ); 165 166 // Conditionally toggle the donation row. 167 if ( (amount > 0 && !isDonationVisible) || (!amount && isDonationVisible) ) { 168 $donationRow.stop().slideToggle(); 169 } 170 171 $donateAmount.text( moneyFormat(amount) ); 172 173 // Add the new donation amount to the total. 174 orderTotal += amount; 175 176 if (orderTotal < 0) { 177 orderTotal = 0; 178 } 179 180 $( '.free-order-payment-info' ).toggle( orderTotal > 0 ); 181 182 $purchasePrice.text( getCurrencySymbol() + moneyFormat(orderTotal) ); 183 184 updatePaymentMethod( orderTotal ); 185 $.themeCheckout.maybeToggleCreditCardFields(); 186 } 187 188 $( '[data-target="#trailer-modal"]' ).click( function () { 189 var $trailerUrl = $( this ).data( 'trailer' ); 190 $( '#trailer-modal' ).find( '.iframe-holder-inner' ).html( $trailerUrl ); 191 } ); 192 193 $( document.getElementById( 'trailer-modal' ) ).on( 'hidden.bs.modal', function ( e ) { 194 $( this ).find( '.iframe-holder-inner' ).html( '' ); 195 } ); 196 197 $( '.show-carousel' ).each( function ( i, e ) { 198 var $this = $( e ); 199 var $prev = $this.closest( '.show-slider' ).find( '.show-slider-prev' ); 200 var $next = $this.closest( '.show-slider' ).find( '.show-slider-next' ); 201 202 $this.slick( { 203 arrows: false, 204 dots: true, 205 infinite: true, 206 slidesToShow: 3, 207 slidesToScroll: 3, 208 variableWidth: false, 209 adaptiveHeight: true, 210 accessibility: false, // Disabling the default accessibility mode prevents Slicks' forceful setting of tabindex=0 on all internal links, which we don't want as it makes navigating with screen-reader more challenging. 211 responsive: [ 212 { 213 breakpoint: 768, 214 settings: { 215 slidesToShow: 2, 216 slidesToScroll: 2 217 } 218 }, 219 { 220 breakpoint: 480, 221 settings: { 222 slidesToShow: 1, 223 slidesToScroll: 1 224 } 225 } 226 ] 227 } ); 228 229 $prev.on( 'click', function () { 230 $this.slick( 'slickPrev' ); 231 } ); 232 233 $next.on( 'click', function () { 234 $this.slick( 'slickNext' ); 235 } ); 236 237 $this.find( '.slick-dots button' ).each(function(i, el) { 238 $(el).attr({ 239 'tabindex': '-1', 240 'aria-hidden': true 241 }); 242 }); 243 } ); 244 245 $( '[data-toggle="tooltip"]' ).tooltip(); 246 247 $( document.getElementById( 'show-slider' ) ).slick( { 248 arrows: true, 249 prevArrow: '.prev-arrow', 250 nextArrow: '.next-arrow', 251 dots: true, 252 infinite: true, 253 slidesToShow: 1, 254 slidesToScroll: 1 255 } ); 256 257 $( '.toggle-ib' ).click( function () { 258 const $tab = $(this); 259 const $builder = $tab.closest( '#itinerary-builder' ); 260 const $tabindexEls = $builder.find( '[tabindex]' ); 261 const $ariaHiddenEls = $builder.find( '[aria-hidden]' ); 262 263 if ( $builder.is( '.ib-open' ) ) { 264 265 $builder.removeClass( 'ib-open' ); 266 267 $tabindexEls.each(function(i, el) { 268 $(el).attr( 'tabindex', '-1' ); 269 } ); 270 271 $ariaHiddenEls.each(function(i, el) { 272 $(el).attr( 'aria-hidden', 'true' ); 273 }); 274 275 $tab.attr( 'aria-expanded', 'false' ); 276 277 } else { 278 279 $builder.addClass( 'ib-open' ); 280 281 $tabindexEls.each(function(i, el) { 282 $(el).attr( 'tabindex', '0' ); 283 } ); 284 285 $ariaHiddenEls.each(function(i, el) { 286 $(el).attr( 'aria-hidden', 'false' ); 287 }); 288 289 $tab.attr( 'aria-expanded', 'true' ); 290 } 291 } ); 292
293 maybeInitExtraCarouselItemClasses(); 294 295 function maybeInitExtraCarouselItemClasses() { 296 const sliders = document.querySelectorAll( '.hero-slider' ); 297 298 if ( 0 === sliders.length ) { 299 return; 300 } 301 302 sliders.forEach( function ( slider ) { 303 const firstSlide = slider.querySelector( '.carousel-item:first-of-type' ); 304 305 slider.dataset.carouselItemType = firstSlide.dataset.carouselItemType || ''; 306 307 $( slider ).on( 'slide.bs.carousel', function(event) { 308 slider.dataset.carouselItemType = event.relatedTarget.dataset.carouselItemType || ''; 309 } ); 310 } ); 311 } 312 313 maybeInitVideoBg(); 314 315 /** 316 * If there are video hero sliders, init their background video players. 317 */ 318 function maybeInitVideoBg() { 319 const players = document.querySelectorAll( '[data-vbg]' ); 320 321 if ( 0 === players.length ) { 322 return; 323 } 324 325 const videos = new window.VideoBackgrounds( '[data-vbg]' ); 326 327 // Ensures both wrapping div and actual iframe are tabindex=-1. 328 if ( 'object' !== typeof videos.index ) { 329 return; 330 } 331 332 Object.keys(videos.index).forEach(function(video) { 333 'object' === typeof videos.index[ video ].element && videos.index[ video ].element.setAttribute( 'tabindex', '-1' ); 334 'object' === typeof videos.index[ video ].iframe && videos.index[ video ].iframe.setAttribute( 'tabindex', '-1' ); 335 336 const $slider = $( videos.index[ video ].element ).parents( '.hero-slider' ); 337 const $play = $slider.find( '.video-hero__play' ); 338 const $pause = $slider.find( '.video-hero__pause' ); 339 340 $play.on( 'click', function(e) { 341 e.preventDefault(); 342 document.body.classList.remove( 'filmbot-vbg-paused' ); 343 videos.playVideos(); 344 } ); 345 346 $pause.on( 'click', function(e) { 347 e.preventDefault(); 348 document.body.classList.add( 'filmbot-vbg-paused' ); 349 videos.pauseVideos(); 350 } ); 351 } ); 352 } 353 354 /** 355 * Share functionality 356 */ 357 $( 'a.tweetthis' ).click( function ( e ) { 358 var n = $( this ).attr( 'data-text' ); 359 360 window.open( 'http://twitter.com/share?url=' + encodeURIComponent( window.location ) + '&text=' + encodeURIComponent( n ) + '&redirect_uri=' + encodeURIComponent( window.location ), '', 'width=626,height=436' ); 361 362 e.preventDefault(); 363 } ); 364 365 $( 'a.facebookthis' ).click( function ( e ) { 366 var n = $( this ).attr( 'data-text' ); 367 368 window.open( 'https://www.facebook.com/sharer/sharer.php?u=' + encodeURIComponent( window.location ) + '&display=popup&description=' + encodeURIComponent( n ), '', 'width=626,height=436' ); 369 'undefined' != typeof ga && ga && ga( 'send', 'event', 'Keyfindings', 'click', 'facebook button', 1 ); 370 e.preventDefault(); 371 } ); 372 373 /** 374 * Checkout 375 */ 376 var $error = $( '.cart-error' ); 377 var $cart = $( '.cart' ); 378 var $discountRow = $( '.ticket-total-discount-row' ); 379 var $discountCode = $( '.discount-code' ); 380 var $discountCodeBtn = $discountCode.find( 'button' ); 381 var $discountCodeInput = $discountCode.find( 'input' ); 382 383 /** 384 * Handle ticket type qty change 385 */ 386 $( document ).on( 'change', '.ticket-quantity select', function ( e ) { 387 e.preventDefault(); 388 389 // Clean up discount code 390 if ( !$discountCodeBtn.hasClass( 'applied' ) ) { 391 $discountCodeInput.val( '' ); 392 } 393 394 cartUpdateRequest( getOrderData(), true ); 395 } ); 396 397 /** 398 * Handle remove ticket type 399 */ 400 $( document ).on( 'click', '.remove-ticket_type-btn', function ( e ) { 401 e.preventDefault(); 402 403 // Do not allow removing tickets if checkout has already been submitted 404 if ( $.themeCheckout.checkoutSubmitted ) { 405 return; 406 } 407 408 var $order = getOrderData(); 409 var ticket_type = $( this ).closest( '.ticket-type' ).attr( 'data-ticket_type' ); 410 var $showtime_id = $( this ).closest( '.film' ).attr( 'data-showtime' ); 411 412 delete $order[ $showtime_id ][ ticket_type ]; 413 414 // Clean up discount code
415 if ( ! $discountCodeBtn.hasClass( 'applied' ) ) { 416 $discountCodeInput.val( '' ); 417 } 418 419 $( this ).closest( '.ticket-type' ).addClass( 'remove-ticket' ); 420 421 cartUpdateRequest( $order, true ); 422 } ); 423 424 /** 425 * Setup aria-attribute adjustments on Bootstrap dropdown elements. 426 */ 427 $( '.dropdown' ).each( function( i, dropdown ) { 428 const $dropdown = $(dropdown); 429 const $menu = $dropdown.find( '.dropdown-menu' ); 430 431 $menu.attr( 'aria-hidden', 'true' ); 432 433 $dropdown.on( 'shown.bs.dropdown', function() { 434 $menu.attr( 'aria-hidden', 'false' ); 435 } ); 436 437 $dropdown.on( 'hidden.bs.dropdown', function() { 438 $menu.attr( 'aria-hidden', 'true' ); 439 } ); 440 }); 441 442 /** 443 * Handle buy a ticket button 444 */ 445 $( document ).on( 'click', '.add-to-cart', function ( e ) { 446 e.preventDefault(); 447 448 var showtimeId = $( this ).attr( 'data-showtime-id' ); 449 var $order = {}; 450 var $this = $( this ); 451 var $dropdown = $this.closest( '.dropdown' ); 452 453 $order[showtimeId] = {}; 454 455 $.each( $( '#form-buy-' + showtimeId ).find( 'select' ), function ( i, element ) { 456 var $input = $( element ); 457 var ttSlug = $input.data( 'ttslug' ); 458 var qty = $input.val(); 459 460 $order[showtimeId][ttSlug] = qty; 461 462 $input.val( '0' ); 463 } ); 464 465 cartUpdateRequest( $order ); 466 467 $dropdown.attr( 'aria-expanded', 'false' ).find( '.dropdown-menu' ).removeClass( 'show' ); 468 } ); 469 470 $( '.dropdown-menu option, .dropdown-menu select' ).click( function ( e ) { 471 e.stopPropagation(); 472 } ); 473 474 /** 475 * Handle Apply discount 476 */ 477 $( document ).on( 'click', '.discount-code button', function ( e ) { 478 e.preventDefault(); 479 480 // Remove discount code 481 if ( $discountCodeBtn.hasClass( 'applied' ) ) { 482 $discountCodeInput.val( '' ); 483 } 484 485 cartUpdateRequest( getOrderData(), true ); 486 } ); 487 488 /** 489 * Handle Apply discount 490 */ 491 $( document ).on( 'filmbot-change-discount-option', function ( e ) { 492 e.preventDefault(); 493 494 cartUpdateRequest( getOrderData(), true ); 495 } ); 496 497 /** 498 * Handle toggling the addition of a donation. 499 * 500 * @param {object} e Originating click event. 501 */ 502 $( document ).on( 'click', '#donate-me', function ( e ) { 503 504 // Do not allow changing donation amount if checkout has already been submitted 505 if ( $.themeCheckout.checkoutSubmitted ) { 506 return; 507 } 508 509 const $checkbox = $( '#donate-me' ); 510 const donationTotal = $checkbox.is( ':checked' ) ? parseFloat( $checkbox.val() ) : 0; 511 setDonationAmount( donationTotal ); 512 } ); 513 514 // include donation in checkout (freeform) 515 $( document ).on( 'click', '.donation-variant', function (event) { 516 517 // Do not allow changing donation amount if checkout has already been submitted 518 if ( $.themeCheckout.checkoutSubmitted ) { 519 return; 520 } 521 522 var $this = $( event.target ); 523 var $donationVariants = $( '.donation-variant' ); 524 var isAlreadySelected = $this.hasClass( 'selected' ); 525 var isCustomVariant = $this.hasClass( 'custom-donation-variant' ); 526 var isTextInput = $this.hasClass( 'custom-donation-amount' ); 527 var donationAmount = parseFloat( $this.data( 'donation-amount' ) ); 528 529 // We can click on input and this can be counted as click on parent element, so we need to check if it's input 530 if ( isTextInput || isCustomVariant ) { 531 var $customVariantInput = isTextInput ? $this : $this.find( '.custom-donation-amount' ); 532 var $customVariantWrapper = isTextInput ? $this.parent() : $this; 533 // Set the donation amount to the value of the donation input. 534 donationAmount = $customVariantInput.val() ? parseFloat( $customVariantInput.val() ) : 0; 535 $donationVariants.removeClass( 'selected' ); 536 // We don't need to remove class 'selected', as custom variant can be clicked many times to change amount 537 $customVariantWrapper.addClass( 'selected' ); 538 setDonationAmount( donationAmount ); 539 $customVariantInput.focus(); 540 } else { 541 $donationVariants.removeClass( 'selected' ); 542 // If standard variant is clicked, and it's already selected, we need to deselect it... 543 isAlreadySelected ? $this.removeClass( 'selected' ) : $this.addClass( 'selected' ); 544 // ...and set the donation amount to 0 instead of the value of the donation variant. 545 setDonationAmount( isAlreadySelected ? 0 : donationAmount ); 546 } 547 } ); 548 549 $( document ).on( 'input', '.custom-donation-amount', function () { 550 var $customDonationAmt = $( '.custom-donation-amount' ); 551 var donationAmount = $customDonationAmt.val().replace( /[^0-9.]/g, '' ); 552 // Check how many . are in the input. If last symbol is . but we already have one, remove all other dots 553 var dots = donationAmount.match( /\./g ); 554 if ( dots && dots.length > 1 && donationAmount.charAt( donationAmount.length - 1 ) === '.' ) { 555 donationAmount = donationAmount.substring( 0, donationAmount.length - 1 ); 556 } 557 // if first symbol is 0 but value is longer than 1 symbol - remove this 0 at the start 558 if ( donationAmount.charAt( 0 ) === '0' && donationAmount.length > 1 ) { 559 donationAmount = donationAmount.substring( 1 ); 560 } 561 // if we have dot, cut 6 symbols instead of 5 562 donationAmount = ( dots && dots.length > 0 ) ? donationAmount.substring( 0, 6 ) : donationAmount.substring( 0, 5 ); 563 // if we have more than 2 symbols after dot, cut it to 2 symbols after dot 564 if ( dots && dots.length > 0 && donationAmount.indexOf( '.' ) !== -1 ) { 565 var parts = donationAmount.split( '.' ); 566 if ( parts[1].length > 2 ) { 567 donationAmount = parts[0] + '.' + parts[1].substring( 0, 2 ); 568 } 569 } 570 donationAmount = donationAmount && donationAmount !== '.' ? donationAmount : 0; 571 $customDonationAmt.val( donationAmount ); 572 setDonationAmount( donationAmount ); 573 } ); 574 575 /** 576 * Handle payment method select 577 */ 578 $( document ).on( 'change', '#pmt-method', function ( e ) { 579 // Toggle credit card fields 580 if ( $( this ).val() == 'cc' ) { 581 showCreditCardFields(); 582 } else if ( $( this ).val() == 'cash' ) {
583 hideCreditCardFields(); 584 } 585 } ); 586 587 /** 588 * Handle comp select 589 */ 590 $( document ).on( 'change', '#is-comp', function ( e ) { 591 // Toggle credit card fields 592 if ( $( this ).prop( 'checked' ) === false ) { 593 showCreditCardFields(); 594 $( document.getElementById( 'pmt-method' ) ).val( 'cc' ); 595 $( '.total_purchase_price' ).text( $( '.total_purchase_price' ).data( 'full-price' ) ); 596 } else { 597 hideCreditCardFields(); 598 $( document.getElementById( 'pmt-method' ) ).val( 'cash' ); 599 $( '.total_purchase_price' ).text( getCurrencySymbol() + '0.00' ); 600 } 601 } ); 602 603 /** 604 * Update cart totals 605 * @param order 606 */ 607 function updateCart( order ) { 608 /** 609 * Update totals 610 */ 611 var currencySymbol = getCurrencySymbol(); 612 613 $( '.ticket-totals .data-by-ticket-type' ).html( order.html_description ); 614 615 $( '.booking_fee_total' ).text( currencySymbol + moneyFormat(order.booking_fee) ); 616 617 $( '.total_purchase_price' ).text( currencySymbol + moneyFormat(order.total) ); 618 619 if ( order.discount_total ) { 620 $( '.discount_total' ).text( '-' + currencySymbol + moneyFormat(order.discount_total) ); 621 } 622 623 /** 624 * Update discount input 625 */ 626 toggleApplyDiscount( order.discount_total ); 627 628 /** 629 * Update cart items 630 */ 631 var ticketsToRemove = $( '.remove-ticket' ); 632 633 if ( ticketsToRemove.closest( '.film' ).find( '.ticket-type' ).length === 1 ) { 634 ticketsToRemove.closest( '.film' ).remove(); 635 } else { 636 $( ticketsToRemove ).remove(); 637 } 638 639 /** 640 * If it's a free order, or a cash order, hide the payment field 641 */ 642 if ( (order.total == 0 && $( '.cart-row.film' ).length) ) { 643 hideCreditCardFields(); 644 } else if ( $( document.getElementById( 'pmt-method' ) ).val() != 'cash' ) { 645 showCreditCardFields(); 646 } 647 648 /** 649 * Update payment method 650 */ 651 updatePaymentMethod( order.total ); 652 $.themeCheckout.maybeToggleCreditCardFields(); 653 654 if ( order.total == 0 ) { 655 $( '.postal-code' ).stop( true ).hide().find( '#postal-code' ).prop( 'disabled', true ); 656 } 657 } 658 659 function updatePaymentMethod( orderTotal ) { 660 if ( orderTotal > 0 ) { 661 $( document.getElementById( 'pmt-method' ) ).val( 'cc' ).trigger( 'change' ); 662 } else { 663 $( document.getElementById( 'pmt-method' ) ).val( 'cash' ).trigger( 'change' ); 664 } 665 } 666 667 function showCreditCardFields() { 668 $( '.payment-info' ).slideDown(); 669 $( '.postal-code' ).slideUp().find( '#postal-code' ).prop( 'disabled', true ); 670 } 671
672 function hideCreditCardFields() { 673 $( '.payment-info' ).slideUp(); 674 $( '.postal-code' ).slideDown().find( '#postal-code' ).prop( 'disabled', false ); 675 } 676 677 /** 678 * Toggle discount code state. 679 * 680 * @param discount_total 681 */ 682 function toggleApplyDiscount( discount_total ) { 683 if ( discount_total ) { 684 $discountCodeBtn.addClass( 'applied' ); 685 $discountCodeBtn.text( 'Remove' ); 686 $discountCodeInput.prop( 'readonly', true ); 687 $discountRow.show(); 688 return; 689 } 690 691 $discountCodeBtn.removeClass( 'applied' ); 692 $discountCodeBtn.text( 'Apply' ); 693 $discountCodeInput.prop( 'readonly', false ); 694 $discountRow.hide(); 695 } 696 697 /** 698 * Update floating cart (qty and subtotal) 699 * @param order 700 */ 701 function updateFloatingCart( order ) { 702 $( '.floating-cart .quantity' ).text( order.qty ); 703 $( '.floating-cart .amount .price' ).text( getCurrencySymbol() + moneyFormat(order.total) ); 704 705 if ( parseInt( order.qty, 10 ) === 0 ) { 706 $( '.floating-cart' ).addClass( 'empty' ); 707 $( '.floating-cart' ).find( 'a' ).attr( 'tabindex', '-1' ); 708 } else { 709 $( '.floating-cart' ).removeClass( 'empty' ); 710 $( '.floating-cart' ).find( 'a' ).attr( 'tabindex', '0' ); 711 } 712 } 713 714 /** 715 * Get order data 716 */ 717 function getOrderData() { 718 var $order = {}; 719 var ticketType; 720 721 $( '.film' ).each( function ( i, showtime ) { 722 var showtime_id = $( this ).attr( 'data-showtime' ); 723 724 $( showtime ).find( '.ticket-type' ).each( function ( i, ticket ) { 725 ticketType = $( ticket ).attr( 'data-ticket_type' ); 726 727 if ( !$order[ showtime_id ] ) { 728 $order[ showtime_id ] = {}; 729 } 730 731 if ( !$order[ showtime_id ][ ticketType ] ) { 732 $order[ showtime_id ][ ticketType ] = 0; 733 } 734 735 $order[ showtime_id ][ ticketType ] += parseInt( $( ticket ).find( '.ticket-quantity select' ).val() ); 736 } ); 737 } ); 738 739 return $order; 740 } 741 742 /** 743 * The donation amount total. 744 * 745 * @returns {Number} 746 */ 747 function getDonationTotal() { 748 var $donateAmountField = $( '.donate-amount' ); 749 return $donateAmountField.length ? parseFloat( $donateAmountField.first().text().replace( /[^0-9.-]+/g, '' ) ) : 0; 750 } 751 752 /** 753 * Make a request to Cart_Handler 754 * @param $order 755 * @param isUpdate 756 */ 757 function cartUpdateRequest( $order, isUpdate ) { 758 $cart.css( 'pointer-events', 'none' ); 759 $error.hide(); 760 761 var $email = $( document.getElementById( 'email' ) ); 762 var email = $email.length ? $email.val() : ''; 763 764 var data = { 765 action: 'exhibition_cart_handler', 766 _ajax_nonce: $( document.getElementById( 'filmbot_checkout_nonce' ) ).val(), 767 promotion_code: $( '.discount-code input' ).val(), 768 email: email, 769 type: isUpdate ? 'update-cart' : 'add-to-cart', 770 order: $order 771 }; 772 773 if ( typeof $.applyFilter !== 'undefined' ) { 774 data = $.applyFilter( 'filmbot-modify-discount-data', data ); 775 } 776 777 $.ajax( { 778 url: themeSettings.ajaxUrl, 779 method: 'POST', 780 timeout: 60000, 781 data: data, 782 success: function ( response ) { 783 if ( response.success ) { 784 updateFloatingCart( response.data ); 785 786 if ( isUpdate ) { 787 response.data.total += getDonationTotal(); 788 updateCart( response.data ); 789 } 790 791 if( data.promotion_code ) { 792 jQuery( document ).trigger( 'filmbot-discount-code-applied', response ); 793 } else { 794 jQuery( document ).trigger( 'filmbot-discount-code-removed' ); 795 } 796 } 797 798 var errors = ''; 799 800 if ( ! response.success ) { 801 errors += response.data; 802 } 803 804 if ( typeof response.promotion_error != 'undefined' ) { 805 errors += response.promotion_error; 806 $discountCodeInput.val( '' ); 807 toggleApplyDiscount( 0 ); 808 } 809 810 if ( errors.length ) { 811 $error.text( errors ).show(); 812 } 813 814 $cart.css( 'pointer-events', 'all' ); 815 }, 816 error: function ( xhr, status, error ) { 817 $error.text( error ).show(); 818 $cart.css( 'pointer-events', 'all' ); 819 } 820 } ); 821 } 822 823 /** 824 * Schedule page 825 */ 826 $( '.schedule-filters select' ).change( function () { 827 var date = $( document.getElementById( 'filter-date' ) ).val(); 828 var postId = $( document.getElementById( 'filter-post-id' ) ).val(); 829 var section = $( document.getElementById( 'filter-section' ) ).val(); 830 var venue = $( document.getElementById( 'filter-venue' ) ).val(); 831 var showtimes = $( '.event-row' ); 832 var groupShowtimes = {}; 833 var show = ''; 834 835 $( '.data-block' ).hide(); 836 837 showtimes.hide(); 838 839 $.each( showtimes, function ( index, element ) { 840 show = $( element ).parent( ' .data-block ' ).attr( 'data-show' ); 841 842 if ( ! groupShowtimes[ show ] ) { 843 groupShowtimes[ show ] = []; 844 } 845 846 groupShowtimes[ show ].push( element ); 847 } ); 848 849 var filteredData = {}; 850 851 $.each( groupShowtimes, function ( key, array ) { 852 853 if ( (date !== 'all') && date !== key ) { 854 return true; 855 } 856 857 var filteredDateShowtimes = []; 858 859 $.each( array, function ( index, element ) { 860 861 if ( ((postId !== 'all') && postId !== $( element ).attr( 'data-
861post-id' )) ) { 862 return true; 863 } 864 865 if ( (section !== 'all' && typeof section != 'undefined') && !$( element ).attr( 'data-section' ).includes( section ) ) { 866 return true; 867 } 868 if ( (venue !== 'all') && !$( element ).attr( 'data-venue' ).includes( venue ) ) { 869 return true; 870 } 871 872 filteredDateShowtimes.push( element ); 873 } ); 874 875 if ( filteredDateShowtimes.length ) { 876 filteredData[ key ] = filteredDateShowtimes; 877 } 878 } ); 879 880 $.each( filteredData, function ( key, array ) { 881 $( '*[data-show="' + key + '"]' ).show(); 882 $.each( array, function ( index, element ) { 883 $( element ).show(); 884 } ); 885 } ); 886 } ); 887 888 if ( $( document.getElementById( 'itinerary-builder' ) ).length ) { 889 if ( localStorage.getItem( 'showtimes' ) ) { 890 var showtimes = JSON.parse( localStorage.getItem( 'showtimes' ) ); 891 892 for ( var i in showtimes ) { 893 var showtimeId = showtimes[i]; 894 895 $( 'a[data-showtime=' + showtimeId + ']' ).addClass( 'saved' ); 896 } 897 898 updateItineraryBuilder(); 899 900 if ( $( '.add-to-itinerary.saved' ).length ) { 901 $( document.getElementById( 'itinerary-builder' ) ).addClass( 'ib-open' ); 902 } 903 } 904 } 905 906 function updateItineraryBuilder() { 907 var showtimes = []; 908 var href = window.location.href.split( '?' )[0]; 909 var itineraryUrl = href; 910 var itineraryTitle = $( '#itinerary-title' ).val(); 911 912 $( 'a.add-to-itinerary.saved' ).each( function ( i, e ) { 913 var showtimeId = $( this ).attr( 'data-showtime' ); 914 915 if ( !showtimes.includes( showtimeId ) ) { 916 showtimes[showtimes.length] = showtimeId; 917 } else { 918 var index = showtimes.indexOf( showtimeId ); 919 if ( index > -1 ) { 920 showtimes.splice( index, 1 ); 921 } 922 } 923 } ); 924 925 if ( showtimes.length == 0 ) { 926 clearItineraryBuilder(); 927 return; 928 } 929 930 localStorage.setItem( 'showtimes', JSON.stringify( showtimes ) ); 931 932 $( document.getElementById( 'count-itinerary' ) ).text( showtimes.length ); 933 934 itineraryUrl = itineraryUrl + '?si=' + showtimes.join( ',' ); 935 936 if ( itineraryTitle ) { 937 itineraryUrl = itineraryUrl + '&t=' + itineraryTitle; 938 } 939 940 $( document.getElementById( 'itinerary-url' ) ).val( itineraryUrl ); 941 $( document.getElementById( 'open-itinerary' ) ).attr( 'href', itineraryUrl ); 942 } 943 944 $( '.add-to-itinerary ' ).click( function ( e ) { 945 e.preventDefault(); 946 947 var $this = $( this ); 948 949 if ( $this.hasClass( 'saved' ) ) { 950 $this.removeClass( 'saved' ); 951 updateItineraryBuilder(); 952 return; 953 } 954 955 var conflicts = checkItineraryConflicts( $this ); 956 957 if ( ! conflicts.length ) { 958 $this.addClass( 'saved' ); 959 } else { 960 var confirmMsg = 'This showtime overlaps with the following:<ul><li>' + conflicts.join( '</li><li>' ) + '</li></ul>'; 961 962 bootbox.confirm( { 963 title: 'Schedule conflict', 964 message: confirmMsg, 965 buttons: { 966 confirm: { label: 'Add anyway' }, 967 cancel: { label: 'Cancel' } 968 }, 969 callback: function ( confirmed ) { 970 if ( confirmed === true ) { 971 $this.addClass( 'saved' ); 972 updateItineraryBuilder(); 973 } 974 } 975 } ); 976 } 977 978 updateItineraryBuilder(); 979 980 if ( $( '.add-to-itinerary.saved' ).length == 1 ) { 981 $( document.getElementById( 'itinerary-builder' ) ).addClass( 'ib-open' ); 982 } 983 } ); 984 985 $( document.getElementById( 'itinerary-title' ) ).change( function () { 986 var itineraryUrl = $( document.getElementById( 'itinerary-url' ) ).val(); 987 var itineraryTitle = $( this ).val(); 988 989 if ( itineraryUrl ) { 990 itineraryUrl = itineraryUrl.split( '&t=' )[0] + '&t=' + itineraryTitle; 991 992 $( document.getElementById( 'itinerary-url' ) ).val( itineraryUrl ); 993 $( document.getElementById( 'open-itinerary' ) ).attr( 'href', itineraryUrl ); 994 } 995 } ); 996 997 function clearItineraryBuilder() { 998 localStorage.removeItem( 'showtimes' ); 999 1000 $( document.getElementById( 'itinerary-url' ) ).val( '' ); 1001 $( document.getElementById( 'open-itinerary' ) ).attr( 'href', '' ); 1002 $( document.getElementById( 'itinerary-title' ) ).val( '' ); 1003 $( document.getElementById( 'count-itinerary' ) ).text( '0' ); 1004 $( 'a.add-to-itinerary' ).removeClass( 'saved' ); 1005 } 1006 1007 $( '.start-over' ).click( function ( e ) { 1008 if ( $( this ).attr( 'href' ) == '#' ) { 1009 e.preventDefault(); 1010 } 1011 1012 clearItineraryBuilder(); 1013 } ); 1014 1015 $( document.getElementById( 'download-ics' ) ).click( function ( e ) { 1016 e.preventDefault(); 1017 1018 var url = $( document.getElementById( 'itinerary-url' ) ).val(); 1019 1020 if ( url ) { 1021 window.open( url + '&ics=1', '_self' ); 1022 } 1023 } ); 1024
1025 function checkItineraryConflicts( $newShowtime ) { 1026 1027 if ( ! localStorage.getItem( 'showtimes' ) ) { 1028 return []; 1029 } 1030 1031 var newShowtimeInfo = $newShowtime.data( 'showtimeInfo' ); 1032 var conflicts = []; 1033 var timeBetween = 900; // 15 mins 1034 var showtimes = JSON.parse( localStorage.getItem( 'showtimes' ) ); 1035 1036 // check if it'll conflict with any other schedule items 1037 // 2 conditions will add a conflict: 1038 // 1) new item starts less than 15 mins after other item ends 1039 // 2) new item ends less than 15 mins before other item starts 1040 $.each( showtimes, function ( index, showtimeId ) { 1041 var existingShowtimeInfo = $( 'a[data-showtime=' + showtimeId + ']' ).data( 'showtimeInfo' ); 1042 1043 // if a schedule item doesn't have an end time, don't compare with it 1044 if ( ! ( 'datetime_end' in existingShowtimeInfo ) || ! ( 'datetime_end' in newShowtimeInfo ) ) { 1045 return; 1046 } 1047 1048 if ( 1049 ( newShowtimeInfo.datetime_start <= existingShowtimeInfo.datetime_end + timeBetween ) && 1050 ( newShowtimeInfo.datetime_end + timeBetween >= existingShowtimeInfo.datetime_start ) 1051 ) { 1052 conflicts.push( (existingShowtimeInfo.title) + ' (' + existingShowtimeInfo.venue + ')' ); 1053 } 1054 } ); 1055 1056 return conflicts; 1057 } 1058 1059 $( '.page-template-schedule a.tweetthis' ).click( function ( e ) { 1060 var text = ''; 1061 var url = $( document.getElementById( 'itinerary-url' ) ).val(); 1062 1063 window.open( 'http://twitter.com/share?url=' + encodeURIComponent( url ) + '&text=' + encodeURIComponent( text ) + '&redirect_uri=' + encodeURIComponent( url ), '', 'width=626,height=436' ); 1064 1065 e.preventDefault(); 1066 } ); 1067 1068 $( '.page-template-schedule a.facebookthis' ).click( function ( e ) { 1069 var n = ''; 1070 var t = $( document.getElementById( 'itinerary-url' ) ).val(); 1071 1072 window.open( 'https://www.facebook.com/sharer/sharer.php?u=' + encodeURIComponent( t ) + '&display=popup&description=' + encodeURIComponent( n ) + '&redirect_uri=' + encodeURIComponent( t ), '', 'width=626,height=436' ); 1073 1074 e.preventDefault(); 1075 } ); 1076 1077 $( '.page-template-schedule a.mail' ).click( function ( e ) { 1078 var t = $( document.getElementById( 'itinerary-url' ) ).val(); 1079 1080 window.open( 'mailto:?Schedule Itinerary ' + encodeURIComponent( t ), '_self' ); 1081 e.preventDefault(); 1082 } ); 1083 1084 $( '.page-template-schedule' ).ready( function () { 1085 var regex = /[?&]([^=#]+)=([^&#]*)/g; 1086 var params = {}; 1087 var href = window.location.href; 1088 var match; 1089 1090 while ( match = regex.exec( href ) ) { 1091 params[match[1]] = match[2]; 1092 } 1093 1094 if ( params.show ) { 1095 $( document.getElementById( 'filter-date' ) ).val( params.show ); 1096 $( '.page-template-schedule #filter-date' ).change(); 1097 } 1098 } ); 1099 1100 // Change states on country change 1101 $( document ).on( 'change', '[data-country-selector]', function ( e ) { 1102 updateSelectableCountryStates( e ); 1103 } ); 1104 1105 /** 1106 * Updates states of selected country in states field. 1107 * 1108 * @param {object} e Original click event. 1109 */ 1110 function updateSelectableCountryStates( e ) { 1111 var $statesFieldId = $( e.target ).data( 'country-selector' ); 1112 var $statesField = $( '#' + $statesFieldId ); 1113 var filmbotCountriesStates = window.filmbotCountriesStates || {}; 1114 1115 // If there is no states field, or there are no countries and states data, do nothing 1116 if ( !$statesFieldId || !$statesField.length || !Object.keys( filmbotCountriesStates ).length ) { 1117 return; 1118 } 1119 1120 var selectedCountry = $( e.target ).val() || 'US'; 1121 var previousCountry = $statesField.data( 'state-selector' ); 1122 1123 var statesList = filmbotCountriesStates[selectedCountry]; 1124 var prevStatesList = filmbotCountriesStates[previousCountry]; 1125 1126 // Make changes only if selected country is different from previous one 1127 if ( !previousCountry || previousCountry === selectedCountry || !statesList ) { 1128 return; 1129 } 1130 1131 // Check if there are no states for the selected country 1132 var currentHasStates = !(Array.isArray( statesList.states ) && statesList.states.length === 0); 1133 var previousHasStates = !(Array.isArray( prevStatesList.states ) && prevStatesList.states.length === 0); 1134 1135 // Remove all states of previous country 1136 $statesField.find( 'option[data-state]' ).remove(); 1137 1138 // Create new options and append them to the states field 1139 $.each( statesList.states, function ( key, value ) { 1140 $statesField.append( $( '<option data-state value="' + key + '">' + value + '</option>' ) ); 1141 } ); 1142 1143 // If current country has no states, but previous had, or vice versa, 1144 // update requiredness and placeholders of the states select field 1145 if ( currentHasStates !== previousHasStates ) { 1146 1147 $statesField.toggleClass( 'required', currentHasStates ); 1148 currentHasStates ? $statesField.attr( 'required', 'required' ) : $statesField.removeAttr( 'required' ); 1149 1150 $statesField.find( 'option[data-empty-state]' ).first().text( currentHasStates ? 'State*' : 'State' ); 1151 } 1152 1153 // set the new selected country as data attribute 1154 $statesField.data( 'state-selector', selectedCountry ); 1155 $statesField.val( '' ); 1156 } 1157 1158} );
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.