1// Global variables 2var inputMaskCurrency = { 3 radixPoint: ".", 4 groupSeparator: ",", 5 digits: 2, 6 autoGroup: true, 7 prefix: appCfg.currency_symbol, 8 rightAlign: false 9}; 10 11// On document loaded 12$(document).ready(function() { 13 // elements 14 var elements = { 15 total: $('.cart-total'), 16 cart: $('.cart-wrapper'), 17 form: $("#payment-form"), 18 intentForm: $('#Form_PaymentIntentForm'), 19 cardArea: $('.client-info-card'), 20 donationTotal: $('.donation-total'), 21 miniCart: $('.mini-cart-total'), 22 clientInfo: $('.client-info') 23 }; 24 25 // update quantity 26 updateQuantity(elements); 27 28 // update donation amount 29 updateDonationAmount(elements); 30 31 // remove cart item 32 removeCartItem(elements); 33 34 // offered product 35 initOfferedProduct(); 36 37 // customize select drop-down 38 jcf.replace('select'); 39 40 // question list 41 initQuestionList(); 42 43 // switch payment method 44 var paymentSwitcher = $('[name="payment"]'); 45 46 //--- un-check on page loads 47 paymentSwitcher.prop('checked', false); 48 49 //--- payment method changing 50 paymentSwitcher.on('change', function(){ 51 // 52 elements.clientInfo.show(); 53 54 // 55 var val = $(this).val(); 56 57 if(val == 'Stripe'){ 58 elements.cardArea.find('[data-parsley-required]').attr('data-parsley-required', true); 59 elements.cardArea.show(); 60 elements.intentForm.show(); 61 } 62 else{ 63 elements.cardArea.find('[data-parsley-required]').attr('data-parsley-required', false); 64 elements.cardArea.hide(); 65 elements.intentForm.hide(); 66 } 67 68 // rebind parsley 69 elements.form.parsley().destroy(); 70 elements.form.parsley(); 71 }); 72 73 // turn on Stripe payment 74 const pmc = document.getElementById('payment_method_card'); 75 76 if (pmc) { 77 pmc.checked = true; 78 pmc.dispatchEvent(new Event('change')); 79 } 80 81 // initialize Stripe 82 stripeMethods.init(elements); 83 84 // submit form 85 elements.form.submit(function(event) { 86 // send FB pixel data 87 sendFbPixelData.checkout(elements); 88 89 // Stripe payment 90 if( $('input[name=payment]:checked').val() == 'Stripe' && elements.form.parsley().validate() ) { 91 stripeMethods.makePayment(elements); 92 return false; 93 } 94 }); 95 96 // send FB pixel data on checkout success 97 sendFbPixelData.success(elements); 98 99 // monitor cart total and do page refresh if needed 100 const cartTotalEle = elements.total.get(0); 101 const cartTotalInit = getCartTotal(elements); 102 103 const cartTotalObserver = new MutationObserver((mutationsList) => { 104 for (const mutation of mutationsList) { 105 if (mutation.type === "characterData" || mutation.type === "childList") { 106 const cartTotalUpdated = getCartTotal(elements); 107 108 if ((cartTotalInit === 0 || cartTotalUpdated === 0) && cartTotalInit !== cartTotalUpdated) { 109 elements.cart.LoadingOverlay("show"); 110 window.location.reload(); 111 } 112 113 break 114 } 115 } 116 }); 117 118 if (cartTotalEle) { 119 cartTotalObserver.observe(cartTotalEle, { 120 characterData: true, 121 childList: true, 122 subtree: false 123 }); 124 } 125}); 126 127// check nested defined 128function isDefined(obj, key) { 129 if (typeof obj === 'undefined') return false; 130 131 return key.split(".").every(function(x) { 132 if(typeof obj != "object" || obj === null || ! x in obj) { 133 return false; 134 } 135 136 obj = obj[x]; 137 138 return true; 139 }); 140} 141 142// Update quantity 143function updateQuantity(elements){ 144 $('.item-quantity').on('change', function(){ 145 var t = $(this), 146 name = t.attr('name'), 147 remaining = parseInt( t.data('tickets') ) || 0, 148 quantity = parseInt( t.data('quantity') ) || 0, 149 total = remaining + quantity, 150 val = parseInt( t.val() ) || 1, 151 type = t.data('type') || 'event'; 152 153 var newQuantity = val; 154 155 if(val > total){ 156 t.val(total); 157 newQuantity = total; 158 } 159 else if(val < 1){ 160 t.val(1); 161 newQuantity = 1; 162 } 163 164 if(newQuantity > 0){ 165 var eid = parseInt( t.data('event') ) || 0, 166 sec = t.data('sec') || ''; 167 168 var url = appCfg.site_url+ 'cart/'+ (name == 't_quantity' ? 'settQuantity' : 'setquantity') +'/' + eid 169 170 var data = { 171 SecurityID: sec, 172 mergedCart: true 173 }; 174 175 if(name == 't_quantity'){ 176 data['Tquantity'] = newQuantity; 177 } 178 else{ 179 data['quantity'] = newQuantity; 180 } 181 182 if(type === 'product'){ 183 data['productSize'] = ( t.data('product-size') || '' ); 184 185 url = appCfg.site_url+ 'shop-checkout/setquantity/' + eid; 186 } 187 188 $.ajax({ 189 url: url, 190 method: "GET", 191 data: data, 192 beforeSend: function(){ 193 elements.cart.LoadingOverlay("show"); 194 }, 195 complete: function(){ 196 elements.cart.LoadingOverlay("hide"); 197 } 198 }) 199 .done(function(data) { 200 elements.total.text(data); 201 202 // update mini cart 203 elements.miniCart.trigger('updateQuantity'); 204 }) 205 .fail(function(jqXHR, textStatus){ 206 bootbox.alert(textStatus); 207 }); 208 } 209 }); 210} 211 212// Remove Cart Item 213function removeCartItem(elements){ 214 $(document).on('click', '.remove-item', function(){ 215 var link = this.href; 216 var message = $(this).data('message') || 'Are you sure you would like to remove this item?'; 217 var labels = { 218 yes: $(this).data('label-yes') || 'Remove', 219 no: $(this).data('label-no') || 'Don\'t remove' 220 }; 221 222 bootbox.confirm({ 223 className: 'confirm-box', 224 message: message, 225 buttons: { 226 cancel: { 227 label: labels.no, 228 className: 'btn-secondary' 229 }, 230 confirm: { 231 label: labels.yes, 232 className: 'btn-outline-secondary' 233 } 234 }, 235 callback: function(result) { 236 if(result){ 237 $.LoadingOverlay("show"); 238 window.location.href = link; 239 } 240 } 241 }); 242 243 return false;
244 }); 245} 246 247// Update donation amount 248function updateDonationAmount(elements){ 249 $(document).on('click', '.change-donation', function(){ 250 var self = $(this), 251 title = self.attr('title'), 252 amount = self.data('amount') || 0.00, 253 eid = self.data('event'), 254 itemId = self.data('item'), 255 sec = self.data('sec'), 256 amountText = self.prev('.item-donation-amount'); 257 258 var modal = bootbox.prompt({ 259 className: 'confirm-box', 260 title: title, 261 callback: function(result){ 262 if(result){ 263 var newAmount = result.length > 1 ? result.substr(appCfg.currency_symbol.length) : 0; 264 265 // update amount 266 $.ajax({ 267 url: appCfg.site_url+ 'cart/setDonationAmount/' + eid, 268 method: "GET", 269 dataType: "json", 270 data: { 271 SecurityID: sec, 272 item_id: itemId, 273 donation_amount: newAmount, 274 mergedCart: true 275 }, 276 beforeSend: function(){ 277 elements.cart.LoadingOverlay("show"); 278 }, 279 complete: function(){ 280 elements.cart.LoadingOverlay("hide"); 281 } 282 }) 283 .done(function(res) { 284 if( res.amount && res.amount_total && res.cart_total ){ 285 // update amount data 286 self.data('amount', res.amount.substr(1) ); 287 288 // update amount text 289 amountText.text(res.amount); 290 291 // update donation total 292 elements.donationTotal.text(res.amount_total); 293 294 // update cart total 295 elements.total.text(res.cart_total); 296 } 297 }) 298 .fail(function(jqXHR, textStatus){ 299 bootbox.alert(textStatus); 300 }); 301 } 302 } 303 }); 304 305 // inputMask 306 modal.on('shown.bs.modal', function (e) { 307 $(e.currentTarget).find('input').val(amount).inputmask("numeric", inputMaskCurrency); 308 }) 309 .on('hide.bs.modal', function (e) { 310 $(e.currentTarget).find('input').remove(); 311 }); 312 }); 313} 314 315// Offered product form 316function initOfferedProduct() { 317 // product options 318 var qf = $('[name="product_quantity"]'), 319 pf = $('.product-quantity'), 320 ps = $('#product-size'), 321 op = $('#offered-product'); 322 323 ps.on('change', function(){ 324 var v = parseInt( $(this).val() ) || 0, 325 o = $('option:selected', this), 326 s = o.attr('data-size') || '', 327 inStock = parseInt(o.attr('data-stock')) || 0; 328 329 qf.find('> option').each(function(){ 330 if( $(this).val() <= inStock ){ 331 $(this).show(); 332 } 333 else{ 334 $(this).hide(); 335 } 336 }); 337 338 if( inStock > 0 ){ 339 pf.show(); 340 } 341 else{ 342 pf.hide(); 343 } 344 }); 345 346 // show offered product if no product in the cart 347 if( $('.type-ShopOrderItem').length <= 0 ) { 348 op.show(); 349 } 350 351 // buy offered product 352 var btnAdd2Card = op.find('.btn-add2cart'); 353 354 btnAdd2Card.on('click', function(){ 355 var id = $(this).data('pro-id') || 0, 356 sec = $(this).data('sec-id') || ''; 357 358 window.location.href = appCfg.site_locale_url + 'shop-checkout/add/' + ps.val() + '?SecurityID=' + sec + '&quantity=' + qf.val(); 359 360 return false; 361 }); 362 363 op.find('.btn-ok-add').on('click', function(){ 364 if (op.find('.product-quantity').is(':visible')) { 365 btnAdd2Card.trigger('click'); 366 return false;
367 } 368 369 var error = $(this).data('error-variation') || 'Please select product size and/or variation'; 370 371 bootbox.alert(error); return false; 372 }); 373 374 op.find('.btn-no-add').on('click', function(){ 375 op.remove(); return false; 376 }); 377} 378 379// Get cart total 380function getCartTotal(elements) { 381 var t = elements.total.first().text().replace(/[^0-9\.]+/g,""); 382 383 return t ? parseFloat(t) : 0; 384} 385 386// Reset recaptcha 387function resetRecaptcha() { 388 if (typeof window.grecaptcha !== "undefined") { 389 window.grecaptcha.reset(); 390 } 391} 392 393// Fire FB pixel event 394var sendFbPixelData = { 395 // checkout page 396 checkout: function(elements){ 397 // vars 398 var select, items = []; 399 400 // collect item 401 $('.cart-item').each(function(){ 402 select = $(this).find('[name="quantity"]') 403 404 if(select.length){ 405 var item = $(this), 406 id = select.data('event'), 407 qty = parseInt( select.val() ), 408 price = item.find('.item-price').first().text().replace(/[^0-9\.]+/g,""), 409 donation = item.find('.item-donation-amount').text().replace(/[^0-9\.]+/g,""); 410 411 items.push({ 412 'id': id, 413 'quantity': qty, 414 'item_price': price 415 }); 416 } 417 }); 418 419 // send data 420 if(items.length && $.inArray(appCfg.fb_method, ["client", "both"]) !== -1){ 421 var fbData = { 422 contents: items, 423 content_type: 'product', 424 value: elements.total.first().text().replace(/[^0-9\.]+/g,""), 425 currency: appCfg.currency_code 426 }; 427 428 fbq('track', 'InitiateCheckout', fbData); 429 fbq('track', 'AddPaymentInfo', fbData); 430 } 431 }, 432 // success page 433 success: function(elements){ 434 // validate 435 if( !document.URL.match(/cart\/success/gi) ){ 436 return; 437 } 438 439 // vars 440 var items = []; 441 var gtm_items = []; 442 var order_id = document.querySelector('.cart-summary')?.dataset?.orderId || ''; 443 444 // collect item 445 $('.cart-item-product').each(function(){ 446 var item = $(this), 447 id = item.data('id'), 448 qty = item.find('.item-quantity-text').first().text().replace(/[^0-9\.]+/g,""), 449 price = item.find('.item-price').first().text().replace(/[^0-9\.]+/g,""), 450 title = item.find('.item-title')?.first()?.text() || '', 451 category = item.find('.item-category')?.first()?.text() || ''; 452 453 items.push({ 454 'id': id, 455 'quantity': qty, 456 'item_price': price 457 }); 458 459 gtm_items.push({ 460 id: item.data('id'), 461 name: title, 462 price: price, 463 quantity: qty, 464 category: category, 465 }); 466 }); 467 468 // send data 469 if(items.length && $.inArray(appCfg.fb_method, ["client", "both"]) !== -1){ 470 var fbData = { 471 contents: items, 472 content_type: 'product', 473 value: getCartTotal(elements), 474 currency: appCfg.currency_code 475 }; 476 477 fbq('track', 'Purchase', fbData); 478 } 479 480 // send data to GTM and RedditPixel 481 window.Tracking.trackPurchase({ 482 id: "#" + order_id, 483 value: getCartTotal(elements), 484 currency: appCfg.currency_code, 485 items: gtm_items, 486 }); 487 } 488}; 489 490// Do payment 491var stripeMethods = { 492 stripe: null, 493 clientSecret: null, 494 cardElement: null, 495 stripeElements: null, 496 canSubmit: false, 497 cardHolder: $('#stripe-card-element'), 498 errorHolder: $('#stripe-card-errors'), 499 // 500 init: function(elements) { 501 var amount = getCartTotal(elements); 502 503 if (appCfg.stripe_pk && this.cardHolder.length && amount > 0) { 504 var self = this; 505 var locale = document.documentElement.lang || 'en'; 506 locale = locale.split('-')[0]; 507 508 this.stripe = Stripe(appCfg.stripe_pk, { 509 locale: locale 510 }); 511 512 this.stripeElements = this.stripe.elements({ 513 mode: 'payment', 514 currency: window.appCfg.currency_code.toLowerCase(),
515 amount: Math.ceil(amount * 100), 516 }); 517 518 this.cardElement = this.stripeElements.create('payment'); 519 520 // mount card element 521 this.cardElement.mount('#stripe-card-element'); 522 523 // validate card 524 this.cardElement.addEventListener('change', function(event) { 525 if (event.error) { 526 self.errorHolder.html('<div class="parsley-errors-list filled" style="position: static;">'+ event.error.message +'</div>'); 527 self.canSubmit = false; 528 } 529 else { 530 self.errorHolder.html(''); 531 self.canSubmit = true; 532 } 533 }); 534 } 535 }, 536 // 537 makePayment: async function(elements) { 538 var self = this; 539 var amount = getCartTotal(elements); 540 541 if (self.stripe && self.canSubmit && self.cardHolder.length && amount > 0) { 542 // display loading indicator 543 $.LoadingOverlay("show"); 544 545 // Update Stripe Elements amount 546 this.stripeElements.update({amount: Math.ceil(amount * 100)}); 547 548 // Trigger form validation and wallet collection 549 const {error: submitError} = await self.stripeElements.submit(); 550 551 if (submitError) { 552 // show errors 553 bootbox.alert(submitError); 554 555 // hide loading overlay 556 $.LoadingOverlay("hide"); 557 558 // 559 return; 560 } 561 562 // create payment intent 563 var paymentForm = $('#payment-form'); 564 var intentForm = $('#Form_PaymentIntentForm'); 565 var ifData = intentForm.serializeArray(); 566 567 paymentForm.serializeArray().forEach((item) => { 568 if (item.name !== 'SecurityID') { 569 ifData.push(item); 570 } 571 }) 572 573 $.ajax({ 574 url: intentForm.attr("action"), 575 method: "POST", 576 data: ifData, 577 complete: function(){} 578 }) 579 .done(function(data) { 580 if (typeof data === "string") { 581 if (data.includes("message validation")) { 582 bootbox.alert($(data).find('.message.validation').text()); 583 } 584 585 $.LoadingOverlay("hide"); 586 } 587 else if (data.status == 'success') { 588 clientSecret = data.client_secret; 589 paymentIntent = data.id; 590 591 // Submit payment form and complete order 592 self.stripe.confirmPayment({ 593 elements: self.stripeElements, 594 clientSecret: clientSecret, 595 confirmParams: { 596 return_url: data.return_url, 597 }, 598 redirect: 'if_required', 599 }) 600 .then(function(result) { 601 if (result.error) { 602 resetRecaptcha(); 603 bootbox.alert(result.error.message); 604 $.LoadingOverlay("hide"); 605 } 606 else if (isDefined(result, 'paymentIntent.status') && result.paymentIntent.status == 'succeeded') { 607 paymentForm.append( 608 '<input type="hidden" name="payment_intent" value="'+ result.paymentIntent.id +'" />' + 609 '<input type="hidden" name="client_secret" value="'+ result.paymentIntent.client_secret +'" />' 610 ); 611 612 paymentForm.get(0).submit(); 613 } 614 else if (isDefined(result, 'paymentIntent.status') && result.paymentIntent.status === 'processing') { 615 window.location.href = appCfg.site_url+ 'cart/processing/'; 616 } 617 else { 618 resetRecaptcha(); 619 $.LoadingOverlay("hide"); 620 621 if (isDefined(result, 'paymentIntent.status')) { 622 bootbox.alert("Can't complete your payment. Status: " + result.paymentIntent.status); 623 } 624 else { 625 console.log(result); 626 } 627 } 628 }); 629 630 /* 631 self.stripe.handleCardPayment(clientSecret, self.cardElement).then(function (result) { 632 if (result.error) { 633 resetRecaptcha(); 634 bootbox.alert(result.error.message); 635 $.LoadingOverlay("hide"); 636 } 637 else if (isDefined(result.paymentIntent, 'status') && result.paymentIntent.status == 'succeeded') { 638 var form = $('#payment-form'); 639 640 form.append( 641 '<input type="hidden" name="payment_intent" value="'+ paymentIntent +'" />' + 642 '<input type="hidden" name="client_secret" value="'+ clientSecret +'" />'
643 ); 644 645 form.get(0).submit(); 646 } 647 }); 648 */ 649 } 650 else { 651 resetRecaptcha(); 652 bootbox.alert(data.message); 653 $.LoadingOverlay("hide"); 654 } 655 }) 656 .fail(function(jqXHR, textStatus){ 657 resetRecaptcha(); 658 bootbox.alert(textStatus); 659 $.LoadingOverlay("hide"); 660 }); 661 } 662 else { 663 bootbox.alert('Please complete credit card details.'); 664 } 665 666 /* 667 data = { 668 number: $('.card-number').val(), 669 cvc: $('.card-cvc').val(), 670 exp_month: $('.card-exp_month').val(), 671 exp_year: $('.card-exp_year').val(), 672 name: $('.card-name').val(), 673 }; 674 675 Stripe.card.createToken(data, stripeMethods.responseHandler); 676 */ 677 }, 678 // 679 responseHandler: function(status, response) { 680 var form = $('#payment-form'); 681 682 if (response.error) { 683 stripeMethods.triggerError(response.error.param, response.error.code, response.error.message); 684 } 685 else { 686 token = response.id; 687 form.append($('<input type="hidden" name="token" />').val(token)); 688 form.get(0).submit(); 689 } 690 }, 691 // 692 triggerError: function(param, code, message) { 693 if( $('.card-'+param).length !== 0 ) { 694 parsleyInstance = $('.card-'+param).parsley(); 695 window.ParsleyUI.removeError(parsleyInstance, code); 696 window.ParsleyUI.addError(parsleyInstance, code, message); 697 } 698 else { 699 bootbox.dialog({ 700 title: 'Error!!!', 701 message: '<div class="alert alert-danger mb-0" role="alert">'+ response.error.message +'</div>' 702 }); 703 } 704 } 705}; 706 707/** 708 * Question list 709 */ 710function initQuestionList() 711{ 712 var qdrClass = '.question-display-rules'; 713 var questions = {}; 714 var form; 715 716 document.querySelectorAll(qdrClass).forEach(function(ele) { 717 if (ele.value) { 718 var qid = ele.dataset.qid; 719 var rules = JSON.parse(ele.value); 720 721 if (rules) { 722 var holder = document.getElementById('question-container-'+ qid +'_Holder'); 723 var input = holder.querySelector('[name="Questions_'+ qid +'"]'); 724 var conditions = {}; 725 726 if (rules.Rules && rules.Rules.length) { 727 rules.Rules.forEach(function(_rule) { 728 conditions[_rule.ConditionQuestion] = { 729 'option': _rule.ConditionOption, 730 'value': _rule.ConditionValue, 731 } 732 }); 733 } 734 735 // store question data 736 questions[qid] = { 737 'holder': holder, 738 'input': input, 739 'conditions': conditions, 740 'DisplayOnLoad': rules.DisplayOnLoad, 741 'DisplayRulesConjunction': rules.DisplayRulesConjunction, 742 }; 743 744 // hide the question holder if not to be displayed on load 745 if (rules.DisplayOnLoad !== 1) { 746 holder.classList.add('d-none'); 747 toggleIgnoreValidation(holder, 'on'); 748 } 749 750 // get the form 751 if (!form) { 752 form = holder.closest('form'); 753 } 754 } 755 } 756 }); 757 758 if (form) { 759 toggleQuestions(questions); 760 761 form.addEventListener("change", function(e) { 762 var regex = /^Question/; 763 764 if (e.target.name.match(regex)) { 765 toggleQuestions(questions); 766 } 767 }); 768 } 769 770 // method to toggle the visibility of questions 771 function toggleQuestions(questions) { 772 if (Object.keys(questions).length > 0) { 773 for (var qid in questions) { 774 var question = questions[qid]; 775 var conditions_size = Object.keys(question.conditions).length; 776 777 if (question.conditions && conditions_size > 0) { 778 var displays = []; 779 780 for (var cid in question.conditions) { 781 var condition = question.conditions[cid]; 782 var conditionCurrVal = questions[cid] && questions[cid].input ? questions[cid].input.value : ""; 783 784 if (questions[cid] && questions[cid].input && questions[cid].input.type === "checkbox" && !questions[cid].input.checked) { 785 conditionCurrVal = ""; 786 } 787 788 var conditionNumbers = { 789 left: parseFloat(conditionCurrVal), 790 right: parseFloat(condition.value) 791 }; 792 793 if (condition.option === "IsBlank" && conditionCurrVal === "") { 794 displays.push(1); 795 } 796 else if (condition.option === "IsNotBlank" && conditionCurrVal !== "") { 797 displays.push(1); 798 } 799 else if (condition.option === "HasValue" && conditionCurrVal === condition.value) { 800 displays.push(1); 801 } 802 else if (condition.option === "ValueNot" && conditionCurrVal !== condition.value) { 803 displays.push(1); 804 } 805 else if (condition.option === "ValueLessThan" && conditionNumbers.left < conditionNumbers.right) { 806 displays.push(1); 807 } 808 else if (condition.option === "ValueLessThanEqual" && conditionNumbers.left <= conditionNumbers.right) { 809 displays.push(1); 810 } 811 else if (condition.option === "ValueGreaterThan" && conditionNumbers.left > conditionNumbers.right) { 812 displays.push(1); 813 } 814 else if (condition.option === "ValueGreaterThanEqual" && conditionNumbers.left >= conditionNumbers.right) { 815 displays.push(1); 816 } 817 } 818 819 if (question.DisplayRulesConjunction === "Or") { 820 if (displays.length) { 821 question.holder.classList.remove('d-none'); 822 toggleIgnoreValidation(question.holder, 'off'); 823 } 824 else { 825 question.holder.classList.add('d-none'); 826 toggleIgnoreValidation(question.holder, 'on'); 827 } 828 } 829 else if (question.DisplayRulesConjunction === "And") { 830 if (displays.length === conditions_size) { 831 question.holder.classList.remove('d-none'); 832 toggleIgnoreValidation(question.holder, 'off'); 833 } 834 else { 835 question.holder.classList.add('d-none'); 836 toggleIgnoreValidation(question.holder, 'on'); 837 } 838 } 839 } 840 } 841 } 842 } 843 844 // method to toogle .ignore-validation class on input 845 function toggleIgnoreValidation(holder, action) { 846 holder.querySelectorAll('input').forEach(function(ele){ 847 if (action === 'on') { 848 ele.classList.add('ignore-validation'); 849 } 850 else { 851 ele.classList.remove('ignore-validation'); 852 } 853 }); 854 } 855}
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.