1! ( function( $, _undefined ) { 2 3 const $ush = window.$ush || {}; 4 5 // The last AJAX object request. 6 var lastUpdateCartRequest; 7 8 /** 9 * UpSolution WooCommerce elements. 10 * Note: All classes and key elements from WooCommerce are retained 11 * 12 * The code depends on: 13 * - `../plugins/woocommerce/assets/js/frontend/cart.js` 14 * - `../plugins/woocommerce/assets/js/frontend/checkout.js` 15 * 16 * @requires $us.$body 17 * @requires $us.$canvas 18 * @requires $ush.debounce 19 * @requires $ush.timeout 20 */ 21 function WooCommerce() { 22 const self = this; 23 24 // Elements 25 self.$cart = $( '.w-cart' ); 26 self.$cartCloser = $( '.w-cart-closer', self.$cart ); 27 self.$cartLink = $( '.w-cart-link', self.$cart ); 28 self.$pageContent = $( '#page-content' ); 29 self.$notice = $( '.w-wc-notices.woocommerce-notices-wrapper:first', $us.$canvas ); 30 self.$addToCart = $( '.w-post-elm.add_to_cart', $us.$canvas ); 31 32 // Private "Variables" 33 self.isCartLayoutPanel = self.$cart.hasClass( 'layout_left_panel' ) || self.$cart.hasClass( 'layout_right_panel' ); 34 self.isCartOpen = false; 35 self.restVars = {}; 36 37 // Event handlers 38 self._events = { 39 ajaxAddToCart: self.ajaxAddToCart.bind( self ), 40 applyCouponCode: self.applyCouponCode.bind( self ), 41 changeCartQuantity: self.changeCartQuantity.bind( self ), 42 couponCodeChange: self.couponCodeChange.bind( self ), 43 couponDisplaySwitch: self.couponDisplaySwitch.bind( self ), 44 enterCouponCode: self.enterCouponCode.bind( self ), 45 showNotification: self.showNotification.bind( self ), 46 loginFieldKeydown: self.loginFieldKeydown.bind( self ), 47 minusCartQuantity: self.minusCartQuantity.bind( self ), 48 moveNotifications: self.moveNotifications.bind( self ), 49 clickOutsideCart: self.clickOutsideCart.bind( self ), 50 plusCartQuantity: self.plusCartQuantity.bind( self ), 51 removeCartItem: self.removeCartItem.bind( self ), 52 showLoginForm: self.showLoginForm.bind( self ), 53 submitLoginForm: self.submitLoginForm.bind( self ), 54 updateCart: self.updateCart.bind( self ), 55 updatedCartTotals: self.updatedCartTotals.bind( self ), 56 showCartOnClick: self.showCartOnClick.bind( self ), 57 showCartOnKeyup: self.showCartOnKeyup.bind( self ), 58 hideCartOnKeyup: self.hideCartOnKeyup.bind( self ), 59 redirectToCartOnMobile: self.redirectToCartOnMobile.bind( self ), 60 }; 61 62 // Init cart elements 63 if ( self.isCart() ) { 64 65 self.$cartNotification = $( '.w-cart-notification', self.$cart ); 66 67 // Events 68 self.$cartNotification.on( 'click', () => { 69 self.$cartNotification.fadeOutCSS(); 70 } ); 71 72 // Cart closer button click events for both mobiles and desktops 73 if ( self.isCartLayoutPanel ) { 74 self.$cartCloser.on( 'click', () => self._toggleCart( false ) ); 75 } 76 77 // Redirect to cart page on mobile devices. 78 if ( $.isMobile ) { 79 self.$cart.on( 'click', '.w-cart-link', self._events.redirectToCartOnMobile ); 80 81 // Handle click event on desktop devices with panel layouts 82 } else { 83 if ( self.$cart.hasClass( 'drop_on_click' ) || self.$cart.is( '[class*="_panel"]' ) ) { 84 // need to separate these events because keyup event also manages focus 85 self.$cartLink.on( 'keyup', self._events.showCartOnKeyup ); 86 self.$cartLink.on( 'click', self._events.showCartOnClick ); 87 } 88 89 $us.$body.on( 'keyup', self._events.hideCartOnKeyup ); 90 91 // Cart closer button events for desktops only 92 if ( self.isCartLayoutPanel ) { 93 self.$cartCloser.on( 'keyup', ( e ) => { 94 if ( e.keyCode === $ush.ENTER_KEYCODE ) { 95 self._toggleCart( false, self.$cartLink ); 96 } 97 } ); 98 } 99 } 100 101 $us.$body 102 // Events of `../plugins/woocommerce/assets/js/frontend/add-to-cart.js`, 103 // `../plugins/woocommerce/assets/js/frontend/cart-fragments.js` 104 .on( 'wc_fragments_loaded wc_fragments_refreshed', self._events.updateCart ) 105 // Events of `../plugins/woocommerce/assets/js/frontend/add-to-cart.js` 106 .on( 'added_to_cart', self._events.showNotification ) 107 .on( 'removed_from_cart', self._events.updateCart ); 108 } 109 110 if ( self.isCartPage() ) { 111 112 // REST API vars 113 self.restVars = $( '.w-cart-table.woocommerce-cart-form:visible', $us.$canvas ) 114 .data( 'rest-vars' ); 115 116 // Events 117 $us.$body 118 .on( 'change', '.w-wc-coupon-form input', self._events.couponCodeChange ) 119 .on( 'keyup', '.w-wc-coupon-form input', self._events.enterCouponCode ) 120 .on( 'click', '.w-wc-coupon-form button', self._events.applyCouponCode ) 121 .on( 'click', 'a.remove', self._events.removeCartItem ) 122 // Events of `../plugins/woocommerce/assets/js/frontend/cart.js`
123 .on( 'applied_coupon removed_coupon', self._events.couponDisplaySwitch ) 124 .on( 'updated_cart_totals', self._events.updatedCartTotals ); 125 126 // Initializing controls after the ready document 127 $( 'input.qty', $us.$canvas ).trigger( 'initControls' ); 128 129 // Get the last active request for cart updates 130 $.ajaxPrefilter( ( _, originalOptions, jqXHR ) => { 131 const data = $ush.toString( originalOptions.data ); 132 if ( data.indexOf( '&update_cart' ) > -1 ) { 133 lastUpdateCartRequest = jqXHR; 134 } 135 // Distance information updates in shortcode `[us_cart_shipping]` 136 if ( data.indexOf( '&us_calc_shipping' ) > -1 ) { 137 jqXHR.done( ( res ) => { 138 $( '.w-cart-shipping .woocommerce-shipping-destination' ) 139 .html( $( '.w-cart-shipping:first .woocommerce-shipping-destination', res ).html() ); 140 } ); 141 } 142 } ); 143 144 $( '.w-cart-shipping form.woocommerce-shipping-calculator', $us.$canvas ) 145 .append( '<input type="hidden" name="us_calc_shipping">' ); 146 } 147 148 if ( self.isCheckoutPage() ) { 149 // Events 150 $us.$body 151 .on( 'change', '.w-wc-coupon-form input', self._events.couponCodeChange ) 152 .on( 'keyup', '.w-wc-coupon-form input', self._events.enterCouponCode ) 153 .on( 'click', '.w-wc-coupon-form button', self._events.applyCouponCode ) 154 // Events of `../plugins/woocommerce/assets/js/frontend/checkout.js` 155 .on( 'applied_coupon_in_checkout removed_coupon_in_checkout', self._events.couponDisplaySwitch ) 156 .on( 'applied_coupon_in_checkout removed_coupon_in_checkout checkout_error', self._events.moveNotifications ) 157 .on( 'click', '.w-checkout-login .showlogin', self._events.showLoginForm ) 158 .on( 'click', '.w-checkout-login button', self._events.submitLoginForm ) 159 .on( 'keydown', '.w-checkout-login input, .w-checkout-login button', self._events.loginFieldKeydown ); 160 161 // Blocks the form from being submitted if the coupon field is in focus 162 // and the Enter key is pressed, this allows the coupon to be applied 163 // correctly, otherwise the form will simply be submitted. 164 const $couponField = $( '.w-wc-coupon-form input', $us.$canvas ); 165 $us.$document.on( 'keypress', ( e ) => { 166 if ( e.keyCode === $ush.ENTER_KEYCODE && $couponField.is( ':focus' ) ) { 167 e.preventDefault(); 168 } 169 } ); 170 } 171 172 // "Add to cart" via Ajax 173 if( $us.$body.hasClass( 'us-ajax_add_to_cart' ) ) { 174 $us.$body.on( 'click', '.single_add_to_cart_button:not(.disabled)', self._events.ajaxAddToCart ); 175 } 176 177 // Quantity Events 178 $us.$body 179 .on( 'click', '.quantity input.minus', self._events.minusCartQuantity ) 180 .on( 'click', '.quantity input.plus', self._events.plusCartQuantity ) 181 .on( 'change initControls', '.quantity input.qty', self._events.changeCartQuantity ); 182 183 $us.$document.on( 'ajaxComplete', ( _, jqXHR, settings ) => { 184 185 if ( 186 settings.url.includes( 'add_to_cart' ) 187 && self.$cart.hasClass( 'open_on_ajax' ) 188 && self.isCartLayoutPanel 189 ) { 190 self._toggleCart( true ); 191 // waiting for transition - cannot focus element with visibility:hidden 192 self.$cart.on( 'transitionend', () => { 193 self.$cartCloser[0].focus(); 194 self.$cart.off( 'transitionend' ); 195 } ); 196 } 197 198 // Skip JSON string 199 if ( String( jqXHR.responseText ).charAt(0) === '{' ) { 200 return; 201 } 202 203 const $fragment = $( new DocumentFragment ).append( jqXHR.responseText ); 204 205 // Updated only visible "Cart Totals" elements, bypassing WooCommerce core logic. See #3935 for details. 206 if ( self.isCartPage() && $( '.cart_totals', $fragment ).length > 1 ) { 207 const notices = self.$notice.html(); 208 self.$pageContent.html( $( '.l-main', $fragment ) ); 209 self.$notice = $( '.w-wc-notices.woocommerce-notices-wrapper:first', $us.$canvas ); 210 // Restore notifications 211 if ( notices ) { 212 self.$notice.html( notices ); 213 } 214 } 215 216 // Intercept messages after apply a coupon. 217 if ( String( settings.url ).includes( 'wc-ajax=apply_coupon' ) ) { 218 const $message = $( '.woocommerce-error, .woocommerce-message', $fragment ); 219 if ( $message.length > 0 ) { 220 self.$notice.html( $message.clone() ); 221 } else { 222 self.$notice.html( '' ); 223 } 224 } 225 } ); 226 } 227 228 // Checkout/Cart Page 229 $.extend( WooCommerce.prototype, { 230 231 /** 232 * Determines if is cart. 233 * 234 * @return {boolean} True if cartesian, False otherwise. 235 */ 236 isCart: function() { 237 return this.$cart.length > 0;
238 }, 239 240 /** 241 * Determines if current cartesian page. 242 * 243 * @return {boolean} True if current cartesian page, False otherwise. 244 */ 245 isCartPage: function() { 246 return $us.$body.hasClass( 'woocommerce-cart' ); 247 }, 248 249 /** 250 * Determines if current checkout page. 251 * 252 * @return {boolean} True if current checkout page, False otherwise 253 */ 254 isCheckoutPage: function() { 255 return $us.$body.hasClass( 'woocommerce-checkout' ); 256 }, 257 258 /** 259 * Update cart elements. 260 */ 261 updateCart: function() { 262 const self = this; 263 $.each( self.$cart, ( _, cart ) => { 264 var $cart = $( cart ), 265 $cartQuantity = $( '.w-cart-quantity', $cart ), 266 miniCartAmount = $( '.us_mini_cart_amount:first', $cart ).text(); 267 268 if ( 269 $cart.hasClass( 'opened' ) 270 && ! $cart.hasClass( 'drop_on_click' ) 271 && ! $cart.hasClass( 'layout_left_panel' ) 272 && ! $cart.hasClass( 'layout_right_panel' ) 273 ) { 274 $cart.removeClass( 'opened' ); 275 } 276 277 if ( miniCartAmount !== _undefined ) { 278 miniCartAmount = String( miniCartAmount ).match( /\d+/g ); 279 $cartQuantity.html( miniCartAmount > 0 ? miniCartAmount : '0' ); 280 $cart[ miniCartAmount > 0 ? 'removeClass' : 'addClass' ]( 'empty' ); 281 } else { 282 // fallback in case our action wasn't fired somehow 283 var total = 0; 284 $( '.quantity', $cart ).each( ( _, quantity ) => { 285 var matches = String( quantity.innerText ).match( /\d+/g ); 286 287 if ( matches ) { 288 total += parseInt( matches[0], 10 ); 289 } 290 } ); 291 $cartQuantity.html( total > 0 ? total : '0' ); 292 $cart[ total > 0 ? 'removeClass' : 'addClass' ]( 'empty' ); 293 } 294 } ); 295 }, 296 297 /** 298 * Show notifications after adding product to cart. 299 * 300 * @event handler 301 * @param {Event} e 302 * @param {{} fragments 303 * @param {node} $button 304 */ 305 showNotification: function( e, fragments, _, $button ) { 306 if ( $ush.isUndefined( e ) ) { 307 return; 308 } 309 const self = this; 310 311 // Update cart element 312 self.updateCart(); 313 314 const $notice = self.$cartNotification; 315 // NOTE: Get product name in product page or popup when available. 316 const theProductName = $button 317 .closest( '.product, .w-popup-box-content' ) 318 .find( '.woocommerce-loop-product__title, .w-post-elm.post_title' ) 319 .first() 320 .text(); 321 322 $( '.product-name', $notice ).html( `"${theProductName}"` ); 323 324 // Skip show message 325 if ( $notice.hasClass( 'skip_message' ) ) { 326 $notice.removeClass( 'skip_message' ); 327 return; 328 } 329 330 $notice.addClass( 'shown' ); 331 $notice.on( 'mouseenter', () => $notice.removeClass( 'shown' ) ); 332 333 $ush.timeout( () => { 334 $notice 335 .removeClass( 'shown' ) 336 .off( 'mouseenter' ); 337 }, 3000 ); 338 }, 339 340 /** 341 * Handler when remove a item. 342 * 343 * @event handler 344 * @param {Event} e 345 */ 346 removeCartItem: function( e ) { 347 var $item = $( e.target ) 348 .closest( '.cart_item' ) 349 .addClass( 'change_process' ); 350 // If the element is the last, then delete the table for correct operation `cart.js:update_wc_div` 351 if ( ! $item.siblings( '.cart_item:not(.change_process)' ).length ) { 352 $( '.w-cart-table', $us.$canvas ).remove(); 353 } 354 }, 355 356 /** 357 * Check and set quantity. 358 * 359 * @event handler 360 * @param {Event} e 361 */ 362 changeCartQuantity: function( e ) { 363 364 if ( $us.usbPreview() ) { 365 return; 366 } 367 368 const self = this; 369 370 const $input = $( e.target ); 371 const isGroupTable = $input.closest( '.cart' ).hasClass( 'grouped_form' ); 372 const max = $ush.parseInt( $input.attr( 'max' ) ) || -1; 373 const min = $ush.parseInt( $input.attr( 'min' ) ) || ( isGroupTable ? 0 : 1 ); 374 375 var value = $ush.parseInt( $input.val() ); 376 377 if ( $input.is( ':disabled' ) ) { 378 return; 379 } 380 if ( min >= value ) { 381 value = min; 382 } 383 if ( max > 1 && value >= max ) { 384 value = max; 385 } 386 if ( value != $input.val() ) { 387 $input.val( value ); 388 } 389 390 $input 391 .siblings( 'input.plus:first' ) 392 .prop( 'disabled', ( max > 0 && value >= max ) ); 393 $input 394 .siblings( 'input.minus:first' ) 395 .prop( 'disabled', ( value <= min ) ); 396 397 // If the event type is `initControls` then this is the 398 // first init when loading the document 399 if ( e.type == 'initControls' ) { 400 return; 401 } 402 403 // Add a flag that there was a change in the quantity to the cart elements 404 $( 'input[name=us_cart_quantity]', $us.$canvas ).val( true ); 405 406 if ( self.isCartPage() ) { 407 self.restApiUpdateCartForm( $input, value ); 408 } 409 }, 410 411 /** 412 * Update cart form (cart table, cart totals) via REST API 413 * 414 * If the REST API is allowed, it will be used 415 * Otherwise the "update cart" button click will be used (updateCartForm) 416 */ 417 restApiUpdateCartForm: function( $input, value ) { 418 const self = this; 419 420 const productKey = $input.closest( '.product-quantity' ).data( 'product-key' );
421 const $cartItem = $input.closest( '.woocommerce-cart-form__cart-item.cart_item' ); 422 const $cartTotals = $( '.w-cart-totals:visible', $us.$canvas ); // Define it here because cart totals may be overridden by other requests 423 424 // Abort previous cart update request 425 if ( typeof self.lastRestRequest?.abort === 'function' ) { 426 self.lastRestRequest.abort(); 427 } 428 429 // Block cart totals and show spinner in WooCommerce way 430 if ( typeof $.fn.block === 'function' && $cartTotals.length ) { 431 $cartTotals.addClass( 'processing' ).block( 432 { 433 message: null, 434 overlayCSS: { 435 opacity: 0.6, 436 }, 437 } 438 ); 439 } 440 441 // Request to store API cart update 442 self.lastRestRequest = $.ajax( { 443 url: `${self.restVars.siteUrl}/wp-json/wc/store/v1/cart/update-item`, 444 method: 'POST', 445 dataType: 'json', 446 data: { 447 key: productKey, 448 quantity: value, 449 }, 450 headers: { 451 'Nonce': self.restVars.nonce, 452 }, 453 success: function( data ) { 454 455 // Find updated item in response 456 const updatedItem = data.items?.find( ( item ) => ( item.key === productKey ) ); 457 458 // Update item subtotal using formatPrice 459 if ( updatedItem && updatedItem.totals ) { 460 const subtotalData = { 461 price: updatedItem.totals.line_subtotal || '', 462 precision: updatedItem.totals.currency_minor_unit || 2, 463 currency_code: updatedItem.totals.currency_code || '', 464 currency_symbol: updatedItem.totals.currency_symbol || '', 465 currency_prefix: updatedItem.totals.currency_prefix || '', 466 currency_suffix: updatedItem.totals.currency_suffix || '' 467 }; 468 $cartItem.find( '.product-subtotal bdi' ).html( self.formatPrice( subtotalData ) ); 469 } 470 471 // Update cart totals section 472 if ( data.totals ) { 473 const subtotalData = { 474 price: data.totals.total_items || '', 475 precision: data.totals.currency_minor_unit || 2, 476 currency_code: data.totals.currency_code || '', 477 currency_symbol: data.totals.currency_symbol || '', 478 currency_prefix: data.totals.currency_prefix || '', 479 currency_suffix: data.totals.currency_suffix || '' 480 }; 481 482 if ( $cartTotals.length ) { 483 // Update subtotal price 484 $( '.cart-subtotal bdi', $cartTotals ).html( self.formatPrice( subtotalData ) ); 485 486 // Update total price 487 const totalData = { ...subtotalData, price: data.totals.total_price }; 488 $( '.order-total bdi', $cartTotals ).html( self.formatPrice( totalData ) ); 489 490 // Update coupon total discount 491 const discountData = { ...subtotalData, price: data.totals.total_discount }; 492 $( '.cart-discount .woocommerce-Price-amount', $cartTotals ).html( self.formatPrice( discountData ) ); 493 494 $us.$body.trigger( 'updated_cart_totals' ); 495 } 496 } 497 498 // Trigger WooCommerce updated event for compatibility 499 $us.$body.trigger( 'updated_wc_div' ); 500 }, 501 error: function() { 502 // Fallback to traditional form update on error (if REST API is blocked) 503 if ( ! $( '.w-cart-table', $us.$canvas ).hasClass( 'processing' ) ) { 504 self.updateCartForm_long( self.updateCartForm.bind( self ) ); 505 } else { 506 self.updateCartForm(); 507 } 508 }, 509 complete: function( _, textStatus) { 510 // Unblock cart totals 511 if ( typeof $.fn.unblock === 'function' && textStatus === 'success' && $cartTotals.length ) { 512 $cartTotals.removeClass( 'processing' ).unblock(); 513 } 514 } 515 } ); 516 }, 517 518 /** 519 * Format price with priceData from REST API response 520 * Analogous to "formatted-monetary-amount" func from WooCommerce blocks 521 * 522 * Variables: https://developer.woocommerce.com/docs/apis/store-api/extending-store-api/extend-store-api-formatters/#currencyformatter 523 * 524 * @param priceData - object containing price data to adjust format 525 */ 526 formatPrice: function( priceData ) { 527 if ( ! priceData.price ) { 528 return ''; 529 } 530 531 const numericPrice = $ush.parseFloat( priceData.price ) / Math.pow( 10, priceData.precision ); 532 533 // Format only the numeric part with locale aware separators
534 const formattedNumber = numericPrice.toLocaleString( 535 document.documentElement.lang, 536 { 537 minimumFractionDigits: priceData.precision, 538 maximumFractionDigits: priceData.precision, 539 useGrouping: true 540 } 541 ); 542 543 var prefix = priceData.currency_prefix, 544 suffix = priceData.currency_suffix; 545 546 // Wrap currency symbol in WooCommerce compatible span 547 if ( priceData.currency_symbol ) { 548 if ( prefix.indexOf( priceData.currency_symbol ) !== -1 ) { 549 prefix = prefix.split( priceData.currency_symbol ).join( 550 `<span class="woocommerce-Price-currencySymbol">${priceData.currency_symbol}</span>` 551 ); 552 } 553 if ( suffix.indexOf( priceData.currency_symbol ) !== -1 ) { 554 suffix = suffix.split( priceData.currency_symbol ).join( 555 `<span class="woocommerce-Price-currencySymbol">${priceData.currency_symbol}</span>` 556 ); 557 } 558 } 559 560 return prefix + formattedNumber + suffix; 561 }, 562 563 /** 564 * Decreasing quantity item in cart. 565 * 566 * @event handler 567 * @param {Event} e 568 */ 569 minusCartQuantity: function( e ) { 570 const self = this; 571 572 var $target = $( e.target ), 573 $input = $target.siblings( 'input.qty:first' ); 574 575 if ( ! $input.length ) { 576 return; 577 } 578 579 const step = $ush.parseInt( $input.attr( 'step' ) || 1 ); 580 $input // Update quantity 581 .val( $ush.parseInt( $input.val() ) - step ) 582 .trigger( 'change' ); 583 }, 584 585 /** 586 * Increasing quantity item in cart. 587 * 588 * @event handler 589 * @param {Event} e 590 */ 591 plusCartQuantity: function( e ) { 592 const self = this; 593 594 var $target = $( e.target ), 595 $input = $target.siblings( 'input.qty:first' ); 596 597 if ( ! $input.length ) { 598 return; 599 } 600 601 const step = $ush.parseInt( $input.attr( 'step' ) || 1 ); 602 $input 603 .val( $ush.parseInt( $input.val() ) + step ) 604 .trigger( 'change' ); 605 }, 606 607 /** 608 * Update the cart form by means of WooCommerce 609 * Note: The code is moved to a separate function since `debounced` 610 * must be initialized before calling 611 * 612 * @param {function} fn The function to be executed 613 * @type debounced 614 */ 615 updateCartForm_long: $ush.debounce( $ush.fn, /* wait */50 ), 616 617 /** 618 * Update the cart form by means of WooCommerce. 619 */ 620 updateCartForm: function() { 621 const self = this; 622 // Abort previous cart update request 623 if ( typeof ( lastUpdateCartRequest || {} ).abort === 'function' ) { 624 lastUpdateCartRequest.abort(); 625 } 626 // Initialize cart update 627 $( '.w-cart-table > button[name=update_cart]', $us.$canvas ) 628 .removeAttr( 'disabled' ) 629 .trigger( 'click' ); 630 }, 631 632 /** 633 * Update cart totals. 634 * 635 * @event handler 636 */ 637 updatedCartTotals: function() { 638 const self = this; 639 // Reset last active request 640 if ( lastUpdateCartRequest ) { 641 lastUpdateCartRequest = _undefined; 642 } 643 // Removing animated class if any element had it 644 var wooElementClasses = [ 645 'w-cart-shipping', 646 'w-cart-table', 647 'w-cart-totals', 648 'w-checkout-billing', 649 'w-checkout-order-review', 650 'w-checkout-payment', 651 'w-wc-coupon-form', 652 ]; 653 for ( const i in wooElementClasses ) { 654 $( `.${wooElementClasses[i]}.us_animate_this`, $us.$canvas ).removeClass( 'us_animate_this' ); 655 } 656 657 // Shipping element sync after totals update 658 const $shipping = $( '.w-cart-shipping .shipping', $us.$canvas ); 659 if ( ! $shipping.length ) { 660 return; 661 } 662 $shipping.html( $( '.w-cart-totals .shipping:first', $us.$canvas ).html() ); 663 }, 664 665 /** 666 * Enter the coupon code in the field. 667 * 668 * @event handler 669 * @param {Event} e 670 */ 671 couponCodeChange: function( e ) { 672 // Transit value to the cart form to add a coupon by WooCommerce logic 673 $( '.w-cart-table, form.checkout_coupon:first', $us.$canvas )
674 .find( 'input[name=coupon_code]' ) 675 .val( e.target.value ); 676 }, 677 678 /** 679 * Enter a coupon code. 680 * 681 * @event handler 682 * @param {Event} e 683 */ 684 enterCouponCode: function( e ) { 685 if ( e.keyCode === $ush.ENTER_KEYCODE ) { 686 $( e.target ) 687 .trigger( 'change' ) 688 .siblings( 'button:first' ) 689 .trigger( 'click' ); 690 } 691 }, 692 693 /** 694 * Click on the "Apply Coupon" button. 695 * 696 * @event handler 697 * @param {Event} e 698 */ 699 applyCouponCode: function( e ) { 700 // Stop event (Important on the checkout page) 701 e.stopPropagation(); 702 e.preventDefault(); 703 // Initialize coupon additions using WooCommerce logic 704 $( '.w-cart-table, form.checkout_coupon:first', $us.$canvas ) 705 .find( 'button[name=apply_coupon]' ) 706 .trigger( 'click' ); 707 // Clear input field 708 $( e.target ).closest( '.w-wc-coupon-form' ).find( 'input:first' ).val( '' ); 709 }, 710 711 /** 712 * Coupon form display switch. 713 * 714 * @param {Event} e 715 */ 716 couponDisplaySwitch: function( e ) { 717 const $coupon = $( '.w-wc-coupon-form', $us.$canvas ); 718 if ( ! $coupon.length ) { 719 return; 720 } 721 // Add a class if the coupon is applied 722 if ( e.type.indexOf( 'applied_coupon' ) > -1 && ! $( '.woocommerce-error', $us.$canvas ).length ) { 723 $coupon.addClass( 'coupon_applied' ); 724 } 725 // Remove a class if all coupons were removed 726 if ( e.type.indexOf( 'removed_coupon' ) > -1 && $( '.woocommerce-remove-coupon', $us.$canvas ).length <= 1 ) { 727 $coupon.removeClass( 'coupon_applied' ); 728 } 729 }, 730 731 /** 732 * Move notifications to `[wc_notices...]`. 733 * 734 * @event handler 735 * @param {Event} e 736 * @param {String} err_html The value is needed for checkout_error. 737 */ 738 moveNotifications: function( e, err_html ) { 739 const self = this; 740 741 // Do not proceed with notices adjustment if there are no US Cart / Checkout elements on the page 742 if ( ! self.$notice.length ) { 743 var $cartTotals = $( '.w-cart-totals', $us.$canvas ), 744 $checkoutPayment = $( '.w-checkout-payment', $us.$canvas ); 745 if ( ! $cartTotals.length && ! $checkoutPayment.length ) { 746 return; 747 } 748 } 749 750 // Get notice 751 var $message; 752 if ( e.type === 'checkout_error' && err_html ) { 753 $message = $( err_html ); 754 } else { 755 $message = $( '.woocommerce-error, .woocommerce-message', $us.$canvas ); 756 } 757 758 // Show notification 759 if ( $message.length > 0 ) { 760 self.$notice.html( $message.clone() ); 761 } 762 $message.remove(); 763 764 // Remove NoticeGroup 765 if ( e.type === 'checkout_error' ) { 766 $( '.woocommerce-NoticeGroup-checkout' ).remove(); 767 } 768 }, 769 } ); 770 771 // Cart element 772 $.extend( WooCommerce.prototype, { 773 774 /** 775 * "Add to cart" via Ajax. 776 * 777 * @event handler 778 * @param {Event} e 779 */ 780 ajaxAddToCart: function( e ) { 781 const self = this; 782 783 const $button = $( e.currentTarget ); 784 const $form = $button.closest( 'form.cart' ); 785 786 /** 787 * Exclude specific product types from ajax as they needed additional processing 788 * Excluded product types: 789 * - External/Affiliate 790 * - Grouped 791 * - Composite 792 */ 793 if ( 794 $form.is( '[method=get]' ) 795 || self.$pageContent.hasClass( 'product-type-grouped' ) 796 || self.$pageContent.hasClass( 'product-type-composite' ) 797 || $form.hasClass( 'grouped_form' ) 798 || $form.hasClass( 'composite_form' ) 799 ) { 800 return; 801 } 802 803 e.preventDefault(); 804 805 // Add text wrapper and preloader 806 if ( $( '.g-preloader', $button ).length === 0 ) { 807 $button.html( `<div class="g-preloader type_1"><div></div></div><span class="w-btn-label">${$button.html()}</span>` ); 808 } 809 810 var data = {}; 811 812 // Serialize Form 813 const formData = new FormData( $form[0], $button[0] ); 814 815 formData.forEach( ( value, key ) => { 816 if ( key.includes( '[' ) ) { 817 const keys = key.split( /\[|\]/ ).filter( ( k ) => { return k; } ); 818 keys.forEach( ( k, index ) => { 819 if ( index === keys.length -1 ) { 820 if ( Array.isArray( data[ k ] ) ) { 821 data[ k ].push( value ); 822 } else if ( data[ k ] ) { 823 data[ k ] = [ data[ k ], value ]; 824 } else { 825 data[ k ] = key.includes( '[]' ) ? [ value ] : value; // кey includes [] without a k, ie: field[]. 826 } 827 } else { 828 if ( ! data[ k ] ) { 829 data[ k ] = {}; 830 } 831 data = data[ k ]; 832 } 833 } ); 834 } else { 835 if ( data[ key ] ) { 836 if ( Array.isArray( data[ key ] ) ) { 837 data[ key ].push( value ); 838 } else { 839 data[ key ] = [ data[ key ], value ]; 840 } 841 } else { 842 data[ key ] = value; 843 } 844 } 845 } ); 846 847 // Check if product_id exists, if not add it with the value of add-to-cart. Use variation_id for variable products. 848 if ( data['variation_id'] ) { 849 data['product_id'] = data['variation_id']; 850 } else if ( ! data['product_id'] && data['add-to-cart'] ) { 851 data['product_id'] = data['add-to-cart']; 852 } 853 854 delete data['add-to-cart']; // need to remove this so that the form handler doesn't try to add the product to the cart again. 855 856 // Events of `../plugins/woocommerce/assets/js/frontend/add-to-cart.js` 857 $us.$body.trigger( 'adding_to_cart', $button, data ); 858 859 $.ajax( { 860 type: 'POST',
861 url: String( woocommerce_params.wc_ajax_url ).replace( '%%endpoint%%', 'add_to_cart' ), 862 data: data, 863 beforeSend: () => { 864 $button 865 .removeClass( 'added' ) 866 .addClass( 'loading' ); 867 }, 868 complete: ( jqXHR ) => { 869 // Redirect to the cart page after successful addition 870 if ( String( jqXHR.responseText ).includes( 'us_redirect_to_cart' ) ) { 871 return; 872 } 873 $button 874 .addClass( 'added' ) 875 .removeClass( 'loading' ); 876 }, 877 success: ( res ) => { 878 if ( res.error && res.product_url ) { 879 window.location = res.product_url; 880 return; 881 } 882 883 // Redirect to the cart page after successful addition 884 if ( res.fragments && res.fragments[ 'us_redirect_to_cart' ] ) { 885 window.location.replace( res.fragments[ 'us_redirect_to_cart' ] ); 886 return; 887 } 888 889 // Skip default message 890 if ( self.isCart() ) { 891 self.$cartNotification.addClass( 'skip_message' ); 892 } 893 894 $us.$body.trigger( 'added_to_cart', [ res.fragments, res.cart_hash, $button ] ); 895 896 // Show Cart content after clicking "Add to Cart" button 897 if ( self.isCart() && self.$cart.hasClass( 'open_on_ajax' ) ) { 898 self._toggleCart( /* open */true ); 899 } 900 901 var message = ''; 902 903 if ( self.isCart() ) { 904 message = self.$cartNotification.text(); 905 } 906 907 // Get "View cart" 908 var $viewCart = $button.next( '.added_to_cart.wc-forward' ).removeClass( 'added_to_cart' ); 909 if ( $viewCart.length ) { 910 message += ' ' + $viewCart.prop( 'outerHTML' ); 911 $viewCart.remove(); 912 } 913 914 // Show notice 915 $form.next( '.woocommerce-notices-wrapper' ).remove(); 916 $form.after( ` 917 <div class="woocommerce-notices-wrapper"> 918 <div class="woocommerce-message" role="alert" tabindex="-1">${message}</div> 919 </div> 920 ` ); 921 }, 922 } ); 923 }, 924 925 /** 926 * Handler for outside click events for both mobile and desktop devices. 927 * 928 * @event handler 929 * @param {Event} e 930 */ 931 clickOutsideCart: function( e ) { 932 const self = this; 933 if ( $.contains( self.$cart[0], e.target ) ) { 934 return; 935 } 936 self._toggleCart( /* open */false ); 937 }, 938 939 /** 940 * Redirect to cart page on mobile devices. 941 * 942 * @event handler 943 * @param {Event} e 944 */ 945 redirectToCartOnMobile: function ( e ) { 946 947 e.preventDefault(); 948 949 const cartUrl = String( ( window.wc_add_to_cart_params || {} ).cart_url ); 950 if ( cartUrl.includes( window.location.host ) ) { 951 window.location.replace( cartUrl ); 952 } 953 }, 954 955 /** 956 * Show the cartesian on click. 957 * 958 * @event handler 959 * 960 * @param {Event} e 961 */ 962 showCartOnClick: function( e ) { 963 const self = this; 964 965 e.preventDefault(); 966 967 // Enter press generates 'click' event firing two events in a row so need to distinguish these events 968 if ( [ 'mouse', 'touch', 'pen' ].includes( e.pointerType ) ) { 969 self._toggleCart( ! self.isCartOpen ); 970 } 971 }, 972 973 /** 974 * Show the cartesian on keyup. 975 * 976 * @event handler 977 * 978 * @param {Event} e 979 */ 980 showCartOnKeyup: function( e ) { 981 const self = this; 982 983 e.preventDefault(); 984 985 if ( e.keyCode !== $ush.ENTER_KEYCODE ) { 986 return; 987 } 988 989 if ( ! self.isCartOpen ) { 990 if ( self.$cartCloser.length > 0 ) { 991 // waiting for transition - cannot focus element with visibility:hidden 992 self.$cart.on( 'transitionend', () => { 993 self.$cartCloser[0].focus(); 994 self.$cart.off( 'transitionend' ); 995 } ); 996 } 997 } 998 999 self._toggleCart( ! self.isCartOpen ); 1000 }, 1001 1002 /** 1003 * Hide the cart on keyup. 1004 * 1005 * @event handler 1006 * 1007 * @param {Event} e 1008 */ 1009 hideCartOnKeyup: function( e ) { 1010 const self = this; 1011 if ( e.keyCode === $ush.ESC_KEYCODE ) { 1012 self._toggleCart( false, self.$cartLink ); 1013 } 1014 }, 1015 1016 /** 1017 * @param {Boolean} open 1018 * @param {*} $elementToFocus 1019 */ 1020 _toggleCart: function( open = true, $elementToFocus = null ) { 1021 const self = this; 1022 1023 self.$cart.toggleClass( 'opened', open ); 1024 self.$cartLink.attr( 'aria-expanded', open ); 1025 1026 if ( self.isCartOpen && $elementToFocus !== null ) { 1027 $elementToFocus.focus(); 1028 } 1029 1030 self.isCartOpen = open; 1031 1032 if ( self.$cart.hasClass( 'layout_dropdown' ) && self.$cart.hasClass( 'drop_on_click' ) ) { 1033 if ( open ) { 1034 $us.$body.on( 'mouseup touchend.noPreventDefault', self._events.clickOutsideCart ); 1035 } else { 1036 $us.$body.off( 'mouseup touchend.noPreventDefault', self._events.clickOutsideCart ); 1037 } 1038 } 1039 } 1040 } ); 1041 1042 // Login Form 1043 $.extend( WooCommerce.prototype, { 1044 1045 /** 1046 * Show the login form. 1047 * 1048 * @event handler 1049 * 1050 * @return {Boolean} 1051 */ 1052 showLoginForm: function() { 1053 $( '.woocommerce-form-login' ).toggleClass( 'hidden' ); 1054 return false; 1055 }, 1056 1057 /** 1058 * Submit the login form. 1059 * 1060 * @event handler 1061 * 1062 * @return {Boolean} 1063 */ 1064 submitLoginForm: function() { 1065 const self = this; 1066 // Prevent double sending 1067 if ( self.isSubmittingLoginForm ) { 1068 return false; 1069 } 1070 self.isSubmittingLoginForm = true; 1071 1072 // Get the form substitute view and all its fields 1073 var $formView = $( '.w-checkout-login' ), 1074 $usernameField = $( '#us_checkout_login_username', $formView ), 1075 $passwordField = $( '#us_checkout_login_password', $formView ), 1076 $redirectField = $( '#us_checkout_login_redirect', $formView ), 1077 $nonceField = $( '#us_checkout_login_nonce', $formView ); 1078 1079 // Make sure all fields are present 1080 if ( 1081 $usernameField.length == 0 1082 || $passwordField.length == 0 1083 || $redirectField.length == 0 1084 || $nonceField.length == 0 1085 ) { 1086 return false; 1087 } 1088 1089 // Append a new form with needed fields to <body> and submit it 1090 var fields = { 1091 'login': 'Login', 1092 'rememberme': 'forever', 1093 'username': $usernameField.val(), 1094 'password': $passwordField.val(), 1095 'redirect': $redirectField.val(), 1096 'woocommerce-login-nonce': $nonceField.val(), 1097 }, 1098 $form = $( '<form>', { 1099 method: 'post' 1100 } ); 1101 $.each( fields, ( key, val ) => { 1102 $( '<input>' ).attr( { 1103 type: "hidden", 1104 name: key, 1105 value: val 1106 } ).appendTo( $form ); 1107 } ); 1108 1109 $form.appendTo( 'body' ).submit(); 1110 1111 return false; 1112 }, 1113 1114 /** 1115 * @event handler 1116 * @param {Event} e 1117 */ 1118 loginFieldKeydown: function( e ) { 1119 if ( e.keyCode === $ush.ENTER_KEYCODE ) { 1120 e.stopPropagation(); 1121 e.preventDefault(); 1122 this.submitLoginForm(); 1123 } 1124 }, 1125 } ); 1126 1127 $us.woocommerce = new WooCommerce; 1128 1129 /** 1130 * Image Slider element 1131 * Set product images for the chosen variation. 1132 * Note: Overriding a default function implemented in WooCommerce logic. 1133 * https://github.com/woocommerce/woocommerce/blob/d4696f043710131d5bbf51455e070791eaa12cf9/plugins/woocommerce/client/legacy/js/frontend/add-to-cart-variation.js#L646 1134 * 1135 * @param {{}} variation The variation. 1136 */ 1137 function us_wc_variations_image_update( variation ) { 1138 var $slider = $( '.w-slider.for_product_image_gallery:not(.w-grid .w-slider)', $( this ).closest( '.product' ) ), 1139 royalSlider = ( $slider.data( 'usImageSlider' ) || {} ).royalSlider; 1140 if ( $ush.isUndefined( royalSlider ) ) { 1141 return; 1142 } 1143 royalSlider.goTo(0); 1144 var $image = $( '.rsImg', royalSlider.slidesJQ[0] ), 1145 $thumb = $( '.rsThumb:first img', $slider ); 1146 if ( variation === false ) { 1147 if ( ! $slider.data( 'orig-img' ) ) { 1148 var src = $image.attr( 'src' ); 1149 $slider.data( 'orig-img', { 1150 src: src, 1151 srcset: src, 1152 full_src: src, 1153 thumb_src: $thumb.attr( 'srcset' ), 1154 gallery_thumbnail_src: $thumb.attr( 'src' ), 1155 } ); 1156 return; 1157 } 1158 variation = { 1159 image: $slider.data( 'orig-img' ), 1160 }; 1161 } 1162 if ( $.isPlainObject( variation.image ) ) { 1163 $image 1164 .attr( 'src', $ush.toString( variation.image.src ) ) 1165 .attr( 'srcset', $ush.toString( variation.image.srcset ) ); 1166 $thumb 1167 .attr( 'src', $ush.toString( variation.image.gallery_thumbnail_src ) ) 1168 .attr( 'srcset', $ush.toString( variation.image.thumb_src ) ); 1169 // Set bigImage for Fullscreen 1170 $.extend( royalSlider.currSlide, { 1171 bigImage: $ush.toString( variation.image.full_src ), 1172 image: $ush.toString( variation.image.src ), 1173 } ); 1174 if ( typeof royalSlider.updateSliderSize === 'function' ) { 1175 royalSlider.updateSliderSize( true ); 1176 } 1177 } 1178 } 1179 $( () => { 1180 if ( $( '.w-slider.for_product_image_gallery:not(.w-grid .w-slider.for_product_image_gallery)' ).length > 0 ) { 1181 $ush.timeout( () => { 1182 $.fn.wc_variations_image_update = us_wc_variations_image_update; 1183 }, 1 ); 1184 } 1185 } ); 1186 1187 /** 1188 * Extension for WC Product Gallery 1189 * Thumbs Arrows functionality 1190 */ 1191 function usProductGallerySlider( $wcSlider ) { 1192 const self = this; 1193 1194 // Elements 1195 self.$wcSlider = $( $wcSlider ); 1196 self.$thumbsNav = $( '.flex-control-thumbs', self.$wcSlider ); 1197 self.$thumbsItems = $( 'li', self.$thumbsNav ); 1198 self.$thumbsArrowsContainer = self.$wcSlider 1199 .closest( '.w-post-elm.product_gallery' ) 1200 .find( '.us-thumbs-nav' ); 1201 self.$thumbArrowNext = $( '.us-thumb-next', self.$thumbsArrowsContainer ); 1202 self.$thumbArrowPrev = $( '.us-thumb-prev', self.$thumbsArrowsContainer ); 1203 1204 // Vars 1205 self._isVertical = self.isVertical(); 1206 self.scrollProp = self._isVertical ? 'scrollTop' : 'scrollLeft'; 1207 const sizeProp = self._isVertical ? 'outerHeight' : 'outerWidth'; 1208 self.clientSizeProp = self._isVertical ? 'clientHeight' : 'clientWidth'; 1209 self.scrollSizeProp = self._isVertical ? 'scrollHeight' : 'scrollWidth'; 1210 self.thumbItemSize = self.$thumbsItems.first()[ sizeProp ]( true ); 1211 1212 // Bindable events 1213 self._events = { 1214 updateThumbsArrowsState: self.updateThumbsArrowsState.bind( self ), 1215 controlThumbsArrows: self.controlThumbsArrows.bind( self ), 1216 syncThumbsWithMainSlider: self.syncThumbsWithMainSlider.bind( self ), 1217 }; 1218 1219 // Main Image Arrows 1220 if ( self.$wcSlider.hasClass( 'with_arrows' ) ) { 1221 // Change arrows position 1222 $( '.flex-direction-nav', self.$wcSlider ).appendTo( $( '.flex-viewport', self.$wcSlider ) ); 1223 1224 // Events 1225 $( '.flex-direction-nav a', self.$wcSlider ).on( 'click', self._events.syncThumbsWithMainSlider ); 1226 } 1227 1228 // Wrap Product Gallery thumbs and move arrows to it 1229 self.$thumbsNav.wrap( '<div class="us-thumbs-wrapper"></div>' ); 1230 self.$thumbsArrowsContainer.appendTo( self.$thumbsNav.parent() ); 1231 1232 // Init thumb arrows state 1233 self.updateThumbsArrowsState(); 1234 1235 // Events 1236 $( 'button', self.$thumbsArrowsContainer ).on( 'click', self._events.controlThumbsArrows ); 1237 1238 self.$thumbsNav.on( 'scroll.noPreventDefault', self._events.updateThumbsArrowsState ); 1239 1240 // Sync thumbs scroll position with main slider on variation change 1241 $( 'form.variations_form' ).on( 'found_variation reset_data', () => { 1242 $ush.timeout( () => self.syncThumbsWithMainSlider(), 100 ); 1243 } ); 1244 } 1245 1246 $.extend( usProductGallerySlider.prototype, { 1247 /** 1248 * Scroll thumbs on thumb arrows click 1249 * 1250 * @param {Event} e 1251 */ 1252 controlThumbsArrows: function ( e ) { 1253 e.preventDefault(); 1254 const self = this;
1255 const scrollObj = {}; 1256 const isNext = $( e.currentTarget ).hasClass( 'us-thumb-next' ); 1257 1258 var direction; 1259 1260 if ( $ush.isRtl() && ! self._isVertical ) { 1261 direction = isNext ? '-=' : '+='; 1262 } else { 1263 direction = isNext ? '+=' : '-='; 1264 } 1265 scrollObj[ self.scrollProp ] = direction + self.thumbItemSize; 1266 self.$thumbsNav.animate( scrollObj, 50, self._events.updateThumbsArrowsState ); 1267 }, 1268 /** 1269 * Update thumbs arrows state depending on scroll position 1270 */ 1271 updateThumbsArrowsState: function () { 1272 const self = this; 1273 1274 if ( ! self.$thumbsNav[0] ) { 1275 return; 1276 } 1277 1278 const scrollPos = self.$thumbsNav[0][ self.scrollProp ]; 1279 const maxScroll = self.$thumbsNav[0][ self.scrollSizeProp ] - self.$thumbsNav[0][ self.clientSizeProp ]; 1280 1281 var isPrevDisabled, isNextDisabled; 1282 1283 if ( $ush.isRtl() && ! self._isVertical ) { 1284 isPrevDisabled = scrollPos >= -1; 1285 isNextDisabled = scrollPos <= -maxScroll + 1; 1286 } else { 1287 isPrevDisabled = scrollPos <= 1; 1288 isNextDisabled = scrollPos >= maxScroll - 1; 1289 } 1290 1291 self.$thumbArrowPrev.toggleClass( 'hidden', isPrevDisabled ); 1292 self.$thumbArrowNext.toggleClass( 'hidden', isNextDisabled ); 1293 }, 1294 /** 1295 * Sync thumbs scroll position with main slider 1296 */ 1297 syncThumbsWithMainSlider: function() { 1298 const self = this; 1299 const $activeSlide = $( '.woocommerce-product-gallery__wrapper .flex-active-slide', self.$wcSlider ); 1300 const scrollObj = {}; 1301 1302 if ( ! $activeSlide.length || ! self.$thumbsItems.length ) { 1303 return; 1304 } 1305 1306 const index = $activeSlide.index(); 1307 1308 if ( index < 0 || index >= self.$thumbsItems.length ) { 1309 return; 1310 } 1311 1312 var targetScroll = index * self.thumbItemSize; 1313 if ( $ush.isRtl() && ! self._isVertical ) { 1314 targetScroll = -targetScroll; 1315 } 1316 1317 scrollObj[ self.scrollProp ] = targetScroll; 1318 1319 self.$thumbsNav.stop().animate( scrollObj, 50, self._events.updateThumbsArrowsState ); 1320 }, 1321 /** 1322 * Get isVertical flag 1323 */ 1324 isVertical: function() { 1325 const self = this; 1326 1327 var isVertical = self.$wcSlider.hasClass( 'thumbpos_left' ); 1328 1329 // Responsive for thumbpos_left 1330 if ( window.matchMedia( '(max-width: 767px)' ).matches ) { 1331 self.$wcSlider.removeClass( 'thumbpos_left' ).addClass( 'thumbpos_bottom' ); 1332 isVertical = false; 1333 } 1334 1335 return isVertical; 1336 } 1337 } ); 1338 1339 $.fn.us_product_gallery = function() { 1340 return this.each( function() { 1341 $( this ).data( 'us_product_gallery', new usProductGallerySlider( this ) ); 1342 } ); 1343 }; 1344 1345 $( () => { 1346 const $wcSlider = $( '.woocommerce-product-gallery.type_slider' ); 1347 if ( $wcSlider.length ) { 1348 $ush.timeout( () => $wcSlider.us_product_gallery(), 200 ); 1349 } 1350 } ); 1351 1352 // Init the "Product Gallery" inside the popup content in loop elements. 1353 $us.$document.on( 'usPopup.itemContentLoaded', ( e, usPopup ) => { 1354 // Functions for single product. 1355 // Dependency on `../plugins/woocommerce/assets/js/frontend/single-product.js` 1356 if ( typeof $.fn.wc_product_gallery === 'function' ) { 1357 const $wcProductGallery = $( '.woocommerce-product-gallery', usPopup.$content ); 1358 $wcProductGallery.wc_product_gallery(); 1359 if ( $wcProductGallery.hasClass( 'type_slider' ) ) { 1360 $ush.timeout( () => $wcProductGallery.us_product_gallery(), 200 ); 1361 } 1362 } 1363 // Functions for which handles variation forms and attributes 1364 // Dependency on `../plugins/woocommerce/assets/js/frontend/add-to-cart-variations.js` 1365 if ( typeof $.fn.wc_variation_form === 'function' ) { 1366 const $variationsForm = $( '.variations_form', usPopup.$content ); 1367 if ( $variationsForm.length > 0 ) { 1368 if ( $( '.product', usPopup.$content ).length == 0 ) { 1369 usPopup.$content.addClass( 'product' ); 1370 } 1371 $variationsForm.wc_variation_form(); 1372 } 1373 } 1374 } ); 1375 1376} )( jQuery );
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.