1var allowSeatsBookedWarning = true; 2 3// True from the order submission POST until that order or its payment fails. 4let orderSubmissionInProgress = false; 5var ticketInsuranceJsLoaded = false; 6var orderData; 7var hasRunCalculate = false; 8var paymentElementLastStatus; 9 10var isInitialPageLoad = true; 11var localCartData; 12 13//fallback in case translated string not in memory 14if (typeof origSubmitButtonText == 'undefined') { 15 var origSubmitButtonText = 'Submit Order'; 16 var qtyNotAvailTitle = 'Quantity Not Available'; 17 var qtyNotAvailMsg = 'Sorry, one or more items you chose are no longer available. Order adjusted.'; 18} 19 20// when the page shows (even if from BACK button) 21$(window).on("pageshow", function() { 22 $('#TicketTypeSelect').val(''); 23 if(typeof iframeSubmission === undefined){ 24 $('input[name^="data[Ticket]["][name$="[quantity]"]').val('0'); 25 } 26 if($('#promo_field').val() != ''){ 27 $("label[for='promo_field']").addClass('label-hover'); 28 $("label[for='promo_field_sm']").addClass('label-hover'); 29 } 30}); 31 32// this was being set at the bottom of the Orders::tickets view 33var questionnaireNotRequired = false; 34 35 36const beforeUnloadListener = (event) => { 37 event.preventDefault(); 38 return event.returnValue = "Are you sure you want to exit?"; 39}; 40function warnAgainstLeaveAttempts() { 41 window.addEventListener("beforeunload", beforeUnloadListener, {capture: true}); 42} 43function ignoreLeaveAttempts() { 44 window.removeEventListener("beforeunload", beforeUnloadListener, {capture: true}); 45} 46 47window.addEventListener('message', function(event) { 48 if (event.data == 'tsModalReloadNeeded') { 49 disableModalUntilReload(); 50 } 51}); 52 53/** 54 * Builds HTML for a standard cart item row in the order summary 55 * @param {string} params.removeIndex - Index for removal 56 * @param {string} params.formId - Form ID 57 * @param {string} params.aittId - Activity Instance Ticket Type ID 58 * @param {number} params.quantity - Ticket quantity 59 * @param {string} params.typeName - Ticket type name 60 * @param {string} params.activityInfo - Activity information HTML 61 * @param {string} params.timeConflict - Time conflict warning HTML 62 * @param {string} params.currencySymbol - Currency symbol 63 * @param {string} params.selectedQuantityPrice - Formatted price 64 * @param {string} params.seatLabelArgs - Seating label arguments 65 * @returns {string} HTML string for cart item row 66 */ 67function buildBackendOrEExOrderSummaryItemRow(params) { 68 const { removeIndex, formId, aittId, quantity, typeName, activityInfo, timeConflict, currencySymbol, selectedQuantityPrice, seatLabelArgs } = params; 69 70 let content = 71 `<div class="table-row"> 72 <div class="table-cell del">`; 73 if (typeof seatLabelArgs !== 'undefined') { 74 content += 75 `<a class="remove-check" title="Remove" href="javascript:void(0);" onclick="chart.deselectSeats(${seatLabelArgs});"> 76 <i class="fa fa-times" aria-hidden="true"></i> 77 </a>`; 78 } else { 79 content += 80 `<a class="remove-check" title="Remove" href="javascript:void(0);" onclick="removeFromCart($(this), '${removeIndex}', '${formId}', '${aittId}');"> 81 <i class="fa fa-times" aria-hidden="true"></i> 82 </a>`; 83 } 84 85 content += 86 `</div> 87 <div class="table-cell type"> 88 <strong>${quantity}x ${typeName}</strong>`; 89 90 if (activityInfo != '') { 91 content += '<br><div class="details">' + timeConflict + activityInfo + '</div>'; 92 } 93 94 content += '</div>'; 95 96 content += `<div class="table-cell price">${formatForCurrency(currencySymbol, selectedQuantityPrice)}</div></div>`; 97 98 return content; 99} 100 101function disableModalUntilReload() { 102 $('#ts-modal .ts-modal-reload-required').fadeIn('fast'); 103} 104 105// DOCUMENT READY 106$(document).ready(function() { 107 108 // this is used by the processes to remove unselected ticket types from being submitted. 109 initializeTicketQuantityInteractionTracking(); 110 111 // checks if the JS for ticket insurance is loaded used below 112 ticketInsuranceJsLoaded = (typeof updateInsuranceQuote === "function"); 113 114 if(typeof tsTheme == 'undefined') { 115 tsTheme = 'Classic'; 116 } 117 118 origSubmitButtonText = $('#orderFormSubmitButton').html(); 119 if (typeof origProcessingButtonText == 'undefined') { 120 //fallback, just in case 121 var origProcessingButtonText = "Processing..."; 122 } 123 124 // checks for linked ticket types and gates them accordingly 125 updateLinkedTypes() 126 127 //Let's use this way of assigning the action so we can unhook the action 128 //on the comp page when necessary to upload a csv instead of placing direct order 129 $('body').on('submit', '#orderForm', ajaxSubmit); 130 131 function ajaxSubmit() { 132 133 //FORMERLY $('#orderForm').on("submit", function() {, but needed to bind it differently 134 disableSubmit(origProcessingButtonText); 135 allowSeatsBookedWarning = false;
136 137 //KLUDGE:: for IOS issue with radio button not being able be focused on if required. 138 if(ticketInsuranceJsLoaded && $('#OrderPolicyTotal').val() != 0){ 139 var field = '[name="data[Order][insurance_opted_in]"]'; 140 // check if there was a selection 141 if ($(field + ':checked').length) { 142 // at least one of the radio buttons was check 143 } else { 144 // no radio button was checked 145 alert('Please select a Refund Protection option'); 146 $('.ti-radioOptions').css({ 147 'margin':'10px 0px', 148 'padding':'10px', 149 'border':'2px solid #FF1200', 150 'border-radius': '10px' 151 }); 152 var field = $(field); 153 field.trigger("focus"); 154 $('body').scrollTop(field.offset().top); 155 enableSubmit(origSubmitButtonText); 156 return false; 157 } 158 } 159 160 //if it is a cash transaction 161 if($('#OrderCashOption').val() && $('#OrderCashOption').prop('checked') == true){ 162 return submitOrder(); 163 } 164 165 //if it is free tickets or voucher 166 if($('#OrderProposedTotal').val() <= 0.00 || $('#OrderPurchaseLocation').val() == 'voucher'){ 167 return submitOrder(); 168 } 169 170 // checks if email is formatted correctly if not iframe and not empty, if the 171 // email field is required, it should not get here empty. 172 if ($('#OrderSource').val() != 'iframe' && $('#OrderEmail').val().length != 0){ 173 var testEmail = /^[A-Z0-9._%+-]+@([A-Z0-9-]+\.)+[A-Z]{2,4}$/i; 174 if (!testEmail.test($('#OrderEmail').val())){ 175 alertify.error('Email address entered is not valid'); 176 enableSubmit(origSubmitButtonText); 177 $('#OrderEmail').trigger("focus"); 178 return false; 179 } 180 } 181 182 switch(paymentGateway){ 183 case "stripe": 184 var paymentType = $('#OrderPaymentType').val(); 185 var $form = $(this); 186 $form.find('button').prop('disabled', true); // Disable the submit button to prevent repeated clicks 187 submitOrder(); 188 return false; 189 break; 190 case "authnet": 191 var $form = $(this); 192 $form.find('button').prop('disabled', true); // Disable the submit button to prevent repeated clicks 193 var isWaitlist = false; 194 if($('#OrderStatus').val() == 6){ 195 isWaitlist = true; 196 } 197 if(isWaitlist){ 198 submitOrder(); 199 } else { 200 sendPaymentDataToAnet(); 201 } 202 return false; 203 break; 204 case "square": 205 requestCardNonce(event); 206 break; 207 default: 208 return submitOrder(); 209 break; 210 } 211 } 212 213 // keeps the form from submitting w/ the enter key 214 // prevent default behavior for enter key unless it's a textarea 215 $(window).on("keydown",function(event) { 216 if (event.key == 'Enter' && event.target.nodeName != 'TEXTAREA') { 217 event.preventDefault(); 218 return false; 219 } 220 }); 221 222 updateCountry(); 223 224 // forces the charity to not accept non-numeric input 225 var charityKeyupTimeout = false; 226 $('.charity [name$="[total]"]').on("keyup", function() { 227 if(isNaN(parseFloat($(this).val()))) { // check if number was entered 228 $(this).val(''); 229 } 230 clearTimeout(charityKeyupTimeout); 231 charityKeyupTimeout = setTimeout(function() { $('.charity [name$="[total]"]').trigger("change"); }, 2000); //calculate_order_total(); 232 }); 233 234 $('.charity [name$="[total]"]').on("blur", function() { 235 clearTimeout(charityKeyupTimeout); 236 }); 237 238 //forces the charity input to be 2 decimal places 239 $('.charity [name$="[total]"]').on("change",function() { 240 clearTimeout(charityKeyupTimeout); 241 $(this).val(parseFloat($(this).val()).toFixed(2)); 242 }); 243 244 // Hide the less info/more info box when the other is shown 245 $(document).on('show.bs.collapse', '[id*=more-info],[id*=less-info]', function() { 246 $(this).parent().find('[id*=more-info],[id*=less-info]').not(this).collapse('hide'); 247 }); 248 249 //select a ticket type in single dropdown (compact view) 250 $('#TicketTypeSelect').on("change", function(e) { 251 var id = $(this).val(); 252 // $('.ticket-type-quantity-selector').hide().val(1); // sets quantity for all of them the 1st item in their respective list 253 $('.ticket-type-quantity-selector').hide(); 254 $('.ticket-type-price-area').hide(); 255 $('.ticket-type-price-area select option:first-child').attr("selected", "selected"); 256 $('.ticket-type-quantity-selector option:first-child').attr("selected", "selected"); 257 $('.ticket .reqmsg').hide();
258 $('[id^="ticket-type-description-"]').slideUp(); 259 if(id) { 260 // check if qty available or has sub types 261 if ($('#TicketType-' + id + '-quantitySelector').children().length != 0 || $('#ticketTypePricesArea-'+id).length > 0) { 262 $('#TicketType-' + id + '-quantitySelector').fadeIn('fast'); 263 $('#ticketTypePricesArea-'+id).show(); 264 $('#ticketOptionsAddButtonArea').fadeIn('fast'); 265 } else { // no qty available due to linked ticked not present 266 var linkmessage = $('.ticket select[name*="[quantity-general-' + id + '"]').attr('data-link-message'); 267 $('#TicketType-' + id + '-quantitySelector').after("<p class='reqmsg sold-out-text'><strong>"+linkmessage+"</strong></p>"); 268 $('.ticket [id^=TicketType-][id$=-quantitySelector]').fadeOut('fast'); 269 $('#ticketOptionsAddButtonArea').fadeOut('fast'); 270 $('#ticket-tip').fadeOut('fast'); 271 } 272 $('#ticket-type-description-' + id).slideDown('fast'); 273 $('#ticket-tip').fadeIn('fast'); 274 } else { 275 $('.ticket [id^=TicketType-][id$=-quantitySelector]').fadeOut('fast'); 276 $('#ticketOptionsAddButtonArea').fadeOut('fast'); 277 $('#ticket-tip').fadeOut('fast'); 278 } 279 }); 280 281 //select a merchandise type 282 $('#MerchandiseTypeSelect').on("change", function(e) { 283 var id = $(this).val(); 284 // $('.merchandise-type-quantity-selector').hide().val(1); // sets quantity for all of them the 1st item in their respective list 285 $('.merchandise-type-quantity-selector').hide(); 286 $('.merchandise-type-price-area').hide(); 287 $('.merchandise-type-price-area select option:first-child').attr("selected", "selected"); 288 $('.merchandise-type-quantity-selector option:first-child').attr("selected", "selected"); 289 $('.merchandise .reqmsg').hide(); 290 $('.merchandise [id^="merchandise-type-description-"]').slideUp(); 291 if(id) { 292 // check if qty available or has sub types 293 if ($('#MerchandiseType-' + id + '-quantitySelector').children().length != 0 || $('#merchandiseTypePricesArea-'+id).length > 0) { 294 $('#MerchandiseType-' + id + '-quantitySelector').fadeIn('fast'); 295 $('#merchandiseTypePricesArea-'+id).show(); 296 $('#merchandiseOptionsAddButtonArea').fadeIn('fast'); 297 } else { // no qty available due to linked ticked not present 298 var linkmessage = $('.merchandise select[name*="[quantity-general-' + id + '"]').attr('data-link-message'); 299 $('#MerchandiseType-' + id + '-quantitySelector').after("<p class='reqmsg sold-out-text'><strong>"+linkmessage+"</strong></p>"); 300 $('.merchandise [id^=MerchandiseType-][id$=-quantitySelector]').fadeOut('fast'); 301 $('#merchandiseOptionsAddButtonArea').fadeOut('fast'); 302 $('#merchandise-tip').fadeOut('fast'); 303 } 304 $('#merchandise-type-description-' + id).slideDown('fast'); 305 $('#merchandise-tip').fadeIn('fast'); 306 } else { 307 $('.merchandise [id^=MerchandiseType-][id$=-quantitySelector]').fadeOut('fast'); 308 $('#merchandiseOptionsAddButtonArea').fadeOut('fast'); 309 $('#merchandise-tip').fadeOut('fast'); 310 } 311 }); 312 313 //select a ticket type on wide format 314 $('#TicketTypeSelectWide').on("change", function(e) { 315 var id = $(this).val(); 316 $('.ticket .ticket-type-quantity-selector').hide(); 317 $('.ticket .ticket-type-price-area').hide(); 318 $('.ticket .ticket-type-price-area select option:first-child').attr("selected", "selected"); 319 $('.ticket .ticket-type-quantity-selector option:first-child').attr("selected", "selected"); 320 $('.ticket .reqmsg').hide(); 321 if(id) { 322 // check if qty available or has sub types 323 if ($('#TicketType-' + id + '-quantitySelector').children().length != 0 || $('#ticketTypePricesArea-'+id).length > 0) { 324 $('#TicketType-' + id + '-quantitySelector').fadeIn('fast'); 325 $('#ticketTypePricesArea-'+id).show(); 326 $('#ticketOptionsAddButtonArea').fadeIn('fast'); 327 $('#ticket-tip').fadeIn('fast'); 328 } else { // no qty available due to linked ticked not present 329 var linkmessage = $('.ticket select[name*="[quantity-general-' + id + '"]').attr('data-link-message'); 330 $('#TicketType-' + id + '-quantitySelector').after("<p class='reqmsg sold-out-text'><strong>"+linkmessage+"</strong></p>"); 331 $('[id^=TicketType-][id$=-quantitySelector]').fadeOut('fast');
332 $('#ticketOptionsAddButtonArea').fadeOut('fast'); 333 $('#ticket-tip').fadeOut('fast'); 334 } 335 $('#ticket-tip').fadeIn('fast'); 336 } else { 337 $('.ticket [id^=TicketType-][id$=-quantitySelector]').fadeOut('fast'); 338 $('#ticketOptionsAddButtonArea').fadeOut('fast'); 339 $('#ticket-tip').fadeOut('fast'); 340 } 341 342 $('.compact').hide(); 343 $('#compact-'+id).show(); 344 }); 345 346 $('#MerchandiseTypeSelectWide').on("change", function(e) { 347 var id = $(this).val(); 348 $('.merchandise-type-quantity-selector').hide(); 349 $('.merchandise-type-price-area').hide(); 350 $('.merchandise-type-price-area select option:first-child').attr("selected", "selected"); 351 $('.merchandise-type-quantity-selector option:first-child').attr("selected", "selected"); 352 $('.merchandise .reqmsg').hide(); 353 if(id) { 354 // check if qty available or has sub types 355 if ($('#MerchandiseType-' + id + '-quantitySelector').children().length != 0 || $('#merchandiseTypePricesArea-'+id).length > 0) { 356 $('#MerchandiseType-' + id + '-quantitySelector').fadeIn('fast'); 357 $('#merchandiseTypePricesArea-'+id).show(); 358 $('#merchandiseOptionsAddButtonArea').fadeIn('fast'); 359 $('#merchandise-tip').fadeIn('fast'); 360 } else { // no qty available due to linked ticked not present 361 var linkmessage = $('.merchandise select[name*="[quantity-general-' + id + '"]').attr('data-link-message'); 362 $('#MerchandiseType-' + id + '-quantitySelector').after("<p class='reqmsg sold-out-text'><strong>"+linkmessage+"</strong></p>"); 363 $('.merchandise [id^=MerchandiseType-][id$=-quantitySelector]').fadeOut('fast'); 364 $('#merchandise OptionsAddButtonArea').fadeOut('fast'); 365 $('#merchandise-tip').fadeOut('fast'); 366 } 367 $('#merchandise-tip').fadeIn('fast'); 368 } else { 369 $('.merchandise [id^=MerchandiseType-][id$=-quantitySelector]').fadeOut('fast'); 370 $('#merchandise OptionsAddButtonArea').fadeOut('fast'); 371 $('merchandise-tip').fadeOut('fast'); 372 } 373 374 $('.compact').hide(); 375 $('#compact-'+id).show(); 376 }); 377 378 //select a ticket type price/option 379 $('.ticket-type-price-select').on("change", function(e) { 380 var id = $(this).val(); 381 // $('.ticket-type-price-area').hide(); 382 $('.ticket-type-quantity-selector').hide(); 383 $('.ticket-type-quantity-selector option:first-child').attr("selected", "selected"); 384 $('.ticket .reqmsg').hide(); 385 if(id) { 386 if ($('#TicketTypePrice-' + id + '-quantitySelector').children().length != 0) { 387 $('#TicketTypePrice-' + id + '-quantitySelector').fadeIn('fast'); 388 } else { 389 // none available due to linked ticket not present 390 $('#ticketOptionsAddButtonArea').hide(); 391 var tickettypeid = $(this).attr('id').replace('TicketTypePriceOptions-',''); 392 var linkmessage = $('.ticket select[name*="[quantity-general-' + tickettypeid + '"]').attr('data-link-message'); 393 $('#TicketTypePrice-' + id + '-quantitySelector').after("<p class='reqmsg sold-out-text'><strong>"+linkmessage+"</strong></p>"); 394 $(this).val(''); 395 } 396 } 397 }); 398 399 //select a ticket type price/option 400 $('.merchandise-type-price-select').on("change", function(e) { 401 var id = $(this).val(); 402 // $('.ticket-type-price-area').hide(); 403 $('.merchandise-type-quantity-selector').hide(); 404 $('.merchandise-type-quantity-selector option:first-child').attr("selected", "selected"); 405 $('.merchandise .reqmsg').hide(); 406 if(id) { 407 if ($('#MerchandiseTypePrice-' + id + '-quantitySelector').children().length != 0) { 408 $('#MerchandiseTypePrice-' + id + '-quantitySelector').fadeIn('fast'); 409 } else { 410 // none available due to linked ticket not present 411 $('#merchandiseOptionsAddButtonArea').hide(); 412 var tickettypeid = $(this).attr('id').replace('MerchandiseTypePriceOptions-',''); 413 var linkmessage = $('.merchandise select[name*="[quantity-general-' + tickettypeid + '"]').attr('data-link-message'); 414 $('#MerchandiseTypePrice-' + id + '-quantitySelector').after("<p class='reqmsg sold-out-text'><strong>"+linkmessage+"</strong></p>"); 415 $(this).val(''); 416 } 417 } 418 }); 419 420 // when the "Add >" button is pressed 421 $('.ticketOptionsAddButton').on("click", function(e) { 422 e.preventDefault(); 423 424 var type = "ticketType"; 425 var maxTicketTypePriceQuantity = 0; 426 var ticketTypeId = $('#TicketTypeSelect').val(); 427 var ticketTypePriceId; 428 var maxTicketTypeQuantity = $("#OrderTicketTypeTmpValues-" + ticketTypeId).attr("data-ticket-type-qty-max"); 429 430 var quantitySelector = $('#TicketType-' + ticketTypeId + '-quantitySelector'); 431 432 if(typeof(quantitySelector.val()) == "undefined") { 433 ticketTypePriceId = $('#ticketTypePricesArea-'+ticketTypeId+' select:visible').val(); 434 quantitySelector = $('#TicketTypePrice-' + ticketTypePriceId + '-quantitySelector'); 435 type = "ticketTypePrice"; 436 } 437 438 row = quantitySelector.attr('data-ticket-type-row-key'); 439 var existingTypeCombinedQuantity = getTotalQuantityOfSpecificType(ticketTypeId); 440 var existingQuantity = parseInt($('#Ticket' + row + 'Quantity').val()); 441 var numToAdd = quantitySelector.val(); 442 443 if(type == 'ticketTypePrice') { 444 maxTicketTypePriceQuantity = parseInt($('#TicketTypePrice-' + ticketTypePriceId + '-quantitySelector option:last-child').val()); 445 } 446 447 numToAdd = parseInt(numToAdd); 448 var newTypeCombinedTotal = numToAdd + existingTypeCombinedQuantity; 449 var newTotal = numToAdd + existingQuantity; 450 var errorThrown = false;
451 452 //fallback for some variables that should be set from php to get translated strings 453 if (typeof maxQuantOption == 'undefined') { 454 var maxQuantOption = 'Max quantity for this specific option is '; 455 } 456 if (typeof maxQuantType == 'undefined') { 457 var maxQuantType = 'Max quantity for this type is '; 458 } 459 460 // check if the new quantity + current quantity of a specific ticket type id go over the ticket type price max 461 if (newTotal > maxTicketTypePriceQuantity && maxTicketTypePriceQuantity > 0) { 462 alertify.alert("Notice", maxQuantOption + maxTicketTypePriceQuantity).set('modal', true).set('basic', false).pin(); 463 errorThrown = true; 464 } 465 466 // check if the new total for a specific ticket TYPE (including this one and all others of this type) go over the type limit 467 if (!errorThrown && newTypeCombinedTotal > maxTicketTypeQuantity) { 468 alertify.alert("Notice", maxQuantType + maxTicketTypeQuantity).set('modal', true).set('basic', false).pin(); 469 errorThrown = true; 470 } 471 472 // otherwise, update the quantity 473 if(!errorThrown) { 474 475 var currentValue = $('#Ticket' + row + 'Quantity').val(); 476 if(!currentValue) { 477 currentValue = 0; 478 } 479 currentValue = parseInt(currentValue); 480 481 var newValue = numToAdd + currentValue; 482 483 $('#Ticket' + row + 'Quantity').val(newValue.toString()); 484 calculate_order_total(); 485 486 } 487 }); 488 489 // when the "Add >" button is pressed 490 $('.merchandiseOptionsAddButton').on("click", function(e) { 491 e.preventDefault(); 492 493 var type = "ticketType"; 494 var maxTicketTypePriceQuantity = 0; 495 var ticketTypeId = $('#MerchandiseTypeSelect').val(); 496 var ticketTypePriceId; 497 var maxTicketTypeQuantity = $("#OrderMerchandiseTypeTmpValues-" + ticketTypeId).attr("data-ticket-type-qty-max"); 498 499 var quantitySelector = $('#MerchandiseType-' + ticketTypeId + '-quantitySelector'); 500 501 if(typeof(quantitySelector.val()) == "undefined") { 502 ticketTypePriceId = $('#merchandiseTypePricesArea-'+ticketTypeId+' select:visible').val(); 503 quantitySelector = $('#MerchandiseTypePrice-' + ticketTypePriceId + '-quantitySelector'); 504 type = "ticketTypePrice"; 505 } 506 507 row = quantitySelector.attr('data-ticket-type-row-key'); 508 var existingTypeCombinedQuantity = getTotalQuantityOfSpecificType(ticketTypeId); 509 var existingQuantity = parseInt($('#Merchandise' + row + 'Quantity').val()); 510 var numToAdd = quantitySelector.val(); 511 512 if(type == 'ticketTypePrice') { 513 maxTicketTypePriceQuantity = parseInt($('#MerchandiseTypePrice-' + ticketTypePriceId + '-quantitySelector option:last-child').val()); 514 } 515 516 numToAdd = parseInt(numToAdd); 517 var newTypeCombinedTotal = numToAdd + existingTypeCombinedQuantity; 518 var newTotal = numToAdd + existingQuantity; 519 var errorThrown = false; 520 521 //fallback for some variables that should be set from php to get translated strings 522 if (typeof maxQuantOption == 'undefined') { 523 var maxQuantOption = 'Max quantity for this specific option is '; 524 } 525 if (typeof maxQuantType == 'undefined') { 526 var maxQuantType = 'Max quantity for this type is '; 527 } 528 529 // check if the new quantity + current quantity of a specific ticket type id go over the ticket type price max 530 if (newTotal > maxTicketTypePriceQuantity && maxTicketTypePriceQuantity > 0) { 531 alertify.alert("Notice", maxQuantOption + maxTicketTypePriceQuantity).set('modal', true).set('basic', false).pin(); 532 errorThrown = true; 533 } 534 535 // check if the new total for a specific ticket TYPE (including this one and all others of this type) go over the type limit 536 if (!errorThrown && newTypeCombinedTotal > maxTicketTypeQuantity) { 537 alertify.alert("Notice", maxQuantType + maxTicketTypeQuantity).set('modal', true).set('basic', false).pin(); 538 errorThrown = true; 539 } 540 541 // otherwise, update the quantity 542 if(!errorThrown) { 543 544 var currentValue = $('#Ticket' + row + 'Quantity').val(); 545 if(!currentValue) { 546 currentValue = 0; 547 } 548 currentValue = parseInt(currentValue); 549 550 var newValue = numToAdd + currentValue; 551 552 $('#Ticket' + row + 'Quantity').val(newValue.toString()); 553 calculate_order_total(); 554 555 } 556 }); 557 558 // Shim <input required="true" /> behavior for Safari 559 //webshims.polyfill('forms'); 560 561 $('#Contribution0Select').on("change", function(){ 562 if ($(this).val() == 'other') { 563 $('#Contribution0Total').val('').show(); 564 calculate_order_total(); 565 } else if ($(this).val() == 'no') { 566 $('#Contribution0Total').val('0').hide(); 567 calculate_order_total(); 568 } else { 569 $('#Contribution0Total').val($(this).val()).hide(); 570 calculate_order_total(); 571 } 572 }); 573 574 $('#promo_field_sm').on('keyup', function(e){ 575 if (e.key === 'Enter' || e.keyCode === 13) { 576 e.preventDefault(); 577 applyPromoCode('#promo_field_sm'); 578 } 579 }); 580 581 $('#promo_field').on('keyup', function(e){ 582 if (e.key === 'Enter' || e.keyCode === 13) { 583 e.preventDefault(); 584 applyPromoCode('#promo_field'); 585 } 586 }); 587 588 $('#promo_field_btm').on('keyup', function(e){ 589 if (e.key === 'Enter' || e.keyCode === 13) { 590 e.preventDefault(); 591 applyPromoCode('#promo_field_btm'); 592 } 593 }); 594 595 596 597 /** 598 * *********************************************************************** 599 * Moved this section from the bottom of the Orders::tickets view to here 600 */ 601 602 toggleShippingFields(); 603 604 $('#OrderDeliveryMethod').on("change", function() { 605 //first, hide all existing verbiage 606 $('.option-verbiage').slideUp('fast'); 607 //then show only the needed one 608 $('#option-' + $(this).val()).slideDown('fast'); 609 //now recalculate the order total to calculate any fees 610 calculate_order_total(); 611 }); 612 613 $('.paymentToggle').on("click", function() { 614 switch ($(this).attr('id')) { 615 case 'OrderToggleCC': 616 switchPaymentMethod('ideal'); 617 break;
618 case 'OrderToggleIDEAL': 619 switchPaymentMethod('creditCard'); 620 break; 621 } 622 }); 623 624 $('#OrderConfirmEmail').on("change", function() { 625 if ($('#OrderEmail').val() != $(this).val() && $('#OrderEmail').val().toUpperCase() != $(this).val().toUpperCase()) { 626 $(this).val(''); 627 $(this).trigger("focus"); 628 alertify.error('The email confirmation field must match the email field.'); 629 } 630 }); 631 /** 632 * *********************************************************************** 633 * END of section moved from bottom of tickets view to here 634 */ 635 636 $("#ts-modal-btn-continue-shopping").on('click', function(event) { 637 window.parent.postMessage({ 638 messageName: 'tsModalClose' 639 }, '*'); 640 }); 641 642 $("#ts-modal .ts-modal-reload-required .btn").on('click', function(event) { 643 const modalMessageData = { 644 ticketsUrl: window.location.origin + window.location.pathname 645 }; 646 window.parent.postMessage({ 647 messageName: 'tsReloadModal', 648 data: modalMessageData 649 }, '*'); 650 }); 651}); 652 653 654 655function getTotalQuantityOfSpecificType(id) { 656 var total = 0; 657 $("[id ^='Ticket'][id $='Quantity'][data-ticket-type-id ='"+id+"']").each(function() { 658 if($(this).val() != '') { 659 total += parseInt($(this).val()); 660 } 661 }); 662 return total; 663} 664 665 666function disableSubmit(text) { 667 disableModalNextButton(); 668 disableContinueShoppingButton(); 669 if(!text) { 670 text = "Sending..."; 671 } 672 $('#orderFormSubmitButton').addClass('disabledButton'); 673 $('#orderFormSubmitButton').attr('disabled', true); 674 $('#orderFormSubmitButton').prop("disabled", true); 675 $('#orderFormSubmitButton').html(text); 676} 677 678 679function enableSubmit(text) { 680 ignoreLeaveAttempts(); // often already done, but adding here for those missed 681 if(!text) { 682 text = origSubmitButtonText; 683 } 684 $('#orderFormSubmitButton').removeClass('disabledButton'); 685 $('#orderFormSubmitButton').prop('disabled', ''); 686 $('#orderFormSubmitButton').html(text); 687} 688 689function disableModalNextButton() { 690 if ($('#ts-modal-btn-next-step').length) { 691 $('#ts-modal-btn-next-step').attr("disabled", true); 692 $('#ts-modal-btn-next-step').prop("disabled", true); 693 } 694} 695function enableModalNextButton() { 696 if ($('#ts-modal-btn-next-step').length) { 697 $('#ts-modal-btn-next-step').prop('disabled', ''); 698 } 699} 700function disableContinueShoppingButton() { 701 if ($('#ts-modal-btn-continue-shopping').length) { 702 $('#ts-modal-btn-continue-shopping').attr("disabled", true); 703 $('#ts-modal-btn-continue-shopping').prop("disabled", true); 704 } 705} 706function enableContinueShoppingButton() { 707 if ($('#ts-modal-btn-continue-shopping').length) { 708 $('#ts-modal-btn-continue-shopping').prop('disabled', ''); 709 } 710} 711 712function _updateLocalCartData() { 713 var cartId = _getCartId(); 714 cartQuantities = { 715 ticketTypePrices: {}, 716 credits: {}, 717 }; 718 document.querySelectorAll("select[name^='data[Ticket]'][name$='[quantity]']").forEach(function(select) { 719 var ticketTypePriceId = select.getAttribute('data-ticket-type-price-id'); 720 var quantity = parseInt(select.value, 10); 721 if (quantity > 0) { 722 cartQuantities['ticketTypePrices'][ticketTypePriceId] = quantity; 723 } 724 }); 725 document.querySelectorAll("select[name^='data[CreditProduct]'][name$='[quantity]']").forEach(function(select) { 726 var creditProductId = select.getAttribute('data-credit-product-id'); 727 var quantity = parseInt(select.value, 10); 728 if (quantity > 0) { 729 cartQuantities['credits'][creditProductId] = quantity; 730 } 731 }); 732 let eventIdElement = document.getElementById("OrderEventId"); 733 if (!eventIdElement || !eventIdElement.value) { 734 return; 735 } 736 previousLocalCartData = localCartData; 737 localCartData = { 738 event_id: eventIdElement.value, 739 id: cartId, 740 quantities: cartQuantities, 741 }; 742} 743 744function _hasLocalCartChanged() { 745 return JSON.stringify(previousLocalCartData) !== JSON.stringify(localCartData); 746} 747 748function _notifySavingCart() { 749 window.parent.postMessage({ 750 messageName: 'tsSavingCart' 751 }, '*'); 752} 753function _notifySavingCartComplete() { 754 window.parent.postMessage({ 755 messageName: 'tsSavingCartComplete' 756 }, '*'); 757} 758 759function isModal() { 760 return typeof thisWindowIsModal !== 'undefined' && thisWindowIsModal === true; 761} 762 763function _ajaxSaveCart() { 764 if (!isModal()) { 765 return; 766 } 767 _updateLocalCartData(); 768 if (!_hasLocalCartChanged()) { 769 return; 770 } 771 _notifySavingCart(); 772 $.ajax({ 773 url: buildURL('/carts/ajax_save'), 774 type: 'POST', 775 contentType: 'application/json', 776 data: JSON.stringify(localCartData), 777 success: function(response) { 778 responseObject = JSON.parse(response); 779 if (responseObject.success) { 780 window.parent.postMessage({ 781 messageName: 'tsModalCartSaveSuccess' 782 }, '*'); 783 } else { 784 window.parent.postMessage({ 785 messageName: 'tsModalCartSaveError' 786 }, '*'); 787 } 788 }, 789 error: function(xhr, status, error) { 790 window.parent.postMessage({ 791 messageName: 'tsModalCartSaveError' 792 }, '*'); 793 }, 794 complete: function(xhr, status) { 795 _notifySavingCartComplete(); 796 } 797 }); 798} 799 800function applyPromoCode(promoFieldSelector, formId) { 801 $('#promo_field').val($(promoFieldSelector).val()); 802 $('#promo_field_sm').val($(promoFieldSelector).val()); 803 $('#promo_field_btm').val($(promoFieldSelector).val()); 804 $('#real-promo-field').val($(promoFieldSelector).val()); 805 806 if (typeof formId == 'undefined' || formId.length < 3) { 807 formId = '#orderForm'; 808 } 809 810 calculate_order_total(formId); 811} 812 813 814// FUNCTION TO SHOW/HIDE THE STATE/PROVINCE/DISTRICT FIELDS BASED ON COUNTRY SELECTION 815function updateCountry() { 816 var selectedCountry = $('#OrderCountry').val(); 817 switch(selectedCountry){ 818 case 'US': 819 $('.orderProvinceArea select').prop('disabled', 'disabled'); 820 $('.orderProvinceArea').hide(); 821 $('.orderMxDistrictArea select').prop('disabled', 'disabled'); 822 $('.orderMxDistrictArea').hide(); 823 $('.orderAustralianStateArea select').prop('disabled', 'disabled'); 824 $('.orderAustralianStateArea').hide(); 825 $('.orderUSSTateArea select').removeAttr('disabled'); 826 $('.orderUSSTateArea').show(); 827 break; 828 case 'CA': 829 $('.orderUSSTateArea select').prop('disabled', 'disabled'); 830 $('.orderUSSTateArea').hide(); 831 $('.orderMxDistrictArea select').prop('disabled', 'disabled'); 832 $('.orderMxDistrictArea').hide(); 833 $('.orderAustralianStateArea select').prop('disabled', 'disabled'); 834 $('.orderAustralianStateArea').hide(); 835 $('.orderProvinceArea select').removeAttr('disabled'); 836 $('.orderProvinceArea').show(); 837 break; 838 case 'MX': 839 $('.orderUSSTateArea select').prop('disabled', 'disabled'); 840 $('.orderUSSTateArea').hide(); 841 $('.orderProvinceArea select').prop('disabled', 'disabled'); 842 $('.orderProvinceArea').hide(); 843 $('.orderAustralianStateArea select').prop('disabled', 'disabled'); 844 $('.orderAustralianStateArea').hide(); 845 $('.orderMxDistrictArea select').removeAttr('disabled'); 846 $('.orderMxDistrictArea').show(); 847 break; 848 case 'AU': 849 $('.orderUSSTateArea select').prop('disabled', 'disabled'); 850 $('.orderUSSTateArea').hide(); 851 $('.orderMxDistrictArea select').prop('disabled', 'disabled'); 852 $('.orderMxDistrictArea').hide(); 853 $('.orderProvinceArea select').prop('disabled', 'disabled'); 854 $('.orderProvinceArea').hide(); 855 $('.orderAustralianStateArea select').removeAttr('disabled'); 856 $('.orderAustralianStateArea').show(); 857 break; 858 default: 859 $('.orderUSSTateArea select').prop('disabled', 'disabled'); 860 $('.orderUSSTateArea').hide(); 861 $('.orderProvinceArea select').prop('disabled', 'disabled'); 862 $('.orderProvinceArea').hide(); 863 $('.orderAustralianStateArea select').prop('disabled', 'disabled'); 864 $('.orderAustralianStateArea').hide(); 865 $('.orderMxDistrictArea select').prop('disabled', 'disabled'); 866 $('.orderMxDistrictArea').hide(); 867 break; 868 } 869} 870 871 872// FUNCTION TO SHOW/HIDE THE SEPARATE SHIPPING STATE/PROVINCE/DISTRICT FIELDS BASED ON COUNTRY SELECTION 873function updateCountryShipping() { 874 var selectedCountry = $('#OrderCountryShipping').val(); 875 switch(selectedCountry){ 876 case 'US': 877 $('.orderProvinceAreaShipping select').prop('disabled', 'disabled'); 878 $('.orderProvinceAreaShipping').hide(); 879 $('.orderMxDistrictAreaShipping select').prop('disabled', 'disabled'); 880 $('.orderMxDistrictAreaShipping').hide(); 881 $('.orderAustralianStateAreaShipping select').prop('disabled', 'disabled'); 882 $('.orderAustralianStateAreaShipping').hide(); 883 $('.orderUSSTateAreaShipping select').removeAttr('disabled'); 884 $('.orderUSSTateAreaShipping').show(); 885 break; 886 case 'CA': 887 $('.orderUSSTateAreaShipping select').prop('disabled', 'disabled'); 888 $('.orderUSSTateAreaShipping').hide(); 889 $('.orderMxDistrictAreaShipping select').prop('disabled', 'disabled'); 890 $('.orderMxDistrictAreaShipping').hide(); 891 $('.orderAustralianStateAreaShipping select').prop('disabled', 'disabled'); 892 $('.orderAustralianStateAreaShipping').hide(); 893 $('.orderProvinceAreaShipping select').removeAttr('disabled'); 894 $('.orderProvinceAreaShipping').show(); 895 break; 896 case 'MX': 897 $('.orderUSSTateAreaShipping select').prop('disabled', 'disabled'); 898 $('.orderUSSTateAreaShipping').hide(); 899 $('.orderProvinceAreaShipping select').prop('disabled', 'disabled'); 900 $('.orderProvinceAreaShipping').hide(); 901 $('.orderAustralianStateAreaShipping select').prop('disabled', 'disabled'); 902 $('.orderAustralianStateAreaShipping').hide(); 903 $('.orderMxDistrictAreaShipping select').removeAttr('disabled'); 904 $('.orderMxDistrictAreaShipping').show(); 905 break; 906 case 'AU': 907 $('.orderUSSTateAreaShipping select').prop('disabled', 'disabled'); 908 $('.orderUSSTateAreaShipping').hide(); 909 $('.orderMxDistrictAreaShipping select').prop('disabled', 'disabled'); 910 $('.orderMxDistrictAreaShipping').hide(); 911 $('.orderProvinceAreaShipping select').prop('disabled', 'disabled'); 912 $('.orderProvinceAreaShipping').hide(); 913 $('.orderAustralianStateAreaShipping select').removeAttr('disabled'); 914 $('.orderAustralianStateAreaShipping').show(); 915 break; 916 default: 917 $('.orderUSSTateAreaShipping select').prop('disabled', 'disabled'); 918 $('.orderUSSTateAreaShipping').hide(); 919 $('.orderProvinceAreaShipping select').prop('disabled', 'disabled'); 920 $('.orderProvinceAreaShipping').hide(); 921 $('.orderAustralianStateAreaShipping select').prop('disabled', 'disabled'); 922 $('.orderAustralianStateAreaShipping').hide(); 923 $('.orderMxDistrictAreaShipping select').prop('disabled', 'disabled'); 924 $('.orderMxDistrictAreaShipping').hide(); 925 break; 926 } 927} 928 929function toggleShippingFields() { 930 if ($('#OrderUseBilling').prop('checked')) {
931 $('.shipping-fields').slideUp('fast'); 932 $('.shipping-fields input').each(function(){ 933 $(this).prop('disabled', true); 934 }); 935 $('.shipping-fields select').each(function(){ 936 $(this).prop('disabled', true); 937 }); 938 } else { 939 $('.shipping-fields input').each(function(){ 940 $(this).removeAttr('disabled'); 941 }); 942 $('.shipping-fields select').each(function(){ 943 $(this).removeAttr('disabled'); 944 }); 945 $('.shipping-fields').slideDown('fast'); 946 updateCountryShipping(); 947 } 948} 949 950 951/** 952 * need to check for ticket insurance and run that before calculation. 953 */ 954function calculate_order_total(formId){ 955 hasRunCalculate = true; 956 // checking here in case they select tickets before the DOM is complete and loaded. 957 ticketInsuranceJsLoaded = (typeof updateInsuranceQuote === "function"); 958 if(ticketInsuranceJsLoaded){ 959 // check for insurance quote before order calculation. 960 updateInsuranceQuote(actual_calculate_order_total); 961 } else { 962 // just run the order calculation 963 actual_calculate_order_total(formId); 964 } 965} 966 967/** 968 * Ajax call to calculate the order and return json results of all the parts making up the total 969 * @return {[type]} [description] 970 */ 971var numCalculations = 0; 972function actual_calculate_order_total(formId) { 973 numCalculations++; 974 var thisCalculationNum = numCalculations; 975 976 disableSubmit(origSubmitButtonText); 977 978 // check to make sure any linked ticket type quantities are fulfilled 979 updateLinkedTypes(); 980 981 // check to make sure allowed quantities of multi-prices are selected 982 // example - if the ticket type allows only 1 per order, but there are 3 price options, they can't be allowed to select more than one 983 984 var maxSelected = 0; 985 var overTheLimit = false; 986 var overRemaining = false; 987 var typeQtySelected = 0; 988 var selectedValue = 0; 989 var waitListOnly = $('#ticket-types-content').hasClass('wait-list-only'); 990 991 $(".ticket-type-prices").each(function() { 992 maxSelected = parseInt($(this).attr('data-max-per-order')); 993 numRemaining = parseInt($(this).attr('data-num-remaining')); 994 typeQtySelected = 0; 995 var fieldName = '';$(this).attr('name'); 996 997 $(this).find('.ticketOptions div select[name$="[quantity]"]').each(function(i, value) { 998 999 var selectedValue = parseInt($(value).val()); 1000 1001 if(isNaN(selectedValue)) { 1002 selectedValue = 0; 1003 } 1004 typeQtySelected += selectedValue; 1005 1006 if(maxSelected != 0 && typeQtySelected > maxSelected) { 1007 overTheLimit = true; 1008 $(value).prop('selectedIndex', 0); 1009 } 1010 if(typeQtySelected > numRemaining && !waitListOnly) { 1011 overRemaining = true; 1012 $(value).prop('selectedIndex', 0); 1013 } 1014 }); 1015 1016 //fallback if translated strings are missing 1017 if (typeof onlyText == 'undefined') { 1018 var onlyText = "Only "; 1019 var maxText = "Max "; 1020 var remainText = " of this type remaining."; 1021 var perOrderText = " of this type per order."; 1022 } 1023 1024 if (overRemaining) { 1025 alertify.alert("Notice", onlyText + numRemaining + remainText).set('modal', true).set('basic', false).pin(); 1026 return false; 1027 } else if (overTheLimit) { 1028 alertify.alert("Notice", maxText + maxSelected + perOrderText).set('modal', true).set('basic', false).pin(); 1029 return false; 1030 } 1031 }); 1032 1033 1034 disableModalNextButton(); 1035 disableContinueShoppingButton(); 1036 $(".orderPriceDetails").slideDown(200); 1037 $(".orderPriceDetails").fadeTo('fast', 0.4); 1038 $(".orderPriceDetails .ajax_loader").fadeIn('fast'); 1039 $(".orderSummary").fadeTo('fast', 0.4); 1040 $('.promo_value').val($('#real-promo-field').val()); 1041 if($('.promo_value').val() == 'Promo Code') { 1042 $('.promo_value').val(''); 1043 } 1044 1045 // repeats through each quantity-select box 1046 var ticketTypeNum = null; 1047 var fieldName = null; 1048 var ticketsPer = null; 1049 var quantity; 1050 var tmpVal = ""; 1051 var ticketTypeName; 1052 var ticketTypePriceId; 1053 var atLeastOneTicketQuantity = false; 1054 var quantityPrices = []; 1055 var totalQuantity = 0; 1056 1057 $('[name$="[quantity]"]').each(function() { 1058 1059 fieldName = $(this).attr('name'); 1060 1061 ticketTypeNum = fieldName.match(/\d+/); 1062 1063 // ticketTypeName = $(this).siblings('[name$="[ticket_type_name]"]').val(); 1064 // ticketTypePriceId = $(this).siblings('[name$="[ticket_type_price_id]"]').val(); 1065 ticketTypeName = $('#Ticket' + ticketTypeNum + 'TicketTypeName').val(); 1066 ticketTypePriceId = $('#Ticket' + ticketTypeNum + 'TicketTypePriceId').val(); 1067 1068 // TODO: this is just getting the first ticket type price id 1069 1070 if(ticketTypeName == undefined) { // for use in the multi-tickets 1071 ticketTypeName = $(this).parent().siblings('[name$="[ticket_type_name]"]').val(); 1072 ticketTypePriceId = $(this).parent().siblings('[name$="[ticket_type_price_id]"]').val(); 1073 } 1074 1075 ticketsPer = $(this).attr('data-tickets-per'); // number of tickets to generate per "1" selected (ie "") 1076 quantity = $(this).val(); 1077 totalQuantity += parseInt(quantity); 1078 1079 // saves if there's been at least one quantity 1080 if ($(this).val() > 0) { 1081 if($('#Ticket' + ticketTypeNum + 'ProductType').val() == 'ticket'){ 1082 atLeastOneTicketQuantity = true; 1083 } 1084 // if Ticket Insurance Is enabled. 1085 if($('#OrderTicketInsuranceStatus').val() == 1){ 1086 // adding up for the policy 1087 for(i = 0; i < quantity; i++){ 1088 if($('#Ticket' + ticketTypeNum + 'PriceEach').val() != "0.00" && $('#Ticket' + ticketTypeNum + 'ProductType').val() == 'ticket'){ 1089 quantityPrices.push($('#Ticket' + ticketTypeNum + 'PriceEach').val()); 1090 } 1091 } 1092 } 1093 } 1094 // get/store previous ticket names for this ticket type/price and removes the name rows 1095 var values = []; 1096 $('[name^="data[TicketName]['+ticketTypePriceId + '"]').each(function() { 1097 if(values[ticketTypePriceId] == undefined) { 1098 values[ticketTypePriceId] = []; 1099 } 1100 1101 // if (ticketsPer > 1) { 1102 values[ticketTypePriceId].push($(this).val()); 1103 $(this).parent().remove(); 1104 }); 1105 1106 var input_required; 1107 1108 }); 1109 1110 if (typeof formId == 'undefined' || formId.length < 3) { 1111 if (typeof gFormId == 'undefined') { 1112 formId = '#' + $('form').attr('id'); 1113 } else { 1114 formId = '#' + gFormId; 1115 } 1116 } 1117 1118 //Handle the timer_start_time field 1119 1120 //get the potential local timer start time (we need the time from the user's machine to get an accurate countdown) 1121 startTime = new Date(); 1122 //convert it to UTC 1123 startTime = new Date(startTime.getTime() + startTime.getTimezoneOffset() * 60000); 1124 //check whether the field already exists or needs to be created 1125 if (!$('input[name="timer_start_time"]').length) { 1126 //put the date in a hidden input appended to the form 1127 $(formId).append('<input type="hidden" name="timer_start_time" value="' + startTime + '" />'); 1128 } else { 1129 //already exists, insert updated value 1130 $('input[name="timer_start_time"]').val(startTime); 1131 } 1132 1133 var fieldsToSubmit = getOrderFormDataWithoutUnmodifiedZeroQuantityTickets(formId); 1134 1135 var overlimit = false;
1136 1137 $.ajax({ 1138 type: "POST", 1139 url: buildURL('/orders/calculate_price'), 1140 data: fieldsToSubmit, 1141 dataType: "json", 1142 success: function(data) { 1143 if (data['Timer'] != false) { 1144 if ($('#registration-timer').css('display') == 'none') { 1145 // start the timer 1146 timerCountdown(data['Timer'], 'registration-timer'); 1147 // $('#registration-timer').show(); 1148 $('#registration-timer').css('display', 'flex'); 1149 } 1150 } else { 1151 //timer is false, this could either mean there was never a timer - no timer even enabled, 1152 //OR it could mean there was a timer previously but now it needs to be turned off, 1153 //so we need to check to see if a timer is in play, and deactivate it if so 1154 if ($('#registration-timer').css('display') == 'flex') { 1155 clearInterval(interval); 1156 $('#registration-timer').hide(); 1157 $('#registration-timer .timer-minutes').text(''); 1158 } 1159 } 1160 1161 if (data['overLimit'] != false) { 1162 alertify.alert(qtyNotAvailMsg).set('title', qtyNotAvailTitle); 1163 $.each(data['overLimit'], function(index, value){ 1164 overlimit = true; 1165 if (value <= 0) { 1166 $('select[data-ticket-type-price-id="' + index + '"]').val(''); 1167 $('*[data-ticket-type-price-id="' + index + '"]').val(''); 1168 } else { 1169 $('select[data-ticket-type-price-id="' + index + '"]').val(value); 1170 $('*[data-ticket-type-price-id="' + index + '"]').val(value); 1171 } 1172 1173 // if was overlimit and adjusted, change the drop down to not allow another attempt 1174 // to overbook. 1175 $('*[data-ticket-type-price-id="' + index + '"] option').filter(function(){ 1176 return parseInt(this.value) > value; 1177 }).remove(); 1178 }); 1179 } 1180 1181 // Leave exchange prefills intact; removing #ticket-<id> cleared required hidden inputs. 1182 if (data['purchase_location'] == 'online' && data['orderTicketLimitPassed'] != false) { 1183 var qtyTicketLimitTitle = 'Order Limit Exceeded'; 1184 // var qtyTicketLimitMsg = 'Orders on this event are limited to ' + data['orderTicketLimit'] + ' items. Please adjust your quantities.'; 1185 var qtyTicketLimitMsg = 'You\'ve exceeded the quantity limits on this event. Please adjust your quantities and try again.'; //CZ - temp generic message for Kokefest launch April 2022, TODO: allow client to customize failure message 1186 alertify.alert(qtyTicketLimitTitle, qtyTicketLimitMsg).setting({ 1187 'message': qtyTicketLimitMsg, 1188 'modal': true, 1189 'basic': false, 1190 'closable': true, 1191 'closableByDimmer': false}) 1192 .set({ 1193 onok: function(closeEvent) { return true; }, // i think it didn't jump down before having to add this 1194 }); 1195 } 1196 1197 if (typeof data['registrationClosed'] != 'undefined' && data['registrationClosed'] == true) { 1198 alertify.alert(qtyNotAvailMsg).set('title', qtyNotAvailTitle); 1199 $.each(data['registrationClosed'], function(index, value){ 1200 $('input[name$="[activity_instance_id]"], input[value="'+value+'"]') 1201 .closest('.ticketOptions') 1202 .find('select[name$="[quantity]"]').val(''); 1203 }); 1204 } 1205 1206 if(thisCalculationNum == numCalculations) { 1207 // does not have a summary or questionnaires on the iframe view 1208 if ($('#OrderSource').val() != 'iframe'){ 1209 if ((data.giftCardStatus === 'expired' || data.giftCardStatus === 'inactive') && data.giftCardMessage) { 1210 alertify.alert(data.giftCardError, data.giftCardMessage).set('modal', true).set('basic', false).pin(); 1211 } 1212 updateQuestionnaires(); 1213 updateStripeMetadataFields(data, formId); 1214 updateSummary(data, formId); 1215 if($('#OrderPaymentIntentId').val()){ 1216 // nothing needed 1217 } else { 1218 enableSubmit(origSubmitButtonText); 1219 } 1220 } 1221 } 1222 if (orderTotalQuantity() <= 0) { 1223 disableModalNextButton(); 1224 setHasModalSelection(false); 1225 } else { 1226 enableModalNextButton(); 1227 setHasModalSelection(true); 1228 } 1229 enableContinueShoppingButton(); 1230 1231 if (typeof updateDripCart === 'function') { 1232 updateDripCart(); // defined in main.js 1233 } 1234 if (isModal()) { 1235 _ajaxSaveCart(); 1236 const modalMessageData = { 1237 windowId: getWindowId(), 1238 totalQuantity: totalQuantity, 1239 quantitiesHash: fnv1aHash(JSON.stringify(localCartData)), 1240 quantitiesUpdated: Date.now(), 1241 ticketsUrl: window.location.origin + window.location.pathname 1242 }; 1243 window.parent.postMessage({ 1244 messageName: 'tsUpdateModalCart', 1245 data: modalMessageData 1246 }, '*'); 1247 } 1248 }, 1249 error: function (errorMessage) { 1250 return false;
1251 } 1252 }); 1253 if(overlimit){ 1254 calculate_order_total(); 1255 } 1256} 1257 1258function fnv1aHash(str) { 1259 let hash = 0x811c9dc5; // FNV offset basis 1260 for (let i = 0; i < str.length; i++) { 1261 hash ^= str.charCodeAt(i); 1262 hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24); 1263 } 1264 return (hash >>> 0).toString(16); // Ensure unsigned 32-bit output 1265} 1266 1267function getWindowId() { 1268 const urlParams = new URLSearchParams(window.location.search); 1269 if (urlParams.has('wid')) { 1270 return urlParams.get('wid'); 1271 } 1272 return ""; 1273} 1274 1275function updateStripeMetadataFields(data, formId) { 1276 let quantityOfTicketProducts = data['totalTicketProducts']; 1277 let calculatedSubtotal = data['subTotal']; 1278 let calculatedLETTax = getLETTaxFromCustomFeesOrderData(data['CustomFeesOrders']); 1279 let calculatedSurchargeFees = getSurchargeFeeFromCustomFeesOrderData(data['CustomFeesOrders']); 1280 let calculatedAdminCharges = data['admin_charges']; 1281 let calculateDiscountAmt = data['promoCodeAmount']; 1282 $('#OrderQtyTickets').val(quantityOfTicketProducts); 1283 $('#OrderSubtotal').val(calculatedSubtotal); 1284 $('#OrderLetTax').val(calculatedLETTax); 1285 $('#OrderTotalSurcharge').val(calculatedSurchargeFees); 1286 $('#OrderAdminCharges').val(calculatedAdminCharges); 1287 $('#OrderDiscountAmt').val(calculateDiscountAmt); 1288} 1289 1290 1291function getSurchargeFeeFromCustomFeesOrderData(data) { 1292 let returnValue = 0; 1293 1294 if (!Array.isArray(data)) { 1295 return returnValue; 1296 } 1297 1298 $.each(data, function(key, value) { 1299 switch(value['custom_fee_name'].toLowerCase()) { 1300 case 'surcharge fee': 1301 case 'surcharge': 1302 returnValue += parseFloat(value['actual_fee_applied']); 1303 break; 1304 } 1305 }); 1306 return returnValue; 1307} 1308 1309function getLETTaxFromCustomFeesOrderData(data) { 1310 let returnValue = 0; 1311 1312 if (!Array.isArray(data)) { 1313 return returnValue; 1314 } 1315 1316 $.each(data, function(key, value) { 1317 switch(value['custom_fee_name'].toLowerCase()) { 1318 case 'let tax': 1319 case 'let': 1320 returnValue += parseFloat(value['actual_fee_applied']); 1321 break; 1322 } 1323 }); 1324 return returnValue; 1325} 1326 1327 1328function setHasModalSelection(hasSelection) { 1329 tsModalHasSelection = hasSelection; 1330 if (hasSelection) { 1331 window.parent.postMessage({ 1332 messageName: 'modalHasSelection' 1333 }, '*'); 1334 } else { 1335 window.parent.postMessage({ 1336 messageName: 'modalNothingSelected' 1337 }, '*'); 1338 evnt = new CustomEvent('modalNothingSelected'); 1339 document.dispatchEvent(evnt); 1340 } 1341} 1342 1343function handleModalOrderComplete() { 1344 const modalMessageData = { 1345 ticketsUrl: window.location.origin + window.location.pathname, 1346 }; 1347 window.parent.postMessage( 1348 { 1349 messageName: "tsModalOrderComplete", 1350 data: modalMessageData, 1351 }, 1352 "*", 1353 ); 1354 setHasModalSelection(false); 1355} 1356 1357function updateLinkedTypes() { 1358 // for each ticket type: 1359 // if master required, check how many in cart, and limit dropdown accordingly 1360 $("div[id$='Quantityreq']").remove(); 1361 1362 // standard display 1363 $('select[name$="[quantity]"]').each(function() { 1364 // admin order tickets page doesn't have the attributes, and limits shouldn't be applied front-end for admin. 1365 if ($(this).attr('data-linked-to')) { 1366 // split the json array 1367 // get the total max allowed qty 1368 var linkto = $(this).data('linked-to'); 1369 var linkmessage = $(this).data('link-message'); 1370 var ticketsper = $(this).data('tickets-per'); 1371 var currentMasterQty = 0; 1372 var allowedQty = 0; 1373 var origMax = 0; 1374 var origMin = 0; 1375 if(linkto != '') { 1376 if ($(this).attr('data-link-orig-max')) { 1377 origMax = $(this).attr('data-link-orig-max'); 1378 origMin = $(this).attr('data-link-orig-min'); 1379 } else { 1380 $(this).attr('data-link-orig-max',$(this).children('option:last').val()); 1381 origMax = $(this).attr('data-link-orig-max'); 1382 $(this).attr('data-link-orig-min',$(this).children('option:eq(1)').val()); 1383 origMin = $(this).attr('data-link-orig-min'); 1384 } 1385 currentval = ''; 1386 currentval = $(this).val(); 1387 $(this).val(''); 1388 currentMasterQty = 0; 1389 $.each(linkto, function(key,value){ 1390 $('[data-ticket-type-id='+ key +']').each(function() { 1391 currentMasterQty += Number($(this).val()) * value; 1392 }); 1393 }) 1394 allowedQty = Math.min(currentMasterQty, origMax); 1395 if (allowedQty < $(this).children('option:last').val()) { 1396 $(this).children().filter(function() { 1397 return this.value > allowedQty; 1398 }).remove(); 1399 } else if (allowedQty > $(this).children('option:last').val()) { 1400 var i=0; 1401 for (i=Number($(this).children('option:last').val()) + 1; i < allowedQty +1; i++) { 1402 if (i>=origMin) { 1403 if (ticketsper > 1) { 1404 $(this).append($('<option>
1404', {value: i, text: i + ' (' + i * ticketsper + ' tickets)'})); 1405 } else { 1406 $(this).append($('<option>', {value:i, text:i})); 1407 } 1408 } 1409 } 1410 } 1411 1412 if(currentval <= allowedQty) $(this).val(currentval); 1413 1414 if($(this).children('option:last').val() == '' || $(this).children('option:last').val() == '0') { 1415 $(this).hide().after("<div id='"+$(this).attr('id')+"req' class='sold-out-text'>"+linkmessage+"</div>"); 1416 } else { 1417 $(this).show(); 1418 $("#"+$(this).attr('id')+"req").hide(); 1419 } 1420 1421 1422 } 1423 } 1424 }); 1425 1426 // modern theme 1427 $('input[name$="[quantity]"]').each(function() { 1428 // admin order tickets page doesn't have the attributes, and limits shouldn't be applied front-end for admin. 1429 if ($(this).attr('data-linked-to')) { 1430 // split the json array 1431 // get the total max allowed qty 1432 var linkto = $(this).data('linked-to'); 1433 var linkmessage = $(this).data('link-message'); 1434 var ticketsper = $(this).data('tickets-per'); 1435 var currentMasterQty = 0; 1436 var allowedQty = 0; 1437 var origMax = 0; 1438 var origMin = 0; 1439 if(linkto != '') { 1440 1441 origMin = $(this).attr('min'); 1442 origMax = $(this).attr('max'); 1443 1444 currentval = ''; 1445 currentval = $(this).val(); 1446 1447 $(this).val(''); 1448 currentMasterQty = 0; 1449 $.each(linkto, function(key,value){ 1450 $('[data-ticket-type-id='+ key +']').each(function() { 1451 currentMasterQty += $(this).val() * value; 1452 }); 1453 }); 1454 1455 allowedQty = currentMasterQty; 1456 $(this).attr('max', allowedQty); 1457 1458 if(currentval <= allowedQty){ 1459 $(this).val(currentval); 1460 } else { 1461 $(this).val(allowedQty); 1462 } 1463 1464 if($(this).attr('max') == 0) { 1465 $(this).parent().parent().find('.btn-input-subtract').hide(); 1466 $(this).parent().parent().find('.btn-input-add').hide(); 1467 $(this).hide().after("<p id='"+$(this).attr('id')+"req' class='ticket-sold-out-text'>" + linkmessage + "</p>"); 1468 } else { 1469 $(this).show(); 1470 $(this).parent().parent().find('.btn-input-subtract').show(); 1471 $(this).parent().parent().find('.btn-input-add').show(); 1472 $("#"+$(this).attr('id')+"req").remove(); 1473 1474 if($(this).val() < $(this).attr('max')){ 1475 $(this).parent().parent().find('.btn-input-add').removeClass('disabled'); 1476 } 1477 } 1478 1479 1480 } 1481 } 1482 }); 1483 1484 // compact display 1485 $('select[name*="[quantity-general"]').each(function() { 1486 // admin order tickets page doesn't have the attributes, and limits shouldn't be applied front-end. 1487 if ($(this).attr('data-linked-to')) { 1488 var linkto = $(this).data('linked-to'); 1489 var linkmessage = $(this).data('link-message'); 1490 var ticketsper = $(this).data('tickets-per'); 1491 var currentMasterQty = 0; 1492 var allowedQty = 0; 1493 var origMax = 0; 1494 var origMin = 0; 1495 if(linkto != '') { 1496 if ($(this).attr('data-link-orig-max')) { 1497 origMax = $(this).attr('data-link-orig-max'); 1498 origMin = $(this).attr('data-link-orig-min'); 1499 } else { 1500 $(this).attr('data-link-orig-max',$(this).children('option:last').val()); 1501 origMax = $(this).attr('data-link-orig-max'); 1502 $(this).attr('data-link-orig-min',$(this).children('option:first').val()); 1503 origMin = $(this).attr('data-link-orig-min'); 1504 } 1505 $.each(linkto, function(key,value){ 1506 $('[data-ticket-type-id='+ key +']').filter('input[type=hidden]').each(function() { 1507 currentMasterQty += Number($(this).val()) * value; 1508 }); 1509 }) 1510 allowedQty = Math.min(currentMasterQty, origMax); 1511 if ((allowedQty < $(this).children('option:last').val())) { 1512 $(this).children().filter(function() { 1513 return this.value > allowedQty; 1514 }).remove(); 1515 } else if ($(this).children().length == 0 || allowedQty > $(this).children('option:last').val()) { 1516 var i=0; 1517 var start=0; 1518 if ($(this).children().length != 0) start=Number($(this).children('option:last').val()); 1519 for (i=start + 1; i < allowedQty +1; i++) { 1520 if (i>=origMin) { 1521 if (ticketsper > 1) { 1522 $(this).append($('<option>', {value: i, text: i + ' (' + i * ticketsper + ' tickets)'})); 1523 } else { 1524 $(this).append($('<option>', {value:i, text:i})); 1525 } 1526 } 1527 } 1528 } 1529 1530 //remove from cart if necessary 1531 var numInCart = 0; 1532 var thisTypeId = $(this).attr('data-ticket-type-id'); 1533 $('[data-ticket-type-id='+ thisTypeId +']').filter('input[type=hidden]').each(function() { 1534 numInCart += Number($(this).val()); 1535 }); 1536 if (numInCart > allowedQty) { 1537 $('[data-ticket-type-id='+ thisTypeId +']').filter('input[type=hidden]').val(0); 1538 } 1539 1540 } 1541 } 1542 }) 1543} 1544 1545function removeThisTicketType(ticketTypeRow) { 1546 $('[name="data[Ticket][' + ticketTypeRow + '][quantity]"]').val(0); 1547 calculate_order_total(); 1548} 1549 1550 1551/**
1552 * Builds the Summary area after calculating the totals...etc 1553 * @return {[type]} [description] 1554 */ 1555function updateSummary(data, formId) { 1556 var seattext = data.textStrings.seattext; 1557 var donation = data.textStrings.donation; 1558 var noitems = data.textStrings.noitems 1559 var content = ""; 1560 var typeName = ''; 1561 var activityInfo = ''; 1562 var aittId = ''; 1563 var quantity = 0; 1564 var row = 0; 1565 var bundledFees; 1566 if (typeof window.currencySymbol === 'undefined') { 1567 window.currencySymbol = "$"; 1568 } 1569 if (typeof formId == 'undefined') { 1570 formId = '#orderForm'; 1571 } 1572 1573 //clear out any failed promo codes to prevent the wrong order summary data 1574 if (data.promoCodeMessage == 'Invalid promo code') { 1575 $('#real-promo-field').val(''); 1576 $('#promo_field').val(''); 1577 $('#promo_field_sm').val(''); 1578 $('#promo_field_btm').val(''); 1579 } 1580 1581 //pre-populate the cash tendered total fields on the day of event sales page 1582 $('#CashTenderedTotal').val(data.total); 1583 $('#CashTendered').val(data.total); 1584 $('#ChangeDue').val(parseFloat(0).toFixed(2)); 1585 1586 var exchange = false; 1587 if($('#OrderPurchaseLocation').val() == 'exchange'){ 1588 exchange = true; 1589 } 1590 1591 const exchangeCreditDisplay = data.exchange_credit_display !== undefined ? data.exchange_credit_display : data.exchange_credit; 1592 1593 let purchasedTicketMap = {}; 1594 if (exchange && data.Ticket && Array.isArray(data.Ticket)) { 1595 data.Ticket.forEach(function(ticket) { 1596 if (ticket.Purchased && ticket.Purchased.Ticket && ticket.id) { 1597 purchasedTicketMap[ticket.id] = ticket.Purchased.Ticket; 1598 } 1599 }); 1600 } 1601 1602 // list the ticket types and their quantities (Ticket Type List View) 1603 $('.ticketOptions').each(function() { 1604 ttPriceId = $(this).find('[name$="ticket_type_price_id]"]').val(); 1605 aittId = $(this).find('[name$="activity_instance_ticket_type_id]"]').val(); 1606 1607 //if exchange, replace the activity instance ticket type id, with ticket id 1608 ticketId = $(this).find('[name$="[Purchased][Ticket][id]"]').val(); 1609 1610 var removeIndex = ttPriceId; 1611 if(exchange === true){ 1612 removeIndex = ticketId; 1613 } 1614 1615 timeConflict = ''; 1616 if (data.time_conflicts.indexOf(aittId) !== -1) { 1617 timeConflict = '<span style="color:red;">*</span> '; 1618 } 1619 typeName = ''; 1620 if (data.Ticket && Array.isArray(data.Ticket)) { 1621 const ttPriceId = $(this).find('[name$="ticket_type_price_id]"]').val(); 1622 1623 const match = data.Ticket.find(function(t) { 1624 return t.ticket_type_price_id === ttPriceId; 1625 }); 1626 1627 if (match && match.ticket_type_name) { 1628 typeName = match.ticket_type_name; 1629 } 1630 } 1631 if (!typeName) { 1632 typeName = $(this).find('[name$="ticket_type_name]"]').val(); // fallback 1633 } 1634 quantity = $(this).find('[name$="quantity]"]').val(); 1635 if($(this).find('[name$="activity_name]"]').val() === undefined || $(this).find('[name$="activity_name]"]').val() === ''){ 1636 activityInfo = ''; 1637 } else { 1638 activityInfo = $(this).find('[name$="activity_name]"]').val() + '<br><i class="fa-regular fa-calendar-days"></i> ' + $(this).find('[name$="activity_instance_date_time]"]').val(); 1639 } 1640 1641 if(exchange === true){ 1642 aittId = ticketId; 1643 } else { 1644 aittId = $(this).find('[name$="activity_instance_ticket_type_id]"]').val(); 1645 } 1646 1647 priceEach = $(this).find('[name$="price_each]"]').val(); 1648 if (priceEach) { 1649 priceEach = priceEach.replace(',',''); 1650 selectedQuantityPrice = parseFloat(Math.round((priceEach*quantity) * 100) / 100).toFixed(2); 1651 } else { 1652 selectedQuantityPrice = '0.00'; 1653 } 1654 1655 var seatLabels = $(this).find('[name$="seat_labels]"]').val(); 1656 if (seatLabels) { 1657 seatLabels = seatLabels.split(','); 1658 var seatLabelArgs = '['; 1659 for (var i = 0; i < seatLabels.length; i++) { 1660 //add each seat label, trimmed and escaped 1661 seatLabelArgs += "'" + seatLabels[i].trim().replace(/'/g, "\\'") + "',"; 1662 } 1663 seatLabelArgs = seatLabelArgs.slice(0, -1); 1664 seatLabelArgs += ']'; 1665 } 1666 1667 if(quantity && parseInt(quantity) > 0) { 1668 switch(tsTheme) { 1669 case 'Modern': 1670 if (data['purchase_location'] == 'exchange') { 1671 const purchasedTicketId = $(this).find('[name*="[Purchased][Ticket][id]"]').val(); 1672 let purchasedTicket = purchasedTicketMap[purchasedTicketId]; 1673 1674 if (!purchasedTicket) { 1675 break; 1676 } 1677 1678 let purchasedTicketName = purchasedTicket['ticket_type_name'] || ''; 1679 let purchasedTicketPriceName = purchasedTicket['ticket_type_price_name'] || ''; 1680 1681 // Determine display name for purchased ticket 1682 let displayName = purchasedTicketName; 1683 if (purchasedTicketPriceName != 'default' && purchasedTicketPriceName != 'standard') { 1684 displayName += ' ' + purchasedTicketPriceName; 1685 } 1686 1687 // Exchange to row 1688 content += `<div class="table-row"><div class="table-cell del"><a class="remove-check" title="Remove" href="javascript:void(0);" onclick="removeFromCart($(this), '${removeIndex}', '${formId}', '${aittId}');"><i class="fa fa-times" aria-hidden="true"></i></a></div><div class="table-cell type"><strong>Exchange to:</strong><br>${typeName}`; 1689 if (activityInfo != '') { 1690 content += '<br><div class="details">' + timeConflict + activityInfo + '</div>'; 1691 } 1692 content += '</div><div class="table-cell price">' + formatForCurrency(currencySymbol, selectedQuantityPrice) + '</div></div>'; 1693 1694 // From row 1695 content += '<div class="table-row" style="padding-top:0;"><div class="table-cell del" style="padding-right:26.8px;"></div><div class="table-cell type"><strong>From:</strong><br>' + displayName; 1696 // Add activity information if it exists in the purchased ticket 1697 if (purchasedTicket['instance_label_name'] && purchasedTicket['instance_label_name'] !== '') { 1698 content += '<br><div class="details">' + purchasedTicket['instance_label_name'] + '</div>'; 1699 } 1700 if (purchasedTicket['instance_label'] && purchasedTicket['instance_label'] !== '') { 1701 // Extract the date/time part from the instance_label (everything after the last dash) 1702 let instanceLabel = purchasedTicket['instance_label']; 1703 let lastDashIndex = instanceLabel.lastIndexOf(' - '); 1704 if (lastDashIndex !== -1) { 1705 let dateTime = instanceLabel.substring(lastDashIndex + 3); 1706 content += '<div class="details"><i class="fa-regular fa-calendar-days"></i> ' + dateTime + '</div>'; 1707 } 1708 } 1709 1710 content += '</div><div class="table-cell price">- ' + formatForCurrency(currencySymbol, exchangeCreditDisplay) + '</div></div>'; 1711 } else { 1712 content += '<div class="product"><div class="product-info">'; 1713 if (seatLabelArgs) { 1714 content += '<a class="remove-check" title="Remove" href="javascript:void(0);" onclick="chart.deselectSeats(' + seatLabelArgs + ');"><i class="fa fa-times" aria-hidden="true"></i></a>'; 1715 } else { 1716 content += '<a class="remove-check" title="Remove" href="javascript:void(0);" onclick="removeFromCart($(this), \'' + removeIndex + '\', \'' + formId + '\', \'' + aittId + '\');"><i class="fa fa-times" aria-hidden="true"></i></a>'; 1717 } 1718 if (activityInfo == '') { 1719 content += '<strong><span class="quantity">' + quantity + 'x</span> ' + typeName + '</strong><br></div>'; 1720 } else { 1721 content += '<strong>' + quantity + 'x ' + typeName + '</strong><br></div><div class="details">' + timeConflict + activityInfo + '</div>'; 1722 } 1723 content += '<div class="product-totals">' + formatForCurrency(currencySymbol, selectedQuantityPrice) + '</div><div class="clear"></div></div>'; 1724 } 1725 break; 1726 case 'Classic': 1727 default: 1728 if (data['purchase_location'] == 'back-end' || data['purchase_location'] == 'voucher') { 1729 params = { 1730 removeIndex, 1731 formId, 1732 aittId, 1733 quantity, 1734 typeName, 1735 activityInfo, 1736 timeConflict, 1737 currencySymbol, 1738 selectedQuantityPrice, 1739 seatLabelArgs 1740 }
1741 content += buildBackendOrEExOrderSummaryItemRow(params); 1742 } else { 1743 content += '<tr class="ticketType">'; 1744 if (seatLabelArgs) { 1745 content += '<td class="del"><a title="Remove" href="javascript:void(0);" onclick="chart.deselectSeats(' + seatLabelArgs + ');"><i class="fa fa-times" aria-hidden="true"></i></a></td><br>'; 1746 } else { 1747 content += '<td class="del"><a title="Remove" href="javascript:void(0);" onclick="removeFromCart($(this), \'' + removeIndex + '\', \'' + formId + '\', \'' + aittId + '\');"><i class="fa fa-times" aria-hidden="true"></i></a></td><br>'; 1748 } 1749 if(activityInfo == ''){ 1750 content += '<td class="type">' + quantity + ' x ' + typeName; 1751 content += '</td><br>'; 1752 content += '<td class="quantity">'+formatForCurrency(currencySymbol, selectedQuantityPrice) + '</td>'; 1753 } else { 1754 content += '<td class="type">' + quantity + ' x ' + typeName + '<div class="details">' + timeConflict + activityInfo + '</div>'; 1755 content += '</td><br>'; 1756 content += '<td class="quantity">'+formatForCurrency(currencySymbol, selectedQuantityPrice)+'</td>'; 1757 } 1758 1759 content += '</tr>'; 1760 } 1761 break; 1762 } 1763 } 1764 }); 1765 1766 $('.giftCardOptions').each(function(removeIndex) { 1767 var creditName = $(this).find('[name$="name]"]').val(); 1768 var quantity = $(this).find('[name$="quantity]"]').val(); 1769 var priceEach = $(this).find('[name$="price_each]"]').val(); 1770 1771 if (priceEach) { 1772 priceEach = priceEach.replace(',',''); 1773 selectedQuantityPrice = parseFloat(Math.round((priceEach*quantity) * 100) / 100).toFixed(2); 1774 } else { 1775 selectedQuantityPrice = '0.00'; 1776 } 1777 1778 if(quantity && parseInt(quantity) > 0) { 1779 switch(tsTheme) { 1780 case 'Modern': 1781 content += '<div class="product"><div class="product-info">'; 1782 content += '<a class="remove-check" title="Remove" href="javascript:removeFromCartGiftCard(\'' + removeIndex + '\', \'' + formId + '\', \'' + aittId + '\');"><i class="fa fa-times" aria-hidden="true"></i></a>'; 1783 1784 content += '<strong>' + quantity + 'x ' + creditName + '</strong><br></div>'; 1785 content += '<div class="product-totals">' + formatForCurrency(currencySymbol, selectedQuantityPrice) + '</div><div class="clear"></div></div>'; 1786 break; 1787 case 'Classic': 1788 default: 1789 content += '<tr class="ticketType">'; 1790 content += '<td class="del"><a title="Remove" href="javascript:removeFromCartGiftCard(\'' + removeIndex + '\', \'' + formId + '\', \'' + aittId + '\');"><i class="fa fa-times" aria-hidden="true"></i></a></td><br>'; 1791 content += '<td class="type">' + quantity + ' x ' + creditName; 1792 content += '</td><br>'; 1793 content += '<td class="quantity">'+formatForCurrency(currencySymbol, selectedQuantityPrice) + '</td>'; 1794 content += '</tr>'; 1795 1796 break; 1797 } 1798 } 1799 1800 }); 1801 1802 // list the ticket types and their quantities (Ticket Type Dropdown View) 1803 $('.ticketOptions-singleDropdown [name$="quantity]"]').each(function() { 1804 1805 fieldName = $(this).attr('name'); 1806 row = fieldName.match(/\d+/)[0]; 1807 typeName = $('#Ticket' + row + 'TicketTypeName').val(); 1808 priceEach = $('#Ticket' + row + 'PriceEach').val(); 1809 quantity = $(this).val(); 1810 selectedQuantityPrice = parseFloat(Math.round((priceEach*quantity) * 100) / 100).toFixed(2); 1811 1812 if(quantity && parseInt(quantity) > 0) { 1813 switch(tsTheme) { 1814 case 'Modern': 1815 content += '<div class="product"><div class="product-info"><a class="remove-check" href="javascript:void(0);" onclick="removeThisTicketType(' + row + ');"><i class="fa fa-times"></i></a><strong>' + quantity + 'x ' + typeName + '</strong><br></div><div class="product-totals">' + formatForCurrency(currencySymbol, selectedQuantityPrice) + '</div><div class="clear"></div></div>'; 1816 break; 1817 case 'Classic': 1818 default: 1819 content += '<tr class="ticketType">'; 1820 content += '<td class="del"><a href="javascript:void(0);" onclick="removeThisTicketType(' + row + ');"><i class="fa fa-times"></i></a></td>'; 1821 content += '<td class="type">' + quantity + ' x ' + typeName + '</td><br>'; 1822 content += '<td class="quantity">' + formatForCurrency(currencySymbol, selectedQuantityPrice) + '</td>'; 1823 content += '</tr>'; 1824 break; 1825 } 1826 } 1827 }); 1828 1829 // list the selected seats (if any) 1830 var seats = $('#OrderSelectedSeatLabels').val(); 1831 if (seats !== undefined && seats.length > 0) { 1832 var seatingInfo = ''; 1833 $.each(data.objectTypes, function(idx, obj){ 1834 if (seatingInfo == '') { 1835 seatingInfo = obj; 1836 } else { 1837 seatingInfo += '<br>' + obj; 1838 } 1839 }); 1840 1841 if (seatingInfo !== undefined && seatingInfo.length > 0) { 1842 switch(tsTheme) { 1843 case 'Modern': 1844 content += '<div class="product"><div class="product-info-seat"><strong>' + seatingInfo + '</strong><br></div><div class="clear"></div></div>'; 1845 break; 1846 case 'Classic': 1847 default: 1848 content += '<tr>'; 1849 content += '<td colspan="2">' + seatingInfo + '</td>'; 1850 content += '</tr>'; 1851 break; 1852 } 1853 } 1854 } 1855 1856 var charityName; 1857 var amount; 1858 $('.charity').each(function() { 1859 charityName = $(this).find('[name$="charity_name]"]').val(); 1860 charityId = $(this).find('[name$="charity_event_id]"]').val(); 1861 amount = $(this).find('[name$="total]"]').val(); 1862 if (data.updatedData.Contribution && data.updatedData.Contribution.length && amount && !isNaN(parseFloat(amount)) && amount > 0) { 1863 switch(tsTheme) { 1864 case 'Modern': 1865 content += '<div class="product"><div class="product-info"><strong>' + donation + ': ' + charityName + '</strong><br></div><div class="product-totals">' + formatForCurrency(currencySymbol, amount) + '</div><div class="clear"></div></div>'; 1866 break; 1867 case 'Classic': 1868 default: 1869 content += '<tr class="ticketType">'; 1870 if (typeof seatedType == 'undefined' || !seatedType) { 1871 content += '<td class="del"><a title="Remove" href="javascript:removeCharityFromCart(\'' + charityId + '\', \'' + formId + '\');"><i class="fa fa-times" aria-hidden="true"></i></a></td><br>'; 1872 } 1873 content += '<td class="type">' + donation + ': <span class="summary_charity_name">'+charityName+'</span></td><br>'; 1874 content += '<td class="quantity">' + formatForCurrency(currencySymbol, amount) + '</td>'; 1875 content += '</tr>'; 1876 } 1877 } else { 1878 $(this).find('[name$="total]"]').val(''); 1879 if (data.minDonationMsg) { 1880 alertify.error(data.minDonationMsg); 1881 } 1882 } 1883 }); 1884 1885 if (data.time_conflicts.length > 0) { 1886 let conflictMessage = data.conflict_type === 'different_days' 1887 ? 'Selected items are on different days' 1888 : 'There are scheduling conflicts in your cart'; 1889 if (tsTheme === 'Modern') { 1890 content += '<div id="time-conflict-warning">* ' + conflictMessage + '</div>'; 1891 } else {
1892 content += '<tr id="time-conflict-warning"><td colspan=3>* ' + conflictMessage + '</td></tr>'; 1893 } 1894 } 1895 1896 if (content == '') { 1897 let isBackEnd = data['purchase_location'] == 'back-end'; 1898 let isExchange = data['purchase_location'] == 'exchange'; 1899 let isVoucher = data['purchase_location'] == 'voucher'; 1900 let isModernTheme = tsTheme == 'Modern'; 1901 1902 if (isBackEnd || isExchange || isVoucher) { 1903 content += '<div class="table-row"><div class="table-cell type"><div class="order_summary_default_message">' + noitems + '</div></div><div class="table-cell price"></div></div>'; 1904 } else if (isModernTheme) { 1905 content += '<div class="order_summary_default_message"><p>' + noitems + '</p></div>'; 1906 } else { 1907 content += '<tr><td class="t-align-right">' + noitems + '</td></tr>'; 1908 } 1909 } 1910 1911 //sets default promo code value 1912 if (!data.promoCodeAmount || data.promoCodeAmount == "") { 1913 data.promoCodeAmount = "0"; 1914 } 1915 1916 1917 // fades out, updates content, then fades back in (BOTH VERSIONS) 1918 $('.orderPriceDetails').fadeTo('fast', 0.4, function() { 1919 switch(tsTheme) { 1920 case 'Modern': 1921 $(".orderSummary").html(content); 1922 break; 1923 case 'Classic': 1924 default: 1925 if (data['purchase_location'] == 'back-end' || data['purchase_location'] == 'voucher') { 1926 $(".orderSummary").html(content); 1927 } else { 1928 $(".orderSummary table").html(content); 1929 } 1930 break; 1931 } 1932 1933 $('.customFeesItem').remove(); 1934 if (typeof data.CustomFeesOrders != 'undefined') { 1935 bundledFees = 0; 1936 $.each(data.CustomFeesOrders, function(i, item) { 1937 if (data.bundle_fees < 2) { 1938 switch(tsTheme) { 1939 case 'Modern': 1940 $('.orderTax').before('<div class="customFeesItem priceDetail"><div class="title">' + data.CustomFeesOrders[i].custom_fee_name + '</div><div class="value">' + currencySymbol + data.CustomFeesOrders[i].actual_fee_applied + '</div></div>'); 1941 break; 1942 case 'Classic': 1943 default: 1944 $('.orderTax').before('<tr class="customFeesItem"><td class="title">' + data.CustomFeesOrders[i].custom_fee_name + '</td><td class="value">' + currencySymbol + data.CustomFeesOrders[i].actual_fee_applied + '</td></tr>'); 1945 break; 1946 } 1947 } else { 1948 bundledFees = Number(bundledFees) + Number(data.CustomFeesOrders[i].actual_fee_applied); 1949 } 1950 }); 1951 } 1952 1953 $(".orderPriceDetails .promoCode .value").html("()" + currencySymbol + data.promoCodeAmount + ")"); 1954 1955 // order subtotal 1956 $(".orderPriceDetails .orderSubtotal .value").html(formatForCurrency(currencySymbol, data.subTotal)); 1957 if(data.subTotal > 0) { 1958 $(".orderPriceDetails .orderSubtotal").show(); 1959 } else { 1960 $(".orderPriceDetails .orderSubtotal").hide(); 1961 } 1962 1963 $(".orderPriceDetails .orderExchange .value").html('(' + formatForCurrency(currencySymbol, exchangeCreditDisplay) + ')'); 1964 if(data.exchange_credit > 0) { 1965 $(".orderPriceDetails .orderExchange").show(); 1966 } else { 1967 $(".orderPriceDetails .orderExchange").hide(); 1968 } 1969 1970 // ticket insurance 1971 $(".orderPriceDetails .totalInsuranceFee .value").html(formatForCurrency(currencySymbol, data.totalInsuranceFee)); 1972 if(data.totalInsuranceFee > 0) { 1973 $(".orderPriceDetails .totalInsuranceFee").show(); 1974 } else { 1975 $(".orderPriceDetails .totalInsuranceFee").hide(); 1976 } 1977 1978 // gratuities 1979 $(".orderPriceDetails .totalGratuity .value").html(formatForCurrency(currencySymbol, data.totalGratuityAmount)); 1980 if(data.totalGratuityAmount > 0) { 1981 $(".orderPriceDetails .totalGratuity").show(); 1982 } else { 1983 $(".orderPriceDetails .totalGratuity").hide(); 1984 } 1985 1986 // order price details 1987 $(".orderPriceDetails .orderTax .value").html(formatForCurrency(currencySymbol, data.tax)); 1988 if(data.tax > 0) { 1989 $(".orderPriceDetails .orderTax").show(); 1990 } else { 1991 $(".orderPriceDetails .orderTax").hide(); 1992 } 1993 1994 if (data.bundle_fees == 1) { 1995 $(".orderPriceDetails .orderServiceFees").hide(); 1996 $(".orderPriceDetails .orderCCFees").hide(); 1997 $(".orderPriceDetails .orderServiceCharges .value").html(formatForCurrency(currencySymbol, parseFloat(Math.round(data.serviceCharge * 100) / 100).toFixed(2))); 1998 if (data.serviceCharge > 0) { 1999 $(".orderPriceDetails .orderServiceCharges").show(); 2000 } else { 2001 $(".orderPriceDetails .orderServiceCharges").hide(); 2002 } 2003 } else if (data.bundle_fees == 2) { 2004 bundledFees = Number(bundledFees) + Number(data.serviceCharge); 2005 $(".orderPriceDetails .orderServiceFees").hide(); 2006 $(".orderPriceDetails .orderCCFees").hide(); 2007 $(".orderPriceDetails .orderServiceCharges .value").html(formatForCurrency(currencySymbol, parseFloat(Math.round(bundledFees * 100) / 100).toFixed(2))); 2008 if (bundledFees > 0) { 2009 $(".orderPriceDetails .orderServiceCharges").show(); 2010 } else { 2011 $(".orderPriceDetails .orderServiceCharges").hide(); 2012 } 2013 } else { 2014 $(".orderPriceDetails .orderServiceFees .value").html(formatForCurrency(currencySymbol, parseFloat(Math.round(data.admin_charges * 100) / 100).toFixed(2))); 2015 if(data.admin_charges > 0) { 2016 $(".orderPriceDetails .orderServiceFees").show(); 2017 } else { 2018 $(".orderPriceDetails .orderServiceFees").hide(); 2019 } 2020 2021 $(".orderPriceDetails .orderCCFees .value").html(formatForCurrency(currencySymbol, parseFloat(Math.round(data.ccFees * 100) / 100).toFixed(2))); 2022 if(data.ccFees > 0) { 2023 $(".orderPriceDetails .orderCCFees").show(); 2024 } else { 2025 $(".orderPriceDetails .orderCCFees").hide(); 2026 } 2027 } 2028 2029 2030 if(data.hasOwnProperty('promoCodeAmount')) { 2031 //To fix a problem where promo codes over $999 were showing up as NaN, 2032 //need to remove any commas because parseFloat doesn't deal with commas correctly 2033 var promoCodeAmount = parseFloat(data.promoCodeAmount.replace(',','')); 2034 //then we will calculate the correct amount to present 2035 promoCodeAmount = parseFloat(Math.round(promoCodeAmount * 100) / 100).toFixed(2); 2036 //then format with currency sign and commas as appropriate 2037 promoCodeAmount = currencySymbol + promoCodeAmount.replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,"); 2038 $(".orderPriceDetails .promoCode .value").html("(" + promoCodeAmount + ")"); 2039 } 2040 if($('#real-promo-field').val()) { 2041 $(".orderPriceDetails .promoCode").show(); 2042 } else { 2043 $(".orderPriceDetails .promoCode").hide(); 2044 } 2045 2046 var newTotal = data.total; 2047 if(data.gift_card_amount > 0) { 2048 $(".giftCard").slideDown(); 2049 $(".giftCard .value").html('(' + formatForCurrency(currencySymbol, data.gift_card_amount) + ')'); 2050 } else {
2051 $(".giftCard").slideUp(); 2052 $(".giftCard .value").html(); 2053 } 2054 2055 $(".orderPriceDetails .orderTotal .value").html(formatForCurrency(currencySymbol, data.total)); 2056 $("#orderTotalReview .amount").html(formatForCurrency(currencySymbol, data.total)); 2057 2058 // update the refund protection "No" option with the order total that 2059 // excludes the BP premium, so it shows what the buyer would pay if they decline 2060 if (ticketInsuranceJsLoaded && typeof formatter !== 'undefined') { 2061 const declineTotal = (typeof data.total_without_insurance !== 'undefined') ? data.total_without_insurance : data.total; 2062 $('.ti-order-total').html(formatter.format(declineTotal)); 2063 } 2064 2065 // ADDED FOR NAV ORDER TOTAL 2066 if(data.total == 0){ 2067 // IF THE TOTAL IS EMPTY, DO NOT SHOW HEADER TOTAL 2068 $(".nav-order-total").slideUp(); 2069 $(".nav-order-total .amount").html(formatForCurrency(currencySymbol, data.total)); 2070 } else { 2071 $(".nav-order-total .amount").html(formatForCurrency(currencySymbol, data.total)); 2072 $(".nav-order-total").slideDown(); 2073 } 2074 2075 $("#OrderProposedTotal").val(data.total); 2076 $(".orderPriceDetails .ajax_loader").fadeOut('fast'); 2077 $(".orderPriceDetails").fadeTo('fast', 1); 2078 $(".orderSummary").fadeTo('fast', 1); 2079 2080 $(".promo_msg").html(data.promoCodeMessage); 2081 }).promise().done(function() { 2082 // no need to run this mulitple times, just run once fadeTo is finished 2083 if (Number(data.total) == 0 && ticketInsuranceJsLoaded) { 2084 // order is free, clear and hide any reference to insurance 2085 clearInsuranceQuote(); 2086 hideShowInsuranceWidget('hide'); 2087 } 2088 }); 2089 2090 //show or hide promo code line-item 2091 if(data.promoCodeAmount > 0) { 2092 $(".orderPriceDetails .promoCode").show(); 2093 } else { 2094 $(".orderPriceDetails .promoCode").hide(); 2095 } 2096 2097 var hideGiftCard = true; 2098 2099 switch(data.giftCardStatus) { 2100 case 'success': 2101 $('#giftCardNumberResponse').html('Gift Card Applied').css('color', '#038514').css('display', 'block'); 2102 var hideGiftCard = false; 2103 break; 2104 case 'failed': 2105 $('#giftCardNumberResponse').html('Gift Card not allowed for this purchase.').css('color', '#D12E5A').css('display', 'block'); 2106 $('#OrderCreditId').val(''); 2107 var hideGiftCard = false; 2108 break; 2109 case 'invalid': 2110 $('#giftCardNumberResponse').html('Invalid Gift Card Number').css('color', '#D12E5A').css('display', 'block'); 2111 $('#OrderCreditId').val(''); 2112 var hideGiftCard = false; 2113 break; 2114 default : 2115 $('#giftCardNumberResponse').html('').css('color', '#038514').css('display', 'block'); 2116 $('#OrderCreditId').val(''); 2117 var hideGiftCard = false; 2118 break; 2119 } 2120 2121 $("#OrderProposedTotal").val(data.total); 2122 2123 if(data.total == '0.00' || data.cash_option) { 2124 if(data.total > 0) { 2125 showPaymentFields(); 2126 } else { 2127 hidePaymentFields(hideGiftCard); 2128 } 2129 hideCCFields(); 2130 } else { 2131 if(data.total > '0.00'){ 2132 if(typeof paymentGateway != 'undefined' && paymentGateway == 'stripe') { 2133 // Caller owns the PaymentIntent on this tick; skip to avoid racing it. 2134 if (typeof suppressPaymentIntentRefresh !== 'undefined' && suppressPaymentIntentRefresh) { 2135 clearSuppressPaymentIntentRefresh(); 2136 } else if ($('#OrderPaymentIntentId').val()) { 2137 // if already have payment intent update the payment intent, unless a submitted 2138 // order is mid-payment - its amount was set when the order was submitted 2139 if (!orderSubmissionInProgress) { 2140 updatePaymentIntent(); 2141 } 2142 } else if (typeof modalSteps === 'undefined' && data.purchase_location != 'voucher') { 2143 // if not modal, load payment element, modal will load when switching to that view 2144 initialize(); 2145 } 2146 } 2147 var giftCardSetting = false; 2148 if(data.totalTicketProducts > 0){ 2149 giftCardSetting = true; 2150 } 2151 showCCFields(giftCardSetting); 2152 } else { 2153 hideCCFields(); 2154 } 2155 } 2156 2157 if (data.cash_option && data.include_fees == 4) { 2158 $('.orderServiceFees').slideUp(); 2159 $('.orderServiceCharges').slideUp(); 2160 } 2161 2162} 2163 2164function formatForCurrency(symbol, data){ 2165 switch(symbol) { 2166 case 'CAD$': 2167 return 'CAD$' + data; 2168 case '£': 2169 return '£' + data; 2170 case 'NZ$': 2171 return 'NZ$' + data; 2172 case 'â¬': 2173 // converts the number to display in the summaries in the correct format for 'EUR' 2174 var currencyConverted = data.toString().split("."); 2175 currencyConverted[0] = currencyConverted[0].replace(/\B(?=(\d{3})+(?!\d))/g, "."); 2176 data = currencyConverted.join(","); 2177 return 'â¬' + data; 2178 case 'R': 2179 // converts the number to display in the summaries in the correct format for 'EUR' 2180 var currencyConverted = data.toString().split("."); 2181 currencyConverted[0] = currencyConverted[0].replace(/\B(?=(\d{3})+(?!\d))/g, " "); 2182 data = currencyConverted.join(","); 2183 return 'R' + data; 2184 default: 2185 var currencyConverted = data.toString().split(","); 2186 currencyConverted[0] = currencyConverted[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); 2187 data = currencyConverted.join(","); 2188 return '$' + data; 2189 } 2190} 2191 2192 2193// only needed where has both cash and credit options and front end exchange 2194function hidePaymentFields(hideGiftCard) {
2195 if($('#creditCardFields').parent().attr('id') == 'payment-fields') { 2196 $('#promo-code-field').slideUp(); 2197 $('#giftCardPaymentField').slideUp(); 2198 } 2199 if(hideGiftCard){ 2200 $('#giftCardPaymentField').slideUp(); 2201 } 2202 2203 if($('.payment-plan-message').length){ 2204 $('.payment-plan-message').slideUp('fast'); 2205 } 2206 2207 if(typeof paymentGateway != 'undefined' && paymentGateway == 'stripe') { 2208 enableSubmit(origSubmitButtonText); 2209 } 2210} 2211 2212 2213/** 2214 * 2215 * @param {*} giftCardSetting 2216 */ 2217function showPaymentFields(giftCardSetting) { 2218 if($('#creditCardFields').parent().attr('id') == 'payment-fields') { 2219 $('#payment-fields').slideDown(); 2220 $('#promo-code-field').slideDown(); 2221 $('#giftCardPaymentField').slideDown(); 2222 } 2223 if(giftCardSetting){ 2224 $('#giftCardPaymentField').slideDown(); 2225 } else { 2226 $('#giftCardPaymentField').slideUp(); 2227 } 2228 2229 // check that it is stripe 2230 if(typeof paymentGateway != 'undefined' && paymentGateway == 'stripe') { 2231 // Wait-list requests collect no card payment and render no Stripe payment element, 2232 // so there's nothing to validate â keep the submit button enabled rather than gating 2233 // it on a payment element that will never report "complete". 2234 const paymentElement = document.getElementById('payment-element'); 2235 // check the last known status of the payment element to determine what is supposed to happen with the submit button 2236 if(!paymentElement || (typeof paymentElementLastStatus != 'undefined' && paymentElementLastStatus.complete)){ 2237 enableSubmit(origSubmitButtonText); 2238 } else { 2239 disableSubmit(origSubmitButtonText); 2240 } 2241 } 2242} 2243 2244function showCCFields(giftCardSetting) { 2245 var paymentType = $('#OrderPaymentType').val(); 2246 $('#creditCardFields').slideDown(); 2247 $('#auth-net-seal-area').slideDown(); 2248 $('#payment-options').slideDown(); 2249 2250 if (paymentType == 'creditCard' || paymentType == 'iDEAL'){ 2251 switch(paymentGateway){ 2252 case 'stripe': 2253 break; 2254 case 'authnet': 2255 $('#OrderCcNumber').removeAttr('disabled'); 2256 $('#OrderCcExpireMonth').removeAttr('disabled'); 2257 $('#OrderCcExpireYear').removeAttr('disabled'); 2258 $('#OrderCcCvv').removeAttr('disabled'); 2259 break; 2260 case 'square': 2261 buildForm(); 2262 break; 2263 } 2264 } 2265 showPaymentFields(giftCardSetting); 2266} 2267 2268function hideCCFields() { 2269 if(!$('#creditCardFields').attr('donthide')) { 2270 $('#creditCardFields').slideUp(); 2271 $('#auth-net-seal-area').slideUp(); 2272 $('#payment-options').slideUp(); 2273 if(typeof paymentGateway != 'undefined' && paymentGateway == 'authnet') { 2274 $('#OrderCcNumber').attr('disabled', 'disabled'); 2275 $('#OrderCcExpireMonth').attr('disabled', 'disabled'); 2276 $('#OrderCcExpireYear').attr('disabled', 'disabled'); 2277 $('#OrderCcCvv').attr('disabled', 'disabled'); 2278 } 2279 } 2280 hidePaymentFields(false); 2281} 2282 2283function togglePaymentFieldsForExchange() { 2284 //display the payment fields 2285 $('#paymentInformation').toggle(); 2286 if($('#paymentInformation').is(":visible")){ 2287 setTimeout(function() { 2288 $(window).scrollTop($(document).height()); 2289 }, 500); // Scroll to payment info for better UX 2290 $('#checkoutShowButton').html(modifyHidePaymentFields); 2291 } else { 2292 $('#checkoutShowButton').html(modifyShowPaymentFields); 2293 } 2294} 2295 2296 2297function focusImage(){ 2298 $('body').scrollTop($("#photo-upload").offset().top - 20); 2299} 2300 2301// any logic that needs to happen before the order is submitted 2302function submitOrder() { 2303 2304 warnAgainstLeaveAttempts(); 2305 2306 //check whether our global translated strings are in memory - if one is missing they'll all be missing 2307 if (typeof SOerror == 'undefined') { 2308 SOerror = 'Please make sure everything is filled out and try again.'; 2309 SOerror1 = 'You must select at least one ticket/registration.'; 2310 SOerror2 = 'Please make sure all required questions are answered.'; 2311 SOerror3 = 'This order exceeds the maximum number of complimentary tickets on this event by '; 2312 SOerror4 = 'Please contact your support representative to increase your allotment. Fees may apply.'; 2313 SOvalidationErrorMsg = 'Please fix this field'; 2314 SOphotoUpload = 'Required Photo Upload'; 2315 SOimageTextSingular = 'image'; 2316 SOimageTextPlural = 'images'; 2317 SOphotoUpload1 = 'You have not uploaded the '; 2318 SOphotoUpload2 = ' required to submit this order.'; 2319 SOsubmittingText = 'Submitting Your Order...'; 2320 SOpaymentAuthText = 'Sending for payment authorization!'; 2321 SOsuccessText = 'Success!'; 2322 } 2323 var error = SOerror; 2324 var useThisAlertBox = true; 2325 var shouldSubmit = true; 2326 var validationErrorMsg = SOvalidationErrorMsg; //this doesn't appear to be used anywhere at all, actually 2327 2328 // checking for empty gratuity field 2329 var gratuityIssues = []; 2330 $('.gratuity-value').each(function(){ 2331 if(!$(this).is(':disabled')){ 2332 if($(this).val() == ''){ 2333 gratuityIssues['notSelected'] = true; 2334 gratuityIssues['optionTitle'] = $(this).data('option-title'); 2335 } 2336 } 2337 }); 2338 2339 if(gratuityIssues['notSelected']){ 2340 var optionTitle = gratuityIssues['optionTitle']; 2341 alertify.error('You must select a ' + optionTitle + ' option'); 2342 var aTag = $("a[name='gratuity']"); 2343 $('html,body').animate({scrollTop: aTag.offset().top-100},'slow'); 2344 ignoreLeaveAttempts(); 2345 enableSubmit(origSubmitButtonText); 2346 return false;
2347 } 2348 2349 // CHECKING ORDER LEVEL SUBMISSION FOR REQUIRED IMAGE UPLOAD 2350 if($('#OrderPhotosUploadRequired').length){ 2351 // IF THE FIELD IS TRUE, NOTHING TO DO 2352 if($('#OrderPhotosUploadRequired').val() == 'true'){ 2353 // PHOTO REQUIREMENTS HAVE BEEN 2354 } else { 2355 // IF THE FIELD IS ANYTHING ELSE - DO NOT CONTINUE WITH THE SUBMIT. 2356 var imageText = SOimageTextPlural; 2357 if($('#OrderMinPhotosUpload').val() < 2){ 2358 imageText = SOimageTextSingular; 2359 } 2360 alertify 2361 .alert() 2362 .setting({ 2363 'title' : SOphotoUpload, 2364 'message': SOphotoUpload1 + $('#OrderMinPhotosUpload').val() + ' ' + imageText + SOphotoUpload2, 2365 'modal': true, 2366 'basic': false, 2367 'closable': true, 2368 'closableByDimmer': false, 2369 }) 2370 .set({ 2371 onclose: function() { 2372 enableSubmit(origSubmitButtonText); 2373 setTimeout(focusImage, 500); 2374 2375 return false; 2376 }, 2377 }) 2378 .show(); 2379 ignoreLeaveAttempts(); 2380 return false; 2381 } 2382 } 2383 2384 // ensure credit card passes any cc restrictions (if there even is a credit card field) 2385 var orderType = $('#OrderPaymentType').val(); 2386 var orderStatus = 0; 2387 2388 if($('#OrderStatus').val()){ 2389 orderStatus = $('#OrderStatus').val(); 2390 } 2391 2392 // force not iDEAL if total is zero 2393 if($('#OrderProposedTotal').val()==0 && (orderType == 'iDEAL')) $('#OrderPaymentType').val('creditCard'); 2394 2395 // prevent order submission if no quantity has been selected (except for waitlist orders which will not have a selected quantity here) 2396 orderTotQty = orderTotalQuantity(); 2397 orderIsWaitlist = $('#OrderPurchaseLocation').val() === 'waitlist-confirmation'; 2398 if (!orderIsWaitlist && orderTotQty <= 0 ) { 2399 shouldSubmit = false; 2400 error = SOerror1; 2401 2402 } else if( !requiredChecksChecked() ) { 2403 shouldSubmit = false; 2404 error = SOerror2; 2405 } else if ($('#ExtraRemainingComp').val() < orderTotQty) { 2406 shouldSubmit = false; 2407 error = SOerror3 + (orderTotQty - $('#ExtraRemainingComp').val()) + '. ' + SOerror4; 2408 //needs its own error box - the message won't fit in the normal one 2409 alertify.alert("Maximum Reached", error); 2410 ignoreLeaveAttempts(); 2411 return false; 2412 } 2413 2414 // check if the opt in status is yes, if not 2415 if(ticketInsuranceJsLoaded && $('#OrderInsuranceOptStatus').val() == 0){ 2416 // clear the policy total 2417 $('#OrderPolicyTotal').val(0); 2418 } 2419 2420 if ( !shouldSubmit ) { 2421 if(useThisAlertBox) { 2422 alertify.alert("Notice", error).set('modal', true).set('basic', false).pin(); 2423 } 2424 ignoreLeaveAttempts(); 2425 enableSubmit(origSubmitButtonText); 2426 return false; 2427 } 2428 2429 submittingAlert = alertify.alert() 2430 .setting({ 2431 'message': SOsubmittingText, 2432 'modal': true, 2433 'closable': false, 2434 'closableByDimmer': false, 2435 'movable': false, 2436 'basic': true, 2437 }) 2438 .set('onok', function(closeEvent) { return false; } ) 2439 .set('onclose', function() { return true }) // to keep the 'fail' one from retaining old data on close 2440 .show(); 2441 // .minimalDialog(); 2442 // 'frameless': true, 2443 2444 // var error = 'Please make sure all required questions are answered.'; 2445 // alertify.alert("Notice", error).set('modal', true).pin(); 2446 2447 var layoutSlug = $('#OrderLayoutSlug').val(); 2448 if(layoutSlug == 'layout_modern'){ 2449 //deactivate submit button 2450 submitButtonAction('disable'); 2451 } 2452 2453 var fieldsToSubmit = getOrderFormDataWithoutUnmodifiedZeroQuantityTickets('#orderForm'); 2454 2455 // Apple Pay needs the wallet sheet to open inside the original click gesture, 2456 // so fire elements.submit() before the order-create ajax â but only when this 2457 // order will actually charge a card (skip cash, free, voucher). 2458 const purchaseLocation = $('#OrderPurchaseLocation').val(); 2459 const orderChargesCard = parseFloat($('#OrderProposedTotal').val()) > 0 2460 && !($('#OrderCashOption').val() && $('#OrderCashOption').prop('checked')) 2461 && purchaseLocation !== 'voucher'; 2462 2463 const needsEarlyElementsSubmit = typeof elements !== 'undefined' && elements 2464 && typeof paymentGateway !== 'undefined' && paymentGateway === 'stripe' 2465 && document.getElementById('payment-element') 2466 && orderChargesCard; 2467 2468 const preSubmit = needsEarlyElementsSubmit ? elements.submit() : Promise.resolve({}); 2469 2470 preSubmit.then(function(submitResult) { 2471 if (submitResult && submitResult.error) { 2472 // Bad payment details (e.g. resubmit after a decline) - show why, don't bail silently. 2473 submittingAlert.close(); 2474 ignoreLeaveAttempts(); 2475 enableSubmit(origSubmitButtonText); 2476 showPreSubmitError(submitResult.error.message); 2477 return; 2478 } 2479 orderSubmissionInProgress = true; 2480 2481 // submits the order via ajax 2482 $.ajax({ 2483 type: "POST", 2484 url: buildURL('/orders/ajax_submit_order/' + $('#OrderEventId').val() + '/' + $('#OrderEventSlug').val()), 2485 data: fieldsToSubmit, 2486 dataType: "json", 2487 success: function(data) { 2488 switch(data['status']) { 2489 case 'success': 2490 2491 if(isModal()) { 2492 handleModalOrderComplete(); 2493 } 2494 2495 if (data.gateway == 'authnet') { 2496 // successful normal order, setup redirect etc. 2497 handlePaymentRedirect(data); 2498 } else if(data.gateway == 'stripe') { 2499 orderData = data; 2500 var $form = $("#orderForm"); 2501 $form.append($('<input type="hidden" name="data[Order][id]" id="OrderId">').val(data.order_id)); 2502 if(data.redirect && data.cash_option == 1) { 2503 ignoreLeaveAttempts(); 2504 window.location = data.redirect; 2505 } else { 2506 // if it has a balance paid and is not waitlist 2507 if(data.total_paid > 0 && data.order_type != 'waitlist'){ 2508 ignoreLeaveAttempts(); 2509 var processStatus = handleSubmit(); 2510 } else { 2511 // if total paid is 0 or it is a waitlist, no need for payment 2512 ignoreLeaveAttempts(); 2513 handlePaymentRedirect(data); 2514 } 2515 } 2516 } 2517 2518 break; 2519
2520 case 'error': 2521 case 'fail': 2522 case false: 2523 default: 2524 orderSubmissionInProgress = false; 2525 if(data['payment_failure'] != 'undefined' && data['payment_failure']){ 2526 clearOutSeatedFields(); 2527 } 2528 ignoreLeaveAttempts(); 2529 if(data['redirect']) { 2530 window.location = data['redirect']; 2531 } 2532 submittingAlert.close(); 2533 if(data['reason']) { 2534 data.refresh = true; 2535 alertAndClose(data); 2536 } 2537 2538 2539 if(data['fieldEnable'] == true && paymentGateway == 'authnet'){ 2540 $( "#OrderCcNumber" ).prop( "disabled", false ); 2541 $( "#OrderCcCvv" ).prop( "disabled", false ); 2542 } 2543 2544 if (data['orderTicketLimitPassed'] == true) { 2545 var qtyTicketLimitTitle = 'Order Limit Exceeded'; 2546 // var qtyTicketLimitMsg = 'Orders on this event are limited to ' + data['orderTicketLimit'] + ' items. Please adjust your quantities.'; 2547 var qtyTicketLimitMsg = 'Youâve exceeded the quantity limits on this event. Please adjust your quantities and try again.'; //CZ - temp generic message for Kokefest launch April 2022, TODO: allow client to customize failure message 2548 2549 alertify.alert(qtyTicketLimitTitle, qtyTicketLimitMsg).setting({ 2550 'message': qtyTicketLimitMsg, 2551 'modal': true, 2552 'basic': false, 2553 'closable': true, 2554 'closableByDimmer': false}) 2555 .set({ 2556 onok: function(closeEvent) { return true; }, // i think it didn't jump down before having to add this 2557 }); 2558 } 2559 enableSubmit(origSubmitButtonText); 2560 break; 2561 } 2562 return false; 2563 }, 2564 2565 }); 2566 }).catch(function(submitError) { 2567 // elements.submit() rejected (network/internal) - bail, but tell the buyer why. 2568 submittingAlert.close(); 2569 ignoreLeaveAttempts(); 2570 enableSubmit(origSubmitButtonText); 2571 showPreSubmitError(submitError && submitError.message ? submitError.message : null); 2572 }); 2573 2574 return false; 2575 2576 // return true; 2577} 2578 2579/** 2580 * Function to handle the payment redirect after a successful order. 2581 * It checks if the orderData object has a redirect property, and if it does, 2582 * it sets the confirmationUrl to the redirect URL. If it doesn't, it constructs 2583 * the confirmation URL using the order_id and lang (if available). 2584 * 2585 * @param {Object} orderData - The object containing the order data. 2586 * @param {string} orderData.order_id - The order ID. 2587 * @param {string} orderData.id - The order ID (in case order_id is not available). 2588 * @param {string} orderData.redirect - The redirect URL (optional). 2589 * @param {string} orderData.lang - The language (optional). 2590 * 2591 * @returns {void} 2592 */ 2593 function handlePaymentRedirect(orderData) { 2594 let thisOrderId; 2595 if (orderData.order_id) { 2596 thisOrderId = orderData.order_id; 2597 } else { 2598 thisOrderId = orderData.id; 2599 } 2600 // successful normal order, setup redirect etc. 2601 if (orderData === undefined || !orderData.redirect) { 2602 let urlString = "/orders/confirmation/" + thisOrderId; 2603 if (orderData.lang) { 2604 urlString += "/" + orderData.lang; 2605 } 2606 var confirmationUrl = buildURL(urlString); 2607 } else { 2608 var confirmationUrl = buildURL(orderData.redirect); 2609 } 2610 2611 confirmationUrl = confirmationUrl + "/ordered=true"; 2612 2613 ignoreLeaveAttempts(); 2614 2615 // needed only for exchange, to refresh the confirmation page when the transaction is complete. 2616 if ($('#OrderPurchaseLocation').val() == 'exchange') { 2617 parent.location.reload(); 2618 parent.jQuery.fancybox.close(); 2619 return; 2620 } 2621 2622 if (typeof isFrame !== 'undefined' && isFrame) { 2623 var emailAddon = '?email=' + $('#OrderEmail').val(); 2624 2625 window.location = confirmationUrl + emailAddon; 2626 } else { 2627 window.location = confirmationUrl; 2628 2629 alertify 2630 .alert() 2631 .setting({ 2632 'message': SOsuccessText, 2633 'modal': true, 2634 'closable': false, 2635 'closableByDimmer': true, 2636 'movable': false, 2637 'basic': true 2638 }) 2639 .set('onok', function (closeEvent) { window.location = confirmationUrl; return false; }) // in case they hit escape to stop the initial redirect, it will trigger "onok" which triggers from "esc" key 2640 .show(); 2641 } 2642} 2643 2644// Shows an elements.submit() failure so a bailed submit isn't a silent no-op. 2645function showPreSubmitError(message) { 2646 if (!message) { 2647 message = 'There was a problem with your payment details. Please check your card and try again.'; 2648 } 2649 $('#payment-response').text(message).addClass('error'); // in-line payment response banner 2650 alertAndClose({ reason: message, refresh: false }); 2651} 2652 2653/** 2654 * 2655 * @param {*} data 2656 */ 2657function alertAndClose(data) { 2658 let refresh = data.refresh; 2659 let redirect = data.redirect; 2660 let redirect_url = data.redirect_url; 2661 let clearPrefill = data.clearPrefill; 2662 2663 alertify 2664 .alert("Order Error", data['reason']) 2665 .setting({ 2666 'message': data['reason'], 2667 'modal': true, 2668 'basic': false, 2669 'closable': true, 2670 'closableByDimmer': false, 2671 }) 2672 .set({ 2673 onshow: null, 2674 onok: function (closeEvent) { return true; }, 2675 onclose: function () { 2676 addValidationErrors(data['validation_errors']); 2677 //if this is a timed entry / selective registration failure, we need to empty the cart and reload the page 2678 if (clearPrefill === true) { 2679 //use an ajax call to clear out the prefill 2680 $.ajax({ 2681 type: "GET", 2682 url: buildURL("/admin/orders/ajax_clear_prefill/"), 2683 dataType: "json", 2684 success: function (data) { 2685 ignoreLeaveAttempts(); 2686 2687 if(refresh === true){ 2688 //need to get the window to reload WITHOUT previous POST data 2689 window.location.href = window.location.href; 2690 } 2691 2692 // used on the return page for ideal and payment plan payments 2693 if(redirect === true){ 2694 //need to get the window to reload WITHOUT previous POST data 2695 window.location.replace(redirect_url); 2696 } 2697 }, 2698 error: function (errorMessage) { 2699 return false;
2700 } 2701 }); 2702 } 2703 }, 2704 }) 2705 .show(); 2706} 2707 2708// adds a validation error/message to a specified field 2709// and adds an on keyup to remove it 2710function addValidationErrors(validationErrors) { 2711 var focusedFirstField = false; 2712 removeAllValidationErrors(); 2713 if(validationErrors) { 2714 $.each(jQuery.parseJSON(validationErrors), function( fieldName, error) { 2715 removeValidationError(fieldName); 2716 var validationErrorMsg = '<div class="error-message">' + error[0] + '</div>'; 2717 var field = $('[name="data[Order][' + fieldName + ']"]'); 2718 if(field.length > 0) { 2719 field.after(validationErrorMsg); 2720 field.addClass('form-error'); 2721 switch (field.prop('type')) { 2722 case 'radio': 2723 case 'select-one': 2724 case 'select': 2725 case 'select-multiple': 2726 field.on('change', function () { 2727 removeValidationError(fieldName); 2728 }); 2729 break; 2730 2731 case 'text': 2732 default: 2733 field.on('keyup', function () { 2734 removeValidationError(fieldName); 2735 }); 2736 break; 2737 } 2738 if (focusedFirstField == false) { 2739 field.trigger("focus"); 2740 $('body').scrollTop(field.offset().top); 2741 focusedFirstField = true; 2742 } 2743 } 2744 }); 2745 } 2746} 2747 2748// removes the validation error on a specific field 2749function removeValidationError(fieldName) { 2750 var field = $('[name="data[Order][' + fieldName + ']"]'); 2751 field.siblings('.error-message').remove(); 2752 field.removeClass('form-error'); 2753} 2754 2755// removes all validations errors, resetting the form from errors 2756function removeAllValidationErrors() { 2757 var formErrorAreas = $('#orderForm .form-error'); 2758 $('#orderForm .error-message').remove(); 2759 $('#orderForm .form-error').removeClass('form-error'); 2760} 2761 2762 2763 2764// makes sure that any multi-checkboxes that are required have at least one checked 2765function requiredChecksChecked() { 2766 var toReturn = true; 2767 $('ul.check-required').each(function(index, ul) { 2768 var atLeastOneChecked = false; 2769 $(this).find('input').each(function(index, input) { 2770 if($(input).prop('checked')) { 2771 atLeastOneChecked = true; 2772 } 2773 }); 2774 if(!atLeastOneChecked) { 2775 $(ul).addClass('requiredAlert'); 2776 toReturn = false; 2777 } else { 2778 $(ul).removeClass('requiredAlert'); 2779 } 2780 }); 2781 return toReturn; 2782} 2783 2784 2785/** 2786 * [orderTotalQuantity description] 2787 * @return {[type]} [description] 2788 */ 2789function orderTotalQuantity() { 2790 var totalQuantity = 0; 2791 var quantity; 2792 $('[name^="data[Ticket]"][name$="[quantity]"]').each(function() { 2793 quantity = $(this).val(); 2794 if(quantity && quantity.length > 0) { 2795 totalQuantity += parseInt($(this).val()); 2796 } 2797 }); 2798 $('.charity [name$="total]"]').each(function() { 2799 quantity = $(this).val(); 2800 if(quantity && quantity.length > 0) { 2801 totalQuantity += parseInt($(this).val()); 2802 } 2803 }); 2804 $('.giftCardOptions [name$="quantity]"]').each(function() { 2805 quantity = $(this).val(); 2806 if(quantity && quantity.length > 0) { 2807 totalQuantity += parseInt($(this).val()); 2808 } 2809 }); 2810 2811 return totalQuantity; 2812 2813} 2814 2815 2816// shows or hides multiple ticket type prices 2817function toggleMultiPrices(id) { 2818 if ($('.ticket-type-prices-'+id).is(':visible')) { 2819 $('.ticket-type-prices-'+id).slideUp(); 2820 $('.btn-buy-tickets-'+id).removeClass('btn-default'); 2821 $('.btn-buy-tickets-'+id).addClass('btn-primary'); 2822 } else { 2823 $('.ticket-type-prices-'+id).slideDown(); 2824 $('.btn-buy-tickets-'+id).removeClass('btn-primary'); 2825 $('.btn-buy-tickets-'+id).addClass('btn-default'); 2826 } 2827} 2828 2829 2830// called when the calculate_total recognizes that there are no items selected 2831function quantityChangedToZero() { 2832 if(ticketInsuranceJsLoaded){ 2833 hideShowInsuranceWidget('hide'); 2834 } 2835} 2836 2837// called when the calculate_total recognizes that there is at least one ticket selected 2838function quantityChagnedToAtLeastOne() { 2839 if(ticketInsuranceJsLoaded){ 2840 hideShowInsuranceWidget('show'); 2841 } 2842} 2843 2844 2845function update_selected_charity(charityId) { 2846 var name = $('#Contribution0CharityId option[value="'+charityId+'"]').text(); 2847 2848 $('#Contribution0CharityName').val(name); 2849 $('.summary_charity_name').html(name); 2850 2851 // hide all charity descriptions
2852 $('.charity_description').slideUp('medium', function() { 2853 // show the correct charity description 2854 $('#charity_description_'+charityId).slideDown('medium'); 2855 }); 2856 2857} 2858 2859 2860// gets the 'quantity' field for the selected seat object / selectedTicketType object (passed from seating chart) 2861// called from on both selecting and deselecting 2862function getQuantityFieldOfSelectedSeat(seat, selectedTicketType) { 2863 2864 var quantityFieldId = false; 2865 2866 // if a specific ticket type price was selected, increment it's quantity 2867 if (selectedTicketType !== null && typeof selectedTicketType === 'object') { 2868 var ticketTypePriceKeys = ticketTypeCategories[seat['category']['key']]['ticket_type_price_keys']; 2869 for (index = 0; index < ticketTypePriceKeys.length; ++index) { 2870 if (selectedTicketType['ticketType'] == ticketTypePrices[ticketTypePriceKeys[index]]['name']) { 2871 quantityFieldId = get_quantity_field_id(null, ticketTypePrices[ticketTypePriceKeys[index]]['id']); 2872 } 2873 } 2874 2875 // otherwise, find the correct field based on ticket type id, and increment it's quantity (for use on ticket types that don't have more than one price) 2876 } else { 2877 quantityFieldId = get_quantity_field_id(ticketTypeCategories[seat['category']['key']]['ticket_type_id'], null); 2878 } 2879 return quantityFieldId; 2880} 2881 2882 2883function seatSelected(seat, selectedTicketType, formId) 2884{ 2885 var quantityField = getQuantityFieldOfSelectedSeat(seat, selectedTicketType); 2886 2887 incrementFieldValue(quantityField); 2888 var seatIdsField = 'Ticket' + quantityField.match(/\d+/)[0] + 'SeatIds'; 2889 var seatsLabelsField = 'Ticket' + quantityField.match(/\d+/)[0] + 'SeatLabels'; 2890 var seatParentField = 'Ticket' + quantityField.match(/\d+/)[0] + 'SeatParents'; 2891 var objTypeField = 'Ticket' + quantityField.match(/\d+/)[0] + 'ObjectType'; 2892 var qtyField = 'Ticket' + quantityField.match(/\d+/)[0] + 'Quantity' 2893 2894 $('#'+seatIdsField).val(customTrim($('#'+seatIdsField).val() + "," + seat['uuid'], ",")); // adds the seat id to the data[Ticket][x][seat_ids] field value 2895 $('#'+seatsLabelsField).val(customTrim($('#'+seatsLabelsField).val() + "," + seat['label'], ",")); // adds the seat id to the data[Ticket][x][seat_ids] field value 2896 if (typeof seat['parent'] != 'undefined') { //have to check step by step to avoid javascript error (can't combine in one if statement) 2897 if (typeof seat['parent']['type'] != 'undefined') { 2898 $('#'+seatParentField).val(customTrim($('#'+seatParentField).val() + "," + seat['parent']['type'], ",")); 2899 $('#OrderSelectedSeatParents').val(customTrim($('#OrderSelectedSeatParents').val() + "," + seat['parent']['type'], ",")); 2900 } 2901 } 2902 $('#OrderSelectedSeatLabels').val(customTrim($('#OrderSelectedSeatLabels').val() + "," + seat['label'], ",")); // adds the seat id to the data[Order][selected_seat_labels] field value 2903 $('#'+objTypeField).val(seat['objectType']); 2904 2905 var transType = $('#OrderPurchaseLocation').val(); 2906 if(transType == 'exchange'){ 2907 $('#Ticket' + quantityField.match(/\d+/) + 'PurchasedTicketId').val($('#OrderPurchasedTicketId').val()); 2908 $('#Ticket' + quantityField.match(/\d+/) + 'PurchasedTicketTicketTypeId').val($('#OrderPurchasedTicketTicketTypeId').val()); 2909 $('#Ticket' + quantityField.match(/\d+/) + 'PurchasedTicketTicketTypePriceId').val($('#OrderPurchasedTicketTicketTypePriceId').val()); 2910 $('#Ticket' + quantityField.match(/\d+/) + 'PurchasedTicketPrice').val($('#OrderPurchasedTicketPrice').val()); 2911 } 2912 2913 calculate_order_total(formId); 2914 2915 checkGratuity('#'+qtyField); 2916 2917} 2918function seatDeselected(seat, selectedTicketType, formId) 2919{ 2920 var quantityField = getQuantityFieldOfSelectedSeat(seat, selectedTicketType); 2921 decrementFieldValue(quantityField); 2922 var seatIdsField = 'Ticket' + quantityField.match(/\d+/)[0] + 'SeatIds'; 2923 var seatsLabelsField = 'Ticket' + quantityField.match(/\d+/)[0] + 'SeatLabels'; 2924 $('#'+seatIdsField).val(customTrim($('#'+seatIdsField).val().replace(seat['uuid'], ''), ",").replace(",,", ",")); // removes the seat id from the data[Ticket][x][seat_ids] field value 2925 $('#'+seatsLabelsField).val(customTrim($('#'+seatsLabelsField).val().replace(seat['label'], ''), ",").replace(",,", ",")); // removes the seat label from the data[Ticket][x][seat_labels] field value 2926 $('#OrderSelectedSeatLabels').val(customTrim($('#OrderSelectedSeatLabels').val().replace(seat['label'], ''), ",").replace(",,", ",")); // removes the seat id from the data[Order][selected_seat_labels] field value 2927 calculate_order_total(formId); 2928 checkGratuity('#'+quantityField); 2929} 2930 2931 2932var selectedSeatsBooked = ''; 2933var selectedSeatsBookedTimeout; 2934function selectedSeatBooked(seat) 2935{ 2936 //fallback if translated strings are not in memory - if one isn't, neither will be 2937 if (typeof seatsBooked1 == 'undefined') { 2938 var seatsBooked1 = "We're sorry, but one or more of your selected seats ("; 2939 var seatsBooked2 = ") have been booked and have been removed from your selection."; 2940 }
2941 if (allowSeatsBookedWarning) 2942 { 2943 clearTimeout(selectedSeatsBookedTimeout); 2944 selectedSeatsBooked += seat.label + ', '; 2945 selectedSeatsBookedTimeout = setTimeout(function() { // does this to allow just one alert instead of more than one per seat 2946 if(selectedSeatsBooked.length > 0) { 2947 alertify.alert("Notice", seatsBooked1 + customTrim(selectedSeatsBooked.trim(), ",") + seatsBooked2).set('modal', true).set('basic', false).pin(); 2948 selectedSeatsBooked = ''; 2949 calculate_order_total(); 2950 } 2951 }, 1000); 2952 } 2953} 2954 2955 2956// trims a specific character from the beginning and end of a string 2957function customTrim(str, chr) 2958{ 2959 var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^'+chr+'+|'+chr+'+$', 'g'); 2960 return str.replace(rgxtrim, ''); 2961} 2962 2963 2964 2965// increments the value of an input field 2966function incrementFieldValue(fieldId) 2967{ 2968 $("#" + fieldId).val(parseInt($("#" + fieldId).val()) + 1); 2969} 2970 2971// decrements the value of an input field 2972function decrementFieldValue(fieldId) 2973{ 2974 $("#" + fieldId).val(parseInt($("#" + fieldId).val()) - 1); 2975} 2976 2977// gets the id of the quantity field based on either a ticketTypeId or a ticketTypePriceId 2978// if a ticket type only has one price, using the ticketTypeId is fine, but if it has more than one, you 2979// need to pass null for the ticketTypeId, then pass the actual ticketTypePriceId 2980function get_quantity_field_id(ticketTypeId, ticketTypePriceId) { 2981 var quantityField = null; 2982 2983 if (typeof isExchange !== 'undefined') { 2984 $("[name^='data[Ticket]'][name$='[ticket_type_id]']").filter(function () { // looking for field ending with [ticket_type_id] that has a value of the passed ticketTypePriceId; 2985 if(this.value == ticketTypeId && $(this).attr('data-actual-seat')) { 2986 // var id = $(this).attr('id'); //siblings("[name$='[ticket_type_price_id]']") 2987 quantityField = $(this).attr('id').replace("TicketTypeId", "Quantity"); 2988 return true; 2989 } 2990 }); 2991 } else { 2992 if (ticketTypeId != null) { 2993 $("[name^='data[Ticket]'][name$='[ticket_type_id]']").filter(function () { // looking for field ending with [ticket_type_id] that has a value of the passed ticketTypePriceId; 2994 if(this.value == ticketTypeId) { 2995 // var id = $(this).attr('id'); //siblings("[name$='[ticket_type_price_id]']") 2996 quantityField = $(this).attr('id').replace("TicketTypeId", "Quantity"); 2997 return true; 2998 } 2999 }); 3000 } else if (ticketTypePriceId != null) { 3001 $("[name$='[ticket_type_price_id]']:not([id^='QuestionResponse'])").filter(function () { // looking for field ending with [ticket_type_id] that has a value of the passed ticketTypePriceId and has an ID that does not start with QuestionResponse; 3002 if(this.value == ticketTypePriceId) { 3003 // var id = $(this).attr('id'); //siblings("[name$='[ticket_type_price_id]']") 3004 quantityField = $(this).attr('id').replace("TicketTypePriceId", "Quantity"); 3005 return true; 3006 } 3007 }); 3008 } 3009 } 3010 3011 return quantityField; 3012} 3013 3014 3015 3016/** 3017 * Seemingly simple function to show the seating chart, but 3018 * have to use visibility instead of display so seats.io chart can render. 3019 * @param {string} categoryKey - seats.io category key used to filter and zoom the chart 3020 * @return {void} 3021 */ 3022function showAndGoToSeatingChart(categoryKey) { 3023 3024 if (categoryKey) { 3025 chart.changeConfig({ 3026 filteredCategories: [categoryKey] 3027 }); 3028 chart.zoomToFilteredCategories(); 3029 } 3030 3031 var element = $('#seatsContentArea'), 3032 innerContent = $('#seatsContentArea .content'), 3033 currentHeight = element.height(), 3034 autoHeight = element.css('height', 'auto').height(); 3035 3036 $('#seatsContentArea').slideDown('fast', function() { 3037 $('#seatsContentArea .content').slideDown('fast', function() { 3038 $('html,body').animate({scrollTop: $("a[name='seating_chart_area']").offset().top},'normal'); 3039 }); 3040 3041 }); 3042 3043 element.css('visibility', 'visible'); 3044 element.css('opacity', '100'); 3045} 3046 3047 3048function showItemDetails(item, extra){ 3049 //fallback in case translated strings are not in memory 3050 if (typeof hideAdditional == 'undefined') { 3051 var hideAdditional = 'Hide Additional Info'; 3052 var showAdditional = 'Show Additional Info'; 3053 } 3054 3055 if ($('#less-info-' + item).length) { 3056 //toggle short vs. long description 3057 $('#less-info-' + item).toggle(200); 3058 $('#more-info-' + item).toggle(200); 3059 } else { 3060 //toggle entire details area 3061 $("#item-details-" + item).toggle(200); 3062 } 3063 if (extra) { 3064 //we have to search for the string within the value rather than just 3065 //doing a simple == because it fails under some circumstances otherwise 3066 var info = $('#more-less-'+item).text(); 3067 if (info.indexOf('Show Additional Info') > -1) { 3068 $('#more-less-'+item).text(hideAdditional); 3069 } else { 3070 $('#more-less-'+item).text(showAdditional); 3071 } 3072 } 3073} 3074 3075/** 3076 * [editTicketDetails description] 3077 * @param {[type]} dir [1=open edit | 0=close edit] 3078 * @return {[type]} [description] 3079 */ 3080function editTicketDetails(dir){ 3081 if(dir == 1){ 3082 // SHOW EDIT FIELDS - HIDE STANDARD DISPLAY 3083 $('#attendee-edit-details').slideDown('fast');
3084 $('#attendee-details').slideUp('fast'); 3085 } else { 3086 // HIDE EDIT FIELDS - SHOW STANDARD DISPLAY 3087 $('#attendee-edit-details').slideUp('fast'); 3088 $('#attendee-details').slideDown('fast'); 3089 } 3090} 3091 3092function switchPaymentMethod(currentMethod){ 3093 // simple switch, takes current payment method, and switches to the other one when called 3094 // so it if is currently set at iDEAL, and this function is called, the function is 3095 // to change the form for credit card submission, and vice versa 3096 switch(currentMethod){ 3097 case 'ideal': // needs to switch to CC - including enable CC fields 3098 $('#OrderPaymentType').val('creditCard'); 3099 3100 // hide ideal, show CC 3101 $('.paymentOptionsCC').slideDown(); 3102 $('.paymentOptionsiDEAL').slideUp(); 3103 3104 // enable CC fields 3105 if(paymentGateway == 'authnet') { 3106 $('#OrderCcNumber').removeAttr('disabled'); 3107 $('#OrderCcExpireMonth').removeAttr('disabled'); 3108 $('#OrderCcExpireYear').removeAttr('disabled'); 3109 $('#OrderCcCvv').removeAttr('disabled'); 3110 } 3111 3112 break; 3113 case 'creditCard': // needs to switch to ideal - including disable CC fields 3114 $('#OrderPaymentType').val('iDEAL'); 3115 3116 // hide CC show ideal 3117 $('.paymentOptionsCC').slideUp(); 3118 $('.paymentOptionsiDEAL').slideDown(); 3119 3120 // disable CC fields 3121 if(paymentGateway == 'authnet') { 3122 $('#OrderCcNumber').attr('disabled', 'disabled'); 3123 $('#OrderCcExpireMonth').attr('disabled', 'disabled'); 3124 $('#OrderCcExpireYear').attr('disabled', 'disabled'); 3125 $('#OrderCcCvv').attr('disabled', 'disabled'); 3126 } 3127 3128 break; 3129 } 3130} 3131 3132var currentlyRemovingFromCart = []; 3133 3134function removeFromCart(buttonObject, ttPriceId, formId, aittId) { 3135 3136 id = $('[data-ticket-type-price-id="' + ttPriceId + '"]').attr('id'); 3137 3138 var rowIdString = ttPriceId + formId + aittId; 3139 3140 if (currentlyRemovingFromCart.indexOf(rowIdString) === -1) { 3141 3142 buttonObject.fadeTo(0, 0.3); 3143 3144 currentlyRemovingFromCart.push(rowIdString); 3145 3146 // need to remove it from the session data as well 3147 $.ajax({ 3148 type: "POST", 3149 url: buildURL("/orders/remove_from_cart/" + ttPriceId + '/' + aittId), 3150 dataType: "json", 3151 success: function (data) { 3152 // if we need anything for success 3153 switch(data['status']){ 3154 case 'success': 3155 alertify.success("Successfully removed from shopping cart."); 3156 switch(tsTheme){ 3157 case 'Modern': 3158 if ($('#OrderPurchaseLocation').val() == 'exchange') { 3159 if ($('#' + id).siblings('[data-field-name="activity_instance_ticket_type_id"]').val() == aittId || aittId == 'undefined') { 3160 $('#' + id).val(''); 3161 } 3162 3163 // ticket exchanges 3164 // removes from previous selections 3165 $('#ticket-' + ttPriceId).empty(); 3166 3167 // removes from current selections 3168 $('#selected-ticket-' + ttPriceId).find('.selectButton').trigger("click"); 3169 } else { 3170 if ($('#' + id).closest('.ticketOptions').find('[data-field-name="activity_instance_ticket_type_id"]').val() == aittId) { 3171 $('#' + id).val('0').prev('a').addClass('disabled'); 3172 } 3173 $('input[data-ticket-type-price-id=' + ttPriceId + ']').val(0); 3174 } 3175 3176 $('#ticket-selection-container-' + aittId).empty(); 3177 $('#ticket-selection-container-' + ttPriceId).empty(); 3178 3179 break; 3180 case 'Classic': 3181 default: 3182 if ($('#' + id).siblings('[data-field-name="activity_instance_ticket_type_id"]').val() == aittId || aittId == 'undefined') { 3183 $('#' + id).val(''); 3184 } 3185 $('#ticket-selection-container-' + aittId).empty(); 3186 $('#ticket-selection-container-' + ttPriceId).empty(); 3187 3188 //ticket exchanges 3189 //--removes from previous selections 3190 $('#ticket-' + ttPriceId).empty(); 3191 3192 //--removes from current selections 3193 $('#selected-ticket-' + ttPriceId).find('.selectButton').trigger("click"); 3194 break; 3195 } 3196 // this is where I want do it, need to get the element of the quantity selector for this one. 3197 var thisElement = $('#' + id); 3198 //set the quantity explicitly to 0 (previously was leaving it blank, which was causing notices under some circumstances) 3199 thisElement.val('0'); 3200 checkGratuity(thisElement); 3201 calculate_order_total(formId); 3202 setTimeout(removeFromRemovingFromCartArray, 3000, rowIdString); 3203 break; 3204 default: 3205 3206 // if not successful, remove from "currentlyRemovingFromCart" array so it will allow clicking again 3207 removeFromRemovingFromCartArray(rowIdString); 3208 buttonObject.fadeTo("fast", 1); 3209 3210 alertify.error("Could not be removed from shopping cart."); 3211 return false;
3212 break; 3213 } 3214 }, 3215 error: function (errorMessage) { 3216 3217 // if errored, remove from "currentlyRemovingFromCart" array so it will allow clicking again 3218 removeFromRemovingFromCartArray(rowIdString); 3219 buttonObject.fadeTo(0, 1); 3220 3221 // if we need anything from error 3222 return false; 3223 }, 3224 complete: function(data) { 3225 $('#mainTicketSelection').removeClass('hidden'); 3226 } 3227 }); 3228 } 3229} 3230 3231function removeFromRemovingFromCartArray(rowIdString) { 3232 let index = currentlyRemovingFromCart.indexOf(rowIdString); 3233 if (index > -1) { 3234 currentlyRemovingFromCart.splice(index, 1); 3235 } 3236} 3237 3238function removeFromCartGiftCard(giftCard, formId) { 3239 switch(tsTheme){ 3240 case 'Modern': 3241 $('[name="data[CreditProduct][' + giftCard + '][quantity]"]').val('0'); 3242 break; 3243 case 'Classic': 3244 default: 3245 $('[name="data[CreditProduct][' + giftCard + '][quantity]"]').val(''); 3246 break; 3247 } 3248 calculate_order_total(formId); 3249} 3250 3251function removeCharityFromCart(charityId, formId) { 3252 name = $('[value="' + charityId + '"]').attr('name'); 3253 name = name.replace('charity_event_id', 'total'); 3254 $('[name="' + name + '"]').val(''); 3255 calculate_order_total(formId); 3256} 3257 3258function disableButton(ticketId, soldOutText){ 3259 $('#showButton-' + ticketId).removeClass('btn'); 3260 $('#showButton-' + ticketId).addClass('ticket-sold-out-text'); 3261 $('#showButton-' + ticketId).removeAttr('href'); 3262 $('#showButton-' + ticketId).removeAttr('style'); 3263 $('#showButton-' + ticketId).html(soldOutText); 3264} 3265 3266var interval; //we need this available globally so we can cancel a timer in progress if necessary 3267function timerCountdown(endTime, controlId) { 3268 var minutes, seconds; 3269 endTime = new Date(endTime).getTime(); 3270 3271 if (isNaN(endTime)) { 3272 return false; 3273 } 3274 3275 interval = setInterval(timerCalculate, 1000); 3276 3277 function timerCalculate() { 3278 //get right now 3279 var startTime = new Date(); 3280 3281 //convert right now to UTC 3282 startTime = new Date(startTime.getTime() + startTime.getTimezoneOffset() * 60000); 3283 3284 //get time remaining 3285 var timeRemaining = parseInt((endTime - startTime) / 1000); 3286 3287 if (timeRemaining < 0) { 3288 timeRemaining = 0; 3289 } 3290 3291 minutes = parseInt(timeRemaining / 60); 3292 timeRemaining = (timeRemaining % 60); 3293 seconds = parseInt(timeRemaining); 3294 3295 //are we all done or not? 3296 var allDone = false; 3297 if (minutes == 0 && seconds == 0) { 3298 allDone = true; 3299 } 3300 3301 //put a leading 0 on minutes if necessary 3302 if (minutes.toString().length == 1) { 3303 minutes = '0' + minutes.toString(); 3304 } 3305 3306 //put a leading 0 on seconds if necessary 3307 if (seconds.toString().length == 1) { 3308 seconds = '0' + seconds.toString(); 3309 } 3310 3311 //put the time into the container's minutes child class 3312 $('#' + controlId + ' .timer-minutes').text(minutes + ":" + seconds); 3313 3314 //if the countdown has reached 0, we need to clear it out then reload the page after a couple of seconds 3315 if (allDone) { 3316 //add the time's up message 3317 $('#' + controlId + ' .timer-message').text("Sorry, time's up!"); 3318 3319 //make the calculations stop 3320 clearInterval(interval); 3321 3322 //use an ajax call to clear out the prefill 3323 $.ajax({ 3324 type: "GET", 3325 url: buildURL("/admin/orders/ajax_clear_prefill/"), 3326 dataType: "json", 3327 success: function (data) { 3328 ignoreLeaveAttempts(); 3329 //need to get the window to reload WITHOUT previous POST data 3330 window.location.href = window.location.href; 3331 }, 3332 error: function (errorMessage) { 3333 return false; 3334 } 3335 }); 3336 } 3337 } 3338} 3339 3340/** 3341 * Finds the highest data[Ticket][n] index already on the page. 3342 * The page-level ticketRowKey is printed before the ticket rows finish rendering, so it can 3343 * be lower than the indexes the server already used. Reading the form keeps appended rows 3344 * from re-using an index and overwriting another instance's selection on submit. 3345 */ 3346function findHighestTicketRowKey() { 3347 let highestRowKey = -1; 3348 3349 $('[name^="data[Ticket]["]').each(function () { 3350 const rowKeyMatch = this.name.match(/^data\[Ticket\]\[(\d+)\]/); 3351 if (rowKeyMatch) { 3352 highestRowKey = Math.max(highestRowKey, parseInt(rowKeyMatch[1], 10)); 3353 } 3354 }); 3355 3356 return highestRowKey; 3357} 3358 3359function processSelectedTickets(selections){ 3360 ticketRowKey = Math.max(ticketRowKey, findHighestTicketRowKey()); 3361 3362 selections.forEach(function(selection){ 3363 if(selection.quantity > 0){ 3364 ticketRowKey++; 3365 //create the container 3366 if(selection.activity_instance_ticket_type_id) { 3367 var containerID = 'ticket-selection-container-' + selection.activity_instance_ticket_type_id; 3368 } else { 3369 var containerID = 'ticket-selection-container-' + selection.ticket_type_price_id; 3370 } 3371 3372 var newSelection = '<div id="' + containerID + '" class="ticketOptions"></div>'; 3373 $('#ticketSelected').append(newSelection); 3374 3375 //create fields 3376 var formFields = ''; 3377 formFields += '<div id="selected_' + selection.activity_instance_ticket_type_id + '">'; 3378 formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][ticket_type_price_name]" value="' + selection.ticket_type_price_name + '" id="Ticket' + ticketRowKey + 'TicketTypePriceName">'; 3379 formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][ticket_type_name]" value="' + selection.ticket_type_name + '" id="Ticket' + ticketRowKey + 'TicketTypeName">'; 3380 formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][price_each]" value="' + selection.price_each + '" id="Ticket' + ticketRowKey + 'PriceEach">'; 3381 formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][ticket_type_price_id]" value="' + selection.ticket_type_price_id + '" id="Ticket' + ticketRowKey + 'TicketTypePriceId">'; 3382 formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][ticket_type_id]" value="' + selection.ticket_type_id + '" id="Ticket' + ticketRowKey + 'TicketTypeId">'; 3383 formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][activity_instance_ticket_type_id]" value="' + selection.activity_instance_ticket_type_id + '" id="Ticket' + ticketRowKey + 'ActivityInstanceTicketTypeId">'; 3384 formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][activity_instance_id]" value="' + selection.activity_instance_id + '" id="Ticket' + ticketRowKey + 'ActivityInstanceId">'; 3385 formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][activity_id]" value="' + selection.activity_id + '" id="Ticket' + ticketRowKey + 'ActivityId">'; 3386 formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][auto_register]" value="' + selection.auto_register + '" id="Ticket' + ticketRowKey + 'AutoRegister">'; 3387 formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][selective_register]" value="' + selection.selective_register + '" id="Ticket' + ticketRowKey + 'SelectiveRegister">'; 3388 formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][activity_name]" value="' + selection.activity_name + '" id="Ticket' + ticketRowKey + 'ActivityName">'; 3389 formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][activity_instance_date_time]" value="' + selection.activity_instance_date_time + '" id="Ticket' + ticketRowKey + 'ActivityInstanceDateTime">'; 3390 formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][hold]" value="holding" id="Ticket' + ticketRowKey + 'Hold">'; 3391 formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][activity_start_time]" value="' + selection.activity_start_time + '" id="Ticket' + ticketRowKey + 'ActivityStartTime">'; 3392 formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][activity_end_time]" value="' + selection.activity_end_time + '" id="Ticket' + ticketRowKey + 'ActivityEndTime">'; 3393 formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][quantity]" value="' + selection.quantity + '" id="Ticket' + ticketRowKey + 'Quantity">'; 3394 formFields += '</div>'; 3395 3396 $('#' + containerID).append(formFields); 3397 } 3398 }); 3399} 3400 3401/** 3402 * Gets `orderPrefillData` 3403 */ 3404function checkForCartItems(){ 3405 $.ajax({ 3406 type: "POST", 3407 url: buildURL('/orders/ajax_get_prefill_data/'), 3408 data: $('#orderForm').serialize(), 3409 dataType: "json", 3410 success: function(response) { 3411 switch(response['status']) { 3412 case 'success': 3413 updateConfirmation(response); 3414 break; 3415
3416 case 'error': 3417 case 'fail': 3418 default: 3419 3420 break; 3421 } 3422 }, 3423 3424 }); 3425} 3426 3427/** 3428 * Resets all exchange buttons to their default "Exchange" state 3429 */ 3430function resetExchangeButtons() { 3431 $('.exchangeSingleTicket').each(function() { 3432 var button = $(this); 3433 var ticketId = button.data('ticket-id'); 3434 3435 // Skip if this is not a valid exchange button or if it's disabled 3436 if (!ticketId || button.hasClass('btn-disabled')) { 3437 return; 3438 } 3439 3440 if (button.hasClass('editExchangeButton')) { 3441 button.removeClass('editExchangeButton'); 3442 var productName = button.data('product-name') || 'Ticket'; 3443 var icon = '<i class="fa-solid fa-rotate" aria-hidden="true"></i>'; 3444 button.html('Exchange ' + productName + icon); 3445 } 3446 }); 3447} 3448 3449/** 3450 * Sets a specific exchange button to "Edit Exchange" state 3451 * @param {string} ticketId - The ticket ID 3452 */ 3453function setExchangeButtonToEditExchange(ticketId) { 3454 var $button = $('.exchangeSingleTicket[data-ticket-id="' + ticketId + '"]'); 3455 if ($button.length && !$button.hasClass('btn-disabled')) { 3456 $button.addClass('editExchangeButton'); 3457 $button.html('Edit Exchange<i class="fa-solid fa-rotate" aria-hidden="true"></i>'); 3458 } 3459} 3460 3461function updateConfirmation(response){ 3462 var selectionCount = 0; 3463 var isModern = tsTheme === 'Modern'; 3464 if (isModern) { 3465 $('#manage-attendees').find('.exchange-info-row').hide(); 3466 $('#manage-attendees').find('.exchangeLabel').empty(); 3467 } else { 3468 $('.attendee-row').removeClass('ticketExchangePending'); 3469 $('.attendee-row').find('.exchangeInfo').empty(); 3470 } 3471 3472 // Reset all exchange buttons to default "Exchange" state 3473 resetExchangeButtons(); 3474 3475 $.each(response, function(key, value){ 3476 if(key != 'Order' && key != 'status' && key != 'Timer'){ 3477 if(value['seat_labels'] != ''){ 3478 exchangeLabel = value['seat_labels']; 3479 } else { 3480 exchangeLabel = value['ticket_type_name']; 3481 } 3482 if (isModern) { 3483 if (value['activity_instance_date_time'].length > 0) { 3484 exchangeLabel += '<br><i class="fa fa-calendar-alt" aria-hidden="true"></i> ' + value['activity_instance_date_time']; 3485 } 3486 $('#exchange-info-' + key + ' .exchangeLabel').html(exchangeLabel); 3487 $('#exchange-info-row-' + key).show(); 3488 3489 // Update the button for this ticket to "Edit Exchange" 3490 setExchangeButtonToEditExchange(key); 3491 } else { 3492 $('#attendee-row-' + key).addClass('ticketExchangePending'); 3493 if (value['activity_instance_date_time'].length > 0) { 3494 exchangeLabel += ' - ' + value['activity_instance_date_time']; 3495 } 3496 $('#attendee-row-' + key).find('.exchangeInfo').html('Pending Exchange to: ' + exchangeLabel); 3497 } 3498 selectionCount++; 3499 } 3500 }); 3501 3502 if(selectionCount > 0){ 3503 if (isModern) { 3504 $('#tickets-summary-footer').show(); 3505 } 3506 $('#exchangeCheckoutButton').show(); 3507 } else { 3508 if (isModern) { 3509 $('#tickets-summary-footer').hide(); 3510 } 3511 $('#exchangeCheckoutButton').hide(); 3512 } 3513} 3514 3515var timeoutVar; 3516function process_popup(qty, id) { 3517 clearTimeout(timeoutVar); 3518 if (qty > 0) { 3519 timeoutVar = setTimeout(function(){ 3520 $('#cart-popup-trigger-' + id).trigger("click"); //triggers the fancybox 3521 }, 500); 3522 } 3523} 3524 3525/** 3526 * 3527 * function calculates and displays the options for gratuity on the tickets page. 3528 */ 3529function checkGratuity(ele){ 3530 var selectedValue = 0; 3531 var priceEach = 0; 3532 var totalPrice = 0; 3533 var allPrices = []; 3534 if($(ele).data('has-active-gratuity')){ 3535 // get ticket type id\ 3536 ticketTypeId = $(ele).data('ticket-type-id'); 3537 // go through each of the options for this ticket type on the page and compile the data 3538 $('[data-ticket-type-id=' + ticketTypeId + '][data-field=quantity]').each(function(ticketTypeId){ 3539 if($(this).data('has-active-gratuity')){ 3540 // set for this selection 3541 thisSelectedValue = parseInt($(this).val(), 10); 3542 if(thisSelectedValue > 0){ 3543 thisPriceEach = $(this).data('price-each'); 3544 thisTotalPrice = thisPriceEach * thisSelectedValue; 3545 3546 // add them up 3547 selectedValue = selectedValue + thisSelectedValue; 3548 totalPrice = totalPrice + thisTotalPrice; 3549 3550 // get array of prices 3551 allPrices.push(thisPriceEach); 3552 } 3553 } 3554 }); 3555 3556 if(selectedValue > 0){
3557 // if selected value is greater than 0 then display the gratuity area for this ticket type 3558 $('.selected-amount-' + ticketTypeId).html(selectedValue); 3559 $('#gratuity-' + ticketTypeId + ' .indGratuityOption').each(function(){ 3560 var value = $(this).data('order'); 3561 var gratuityTotal = totalPrice * value / 100; 3562 $(this).find('.gratuity-option-preview').html('$' + gratuityTotal.toFixed(2)); 3563 }); 3564 3565 if(allPrices.length == 1){ 3566 // single ticket type (price) being selected 3567 var displayTotal = parseFloat(allPrices[0], 2); 3568 $('#selected-total-' + ticketTypeId).html('($' + displayTotal.toFixed(2) + ')'); 3569 } else { 3570 // multiple ticket type (price) selected 3571 var min = Math.min.apply(Math, allPrices); 3572 var max = Math.max.apply(Math,allPrices); 3573 $('#selected-total-' + ticketTypeId).html('@ ($' + min.toFixed(2) + ' - $' + max.toFixed(2) + ')'); 3574 } 3575 // show gratuity area for this ticket type 3576 $('#gratuities').fadeIn(); 3577 $('#gratuity-content').fadeIn(); 3578 $('#gratuity-' + ticketTypeId).fadeIn(); 3579 $('#Gratuity' + ticketTypeId + 'Value').removeAttr('disabled'); 3580 } else { 3581 // if selected value is 0 clear and hide gratuity area for this ticket type. 3582 $('#gratuity-' + ticketTypeId).fadeOut(); 3583 $('.selected-amount-' + ticketTypeId).html(0); 3584 $('.selected-amount-' + ticketTypeId).closest('gratuity-option-preview').html(0); 3585 $('#Gratuity' + ticketTypeId + 'Value').attr('disabled', 'disabled'); 3586 } 3587 } 3588 return true; 3589} 3590 3591function clearOutSeatedFields() { 3592 if (typeof renderSeatingChart === 'function') { 3593 $('input[name="data[Order][selected_seat_labels]"]').val(''); 3594 $('input[name="data[Order_selected_seats]"]').val(''); 3595 $('input[name="data[Order][selected_seat_parents]"]').val(''); 3596 $('input[name$="][seat_labels]"]').val(''); 3597 $('input[name$="][seat_ids]"]').val(''); 3598 $('input[name$="][object_type]"]').val(''); 3599 $('input[name$="][seat_parents]"]').val(''); 3600 $('input[name$="][seat_labels]"]').parent().find('input[name$="][quantity]"]').val('0'); 3601 } 3602} 3603 3604function toggleTicketTypeGroupVisibility(ticketTypeGroupId) { 3605 let elem = $('#ticketTypeGroup_' + ticketTypeGroupId + ' .ticketTypeGroupContent'); 3606 if (elem.is(":visible")) { 3607 elem.slideUp('fast'); 3608 $('#ticketTypeGroup_' + ticketTypeGroupId).addClass('collapsed').removeClass('expanded'); 3609 $('#ticketTypeGroup_' + ticketTypeGroupId + ' .group-status').addClass('fa-caret-right').removeClass('fa-caret-down'); 3610 } else { 3611 elem.slideDown('fast'); 3612 $('#ticketTypeGroup_' + ticketTypeGroupId).addClass('expanded').removeClass('collapsed'); 3613 $('#ticketTypeGroup_' + ticketTypeGroupId + ' .group-status').addClass('fa-caret-down').removeClass('fa-caret-right'); 3614 } 3615 3616} 3617 3618/** 3619 * Removes DOM elements for tickets that have zero quantity and haven't been modified by users. 3620 * 3621 * This function identifies unmodified zero-quantity tickets and removes all associated 3622 * form elements (inputs and selects) from the DOM to prevent them from being submitted. 3623 * 3624 * @author Brig Evans 3625 */ 3626function removeDomElementsForUnmodifiedZeroQuantityTickets() { 3627 var unmodifiedZeroQuantityTicketIndices = getZeroQuantityUnmodifiedTicketIndices(); 3628 3629 if (unmodifiedZeroQuantityTicketIndices.length > 0) { 3630 unmodifiedZeroQuantityTicketIndices.forEach(function (ticketIndexToRemove) { 3631 var ticketFormElementsSelector = 'input[name*="[Ticket][' + ticketIndexToRemove + ']"], select[name*="[Ticket][' + ticketIndexToRemove + ']"]'; 3632 $(ticketFormElementsSelector).remove(); 3633 }); 3634 } 3635} 3636 3637/** 3638 * Retrieves form data with unmodified zero-quantity ticket fields excluded. 3639 * 3640 * Serializes the specified form, identifies ticket fields that should be excluded 3641 * (zero quantity tickets that haven't been modified by users), filters them out, 3642 * and returns the remaining data as a URL-encoded parameter string suitable for submission. 3643 * 3644 * @param string formId The jQuery selector for the form to process 3645 * @return string URL-encoded form data with excluded ticket fields removed 3646 * @author Brig Evans 3647 */ 3648function getOrderFormDataWithoutUnmodifiedZeroQuantityTickets(formId) { 3649 var unmodifiedZeroQuantityTicketIndices = getZeroQuantityUnmodifiedTicketIndices(); 3650 var allSerializedFormData = $(formId).serializeArray(); 3651 3652 var formDataWithoutExcludedTickets = allSerializedFormData.filter(function (currentFormField) { 3653 var shouldExcludeCurrentField = false;
3654 3655 // Check if current field belongs to any excluded ticket index 3656 for (var currentExcludedIndex = 0; currentExcludedIndex < unmodifiedZeroQuantityTicketIndices.length; currentExcludedIndex++) { 3657 var ticketIndexPattern = '[Ticket][' + unmodifiedZeroQuantityTicketIndices[currentExcludedIndex] + ']'; 3658 3659 if (currentFormField.name.includes(ticketIndexPattern)) { 3660 shouldExcludeCurrentField = true; 3661 break; // Found match, no need to continue checking 3662 } 3663 } 3664 3665 return !shouldExcludeCurrentField; // Include field if it shouldn't be excluded 3666 }); 3667 3668 return $.param(formDataWithoutExcludedTickets); 3669} 3670 3671/** 3672 * Identifies ticket type indices that have zero quantity and haven't been modified by user interaction. 3673 * 3674 * Scans all ticket quantity select elements to find those with quantity 0 that don't have 3675 * the data-exists-in-prefill attribute, indicating they haven't been interacted with. 3676 * 3677 * @return array Array of ticket indices (strings) representing unselected ticket types 3678 * @author Brig Evans 3679 */ 3680function getZeroQuantityUnmodifiedTicketIndices() { 3681 var unmodifiedZeroQuantityTicketIndices = []; 3682 3683 $('select[name*="[Ticket]"][name*="[quantity]"]').each(function () { 3684 var currentTicketQuantitySelect = $(this); 3685 var selectedTicketQuantity = parseInt(currentTicketQuantitySelect.val()) || 0; 3686 var ticketIndexExtractionMatch = currentTicketQuantitySelect.attr('name').match(/\[Ticket\]\[(\d+)\]/); 3687 3688 if (!ticketIndexExtractionMatch) { 3689 return true; // Continue iteration - no valid ticket index found 3690 } 3691 3692 var extractedTicketIndex = ticketIndexExtractionMatch[1]; 3693 var hasBeenModifiedByUser = currentTicketQuantitySelect.attr('data-exists-in-prefill'); 3694 3695 // Include ticket index only if quantity is zero AND user hasn't modified it 3696 if (selectedTicketQuantity === 0 && !hasBeenModifiedByUser) { 3697 unmodifiedZeroQuantityTicketIndices.push(extractedTicketIndex); 3698 } 3699 }); 3700 3701 // Dropdown-view and seating ticket types store quantity in a hidden input (not a select). 3702 // Users never interact with these inputs directly â they're set programmatically via the 3703 // "Add >" button or seating chart â so value 0 always means the ticket wasn't selected. 3704 $('input[type="hidden"][name*="[Ticket]"][name*="[quantity]"]').each(function () { 3705 const hiddenInput = $(this); 3706 const hiddenQuantity = parseInt(hiddenInput.val()) || 0; 3707 const hiddenIndexMatch = hiddenInput.attr('name').match(/\[Ticket\]\[(\d+)\]/); 3708 3709 if (!hiddenIndexMatch) { 3710 return true; // Continue iteration - no valid ticket index found 3711 } 3712 3713 if (hiddenQuantity === 0) { 3714 unmodifiedZeroQuantityTicketIndices.push(hiddenIndexMatch[1]); 3715 } 3716 }); 3717 3718 return unmodifiedZeroQuantityTicketIndices; 3719} 3720 3721/** 3722 * Initializes tracking system for ticket quantity interactions to distinguish between 3723 * pre-filled quantities and user modifications. 3724 * 3725 * Sets up initial state by marking tickets with non-zero quantities as pre-filled, 3726 * then establishes event handlers to track when users interact with quantity selectors. 3727 * This tracking prevents removal of tickets that users have explicitly set to zero. 3728 * 3729 * @author Brig Evans 3730 */ 3731function initializeTicketQuantityInteractionTracking() { 3732 // Mark tickets with initial non-zero quantities as pre-filled to prevent removal 3733 $('select[name*="[Ticket]"][name*="[quantity]"]').each(function () { 3734 var currentTicketQuantitySelect = $(this); 3735 var initialTicketQuantity = parseInt(currentTicketQuantitySelect.val()) || 0; 3736 3737 // Mark as pre-filled only if initial quantity is greater than zero 3738 if (initialTicketQuantity > 0) { 3739 currentTicketQuantitySelect.attr('data-exists-in-prefill', 1); 3740 } 3741 }); 3742 3743 // Establish event handler to mark quantity selectors as modified when user changes them 3744 $(document).on('change', 'select[name*="[Ticket]"][name*="[quantity]"]', function () { 3745 var changedTicketQuantitySelect = $(this); 3746 3747 // Mark this ticket as having been interacted with to prevent future removal 3748 changedTicketQuantitySelect.attr('data-exists-in-prefill', 1); 3749 }); 3750}
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.