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