1/* eslint-disable @wordpress/no-unused-vars-before-return */ 2( function ( $ ) { 3 ( 'use strict' ); 4 5 const makeTemplatesVisible = () => { 6 const templates = document.querySelectorAll( '.revx-template' ); 7 templates.forEach( ( template ) => { 8 template.style.visibility = 'visible'; 9 template.style.opacity = '1'; 10 } ); 11 }; 12 13 // Run on first load 14 makeTemplatesVisible(); 15 16 // add events that does partial refresh of the DOM instead of full reload, to make the templates visible again 17 // Listen for cart / checkout partial refreshes 18 $( document.body ).on( 19 'updated_checkout updated_cart_totals woocommerce_update_order_review', 20 makeTemplatesVisible 21 ); 22 // Listen for mini-cart updates 23 $( document.body ).on( 24 'wc_fragments_loaded wc_fragments_refreshed', 25 makeTemplatesVisible 26 ); 27 28 // ----------------- 29 // Plugins 30 $.fn.revxSlider = function () { 31 return this.each( function () { 32 const $container = $( this ); 33 const $sliderIcons = $container.find( '.revx-slider-icons' ); 34 const $items = $container.find( '.revx-grid-item' ); 35 const itemCount = $items.length; 36 37 let currentPosition = 0; 38 39 function getBreakpoint( containerWidth ) { 40 if ( containerWidth <= 300 ) { 41 return 'sm'; 42 } 43 if ( containerWidth <= 500 ) { 44 return 'md'; 45 } 46 return 'lg'; 47 } 48 49 function getVisibleItems() { 50 const containerWidth = $container.width(); 51 const layout = $container.data( 'layout' ) || 'inpage'; 52 const sliderColumns = $container.data( 'slider-columns' ) || {}; 53 const breakpoint = getBreakpoint( containerWidth ); 54 55 if ( 56 sliderColumns[ layout ] && 57 sliderColumns[ layout ][ breakpoint ] 58 ) { 59 return parseInt( 60 sliderColumns[ layout ][ breakpoint ], 61 10 62 ); 63 } 64 65 return 1; 66 } 67 68 function getMaxStartIndex() { 69 return itemCount - getVisibleItems(); 70 } 71 72 function updateSliderPosition() { 73 $container.css( 74 '--revx-slider-translate', 75 `-${ currentPosition * 100 }%` 76 ); 77 } 78 79 function handleNext() { 80 const maxStartIndex = getMaxStartIndex(); 81 currentPosition = 82 currentPosition >= maxStartIndex ? 0 : currentPosition + 1; 83 updateSliderPosition(); 84 } 85 86 function handlePrev() { 87 const maxStartIndex = getMaxStartIndex(); 88 currentPosition = 89 currentPosition <= 0 ? maxStartIndex : currentPosition - 1; 90 updateSliderPosition(); 91 } 92 93 function updateSliderVisibility() { 94 const visibleItems = getVisibleItems(); 95 const isSliderNeeded = itemCount > visibleItems; 96 97 $container.toggleClass( 'has-slider', isSliderNeeded ); 98 99 return isSliderNeeded; 100 } 101 102 function initializeSlider() { 103 const visibleItems = getVisibleItems(); 104 const maxStartIndex = itemCount - visibleItems; 105 106 if ( currentPosition > maxStartIndex ) { 107 currentPosition = Math.max( 0, maxStartIndex ); 108 updateSliderPosition(); 109 } 110 111 updateSliderVisibility(); 112 } 113 114 // Attach events 115 $sliderIcons 116 .find( '.next' ) 117 .off( 'click' ) 118 .on( 'click', handleNext ); 119 $sliderIcons 120 .find( '.prev' ) 121 .off( 'click' ) 122 .on( 'click', handlePrev ); 123 124 // Resize event 125 $( window ).on( 'resize', initializeSlider ); 126 127 // Resize observer for wrapper 128 if ( typeof ResizeObserver !== 'undefined' ) { 129 const resizeObserver = new ResizeObserver( initializeSlider ); 130 const wrapper = $container 131 .find( '.revx-items-wrapper' ) 132 .get( 0 ); 133 if ( wrapper ) { 134 resizeObserver.observe( wrapper ); 135 } 136 } 137 138 // Initial setup 139 initializeSlider(); 140 } ); 141 }; 142 143 $.fn.revxCountdownTimer = function ( options ) { 144 const settings = $.extend( 145 { 146 endTimeAttr: 'data-end-time', 147 startTimeAttr: 'data-start-time', 148 formatTimeUnit( unit ) { 149 return unit < 10 ? '0' + unit : unit; 150 }, 151 onEnd( $el ) { 152 $el.html( '<span>00</span><span>:</span><span>00</span>' ); 153 }, 154 onBeforeStart( $el ) { 155 $el.html( '<span>00</span><span>:</span><span>00</span>' ); // You can change to "Coming Soon..." etc 156 }, 157 }, 158 options 159 ); 160 161 function initTimer( $timer ) { 162 if ( $timer.data( 'revxCountdownInitialized' ) ) { 163 return; 164 } 165 $timer.data( 'revxCountdownInitialized', true ); 166 167 const endTime = parseInt( $timer.attr( settings.endTimeAttr ), 10 ); 168 const startTime = 169 parseInt( $timer.attr( settings.startTimeAttr ), 10 ) || null; 170 171 if ( isNaN( endTime ) ) { 172 settings.onEnd( $timer ); 173 return; 174 } 175
176 function update() { 177 const now = Date.now(); 178 179 if ( startTime && now < startTime ) { 180 settings.onBeforeStart( $timer ); 181 return; 182 } 183 184 const diff = endTime - now; 185 186 if ( diff <= 0 ) { 187 clearInterval( interval ); 188 settings.onEnd( $timer ); 189 return; 190 } 191 192 const days = Math.floor( diff / ( 1000 * 60 * 60 * 24 ) ); 193 const hours = Math.floor( ( diff / ( 1000 * 60 * 60 ) ) % 24 ); 194 const minutes = Math.floor( ( diff / ( 1000 * 60 ) ) % 60 ); 195 const seconds = Math.floor( ( diff / 1000 ) % 60 ); 196 197 let html = ''; 198 199 if ( days > 0 ) { 200 html += 201 '<span>' + 202 settings.formatTimeUnit( days ) + 203 '</span><span>:</span>'; 204 } 205 if ( days > 0 || hours > 0 ) { 206 html += 207 '<span>' + 208 settings.formatTimeUnit( hours ) + 209 '</span><span>:</span>'; 210 } 211 html += 212 '<span>' + 213 settings.formatTimeUnit( minutes ) + 214 '</span><span>:</span>'; 215 html += 216 '<span>' + settings.formatTimeUnit( seconds ) + '</span>'; 217 218 $timer.html( html ); 219 } 220 221 update(); 222 var interval = setInterval( update, 1000 ); 223 $timer.data( 'revxCountdownInterval', interval ); 224 } 225 226 // Initialize on each element 227 this.each( function () { 228 const $this = $( this ); 229 initTimer( $this ); 230 } ); 231 232 return this; 233 }; 234 235 function calculateCurrentPrice( offerData, productId, quantity ) { 236 const product = offerData[ productId ]; 237 238 if ( ! product ) { 239 throw new Error( 'Product not found' ); 240 } 241 242 const regularPrice = parseFloat( product.regular_price ); 243 let currentPrice = regularPrice; 244 245 if ( product.offer && Array.isArray( product.offer ) ) { 246 for ( let i = 0; i < product.offer.length; i++ ) { 247 const offer = product.offer[ i ]; 248 const minQty = parseInt( offer.qty, 10 ); 249 250 if ( offer.type == 'free' ) { 251 if ( quantity >= minQty ) { 252 switch ( offer.type ) { 253 case 'free': 254 currentPrice = 0; 255 256 break; 257 258 default: 259 break; 260 } 261 // Add more offer types if needed (e.g., fixed discount) 262 } 263 } else if ( quantity >= minQty ) { 264 switch ( offer.type ) { 265 case 'percentage': 266 currentPrice = 267 regularPrice - 268 ( parseFloat( offer.value ) / 100 ) * 269 regularPrice; 270 break; 271 case 'amount': 272 case 'fixed_discount': 273 currentPrice = 274 regularPrice - parseFloat( offer.value ); 275 276 break; 277 case 'fixed_price': 278 currentPrice = parseFloat( offer.value ); 279 280 break; 281 case 'no_discount': 282 currentPrice = regularPrice; 283 284 break; 285 case 'free': 286 currentPrice = 0; 287 288 break; 289 290 default: 291 break; 292 } 293 // Add more offer types if needed (e.g., fixed discount) 294 } 295 } 296 } 297 298 return parseFloat( currentPrice * quantity ); 299 } 300 301 const formatPrice = ( price ) => { 302 const currencyFormat = revenue_campaign?.currency_format; 303 const currencySymbol = revenue_campaign?.currency_format_symbol; 304 const decimalSeparator = revenue_campaign?.currency_format_decimal_sep; 305 const thousandSeparator = 306 revenue_campaign?.currency_format_thousand_sep; 307 const numDecimals = revenue_campaign?.currency_format_num_decimals; 308 309 const fixedPrice = parseFloat( price ).toFixed( numDecimals || 2 ); 310 311 const parts = fixedPrice.split( '.' ); 312 let integerPart = parts[ 0 ]; 313 let decimalSepAndNum = ''; 314 if ( numDecimals > 0 ) { 315 decimalSepAndNum = decimalSeparator + ( parts[ 1 ] || '00' ); 316 } 317 318 integerPart = integerPart.replace( 319 /\B(?=(\d{3})+(?!\d))/g, 320 thousandSeparator 321 ); 322 323 const formattedPrice = integerPart + decimalSepAndNum; 324 325 return currencyFormat 326 .replace( '%1$s', currencySymbol ) 327 .replace( '%2$s', formattedPrice ); 328 }; 329 330 // Volume Discount 331 $( '.revx-volume-discount .revx-campaign-item' ).on( 332 'click', 333 function ( e ) { 334 // Remove selected style from all items 335 e.stopPropagation(); 336 const that = $( this ); 337 $( '.revx-volume-discount .revx-campaign-item' ).each( function () { 338 const item = $( this ).find( '.revx-volume-discount__tag' ); 339 const defaultStyle = item.data( 'default-style' ); 340 $( item ).attr( 'style', defaultStyle ); 341 $( this ).attr( 'data-revx-selected', false ); 342 } ); 343 const clickedItem = that.find( '.revx-volume-discount__tag' ); 344 345 // Apply selected style to the clicked item 346 const selectedStyle = clickedItem.data( 'selected-style' ); 347 clickedItem.attr( 'style', selectedStyle ); 348 that.attr( 'data-revx-selected', true ); 349 350 $( '.revx-ticket-type' ).trigger( 'change' ); 351 } 352 ); 353 354 $( 'select.revx-productAttr-wrapper__field' ).on( 'change', function () { 355 const attributeData = 356 $( '.variations_form' ).data( 'product_variations' ); 357 358 const attributeName = $( this ).data( 'attribute_name' ); 359 360 const parentWrapper = $( this ).closest( '.revx-productAttr-wrapper' ); 361 const fieldsInParent = parentWrapper.find( 362 '.revx-productAttr-wrapper__field' 363 ); 364 365 let allFieldsHaveValue = true; 366 const values = {}; 367 fieldsInParent.each( function () { 368 if ( $( this ).val() === '' ) { 369 allFieldsHaveValue = false;
370 return false; // Break the loop 371 } 372 values[ $( this ).data( 'attribute_name' ) ] = $( this ).val(); 373 } ); 374 375 let selectedVariation = false; 376 377 if ( allFieldsHaveValue ) { 378 attributeData.forEach( ( element ) => { 379 if ( 380 JSON.stringify( element.attributes ) == 381 JSON.stringify( values ) 382 ) { 383 selectedVariation = element; 384 } 385 } ); 386 } 387 388 $( '.revx-campaign-item' ).removeAttr( 'data-product-id' ); 389 if ( selectedVariation ) { 390 const parent = $( this ).closest( '.revx-campaign-item' ); 391 const campaignID = parent.data( 'campaignId' ); 392 const quantity = parent.data( 'quantity' ); 393 394 const offerData = JSON.parse( 395 $( `input[name="revx-offer-data-${ campaignID }"]` ).val() 396 ); 397 398 const variation_id = selectedVariation.variation_id; 399 400 const offer = offerData[ variation_id ]; 401 402 let nearestOffer = offer.offer[ 0 ]; 403 for ( let i = 1; i < offer.offer.length; i++ ) { 404 if ( offer.offer[ i ].qty <= quantity ) { 405 nearestOffer = offer.offer[ i ]; 406 } else { 407 break; 408 } 409 } 410 411 let regular_price = offer.regular_price; 412 let sale_price = ''; 413 414 switch ( nearestOffer.type ) { 415 case 'percentage': 416 sale_price = 417 parseFloat( offer.regular_price * nearestOffer.qty ) * 418 ( 1 - nearestOffer.value / 100 ); 419 420 break; 421 case 'amount': 422 case 'fixed_discount': 423 sale_price = 424 Math.max( 425 0, 426 parseFloat( offer.regular_price ) - 427 parseFloat( nearestOffer.value ) 428 ) * nearestOffer.qty; 429 430 break; 431 case 'fixed_price': 432 sale_price = 433 parseFloat( nearestOffer.value ) * nearestOffer.qty; 434 regular_price = false; 435 436 break; 437 case 'no_discount': 438 sale_price = 439 parseFloat( offer.regular_price ) * nearestOffer.qty; 440 regular_price = false; 441 442 break; 443 case 'free': 444 sale_price = 0; 445 446 break; 447 448 default: 449 break; 450 } 451 452 parent.attr( 'data-product-id', variation_id ); 453 454 parent 455 .parent() 456 .parent() 457 .find( '.revx-campaign-add-to-cart-btn' ) 458 .attr( 'data-product-id', variation_id ); 459 460 parent.find( 'input[data-name=revx_quantity]' ).trigger( 'change' ); 461 462 parent 463 .find( '.revx-campaign-item__regular-price' ) 464 .html( formatPrice( regular_price * quantity ) ); 465 parent 466 .find( '.revx-campaign-item__sale-price' ) 467 .html( formatPrice( sale_price ) ); 468 } else { 469 const parent = $( this ).closest( '.revx-campaign-item' ); 470 parent.find( '.revx-campaign-item__regular-price' ).html( '' ); 471 parent.find( '.revx-campaign-item__sale-price' ).html( '' ); 472 } 473 } ); 474 475 function updatePriceDisplay( parent, quantity, salePrice, regularPrice ) { 476 salePrice = parseFloat( salePrice ); 477 regularPrice = parseFloat( regularPrice ); 478 const salePriceElement = $( parent ).find( 479 '.revx-campaign-item__sale-price' 480 ); 481 const regularPriceElement = $( parent ).find( 482 '.revx-campaign-item__regular-price' 483 ); 484 const savingsTag = $( parent ).find( '.revx-builder-savings-tag' ); 485 486 if ( quantity == 0 ) { 487 salePriceElement.html( formatPrice( 0 ) ); 488 savingsTag.hide(); 489 return; 490 } 491 492 if ( salePrice !== regularPrice ) { 493 salePriceElement.html( 494 quantity > 1 495 ? `${ quantity } x ` + formatPrice( salePrice ) 496 : formatPrice( salePrice ) 497 ); 498 regularPriceElement.html( 499 quantity > 1 500 ? `${ quantity } x ` + formatPrice( regularPrice ) 501 : formatPrice( regularPrice ) 502 ); 503 savingsTag.show(); 504 } else { 505 salePriceElement.html( 506 quantity > 1 507 ? `${ quantity } x ` + formatPrice( regularPrice ) 508 : formatPrice( regularPrice ) 509 ); 510 regularPriceElement.empty(); 511 savingsTag.hide(); 512 } 513 } 514 515 $( 516 '.revx-volume-discount .revx-builder__quantity input[data-name=revx_quantity]' 517 ).on( 'change', function () { 518 const parent = $( this ).closest( '.revx-campaign-item' ); 519 const product_id = $( this ) 520 .closest( '.revx-campaign-item' ) 521 .data( 'product-id' ); 522 523 if ( ! product_id ) { 524 return; 525 } 526 527 const quantity = $( this ).val(); 528 529 parent 530 .parent() 531 .parent() 532 .find( '.revx-campaign-add-to-cart-btn' ) 533 .attr( 'data-quantity', quantity ); 534 535 const campaignId = $( this ).data( 'campaign-id' ); // Get data-campaign-id attribute value 536 537 let offerData = $( `input[name="revx-offer-data-${ campaignId }"]` ); 538 offerData = offerData[ 0 ].value; 539 const jsonData = JSON.parse( offerData ); 540 541 const regularPrice = ( 542 jsonData[ product_id ].regular_price * quantity 543 ).toFixed( 2 ); 544 const salePrice = calculateCurrentPrice( 545 jsonData, 546 product_id, 547 quantity 548 ).toFixed( 2 ); 549 550 if ( salePrice != regularPrice ) { 551 $( parent ) 552 .find( '.revx-campaign-item__sale-price' ) 553 .html( formatPrice( salePrice ) ); 554 $( parent ) 555 .find( '.revx-campaign-item__regular-price' ) 556 .html( formatPrice( regularPrice ) ); 557 } else { 558 $( parent ) 559 .find( '.revx-campaign-item__regular-price' ) 560 .html( formatPrice( salePrice ) ); 561 } 562 } ); 563 564 // Bundle Discount 565 $( 566 '.revx-bundle-discount .revx-builder__quantity input[data-name=revx_quantity]' 567 ).on( 'change', function () { 568 const parent = $( this ).closest( '.revx-campaign-container__wrapper' ); 569 const bundle_products = $( this ) 570 .closest( '.revx-campaign-container__wrapper' ) 571 .data( 'bundle_products' ); 572 573 if ( bundle_products.length == 0 ) { 574 return; 575 } 576 577 const quantity = $( this ).val(); 578 579 parent 580 .find( '.revenue-campaign-add-bundle-to-cart' ) 581 .attr( 'data-quantity', quantity ); 582 583 const campaignId = $( this ).data( 'campaign-id' ); // Get data-campaign-id attribute value 584 585 let offerData = $( `input[name="revx-offer-data-${ campaignId }"]` ); 586 offerData = offerData[ 0 ].value; 587 const jsonData = JSON.parse( offerData ); 588 589 let totalRegularPrice = 0; 590 let totalSalePrice = 0; 591
592 bundle_products.forEach( ( product ) => { 593 const productId = product.item_id; 594 // let quantity = product.quantity; 595 596 if ( jsonData[ productId ] ) { 597 totalRegularPrice += parseFloat( 598 ( 599 jsonData[ productId ].regular_price * 600 quantity * 601 product.quantity 602 ).toFixed( 2 ) 603 ); 604 totalSalePrice += parseFloat( 605 calculateCurrentPrice( 606 jsonData, 607 productId, 608 product.quantity * quantity 609 ).toFixed( 2 ) 610 ); 611 } 612 } ); 613 614 if ( totalRegularPrice != totalSalePrice ) { 615 $( parent ) 616 .find( 617 '.revx-total-price__offer-price .revx-campaign-item__sale-price' 618 ) 619 .html( formatPrice( totalSalePrice ) ); 620 $( parent ) 621 .find( 622 '.revx-total-price__offer-price .revx-campaign-item__regular-price' 623 ) 624 .html( formatPrice( totalRegularPrice ) ); 625 } else { 626 $( parent ) 627 .find( 628 '.revx-total-price__offer-price .revx-campaign-item__regular-price' 629 ) 630 .html( formatPrice( totalRegularPrice ) ); 631 } 632 } ); 633 634 // Normal Discount 635 $( 636 '.revx-normal-discount .revx-builder__quantity input[data-name=revx_quantity]' 637 ).on( 'change', function () { 638 const parent = $( this ).closest( '.revx-campaign-item' ); 639 const product_id = $( this ) 640 .closest( '.revx-campaign-item' ) 641 .data( 'product-id' ); 642 643 if ( ! product_id ) { 644 return; 645 } 646 647 const quantity = $( this ).val(); 648 649 parent 650 .find( '.revx-campaign-add-to-cart-btn' ) 651 .attr( 'data-quantity', quantity ); 652 const campaignId = $( this ).data( 'campaign-id' ); // Get data-campaign-id attribute value 653 654 let offerData = $( `input[name="revx-offer-data-${ campaignId }"]` ); 655 offerData = offerData[ 0 ].value; 656 const jsonData = JSON.parse( offerData ); 657 658 if ( ! jsonData[ product_id ] ) { 659 return; 660 } 661 662 const inRP = jsonData[ product_id ].regular_price; 663 664 const regularPrice = ( 665 jsonData[ product_id ].regular_price * quantity 666 ).toFixed( 2 ); 667 668 const salePrice = calculateCurrentPrice( 669 jsonData, 670 product_id, 671 quantity 672 ).toFixed( 2 ); 673 674 const inSP = ( salePrice / quantity ).toFixed( 2 ); 675 676 updatePriceDisplay( parent, quantity, inSP, inRP ); 677 678 // if (salePrice != regularPrice) { 679 // $(parent).find('.revx-campaign-item__sale-price').html(`${qty} x `+formatPrice(inSP)); 680 // $(parent).find('.revx-campaign-item__regular-price').html(`${qty} x `+formatPrice(inRP)); 681 // } else { 682 // $(parent).find('.revx-campaign-item__sale-price').html(`${qty} x `+formatPrice(inRP)); 683 // } 684 } ); 685 686 // Delegate click event for the 'minus' button 687 // $( document ).on( 'click', '.revx-quantity-minus', function ( e ) { 688 // e.preventDefault(); 689 // e.stopPropagation(); // Stop event propagation to parent elements 690 // const $input = $( this ).siblings( 'input[type="number"]' ); 691 692 // if ( $( this ).data( 'skip-global' ) ) { 693 // return; 694 // } 695 696 // if ( ! $input ) { 697 // return; 698 // } 699 // $input.focus(); // Focus on the input field after updating its value 700 701 // const currentValue = parseInt( $input.val(), 10 ); 702 // const min = $input.attr( 'min' ); 703 704 // if ( min && currentValue - 1 >= min ) { 705 // if ( ! isNaN( currentValue ) && currentValue > 0 ) { 706 // $input.val( currentValue - 1 ); 707 // } 708 // } 709 // // else if ( ! isNaN( currentValue ) && currentValue - 1 > 0 ) { 710 // // $input.val( currentValue - 1 ); 711 // // } 712 713 // $input.trigger( 'change' ); 714 // } ); 715 716 // // Delegate click event for the 'plus' button 717 // $( document ).on( 'click', '.revx-quantity-plus', function ( e ) { 718 // e.preventDefault(); 719 // e.stopPropagation(); // Stop event propagation to parent elements 720 721 // // Skip if it has data-skip-global attribute 722 // if ( $( this ).data( 'skip-global' ) ) { 723 // return; 724 // } 725 726 // const $input = $( this ).siblings( 'input[type="number"]' ); 727 // if ( ! $input.length ) { 728 // return; 729 // } 730 // $input.focus(); // Focus on the input field after updating its value 731 732 // const currentValue = parseInt( $input.val(), 10 ); 733 // const maxValue = parseInt( $input.attr( 'max' ), 10 ); // Get the max attribute value 734 735 // if ( ! isNaN( currentValue ) ) { 736 // // Check if current value is less than the max value 737 // if ( ! isNaN( maxValue ) && currentValue < maxValue ) { 738 // $input.val( currentValue + 1 ); 739 // } else if ( isNaN( maxValue ) ) { 740 // $input.val( currentValue + 1 ); // No max set, just increment 741 // } 742 // } else { 743 // $input.val( 1 ); // Set default value if current value is not a number 744 // } 745 746 // $input.trigger( 'change' ); 747 // } ); 748 749 // Delegate input event for the quantity input field 750 $( document ).on( 751 'input', 752 'input[data-name=revx_quantity]', 753 function ( e ) { 754 const minVal = parseInt( $( this ).attr( 'min' ) ) || 0; // Default to 0 if min is not set 755 const maxVal = parseInt( $( this ).attr( 'max' ) ); // Parse max value from the attribute 756 const val = parseInt( $( this ).val() ); // Get the current value of the input 757 758 // If the current value is less than min, set it to min 759 if ( val < minVal ) { 760 $( this ).val( minVal ); 761 } 762 763 // If the current value is greater than max, set it to max 764 if ( ! isNaN( maxVal ) && val > maxVal ) { 765 $( this ).val( maxVal ); 766 } 767 // Trigger change event after updating the value 768 $( this ).trigger( 'change' ); 769 } 770 ); 771 772 function getCookie( cname ) { 773 const name = 'revx_' + cname + '='; 774 const decodedCookie = decodeURIComponent( document.cookie ); 775 const ca = decodedCookie.split( ';' ); 776 for ( let i = 0; i < ca.length; i++ ) { 777 let c = ca[ i ]; 778 while ( c.charAt( 0 ) == ' ' ) { 779 c = c.substring( 1 ); 780 } 781 if ( c.indexOf( name ) == 0 ) { 782 return c.substring( name.length, c.length ); 783 } 784 } 785 return ''; 786 } 787 788 // Function to set the cookie 789 function setCookie( name, value, days ) { 790 const date = new Date(); 791 date.setTime( date.getTime() + days * 24 * 60 * 60 * 1000 ); 792 const expires = 'expires=' + date.toUTCString(); 793 document.cookie = 794 'revx_' + 795 name + 796 '=' + 797 encodeURIComponent( value ) + 798 ';' + 799 expires + 800 ';path=/'; 801 } 802 803 // Mix Match campaigns single product add to cart button. 804 $( '.revx-campaign-product-card .revx-mix-match-product-btn' ).on( 805 'click', 806 function ( e ) { 807 e.preventDefault(); 808 809 const campaignId = $( this ).data( 'campaign-id' ); 810 const item = $( this ).closest( '.revx-mix_match-add-to-cart' ); 811 let productId = item.data( 'product-id' ); 812 let productType = item.attr( 'product_type' ); 813 814 const quantity = 815 item.find( `input[data-name="revx_quantity"]` ).val() ?? 1; 816 817 const offerData = $( 818 `input[name="revx-offer-data-${ campaignId }"]` 819 ).val(); 820 const container = $( this ).closest( 821 '.revx-campaign-product-card' 822 ); 823 const jsonData = JSON.parse( offerData ); 824
825 const qtyData = $( 826 `input[name="revx-qty-data-${ campaignId }"]` 827 ).val(); 828 const jsonQtyData = JSON.parse( qtyData ); 829 let parentId = null; 830 let variationProductDetails = null; 831 let variationAttributes = null; 832 let selectedData = null; 833 834 // Addon: split single-attribute variation card. The card already carries the fixed 835 // variation context, so add it directly without resolving a dropdown selection. 836 const splitRaw = item.attr( 'data-split-variation' ); 837 const isSplitVariation = !! splitRaw; 838 if ( isSplitVariation ) { 839 const split = JSON.parse( splitRaw ); 840 productId = split.variation_id; 841 parentId = split.parent_id; 842 selectedData = split.attributes || {}; 843 variationAttributes = Object.values( selectedData ).join( ' - ' ); 844 variationProductDetails = { 845 regular_price: split.regular_price, 846 sale_price: split.sale_price, 847 image_url: split.thumbnail, 848 }; 849 // Downstream selected-item clone + add-to-cart expect the 'variable' branch. 850 productType = 'variable'; 851 // Ensure the parent name prefix resolves even though offer data omits variations. 852 if ( ! jsonData[ parentId ] ) { 853 jsonData[ parentId ] = { item_name: split.item_name }; 854 } 855 jsonData[ productId ] = { 856 item_name: split.item_name, 857 regular_price: split.regular_price, 858 sale_price: split.sale_price, 859 thumbnail: split.thumbnail, 860 }; 861 } 862 863 if ( productType === 'variable' && ! isSplitVariation ) { 864 const variationMap = item 865 .find( '[data-variation-map]' ) 866 .data( 'variation-map' ); 867 selectedData = getSelectedAttributes( item ); 868 const odata = container.attr( 'data-variations' ); 869 parentId = container.data( 'product-id' ); 870 const a = JSON.parse( odata ); 871 872 if ( ! selectedData ) { 873 // throw new Error( 'Selected data is undefined or null.' ); 874 showToast( 875 revenue_campaign?.select_all_attributes || 876 'Please select all product attributes before adding to cart.', 877 'error', 878 3000 879 ); 880 return; 881 } 882 883 // Check for empty or undefined attribute values 884 for ( const [ , value ] of Object.entries( selectedData ) ) { 885 if ( 886 value === '' || 887 value === null || 888 value === undefined 889 ) { 890 showToast( 891 revenue_campaign?.select_all_attributes || 892 'Please select all required attributes', 893 'error', 894 3000 895 ); 896 return; 897 } 898 } 899 900 // now i need to match product id from variation map with selected data 901 let matchedVariationId = null; 902 903 const matchedVariation = variationMap.find( ( variation ) => { 904 if ( ! variation.attributes ) { 905 return false; 906 } 907 return Object.entries( selectedData ).every( 908 ( [ key, val ] ) => 909 ! val || 910 ! variation.attributes[ key ] || 911 variation.attributes[ key ] === val 912 ); 913 } ); 914 915 if ( matchedVariation ) { 916 matchedVariationId = matchedVariation.variation_id; 917 productId = matchedVariationId; 918 } 919 920 variationProductDetails = a.find( ( v ) => { 921 return parseInt( v.id ) === parseInt( matchedVariationId ); 922 } ); 923 924 variationAttributes = Object.entries( selectedData ) 925 .filter( ( [ key ] ) => key.startsWith( 'attribute_' ) ) // only attributes 926 .map( ( [ , value ] ) => value ) // just take the value 927 .join( ' - ' ); // join with dash 928 929 jsonData[ matchedVariationId ] = { 930 item_id: parseInt( matchedVariationId ), 931 item_name: `${ jsonData[ parentId ]?.item_name || '' }${ 932 variationAttributes ? ' - ' + variationAttributes : '' 933 }`, 934 thumbnail: variationProductDetails.image_url || '', 935 regular_price: variationProductDetails.regular_price || '', 936 sale_price: variationProductDetails.sale_price || '', 937 quantity: 1, 938 parent_id: parentId, 939 }; 940 } 941 942 const data = { 943 id: productId, 944 productName: isSplitVariation 945 ? jsonData[ productId ]?.item_name 946 : productType === 'variable' 947 ? `${ jsonData[ parentId ]?.item_name || '' }${ 948 variationAttributes 949 ? ' - ' + variationAttributes 950 : '' 951 }` 952 : jsonData[ productId ]?.item_name, 953 regularPrice: 954 productType === 'variable' 955 ? variationProductDetails.regular_price 956 : jsonData[ productId ]?.regular_price, 957 thumbnail: 958 productType === 'variable' 959 ? variationProductDetails.image_url 960 : jsonData[ productId ]?.thumbnail, 961 quantity, 962 }; 963 964 const cookieName = `mix_match_${ campaignId }`; 965 let prevData = getCookie( cookieName ); 966 967 let prevSelectedItems = $( 968 `input[name="revx-selected-items-${ campaignId }"]` 969 ).val(); 970 971 prevSelectedItems = prevSelectedItems 972 ? JSON.parse( prevSelectedItems ) 973 : {}; 974 975 prevData = prevData ? JSON.parse( prevData ) : {}; 976 977 // handles the case of initial render. when the required items are pre-selected and not in cookie, 978 // merge the cookie data with pre selected data and then update accordingly. 979 if ( Object.keys( prevSelectedItems ).length !== 0 ) { 980 for ( const itemId in prevSelectedItems ) { 981 if ( ! prevData[ itemId ] ) { 982 prevData[ itemId ] = prevSelectedItems[ itemId ]; 983 } 984 } 985 } 986 let clonedItem; 987 988 if ( prevData[ productId ] ) { 989 prevData[ productId ].quantity = 990 parseInt( prevData[ productId ].quantity ) + 991 parseInt( quantity ); 992 993 $( 994 `.revx-selected-item[data-campaign-id=${ campaignId }][data-product-id=${ productId }] .revx-selected-item__product-price .woocommerce-Price-amount` 995 ).text( `${ formatPrice( data.regularPrice ) }` ); 996 $( 997 `.revx-selected-item[data-campaign-id=${ campaignId }][data-product-id=${ productId }] .revx-selected-item__product-price .revx-qty` 998 ).text( `(x ${ prevData[ productId ].quantity })` ); 999 } else { 1000 const selectedContainer = $( 1001 `.revx-campaign-${ campaignId } .revx-selected-container` 1002 ); 1003 1004 selectedContainer.removeClass( 'revx-d-none' ); 1005 selectedContainer.removeClass( 'revx-empty-selected-items' ); 1006 1007 $( 1008 `.revx-campaign-${ campaignId } .revx-empty-mix-match` 1009 ).addClass( 'revx-d-none' ); 1010 1011 prevData[ productId ] = data; 1012 1013 const placeholderItem = $( 1014 `.revx-selected-item.revx-d-none[data-campaign-id=${ campaignId }]` 1015 ).first(); // ensure only one placeholder. 1016 clonedItem = placeholderItem.clone(); 1017 clonedItem 1018 .find( '.revx-selected-title' ) 1019 .html( data.productName ); 1020 clonedItem 1021 .find( '.revx-campaign-item__image img' ) 1022 .attr( 'src', data.thumbnail ); 1023 clonedItem 1024 .find( '.revx-campaign-item__image img' ) 1025 .attr( 'alt', data.productName ); 1026 clonedItem 1027 .find( 1028 '.revx-selected-item__product-price .revx-price-placeholder' 1029 ) 1030 .text( `${ formatPrice( data.regularPrice ) }` ); 1031 clonedItem 1032 .find( '.revx-selected-item__product-price .revx-qty' ) 1033 .text( `(x ${ quantity })` ); 1034 clonedItem.removeClass( 'revx-d-none' );
1035 clonedItem.attr( 'data-product-id', productId ); 1036 if ( productType === 'variable' ) { 1037 clonedItem.attr( 1038 'data-selected-attribute', 1039 JSON.stringify( selectedData ) 1040 ); 1041 } 1042 clonedItem.attr( 'data-parent-id', parentId ); 1043 clonedItem.attr( 'data-product-type', productType ); 1044 placeholderItem.before( clonedItem ); 1045 } 1046 1047 $( `input[name="revx-selected-items-${ campaignId }"]` ).val( 1048 JSON.stringify( prevData ) 1049 ); 1050 1051 setCookie( cookieName, JSON.stringify( prevData ), 7 ); 1052 1053 updateMixMatchHeaderAndPrices( campaignId, prevData, jsonQtyData ); 1054 1055 $( this ) 1056 .parent() 1057 .find( `input[data-name="revx_quantity"]` ) 1058 .val( 1 ); 1059 // make the reset button visible after adding any product 1060 $( this ) 1061 .closest( '.revx-template' ) 1062 .find( '[data-mix-match-reset-btn]' ) 1063 .removeClass( 'revx-d-none' ); 1064 } 1065 ); 1066 1067 function removeMixMatchSelectedItem() { 1068 const productId = $( this ) 1069 .closest( '[data-product-id]' ) 1070 .data( 'product-id' ); 1071 const campaignId = $( this ) 1072 .closest( '[data-campaign-id]' ) 1073 .data( 'campaign-id' ); 1074 const item = $( 1075 `.revx-selected-item[data-campaign-id=${ campaignId }][data-product-id="${ productId }"]` 1076 ); 1077 1078 item.remove(); 1079 1080 const cookieName = `mix_match_${ campaignId }`; 1081 let prevData = getCookie( cookieName ); 1082 let prevSelectedItems = $( 1083 `input[name=revx-selected-items-${ campaignId }]` 1084 ).val(); 1085 prevSelectedItems = prevSelectedItems 1086 ? JSON.parse( prevSelectedItems ) 1087 : {}; 1088 1089 prevData = prevData ? JSON.parse( prevData ) : {}; 1090 1091 // handles the case of initial render, when the required items are pre-selected, 1092 // merge the cookie data with pre selected data and then update accordingly. 1093 if ( Object.keys( prevSelectedItems ).length !== 0 ) { 1094 for ( const itemId in prevSelectedItems ) { 1095 if ( ! prevData[ itemId ] ) { 1096 prevData[ itemId ] = prevSelectedItems[ itemId ]; 1097 } 1098 } 1099 } 1100 1101 delete prevData[ productId ]; 1102 setCookie( cookieName, JSON.stringify( prevData ), 7 ); 1103 $( `input[name=revx-selected-items-${ campaignId }]` ).val( 1104 JSON.stringify( prevData ) 1105 ); 1106 1107 const qtyData = $( `input[name=revx-qty-data-${ campaignId }]` ).val(); 1108 const jsonQtyData = JSON.parse( qtyData ); 1109 1110 if ( Object.keys( prevData ).length === 0 ) { 1111 $( 1112 `.revx-campaign-${ campaignId } .revx-empty-selected-products` 1113 ).removeClass( 'revx-d-none' ); 1114 $( 1115 `.revx-campaign-${ campaignId } .revx-selected-product-container` 1116 ).addClass( 'revx-empty-selected-items' ); 1117 $( 1118 `.revx-campaign-${ campaignId } .revx-empty-mix-match` 1119 ).removeClass( 'revx-d-none' ); 1120 1121 $( 1122 `.revx-campaign-${ campaignId } [data-mix-match-reset-btn]` 1123 ).addClass( 'revx-d-none' ); 1124 } 1125 1126 updateMixMatchHeaderAndPrices( campaignId, prevData, jsonQtyData ); 1127 } 1128 1129 $( '[data-container-level="mix_match_file"]' ).on( 1130 'click', 1131 '.revx-selected-remove', 1132 removeMixMatchSelectedItem 1133 ); 1134 1135 // refactor later to make DRY 1136 function resetMixMatchSelectedItem() { 1137 const campaignId = $( this ) 1138 .closest( '[data-campaign-id]' ) 1139 .data( 'campaign-id' ); 1140 1141 const cookieName = `mix_match_${ campaignId }`; 1142 1143 let prevSelectedItems = $( 1144 `input[name=revx-selected-items-${ campaignId }]` 1145 ).val(); 1146 prevSelectedItems = prevSelectedItems 1147 ? JSON.parse( prevSelectedItems ) 1148 : {}; 1149 1150 for ( const itemId in prevSelectedItems ) { 1151 const $item = $( this ) 1152 .closest( '[revx-campaign-id]' ) 1153 .find( 1154 `.revx-selected-item[data-campaign-id=${ campaignId }][data-product-id="${ itemId }"]` 1155 ); 1156 if ( prevSelectedItems[ itemId ].is_required === 'yes' ) { 1157 prevSelectedItems[ itemId ].quantity = 1; 1158 $item.find( '.revx-qty' ).text( '(x 1)' ); 1159 } else { 1160 $item.remove(); 1161 delete prevSelectedItems[ itemId ]; 1162 } 1163 } 1164 setCookie( cookieName, JSON.stringify( prevSelectedItems ), 7 ); 1165 $( `input[name=revx-selected-items-${ campaignId }]` ).val( 1166 JSON.stringify( prevSelectedItems ) 1167 ); 1168 1169 const qtyData = $( `input[name=revx-qty-data-${ campaignId }]` ).val(); 1170 const jsonQtyData = JSON.parse( qtyData ); 1171 1172 if ( Object.keys( prevSelectedItems ).length === 0 ) { 1173 $( 1174 `.revx-campaign-${ campaignId } .revx-empty-selected-products` 1175 ).removeClass( 'revx-d-none' ); 1176 $( 1177 `.revx-campaign-${ campaignId } .revx-selected-product-container` 1178 ).addClass( 'revx-empty-selected-items' ); 1179 $( 1180 `.revx-campaign-${ campaignId } .revx-empty-mix-match` 1181 ).removeClass( 'revx-d-none' ); 1182 $( this ).addClass( 'revx-d-none' ); 1183 } 1184
1185 updateMixMatchHeaderAndPrices( 1186 campaignId, 1187 prevSelectedItems, 1188 jsonQtyData 1189 ); 1190 } 1191 $( '[data-container-level="mix_match_file"]' ).on( 1192 'click', 1193 '[data-mix-match-reset-btn]', 1194 resetMixMatchSelectedItem 1195 ); 1196 1197 function setTierState( $tier, tierClass, checkboxClass, isChecked ) { 1198 $tier.addClass( tierClass.add ).removeClass( tierClass.remove ); 1199 const $checkboxContainer = $tier.find( '.revx-checkbox-container' ); 1200 $checkboxContainer 1201 .addClass( checkboxClass.add ) 1202 .removeClass( checkboxClass.remove ) 1203 .attr( 'data-is-checked', isChecked ? 'yes' : 'no' ) 1204 .css( 'display', isChecked ? '' : 'none' ); // Ensure visibility based on state 1205 } 1206 1207 function updateMixMatchHeaderAndPrices( 1208 campaignId, 1209 prevData, 1210 jsonQtyData = {} 1211 ) { 1212 const header = $( 1213 `.revx-campaign-${ campaignId } .revx-price-container` 1214 ); 1215 const itemCounts = Object.keys( prevData ).length; 1216 // Eligibility basis: 'total' sums offered-product quantities, otherwise count distinct products. 1217 const countMode = 1218 typeof revenue_campaign !== 'undefined' && 1219 revenue_campaign.mix_match_count_mode === 'total' 1220 ? 'total' 1221 : 'unique'; 1222 const eligibleQty = 1223 countMode === 'total' 1224 ? Object.values( prevData ).reduce( 1225 ( sum, i ) => sum + parseInt( i.quantity ), 1226 0 1227 ) 1228 : itemCounts; 1229 let $selectedTier = null; 1230 $( '.revx-tier-button' ).each( function () { 1231 // Extract the item count from the mix-match-title text 1232 const titleText = $( this ) 1233 .find( '.revx-mix-match-title' ) 1234 .text() 1235 .trim(); 1236 const itemCount = parseInt( titleText.split( ' ' )[ 0 ], 10 ); // Extract number from "X item" 1237 1238 if ( eligibleQty >= itemCount ) { 1239 $selectedTier = $( this ); 1240 } 1241 } ); 1242 const selectedClass = { 1243 tierClass: { 1244 add: 'revx-tier-selected', 1245 remove: 'revx-tier-regular', 1246 }, 1247 checkboxClass: { 1248 add: 'revx-active', 1249 remove: 'revx-inactive revx-d-none', 1250 }, 1251 }; 1252 const unselectedClass = { 1253 tierClass: { 1254 add: 'revx-tier-regular', 1255 remove: 'revx-tier-selected', 1256 }, 1257 checkboxClass: { 1258 add: 'revx-inactive revx-d-none', 1259 remove: 'revx-active', 1260 }, 1261 }; 1262 // Update tier button styles based on selection 1263 $( '.revx-tier-button' ).each( function () { 1264 if ( $selectedTier && $( this ).is( $selectedTier ) ) { 1265 setTierState( 1266 $( this ), 1267 selectedClass.tierClass, 1268 selectedClass.checkboxClass, 1269 true 1270 ); 1271 } else { 1272 setTierState( 1273 $( this ), 1274 unselectedClass.tierClass, 1275 unselectedClass.checkboxClass, 1276 false 1277 ); 1278 } 1279 } ); 1280 const qtyData = $( `input[name=revx-qty-data-${ campaignId }]` ).val(); 1281 jsonQtyData = qtyData ? JSON.parse( qtyData ) : []; 1282 1283 header.toggleClass( 'revx-d-none', ! itemCounts ); // remove none if more than 0, adds none when item count is 0 1284 1285 // Addon: hide the whole footer (selected list + total + add to cart) until a product is added. 1286 if ( 1287 typeof revenue_campaign !== 'undefined' && 1288 revenue_campaign.mix_match_hide_footer_until_selected 1289 ) { 1290 $( `.revx-mixmatch-footer[revx-campaign-id="${ campaignId }"]` ).toggleClass( 1291 'revx-d-none', 1292 ! itemCounts 1293 ); 1294 } 1295 1296 // const addToCart = $(`.revx-campaign-add-to-cart-btn[data-campaign-id=${campaign_id}]`); 1297 // if(item_counts==0 && !addToCart.hasClass('revx-d-none') ) { 1298 // $(`.revx-campaign-add-to-cart-btn[data-campaign-id=${campaign_id}]`).addClass('revx-d-none'); 1299 // } else if(item_counts>0) { 1300 // $(`.revx-campaign-add-to-cart-btn[data-campaign-id=${campaign_id}]`).removeClass('revx-d-none'); 1301 // } 1302 header.find( '.revx-selected-product-count' ).html( eligibleQty ); 1303 1304 let totalRegularPrice = 0; 1305 let totalSalePrice = 0; 1306 let totalQuantity = 0; 1307 Object.values( prevData ).forEach( ( item ) => { 1308 totalRegularPrice += 1309 parseFloat( item.regularPrice ) * parseInt( item.quantity ); 1310 totalQuantity += parseInt( item.quantity ); 1311 } ); 1312 1313 let selectedIndex = -1; 1314 jsonQtyData.forEach( ( item, idx ) => { 1315 if ( eligibleQty >= item.quantity ) { 1316 selectedIndex = idx; 1317 1318 switch ( item.type ) { 1319 case 'percentage': 1320 totalSalePrice = 1321 totalRegularPrice * ( 1 - item.value / 100 ); 1322 break; 1323 case 'fixed_discount': 1324 totalSalePrice = Math.max( 1325 0, 1326 parseFloat( totalRegularPrice ) - 1327 parseFloat( item.value * totalQuantity ) 1328 ); 1329 1330 break; 1331 case 'no_discount': 1332 totalSalePrice = totalRegularPrice; 1333 1334 break; 1335 case 'fixed_price': 1336 totalSalePrice = item.value * totalQuantity; 1337 break; 1338 default: 1339 break; 1340 } 1341 } 1342 } ); 1343 // make display none for no_discount, other cases, remove the class 1344 header 1345 .find( '.revx-product-old-price' ) 1346 .toggleClass( 1347 'revx-d-none', 1348 jsonQtyData[ selectedIndex ]?.type === 'no_discount' || 1349 totalSalePrice === 0 1350 ); 1351 const that = $( 1352 `.revx-campaign-${ campaignId } .revx-mixmatch-quantity` 1353 ); 1354 that.each( function () { 1355 const item = $( this ).find( '.revx-mixmatch-regular-quantity' ); 1356 1357 $( item ) 1358 .find( '.revx-checkbox-container' ) 1359 .addClass( 'revx-d-none' ); 1360 } ); 1361 1362 const clickedItem = that.find( `div[data-index=${ selectedIndex }]` ); 1363 $( clickedItem ) 1364 .find( '.revx-checkbox-container' ) 1365 .removeClass( 'revx-d-none' ); 1366 if ( totalSalePrice === 0 ) { 1367 header 1368 .find( '.revx-campaign-item__sale-price' ) 1369 .html( formatPrice( totalRegularPrice ) ); 1370 } else { 1371 if ( 1372 totalSalePrice && 1373 header 1374 .find( '.revx-campaign-item__sale-price' ) 1375 .hasClass( 'revx-d-none' ) 1376 ) { 1377 header 1378 .find( '.revx-campaign-item__sale-price' ) 1379 .removeClass( 'revx-d-none' ); 1380 } 1381 if ( 1382 totalRegularPrice && 1383 header 1384 .find( '.revx-campaign-item__regular-price' ) 1385 .hasClass( 'revx-d-none' ) 1386 ) { 1387 header 1388 .find( '.revx-campaign-item__regular-price' ) 1389 .removeClass( 'revx-d-none' ); 1390 } 1391 1392 header 1393 .find( '.revx-campaign-item__sale-price' ) 1394 .html( formatPrice( totalSalePrice ) ); 1395 header 1396 .find( '.revx-product-old-price' ) 1397 .html( formatPrice( totalRegularPrice ) ); 1398 } 1399 } 1400 1401 // old fbt - available for old campaigns in v1 1402 // Frequently Bought Together 1403 // Function to update the styles based on selection 1404 // function updateStyles( $checkbox, selected ) { 1405 // const selectedStyles = $checkbox.data( 'selected-style' ); 1406 // const defaultStyles = $checkbox.data( 'default-style' ); 1407 // if ( selected ) { 1408 // $checkbox.attr( 'style', selectedStyles ); 1409 // } else { 1410 // $checkbox.attr( 'style', defaultStyles ); 1411 // } 1412 // } 1413 // $( '.revx-frequently-bought-together' ).on( 1414 // 'click', 1415 // '.revx-item-options .revx-item-option', 1416 // function ( e ) { 1417 // e.preventDefault(); 1418 1419 // const $this = $( this ); 1420 // if ( $this.hasClass( 'revx-item-required' ) ) { 1421 // return; 1422 // } 1423 // const $checkbox = $this.find( '.revx-builder-checkbox' ); 1424 1425 // const parent = $this.closest( '.revx-campaign-container__wrapper' ); 1426 1427 // const campaign_id = parent.data( 'campaign-id' ); 1428 // const cookieName = `campaign_${ campaign_id }`; 1429 // let selectedProducts = getCookie( cookieName ); 1430 // let prevSelectedItems = $( 1431 // `input[name=revx-fbt-selected-items-${ campaign_id }]` 1432 // ).val(); 1433 // prevSelectedItems = prevSelectedItems 1434 // ? JSON.parse( prevSelectedItems ) 1435 // : {}; 1436 // selectedProducts = selectedProducts 1437 // ? JSON.parse( selectedProducts ) 1438 // : {}; 1439 // if ( Object.keys( selectedProducts ) == 0 ) { 1440 // selectedProducts = { ...prevSelectedItems }; 1441 // } 1442 1443 // const productId = $this.data( 'product-id' ); 1444 1445 // // Toggle the selected state 1446 // if ( selectedProducts[ productId ] ) { 1447 // // selectedProducts = selectedProducts.filter(id => id !== productId); 1448 // delete selectedProducts[ productId ]; 1449 // updateStyles( $checkbox, false ); 1450 // } else { 1451 // const quantityInput = 1452 // $( 1453 // `input[name="revx-quantity-${ campaign_id }-${ productId }"]` 1454 // ).val() ?? $this.data( 'min-quantity' ); 1455 1456 // selectedProducts[ productId ] = quantityInput; 1457 // updateStyles( $checkbox, true ); 1458 // } 1459 // $( `input[name="revx-fbt-selected-items-${ campaign_id }"]` ).val( 1460 // JSON.stringify( selectedProducts ) 1461 // ); 1462 1463 // // Update the cookie 1464 // setCookie( cookieName, JSON.stringify( selectedProducts ), 1 ); 1465 1466 // fbtCalculation( parent, campaign_id ); 1467 // } 1468 // ); 1469 1470 // const fbtCalculation = ( parent, campaign_id ) => { 1471 // const cookieName = `campaign_${ campaign_id }`; 1472 // let selectedProducts = getCookie( cookieName ); 1473 // let prevSelectedItems = $( 1474 // `input[name=revx-fbt-selected-items-${ campaign_id }]` 1475 // ).val(); 1476 // prevSelectedItems = prevSelectedItems 1477 // ? JSON.parse( prevSelectedItems ) 1478 // : {}; 1479 // selectedProducts = selectedProducts 1480 // ? JSON.parse( selectedProducts ) 1481 // : {}; 1482 // if ( Object.keys( selectedProducts ) == 0 ) { 1483 // selectedProducts = { ...prevSelectedItems }; 1484 // } 1485 1486 // const calculateSalePrice = ( data, qty = 1 ) => { 1487 // if ( ! data?.type ) { 1488 // return data.regular_price * qty; 1489 // } 1490 // let total = 0; 1491 // switch ( data.type ) { 1492 // case 'percentage': 1493 // total = 1494 // parseFloat( data.regular_price ) * 1495 // ( 1 - data.value / 100 ); 1496 1497 // break; 1498 // case 'amount': 1499 // case 'fixed_discount': 1500 // total = Math.max( 1501 // 0, 1502 // parseFloat( data.regular_price ) - 1503 // parseFloat( data.value ) 1504 // ); 1505 1506 // break; 1507 // case 'fixed_price': 1508 // total = parseFloat( data.value ); 1509 1510 // break; 1511 // case 'no_discount': 1512 // total = parseFloat( data.regular_price ); 1513 1514 // break; 1515 // case 'free': 1516 // total = 0; 1517 1518 // break; 1519 1520 // default: 1521 // break; 1522 // } 1523 1524 // return parseFloat( total ) * parseInt( qty ); 1525 // }; 1526 // let offerData = $( 1527 // `input[name=revx-offer-data-${ campaign_id }]` 1528 // ).val(); 1529 1530 // offerData = JSON.parse( offerData ); 1531 1532 // let totalRegularPrice = 0; 1533 // let totalSalePrice = 0; 1534 1535 // Object.keys( selectedProducts ).forEach( ( id ) => { 1536 // totalRegularPrice += 1537 // parseFloat( offerData[ id ]?.regular_price ) * 1538 // parseInt( selectedProducts[ id ] ); 1539 // totalSalePrice += parseFloat( 1540 // calculateSalePrice( 1541 // offerData[ id ], 1542 // parseInt( selectedProducts[ id ] ) 1543 // ) 1544 // ); 1545 // } ); 1546 1547 // if ( totalRegularPrice != totalSalePrice ) { 1548 // parent 1549 // .find( 1550 // `.revx-triggerProduct .revx-campaign-item__regular-price` 1551 // ) 1552 // .html( formatPrice( totalRegularPrice ) ); 1553 // parent 1554 // .find( `.revx-triggerProduct .revx-campaign-item__sale-price` ) 1555 // .html( formatPrice( totalSalePrice ) ); 1556 // } else { 1557 // parent 1558 // .find( `.revx-triggerProduct .revx-campaign-item__sale-price` ) 1559 // .html( formatPrice( totalSalePrice ) ); 1560 // parent 1561 // .find( 1562 // `.revx-triggerProduct .revx-campaign-item__regular-price` 1563 // ) 1564 // .html( '' ); 1565 // } 1566 // parent 1567 // .find( `.revx-triggerProduct .revx-selected-product-count` ) 1568 // .html( Object.keys( selectedProducts ).length ); 1569 // }; 1570 1571 // $( '.revx-frequently-bought-together' ).on( 1572 // 'change', 1573 // 'input[data-name=revx_quantity]', 1574 // function ( e ) { 1575 // e.preventDefault(); 1576 // const parent = $( this ).closest( 1577 // '.revx-campaign-container__wrapper' 1578 // ); 1579 1580 // const quantity = $( this ).val(); 1581 1582 // const campaign_id = parent.data( 'campaign-id' ); 1583 1584 // // addFbtRequiredProductsIfNotAdded(campaign_id,false); 1585 // const product_id = $( this ).data( 'product-id' ); 1586 // const cookieName = `campaign_${ campaign_id }`; 1587 1588 // let selectedProducts = getCookie( cookieName ); 1589 // let prevSelectedItems = $( 1590 // `input[name=revx-fbt-selected-items-${ campaign_id }]` 1591 // ).val(); 1592 // prevSelectedItems = prevSelectedItems 1593 // ? JSON.parse( prevSelectedItems ) 1594 // : {}; 1595 // selectedProducts = selectedProducts 1596 // ? JSON.parse( selectedProducts ) 1597 // : {}; 1598 // if ( Object.keys( selectedProducts ) == 0 ) { 1599 // selectedProducts = { ...prevSelectedItems }; 1600 // } 1601 1602 // if ( selectedProducts[ product_id ] ) { 1603 // selectedProducts[ product_id ] = quantity; 1604
1605 // setCookie( cookieName, JSON.stringify( selectedProducts ), 1 ); 1606 // fbtCalculation( parent, campaign_id ); 1607 // } 1608 1609 // $( `input[name=revx-fbt-selected-items-${ campaign_id }]` ).val( 1610 // JSON.stringify( selectedProducts ) 1611 // ); 1612 1613 // const calculateSalePrice = ( data, qty = 1 ) => { 1614 // if ( ! data?.type ) { 1615 // return data.regular_price * qty; 1616 // } 1617 // let total = 0; 1618 // switch ( data.type ) { 1619 // case 'percentage': 1620 // total = 1621 // parseFloat( data.regular_price ) * 1622 // ( 1 - data.value / 100 ); 1623 1624 // break; 1625 // case 'amount': 1626 // case 'fixed_discount': 1627 // total = Math.max( 1628 // 0, 1629 // parseFloat( data.regular_price ) - 1630 // parseFloat( data.value ) 1631 // ); 1632 1633 // break; 1634 // case 'fixed_price': 1635 // total = parseFloat( data.value ); 1636 1637 // break; 1638 // case 'no_discount': 1639 // total = parseFloat( data.regular_price ); 1640 1641 // break; 1642 // case 'free': 1643 // total = 0; 1644 1645 // break; 1646 1647 // default: 1648 // break; 1649 // } 1650 1651 // return parseFloat( total ) * parseInt( qty ); 1652 // }; 1653 // let offerData = $( `input[name=revx-offer-data-${ campaign_id }]` ); 1654 1655 // offerData = offerData[ 0 ].value; 1656 // const jsonData = JSON.parse( offerData ); 1657 1658 // const salePrice = calculateSalePrice( 1659 // jsonData[ product_id ], 1660 // quantity 1661 // ).toFixed( 2 ); 1662 1663 // const inRP = jsonData[ product_id ].regular_price; 1664 1665 // const inSP = ( salePrice / quantity ).toFixed( 2 ); 1666 1667 // const itemParent = $( this ).closest( '.revx-campaign-item' ); 1668 1669 // updatePriceDisplay( itemParent, quantity, inSP, inRP ); 1670 // } 1671 // ); 1672 1673 // Buy X Get Y 1674 $( '.revx-buyx-gety' ).on( 1675 'change', 1676 'input[data-name=revx_quantity]', 1677 function ( e ) { 1678 e.preventDefault(); 1679 const parent = $( this ).closest( '.revx-campaign-container' ); 1680 1681 const quantity = $( this ).val(); 1682 1683 const campaign_id = parent.data( 'campaign-id' ); 1684 1685 const product_id = $( this ).data( 'product-id' ); 1686 1687 let offerData = $( `input[name=revx-offer-data-${ campaign_id }]` ); 1688 1689 offerData = offerData[ 0 ].value; 1690 const jsonData = JSON.parse( offerData ); 1691 1692 const regularPrice = ( 1693 jsonData[ product_id ].regular_price * quantity 1694 ).toFixed( 2 ); 1695 const salePrice = calculateCurrentPrice( 1696 jsonData, 1697 product_id, 1698 quantity 1699 ).toFixed( 2 ); 1700 1701 const item = $( this ).closest( '.revx-campaign-item__content' ); 1702 1703 item.find( '.revx-campaign-item__regular-price' ).text( 1704 formatPrice( regularPrice ) 1705 ); 1706 item.find( '.revx-campaign-item__sale-price' ).text( 1707 formatPrice( salePrice ) 1708 ); 1709 1710 if ( regularPrice == salePrice ) { 1711 if ( 1712 ! item 1713 .find( '.revx-campaign-item__regular-price' ) 1714 .hasClass( 'revx-d-none' ) 1715 ) { 1716 item.find( '.revx-campaign-item__regular-price' ).addClass( 1717 'revx-d-none' 1718 ); 1719 } 1720 } else if ( 1721 item 1722 .find( '.revx-campaign-item__regular-price' ) 1723 .hasClass( 'revx-d-none' ) 1724 ) { 1725 item.find( '.revx-campaign-item__regular-price' ).removeClass( 1726 'revx-d-none' 1727 ); 1728 } 1729 1730 let totalRegularPrice = 0; 1731 let totalSalePrice = 0; 1732 1733 Object.keys( jsonData ).forEach( ( pid ) => { 1734 const qty = $( 1735 `input[name="revx-quantity-${ campaign_id }-${ pid }"]` 1736 ).val(); 1737 const rp = ( jsonData[ pid ].regular_price * qty ).toFixed( 2 ); 1738 const sp = parseFloat( 1739 calculateCurrentPrice( jsonData, pid, qty ).toFixed( 2 ) 1740 ); 1741 totalRegularPrice += parseFloat( rp ); 1742 totalSalePrice += parseFloat( sp ); 1743 } ); 1744 1745 parent 1746 .find( '.revx-total-price .revx-campaign-item__regular-price' ) 1747 .html( formatPrice( totalRegularPrice ) ); 1748 parent 1749 .find( '.revx-total-price .revx-campaign-item__sale-price' ) 1750 .html( formatPrice( totalSalePrice ) ); 1751 } 1752 ); 1753 1754 // Slider ----------------------- 1755 1756 function checkOverflow( container ) { 1757 $( container ).each( function () { 1758 const $this = $( this ); 1759 1760 const isOverflowing = 1761 $this[ 0 ].scrollWidth > $this[ 0 ].offsetWidth; 1762 1763 if ( isOverflowing ) { 1764 $( this ) 1765 .find( '.revx-builderSlider-icon' ) 1766 .addClass( 'revx-has-overflow' ); 1767 } else { 1768 $( this ) 1769 .find( '.revx-builderSlider-icon' ) 1770 .removeClass( 'revx-has-overflow' ); 1771 } 1772 } ); 1773 } 1774 1775 // function initializeSlider( 1776 // $sliderContainer, 1777 // $containerSelector = '.revx-inpage-container', 1778 // $campaign_type = '' 1779 // ) { 1780 // const $container = $sliderContainer.closest( $containerSelector ); 1781 // const containerElement = $container.get( 0 ); 1782 // const computedStyle = getComputedStyle( containerElement ); 1783 // const gridColumnValue = computedStyle 1784 // .getPropertyValue( '--revx-grid-column' ) 1785 // .trim(); 1786 // let itemGap = parseInt( 1787 // computedStyle.getPropertyValue( 'gap' ).trim() 1788 // ); 1789 1790 // if ( ! itemGap ) { 1791 // itemGap = 16; 1792 // } 1793 1794 // const $slides = $sliderContainer.find( '.revx-campaign-item' ); 1795 // const minSlideWidth = 100; // 12rem in pixels (assuming 1rem = 16px) 1796 1797 // let containerWidth = $sliderContainer.parent().width(); 1798 1799 // if ( $campaign_type == 'mix_match' ) { 1800 // containerWidth = $sliderContainer
1801 // .closest( '.revx-slider-items-wrapper' ) 1802 // .innerWidth(); 1803 // } 1804 // if ( $campaign_type == 'bundle_discount' ) { 1805 // containerWidth = $sliderContainer 1806 // .closest( '.revx-slider-items-wrapper' ) 1807 // .outerWidth(); 1808 // itemGap = 0; 1809 // } 1810 // if ( $campaign_type == 'fbt' ) { 1811 // containerWidth = $container 1812 // .find( '.revx-slider-items-wrapper' ) 1813 // .innerWidth(); 1814 // } 1815 // if ( $campaign_type == 'normal_discount' ) { 1816 // containerWidth = $container 1817 // .closest( '.revx-slider-items-wrapper' ) 1818 // .innerWidth(); 1819 // itemGap = 0; 1820 // } 1821 1822 // let slidesVisible = Math.min( 1823 // gridColumnValue, 1824 // Math.floor( containerWidth / minSlideWidth ) 1825 // ); // Calculate initial slides visible 1826 1827 // let slideWidth = containerWidth / slidesVisible; 1828 // slideWidth -= itemGap; 1829 1830 // if ( $campaign_type == 'bundle_discount' ) { 1831 // slideWidth -= $container 1832 // .find( '.revx-builder__middle_element' ) 1833 // .width(); 1834 // } 1835 1836 // const totalSlides = $slides.length; 1837 // let slideIndex = 0; 1838 1839 // function updateSlideWidth() { 1840 // containerWidth = $sliderContainer 1841 // .closest( '.revx-slider-items-wrapper' ) 1842 // .innerWidth(); 1843 1844 // slidesVisible = Math.min( 1845 // gridColumnValue, 1846 // Math.floor( containerWidth / minSlideWidth ) 1847 // ); // Recalculate slides visible 1848 // slideWidth = containerWidth / slidesVisible; 1849 // slideWidth -= itemGap; 1850 1851 // if ( $campaign_type == 'bundle_discount' ) { 1852 // slideWidth -= $sliderContainer 1853 // .find( '.revx-builder__middle_element' ) 1854 // .width(); 1855 // } 1856 1857 // $slides.css( 'width', slideWidth + 'px' ); 1858 1859 // moveToSlide( slideIndex ); 1860 // } 1861 1862 // setTimeout( () => { 1863 // updateSlideWidth(); 1864 // } ); 1865 1866 // function moveToSlide( index ) { 1867 // let tempWidth = slideWidth; 1868 // if ( $campaign_type == 'fbt' ) { 1869 // tempWidth += $sliderContainer 1870 // .find( '.revx-product-bundle' ) 1871 // .width(); 1872 // } 1873 // if ( $campaign_type == 'bundle_discount' ) { 1874 // tempWidth += $sliderContainer 1875 // .find( '.revx-builder__middle_element' ) 1876 // .width(); 1877 // } 1878 // if ( $campaign_type == 'mix_match' ) { 1879 // tempWidth += itemGap; 1880 // } 1881 // const offset = -tempWidth * index; 1882 1883 // $sliderContainer.css( { 1884 // transition: 'transform 0.5s ease-in-out', 1885 // transform: `translateX(${ offset }px)`, 1886 // } ); 1887 // } 1888 1889 // function moveToNextSlide() { 1890 // slideIndex++; 1891 1892 // if ( slideIndex > totalSlides - slidesVisible ) { 1893 // slideIndex = 0; 1894 // } 1895 1896 // moveToSlide( slideIndex ); 1897 // } 1898 1899 // function moveToPrevSlide() { 1900 // slideIndex--; 1901 1902 // if ( slideIndex < 0 ) { 1903 // slideIndex = totalSlides - slidesVisible; 1904 // } 1905 1906 // moveToSlide( slideIndex ); 1907 // } 1908 1909 // $sliderContainer 1910 // .siblings( '.revx-builderSlider-right' ) 1911 // .click( function () { 1912 // if ( ! $sliderContainer.is( ':animated' ) ) { 1913 // moveToNextSlide(); 1914 // } 1915 // } ); 1916 1917 // $sliderContainer 1918 // .siblings( '.revx-builderSlider-left' ) 1919 // .click( function () { 1920 // if ( ! $sliderContainer.is( ':animated' ) ) { 1921 // moveToPrevSlide(); 1922 // } 1923 // } ); 1924 1925 // setTimeout( () => { 1926 // // // const initialWidth = $sliderContainer.width(); 1927 // // $sliderContainer.width(containerWidth + 1); // Increase width by 1px 1928 // // $sliderContainer.width(containerWidth); // Reset to original width 1929 // $sliderContainer.parent().width( containerWidth ); // Reset to original width 1930 // $sliderContainer.parent().width( containerWidth + 1 ); // Reset to original width 1931 // $( window ).trigger( 'resize' ); // Trigger window resize 1932 // } ); 1933 1934 // $( window ).resize( function () { 1935 // updateSlideWidth(); 1936 // } ); 1937 1938 // $sliderContainer 1939 // .closest( '.revx-inpage-container' ) 1940 // .css( 'visibility', 'visible' ); 1941 // } 1942 1943 // function buxXGetYSlider() { 1944 // $( '.revx-inpage-container.revx-buyx-gety-grid' ).each( function () { 1945 // const $container = $( this ).find( 1946 // '.revx-campaign-container__wrapper' 1947 // ); 1948 // const containerElement = $container.get( 0 ); 1949 // const computedStyle = getComputedStyle( containerElement ); 1950 1951 // let gridColumnValue = parseInt( 1952 // computedStyle.getPropertyValue( '--revx-grid-column' ).trim() 1953 // ); 1954 // const minSlideWidth = 132; // 12rem in pixels (assuming 1rem = 16px) 1955 1956 // const $triggerItemContainer = $container.find( 1957 // '.revx-bxgy-trigger-items' 1958 // ); 1959 // const $offerItemContainer = $container.find( 1960 // '.revx-bxgy-offer-items' 1961 // ); 1962 1963 // let triggerItemColumn = parseInt( 1964 // getComputedStyle( $triggerItemContainer.get( 0 ) ) 1965 // .getPropertyValue( '--revx-grid-column' ) 1966 // .trim() 1967 // ); 1968 // let offerItemColumn = parseInt( 1969 // getComputedStyle( $offerItemContainer.get( 0 ) ) 1970 // .getPropertyValue( '--revx-grid-column' ) 1971 // .trim() 1972 // ); 1973 1974 // let containerWidth = $container.width(); 1975 1976 // const seperatorWidth = $container 1977 // .find( '.revx-product-bundle' ) 1978 // .width(); 1979 1980 // containerWidth -= seperatorWidth - 16; 1981 1982 // gridColumnValue = gridColumnValue ? gridColumnValue : 4; 1983 1984 // gridColumnValue = Math.min( 1985 // gridColumnValue, 1986 // Math.floor( containerWidth / minSlideWidth ) 1987 // ); 1988 // triggerItemColumn = Math.min( 1989 // $triggerItemContainer.find( '.revx-campaign-item' ).length, 1990 // triggerItemColumn 1991 // ); 1992 // offerItemColumn = Math.min( 1993 // $offerItemContainer.find( '.revx-campaign-item' ).length, 1994 // offerItemColumn 1995 // ); 1996 1997 // gridColumnValue = Math.min( 1998 // gridColumnValue, 1999 // triggerItemColumn + offerItemColumn 2000 // ); 2001 2002 // // gridColumnValue = gridColumnValue ? gridColumnValue : 4; 2003 2004 // // Ensure the total columns for trigger and offer items do not exceed the available grid columns 2005 // if ( triggerItemColumn + offerItemColumn > gridColumnValue ) { 2006 // const excessColumns = 2007 // triggerItemColumn + offerItemColumn - gridColumnValue; 2008 2009 // // Adjust columns proportionally to ensure total columns match gridColumnValue 2010 // const triggerAdjustment = Math.floor( 2011 // ( triggerItemColumn / 2012 // ( triggerItemColumn + offerItemColumn ) ) * 2013 // excessColumns 2014 // ); 2015 // const offerAdjustment = excessColumns - triggerAdjustment; 2016 2017 // triggerItemColumn -= triggerAdjustment; 2018 // offerItemColumn -= offerAdjustment; 2019 // } 2020 2021 // const slideWidth = containerWidth / gridColumnValue; 2022 2023 // initializeSubSlider( 2024 // $triggerItemContainer, 2025 // triggerItemColumn, 2026 // slideWidth, 2027 // 'trigger' 2028 // ); 2029 // initializeSubSlider( 2030 // $offerItemContainer, 2031 // offerItemColumn, 2032 // slideWidth, 2033 // 'offer' 2034 // ); 2035 2036 // $( this ).css( 'visibility', 'visible' ); 2037 // } ); 2038 // } 2039 2040 // function initializeSubSlider( 2041 // $sliderContainer, 2042 // itemColumn, 2043 // slideWidth, 2044 // type 2045 // ) {
2046 // const $container = $sliderContainer.find( '.revx-slider-container' ); 2047 // const itemGap = parseInt( 2048 // getComputedStyle( $container.get( 0 ) ) 2049 // .getPropertyValue( 'gap' ) 2050 // .trim() 2051 // ); 2052 2053 // // slideWidth -=itemGap; 2054 // slideWidth -= itemGap; 2055 // const containerWidth = itemColumn * slideWidth; 2056 // $sliderContainer.width( containerWidth ); 2057 // slideWidth -= 16; 2058 2059 // if ( type == 'offer' ) { 2060 // slideWidth += itemGap; 2061 // } 2062 2063 // $sliderContainer = $container; 2064 2065 // const $slides = $sliderContainer.find( '.revx-campaign-item' ); 2066 // $slides.css( { width: slideWidth + 'px' } ); 2067 2068 // const totalSlides = $slides.length; 2069 // let slideIndex = 0; // Start at the first slide 2070 2071 // function moveToSlide( index ) { 2072 // let tempWidth = slideWidth; 2073 // tempWidth += itemGap + 16; 2074 // tempWidth += index; 2075 2076 // if ( itemColumn == 1 ) { 2077 // tempWidth += itemGap; 2078 // } 2079 2080 // if ( type == 'offer' ) { 2081 // tempWidth -= 16; 2082 // } 2083 2084 // const offset = -tempWidth * index; 2085 2086 // $sliderContainer.css( { 2087 // transition: 'transform 0.5s ease-in-out', 2088 // transform: `translateX(${ offset }px)`, 2089 // } ); 2090 // } 2091 2092 // function moveToNextSlide() { 2093 // slideIndex++; 2094 // if ( slideIndex > totalSlides - itemColumn ) { 2095 // slideIndex = 0; 2096 // } 2097 // moveToSlide( slideIndex ); 2098 // } 2099 2100 // function moveToPrevSlide() { 2101 // slideIndex--; 2102 // if ( slideIndex < 0 ) { 2103 // slideIndex = totalSlides - itemColumn; 2104 // } 2105 // moveToSlide( slideIndex ); 2106 // } 2107 2108 // $sliderContainer 2109 // .siblings( '.revx-builderSlider-right' ) 2110 // .click( function () { 2111 // if ( ! $sliderContainer.is( ':animated' ) ) { 2112 // moveToNextSlide(); 2113 // } 2114 // } ); 2115 2116 // $sliderContainer 2117 // .siblings( '.revx-builderSlider-left' ) 2118 // .click( function () { 2119 // if ( ! $sliderContainer.is( ':animated' ) ) { 2120 // moveToPrevSlide(); 2121 // } 2122 // } ); 2123 2124 // $( window ).resize( function () { 2125 // moveToSlide( slideIndex ); 2126 // } ); 2127 2128 // moveToSlide( slideIndex ); 2129 // } 2130 2131 // buxXGetYSlider(); 2132 2133 // $( window ).resize( function () { 2134 // buxXGetYSlider(); 2135 // } ); 2136 2137 // $( 2138 // '.revx-inpage-container.revx-normal-discount-grid .revx-slider-container' 2139 // ).each( function () { 2140 // initializeSlider( 2141 // $( this ), 2142 // '.revx-campaign-view__items', 2143 // 'normal_discount' 2144 // ); 2145 // } ); 2146 // $( 2147 // '.revx-inpage-container.revx-mix-match-grid .revx-slider-container' 2148 // ).each( function () { 2149 // initializeSlider( 2150 // $( this ), 2151 // '.revx-campaign-view__items', 2152 // 'mix_match' 2153 // ); 2154 // } ); 2155 // $( 2156 // '.revx-inpage-container.revx-bundle-discount-grid .revx-slider-container' 2157 // ).each( function () { 2158 // initializeSlider( 2159 // $( this ), 2160 // '.revx-campaign-view__items', 2161 // 'bundle_discount' 2162 // ); 2163 // } ); 2164 // $( 2165 // '.revx-inpage-container.revx-frequently-bought-together-grid .revx-slider-container' 2166 // ).each( function () { 2167 // initializeSlider( $( this ), '.revx-inpage-container', 'fbt' ); 2168 // } ); 2169 2170 // $( window ).on( 'load resize', function () { 2171 // checkOverflow( '.revx-slider' ); 2172 // } ); 2173 2174 // ---------------- Slider 2175 2176 // $('.revx-ticket-type') 2177 2178 $( '.revx-ticket-type' ).change( function () { 2179 const selectedSlug = $( this ).val(); 2180 const campaignID = $( this ).data( 'campaign-id' ); 2181 const offerQty = $( this ).data( 'quantity' ); 2182 2183 const prices = $( this ).data( 'prices' ); 2184 2185 $( this ) 2186 .closest( '.revx-campaign-item' ) 2187 .find( '.revx-total-ticket-price' ) 2188 .text( 2189 `Total Price: ${ formatPrice( 2190 prices[ selectedSlug ] * offerQty 2191 ) }` 2192 ); 2193 2194 const selectedItem = $( this ) 2195 .closest( '.revx-volume-discount' ) 2196 .find( '.revx-campaign-item[data-revx-selected=true]' ); 2197 2198 if ( selectedItem.length ) { 2199 // Check if the selectedItem exists 2200 // selectedItem.data('data-selected-ticket', selectedSlug); 2201 selectedItem.attr( 'data-selected-ticket', selectedSlug ); 2202 selectedItem.attr( 2203 'data-selected-ticket-price', 2204 prices[ selectedSlug ] * offerQty 2205 ); 2206 } 2207 } ); 2208 2209 $( '.revx-ticket-type' ).trigger( 'change' ); 2210 2211 // Tooltip - Spending Goal 2212 function updateTooltipPosition( $container ) { 2213 const $tooltip = $container.find( '.revx-gift-tooltip' ); 2214 if ( ! $tooltip.length ) { 2215 return; 2216 } 2217 2218 const containerRect = $container[ 0 ].getBoundingClientRect(); 2219 const tooltipRect = $tooltip[ 0 ].getBoundingClientRect(); 2220 const windowHeight = $( window ).height(); 2221 const windowWidth = $( window ).width(); 2222 2223 // Calculate available space 2224 const spaceAbove = containerRect.top; 2225 const spaceBelow = windowHeight - containerRect.bottom; 2226 2227 // Determine position (top or bottom) 2228 const position = spaceAbove > spaceBelow ? 'top' : 'bottom';
2229 $tooltip.attr( 'data-position', position ); 2230 2231 // Handle horizontal overflow 2232 const tooltipWidth = $tooltip.outerWidth(); 2233 const containerCenterX = containerRect.left + containerRect.width / 2; 2234 const spaceLeft = containerCenterX; 2235 const spaceRight = windowWidth - containerCenterX; 2236 2237 if ( tooltipWidth / 2 > spaceLeft ) { 2238 // Not enough space on the left 2239 $tooltip.css( { 2240 left: '0', 2241 transform: 'translateX(0)', 2242 } ); 2243 } else if ( tooltipWidth / 2 > spaceRight ) { 2244 // Not enough space on the right 2245 $tooltip.css( { 2246 left: 'auto', 2247 right: '0', 2248 transform: 'translateX(0)', 2249 } ); 2250 } else { 2251 // Center aligned 2252 $tooltip.css( { 2253 // left: '50%', 2254 right: 'auto', 2255 transform: 'translateX(-50%)', 2256 } ); 2257 } 2258 } 2259 2260 // Handle mouse enter 2261 $( '.revx-step-icon-container' ).on( 'mouseenter', function () { 2262 updateTooltipPosition( $( this ) ); 2263 } ); 2264 2265 // Update positions on window resize 2266 let resizeTimer; 2267 $( window ).on( 'resize', function () { 2268 clearTimeout( resizeTimer ); 2269 resizeTimer = setTimeout( function () { 2270 $( '.revx-step-icon-container:hover' ).each( function () { 2271 updateTooltipPosition( $( this ) ); 2272 } ); 2273 }, 250 ); 2274 } ); 2275 2276 function updateGiftPosition( $container ) { 2277 const $gift = $container.find( '.revx-gift-container' ); 2278 const $giftWrapper = $container.find( '.revx-spending-gift' ); 2279 if ( ! $gift.length ) { 2280 return; 2281 } 2282 // remove the class before. otherwise top calculation is wrong. 2283 $gift.removeClass( 'revx-d-none' ); 2284 2285 const iconRect = $container[ 0 ].getBoundingClientRect(); // relative to viewport 2286 const giftRect = $gift[ 0 ].getBoundingClientRect(); 2287 2288 const giftWidth = giftRect?.width; 2289 const iconWidth = iconRect?.width; 2290 const windowWidth = window.innerWidth; 2291 const giftCenterToLeft = Math.abs( giftWidth - iconWidth ) / 2; 2292 2293 const isAbove = iconRect?.top > giftRect?.height + 10; 2294 const isRightSpace = iconRect?.left + giftCenterToLeft < windowWidth; 2295 const isLeftSpace = iconRect?.left - giftCenterToLeft >= 0; 2296 2297 const top = isAbove 2298 ? ( giftRect?.height + 8 ) * -1 2299 : iconRect?.height + 8; 2300 2301 let left = 0; 2302 if ( ! isLeftSpace && isRightSpace ) { 2303 left = -20; // small offset from left edge 2304 } else if ( isLeftSpace && ! isRightSpace ) { 2305 left = iconWidth - giftWidth; 2306 } else { 2307 // for space both left/right OR no space left/right keep center. 2308 left = giftCenterToLeft * -1; 2309 } 2310 2311 $gift.css( { 2312 position: 'fixed', 2313 top: `${ top }px`, 2314 left: `${ left }px`, 2315 zIndex: 999999, 2316 visibility: 'visible', 2317 opacity: 1, 2318 } ); 2319 $giftWrapper.css( { 2320 transformOrigin: `${ isAbove ? 'bottom' : 'top' } ${ 2321 ! isLeftSpace && isRightSpace 2322 ? 'left' 2323 : isLeftSpace && ! isRightSpace 2324 ? 'right' 2325 : 'center' 2326 }`, 2327 } ); 2328 } 2329 2330 // Stock Scarcity 2331 $( '.revx-flip-wrapper' ).each( function () { 2332 const $this = $( this ); // Define $this as the current .revx-flip-wrapper element 2333 const flipTextHeight = $this.find( '.revx-flip-text' ).outerHeight(); 2334 $this.css( 'min-height', flipTextHeight + 'px' ); 2335 } ); 2336 2337 let giftHideTimer; 2338 $( document ) 2339 .on( 'mouseenter', '.revx-progress-step-icon-container', function () { 2340 clearTimeout( giftHideTimer ); // prevent hiding 2341 updateGiftPosition( $( this ) ); 2342 } ) 2343 .on( 'mouseleave', '.revx-progress-step-icon-container', function () { 2344 const $this = $( this ); 2345 giftHideTimer = setTimeout( () => { 2346 $this 2347 .find( '.revx-gift-container' ) 2348 .addClass( 'revx-d-none' ) 2349 .css( { 2350 visibility: 'hidden', 2351 opacity: 0, 2352 left: '-250px', // to handle the overflow x case 2353 top: 'unset', 2354 } ); 2355 }, 200 ); // adjust delay as needed 2356 } ); 2357 2358 $( document ) 2359 .on( 'mouseenter', '.revx-gift-container', function () { 2360 clearTimeout( giftHideTimer ); // prevent hiding 2361 } ) 2362 .on( 'mouseleave', '.revx-gift-container', function () { 2363 $( this ).addClass( 'revx-d-none' ).css( { 2364 visibility: 'hidden', 2365 opacity: 0, 2366 left: 'unset', 2367 top: 'unset', 2368 } ); 2369 } ); 2370 2371 // Reposition gift on resize or scroll 2372 $( window ).on( 'resize scroll', function () { 2373 $( '.revx-progress-step-icon-container:hover' ).each( function () { 2374 updateGiftPosition( $( this ) ); 2375 } ); 2376 } ); 2377 2378 function adjustContentMarginTop() { 2379 // const helloBar = $( '.revx-spending-goal-top' ).length 2380 // ? $( '.revx-spending-goal-top' ) 2381 // : $( '.revx-campaign-fsb-top' ); 2382 const helloBar = $( '.revx-campaign-top' ).length 2383 ? $( '.revx-campaign-top' ) 2384 : $( '.revx-campaign-fsb-top' ); 2385 2386 // Check if hello bar exists 2387 if ( helloBar.length === 0 ) { 2388 return; 2389 } 2390 2391 const helloBarHeight = helloBar.outerHeight(); 2392 2393 // Only adjust if we have a valid height 2394 if ( helloBarHeight ) { 2395 $( 'body' ).css( 'margin-top', helloBarHeight ); 2396 2397 // Remove margin-bottom as it's causing double spacing 2398 // helloBar.css( 'margin-top', -helloBarHeight ); 2399 } 2400 } 2401 2402 adjustContentMarginTop(); 2403 function adjustContentMarginBottom() { 2404 const helloBar = $( '.revx-campaign-bottom' ).length 2405 ? $( '.revx-campaign-bottom' ) 2406 : $( '.revx-campaign-fsb-bottom' ); 2407 2408 // Check if hello bar exists 2409 if ( helloBar.length === 0 ) { 2410 return; 2411 } 2412 2413 const helloBarHeight = helloBar.outerHeight(); 2414 2415 // Only adjust if we have a valid height 2416 if ( helloBarHeight ) { 2417 $( 'body' ).css( 'margin-bottom', helloBarHeight ); 2418 2419 // Remove margin-bottom as it's causing double spacing 2420 // helloBar.css('margin-bottom', -helloBarHeight); 2421 } 2422 } 2423 2424 adjustContentMarginBottom(); 2425 2426 function showToast( message, type = 'success', duration = 3000 ) { 2427 // Check if toast container exists, otherwise create it 2428 let $toastContainer = $( '.revx-toaster-container' ); 2429 if ( $toastContainer.length === 0 ) { 2430 $toastContainer = $( '<div class="revx-toaster-container"></div>' ); 2431 $( 'body' ).append( $toastContainer ); 2432 } 2433 2434 // Determine toast class and icon based on type 2435 const toastClasses = { 2436 success: 'revx-toaster__success', 2437 warning: 'revx-toaster__warning', 2438 error: 'revx-toaster__error', 2439 }; 2440 2441 const icon = ` 2442 <svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" fill="none" viewBox="0 0 16 16" class="revx-toaster__close-icon revx-toaster__icon"> 2443 <path stroke="#fff" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.2" d="m12 4-8 8M4 4l8 8"></path> 2444 </svg> 2445 `; 2446 2447 // Create a new toast element as a jQuery object 2448 const $toast = $( ` 2449 <div class="revx-toaster revx-justify-space revx-toaster-lg ${ toastClasses[ type ] }" style="display: flex;"> 2450 <div class="revx-paragraph--xs revx-align-center-xs"> 2451 ${ message }
2452 </div> 2453 <div class="revx-paragraph--xs revx-align-center"> 2454 ${ icon } 2455 </div> 2456 </div> 2457 ` ); 2458 2459 // Add close button functionality 2460 $toast.find( '.revx-toaster__close-icon' ).on( 'click', function () { 2461 $toast.fadeOut( 400, function () { 2462 $( this ).remove(); // Remove the toast from DOM 2463 } ); 2464 } ); 2465 2466 // Append the toast to the toast container 2467 $toastContainer.append( $toast ); 2468 2469 // Show the toast 2470 $toast.fadeIn( 400 ); 2471 2472 // Set timeout to hide the toast after the specified duration 2473 setTimeout( function () { 2474 if ( $toast.is( ':visible' ) ) { 2475 // Only remove if still visible 2476 $toast.fadeOut( 400, function () { 2477 $( this ).remove(); // Remove the toast from DOM 2478 } ); 2479 } 2480 }, duration ); 2481 } 2482 2483 const $drawerContainer = $( '.revx-drawer-container' ).first(); 2484 if ( $drawerContainer.length ) { 2485 // Clone the entire container including content 2486 // const $drawerContent = $drawerContainer.find( '.revx-drawer-content' ); 2487 // $drawerContent.each( function () { 2488 // this.style.setProperty( 'max-width', '0vh', 'important' ); 2489 // this.style.setProperty( 'padding', '0vh', 'important' ); 2490 // } ); 2491 2492 const $clone = $drawerContainer.clone( true, true ); 2493 const $cloneContent = $clone.find( '.revx-drawer-content' ); 2494 // Remove max-width and padding limits on drawer-content inside clone 2495 $cloneContent.each( function () { 2496 this.style.setProperty( 'max-width', 'none', 'important' ); 2497 this.style.setProperty( 2498 'padding-top', 2499 'var(--revx-drawer-padding-top)', 2500 'important' 2501 ); 2502 this.style.setProperty( 2503 'padding-right', 2504 'var(--revx-drawer-padding-right)', 2505 'important' 2506 ); 2507 this.style.setProperty( 2508 'padding-bottom', 2509 'var(--revx-drawer-padding-bottom)', 2510 'important' 2511 ); 2512 this.style.setProperty( 2513 'padding-left', 2514 'var(--revx-drawer-padding-left)', 2515 'important' 2516 ); 2517 // this.style.setProperty( 'position', 'static', 'important' ); 2518 } ); 2519 // $cloneContent.find( '*' ).each( function () { 2520 // this.style.setProperty( 'position', 'static', 'important' ); 2521 // } ); 2522 2523 $clone.css( { 2524 display: 'flex', 2525 position: 'absolute', 2526 top: '-9999px', 2527 left: '-9999px', 2528 visibility: 'hidden', 2529 bottom: 'unset', 2530 right: 'unset', 2531 } ); 2532 // Append clone to body so you can see it on top 2533 $( 'body' ).append( $clone ); 2534 const cloneHeight = $clone.innerHeight(); 2535 // const cloneHeight = $clone.outerHeight( true ); 2536 $drawerContainer.each( function () { 2537 this.style.setProperty( 'height', cloneHeight + 'px', 'important' ); 2538 this.style.setProperty( 'display', 'flex', 'important' ); 2539 } ); 2540 } 2541 2542 // Open campaign drawer 2543 $( document ).on( 'click', '.revx-drawer-opener', function () { 2544 const $container = $( this ).closest( '.revx-drawer-container' ); 2545 const $drawerContent = $container.find( '.revx-drawer-content' ); 2546 2547 // $drawerContent.each( function () { 2548 // this.style.setProperty( 'transition', 'all 0.2s', 'important' ); 2549 // } ); 2550 $drawerContent.addClass( 'revx-transition' ); 2551 2552 if ( $container.hasClass( 'revx-active' ) ) { 2553 $container.removeClass( 'revx-active' ); 2554 $container.css( { overflow: 'hidden' } ); 2555 // $drawerContent.each( function () { 2556 // this.style.setProperty( 'max-width', '0vh', 'important' ); 2557 // this.style.setProperty( 'padding', '0vh', 'important' ); 2558 // } ); 2559 } else { 2560 $container.addClass( 'revx-active' ); 2561 $container.css( { overflow: 'visible' } ); 2562 // $drawerContent.each( function () { 2563 // this.style.removeProperty( 'max-width' ); 2564 // this.style.removeProperty( 'padding' ); 2565 // } ); 2566 } 2567 } ); 2568 // Close campaign drawer 2569 $( document ).on( 'click', '.revx-drawer-closer', function () { 2570 const $container = $( this ).closest( '.revx-drawer-container' ); 2571 // const $drawerContent = $container.find( '.revx-drawer-content' ); 2572 $container.removeClass( 'revx-active' ); 2573 // $drawerContent.each( function () { 2574 // this.style.setProperty( 'max-width', '0vh', 'important' ); 2575 // this.style.setProperty( 'padding', '0vh', 'important' ); 2576 // } ); 2577 } ); 2578 2579 // Hide Campaign on Close Button Click 2580 $( document ).on( 'click', '.revx-campaign-close', function () { 2581 const campaignID = $( this ).data( 'campaign-id' ); 2582 $( `.revx-campaign-${ campaignID }` ).hide(); 2583 $( 'body' ).css( 'margin-bottom', 0 ); 2584 $( 'body' ).css( 'margin-top', 0 ); 2585 } ); 2586 2587 // Next Order Coupon Copy Clip-Borad 2588 $( '.revx-coupon-copy-btn' ).on( 'click', function () { 2589 const $btn = $( this ); 2590 2591 // // const $content = $btn.closest( '.revx-Coupon-button' ); 2592 // const text = $content 2593 // .clone() // Clone to avoid modifying original 2594 // .children() // Remove children (like .revx-coupon-copy-btn) 2595 // .remove() 2596 // .end() // Go back to cloned parent 2597 // .text() 2598 // .trim(); // Get just the raw text 2599 2600 const $content = $btn.siblings( '.revx-coupon-value' ); 2601 const text = $content.text().trim(); 2602 2603 // Fallback for browsers that do not support navigator.clipboard 2604 const tempInput = $( '<input>' ); 2605 $( 'body' ).append( tempInput ); 2606 tempInput.val( text ).select(); 2607 try { 2608 document.execCommand( 'copy' ); 2609 // $btn.text( 'Copied!' ); 2610 $( '.revx-coupon-value' ).css( 'background-color', '#008000' ); 2611 $( '.revx-coupon-value' ).css( { 2612 transition: 'background-color 0.4s', 2613 } ); 2614 2615 setTimeout( 2616 () => 2617 $( '.revx-coupon-value' ).css( 2618 'background-color', 2619 'unset' 2620 ), 2621 400 2622 ); 2623 } catch ( err ) { 2624 console.error( 'Fallback: Failed to copy:', err ); 2625 } 2626 tempInput.remove(); 2627 } ); 2628 2629 window.Revenue = { 2630 getCookie, 2631 setCookie, 2632 calculateCurrentPrice, 2633 formatPrice, 2634 updatePriceDisplay,
2635 updateMixMatchHeaderAndPrices, 2636 // fbtCalculation, 2637 // updateStyles, 2638 showToast, 2639 }; 2640 // eslint-disable-next-line no-undef 2641 2642 $( '.revx-slider-wrapper' ).each( function () { 2643 if ( ! $( this ).data( 'slider-initialized' ) ) { 2644 $( this ).revxSlider(); 2645 $( this ).data( 'slider-initialized', true ); 2646 } 2647 } ); 2648 2649 // Auto initialize on page load 2650 // function initTimers( $context ) { 2651 // $context.find( '.revx-countdown-timer' ).revxCountdownTimer(); 2652 // } 2653 2654 // $( document ).ready( function () { 2655 // initTimers( $( document ) ); 2656 2657 // // MutationObserver for dynamically added timers 2658 // const observer = new MutationObserver( function ( mutations ) { 2659 // mutations.forEach( function ( mutation ) { 2660 // $( mutation.addedNodes ).each( function () { 2661 // if ( this.nodeType === 1 ) { 2662 // const $el = $( this ); 2663 // if ( $el.is( '.revx-countdown-timer' ) ) { 2664 // $el.revxCountdownTimer(); 2665 // } else { 2666 // initTimers( $el ); 2667 // } 2668 // } 2669 // } ); 2670 // } ); 2671 // } ); 2672 2673 // observer.observe( document.body, { 2674 // childList: true, 2675 // subtree: true, 2676 // } ); 2677 // } ); 2678 2679 function getSelectedAttributes( $form ) { 2680 const selectedData = {}; 2681 $form.find( 'select[name^="attribute_"]' ).each( function () { 2682 const key = $( this ).attr( 'name' ); 2683 selectedData[ key ] = $( this ).val() || ''; 2684 } ); 2685 return selectedData; 2686 } 2687 2688 // Update data-selected-value when any attribute dropdown changes 2689 $( '.product' ).on( 'change', 'select[name^="attribute_"]', function () { 2690 const $form = $( this ).closest( '.product' ); 2691 const selectedData = getSelectedAttributes( $form ); 2692 // Update the data-selected-value attribute 2693 $form.attr( 'data-selected-value', JSON.stringify( selectedData ) ); 2694 } ); 2695} )( jQuery ); 2696 2697// move jquery code from the template1 volume discount selection. 2698jQuery( function ( $ ) { 2699 $( document ).on( 'click', '.revx-volume-discount-item', function () { 2700 const $radio = $( this ).find( '.revx-radio-wrapper' ); 2701 const $attribute = $( this ).find( '.revx-volume-attributes' ); 2702 2703 // deactivate every other option 2704 $( '.revx-radio-wrapper, .revx-volume-attributes' ) 2705 .removeClass( 'revx-active' ) 2706 .addClass( 'revx-inactive' ); 2707 2708 // activate the clicked one 2709 $radio 2710 .add( $attribute ) 2711 .addClass( 'revx-active' ) 2712 .removeClass( 'revx-inactive' ); 2713 } ); 2714} );
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.