! ( function( $, _undefined ) { const $ush = window.$ush || {}; // The last AJAX object request. var lastUpdateCartRequest; /** * UpSolution WooCommerce elements. * Note: All classes and key elements from WooCommerce are retained * * The code depends on: * - `../plugins/woocommerce/assets/js/frontend/cart.js` * - `../plugins/woocommerce/assets/js/frontend/checkout.js` * * @param container * @requires $us.$body * @requires $us.$canvas * @requires $ush.debounce * @requires $ush.timeout * @constructor */ function WooCommerce() { const self = this; // Elements self.$cart = $( '.w-cart' ); self.$cartCloser = $( '.w-cart-closer', self.$cart ); self.$cartLink = $( '.w-cart-link', self.$cart ); self.$pageContent = $( '#page-content' ); self.$notice = $( '.w-wc-notices.woocommerce-notices-wrapper:first', $us.$canvas ); self.$addToCart = $( '.w-post-elm.add_to_cart', $us.$canvas ); // Private "Variables" self.isCartLayoutPanel = self.$cart.hasClass( 'layout_left_panel' ) || self.$cart.hasClass( 'layout_right_panel' ); self.isCartOpen = false; // Event handlers self._events = { ajaxAddToCart: self.ajaxAddToCart.bind( self ), applyCouponCode: self.applyCouponCode.bind( self ), changeCartQuantity: self.changeCartQuantity.bind( self ), couponCodeChange: self.couponCodeChange.bind( self ), couponDisplaySwitch: self.couponDisplaySwitch.bind( self ), enterCouponCode: self.enterCouponCode.bind( self ), showNotification: self.showNotification.bind( self ), loginFieldKeydown: self.loginFieldKeydown.bind( self ), minusCartQuantity: self.minusCartQuantity.bind( self ), moveNotifications: self.moveNotifications.bind( self ), clickOutsideCart: self.clickOutsideCart.bind( self ), plusCartQuantity: self.plusCartQuantity.bind( self ), removeCartItem: self.removeCartItem.bind( self ), showLoginForm: self.showLoginForm.bind( self ), submitLoginForm: self.submitLoginForm.bind( self ), updateCart: self.updateCart.bind( self ), updatedCartTotals: self.updatedCartTotals.bind( self ), showCartOnClick: self.showCartOnClick.bind( self ), showCartOnKeyup: self.showCartOnKeyup.bind( self ), hideCartOnKeyup: self.hideCartOnKeyup.bind( self ), redirectToCartOnMobile: self.redirectToCartOnMobile.bind( self ), }; // Init cart elements if ( self.isCart() ) { self.$cartNotification = $( '.w-cart-notification', self.$cart ); // Events self.$cartNotification.on( 'click', () => { self.$cartNotification.fadeOutCSS(); } ); // Cart closer button click events for both mobiles and desktops if ( self.isCartLayoutPanel ) { self.$cartCloser.on( 'click', () => self._toggleCart( false ) ); } // Redirect to cart page on mobile devices. if ( $.isMobile ) { self.$cart.on( 'click', '.w-cart-link', self._events.redirectToCartOnMobile ); // Handle click event on desktop devices with panel layouts } else { if ( self.$cart.hasClass( 'drop_on_click' ) || self.$cart.is( '[class*="_panel"]' ) ) { // need to separate these events because keyup event also manages focus self.$cartLink.on( 'keyup', self._events.showCartOnKeyup ); self.$cartLink.on( 'click', self._events.showCartOnClick ); } $us.$body.on( 'keyup', self._events.hideCartOnKeyup ); // Cart closer button events for desktops only if ( self.isCartLayoutPanel ) { self.$cartCloser.on( 'keyup', ( e ) => { if ( e.keyCode === $ush.ENTER_KEYCODE ) { self._toggleCart( false, self.$cartLink ); } } ); } } $us.$body // Events of `../plugins/woocommerce/assets/js/frontend/add-to-cart.js`, // `../plugins/woocommerce/assets/js/frontend/cart-fragments.js` .on( 'wc_fragments_loaded wc_fragments_refreshed', self._events.updateCart ) // Events of `../plugins/woocommerce/assets/js/frontend/add-to-cart.js` .on( 'added_to_cart', self._events.showNotification ) .on( 'removed_from_cart', self._events.updateCart ); } if ( self.isCartPage() ) { // Events $us.$body .on( 'change', '.w-wc-coupon-form input', self._events.couponCodeChange ) .on( 'keyup', '.w-wc-coupon-form input', self._events.enterCouponCode ) .on( 'click', '.w-wc-coupon-form button', self._events.applyCouponCode ) .on( 'click', 'a.remove', self._events.removeCartItem ) // Events of `../plugins/woocommerce/assets/js/frontend/cart.js` .on( 'applied_coupon removed_coupon', self._events.couponDisplaySwitch ) .on( 'updated_cart_totals', self._events.updatedCartTotals ); // Initializing controls after the ready document $( 'input.qty', $us.$canvas ).trigger( 'initControls' ); // Get the last active request for cart updates $.ajaxPrefilter( ( _, originalOptions, jqXHR ) => { const data = $ush.toString( originalOptions.data ); if ( data.indexOf( '&update_cart' ) > -1 ) { lastUpdateCartRequest = jqXHR; } // Distance information updates in shortcode `[us_cart_shipping]` if ( data.indexOf( '&us_calc_shipping' ) > -1 ) { jqXHR.done( ( res ) => { $( '.w-cart-shipping .woocommerce-shipping-destination' ) .html( $( '.w-cart-shipping:first .woocommerce-shipping-destination', res ).html() ); } ); } } ); $( '.w-cart-shipping form.woocommerce-shipping-calculator', $us.$canvas ) .append( '' ); } if ( self.isCheckoutPage() ) { // Events $us.$body .on( 'change', '.w-wc-coupon-form input', self._events.couponCodeChange ) .on( 'keyup', '.w-wc-coupon-form input', self._events.enterCouponCode ) .on( 'click', '.w-wc-coupon-form button', self._events.applyCouponCode ) // Events of `../plugins/woocommerce/assets/js/frontend/checkout.js` .on( 'applied_coupon_in_checkout removed_coupon_in_checkout', self._events.couponDisplaySwitch ) .on( 'applied_coupon_in_checkout removed_coupon_in_checkout checkout_error', self._events.moveNotifications ) .on( 'click', '.w-checkout-login .showlogin', self._events.showLoginForm ) .on( 'click', '.w-checkout-login button', self._events.submitLoginForm ) .on( 'keydown', '.w-checkout-login input, .w-checkout-login button', self._events.loginFieldKeydown ); // Blocks the form from being submitted if the coupon field is in focus // and the Enter key is pressed, this allows the coupon to be applied // correctly, otherwise the form will simply be submitted. const $couponField = $( '.w-wc-coupon-form input', $us.$canvas ); $us.$document.on( 'keypress', ( e ) => { if ( e.keyCode === $ush.ENTER_KEYCODE && $couponField.is( ':focus' ) ) { e.preventDefault(); } } ); } // "Add to cart" via Ajax if( $us.$body.hasClass( 'us-ajax_add_to_cart' ) ) { $us.$body.on( 'click', '.single_add_to_cart_button:not(.disabled)', self._events.ajaxAddToCart ); } // Quantity Events $us.$body .on( 'click', '.quantity input.minus', self._events.minusCartQuantity ) .on( 'click', '.quantity input.plus', self._events.plusCartQuantity ) .on( 'change initControls', '.quantity input.qty', self._events.changeCartQuantity ); $us.$document.on( 'ajaxComplete', ( _, jqXHR, settings ) => { if ( settings.url.includes( 'add_to_cart' ) && self.$cart.hasClass( 'open_on_ajax' ) && self.isCartLayoutPanel ) { self._toggleCart( true ); // waiting for transition - cannot focus element with visibility:hidden self.$cart.on( 'transitionend', () => { self.$cartCloser[0].focus(); self.$cart.off( 'transitionend' ); } ); } // Skip JSON string if ( String( jqXHR.responseText ).charAt(0) === '{' ) { return; } const $fragment = $( new DocumentFragment ).append( jqXHR.responseText ); // Updated only visible "Cart Totals" elements, bypassing WooCommerce core logic. See #3935 for details. if ( self.isCartPage() && $( '.cart_totals', $fragment ).length > 1 ) { const notices = self.$notice.html(); self.$pageContent.html( $( '.l-main', $fragment ) ); self.$notice = $( '.w-wc-notices.woocommerce-notices-wrapper:first', $us.$canvas ); // Restore notifications if ( notices ) { self.$notice.html( notices ); } } // Intercept messages after apply a coupon. if ( String( settings.url ).includes( 'wc-ajax=apply_coupon' ) ) { const $message = $( '.woocommerce-error, .woocommerce-message', $fragment ); if ( $message.length > 0 ) { self.$notice.html( $message.clone() ); } else { self.$notice.html( '' ); } } } ); } // Checkout/Cart Page $.extend( WooCommerce.prototype, { /** * Determines if cart. * * @return {boolean} True if cartesian, False otherwise. */ isCart: function() { return this.$cart.length > 0; }, /** * Determines if current cartesian page. * * @return {boolean} True if current cartesian page, False otherwise. */ isCartPage: function() { return $us.$body.hasClass( 'woocommerce-cart' ); }, /** * Determines if current checkout page. * * @return {boolean} True if current checkout page, False otherwise */ isCheckoutPage: function() { return $us.$body.hasClass( 'woocommerce-checkout' ); }, /** * Update cart elements. */ updateCart: function() { const self = this; $.each( self.$cart, ( _, cart ) => { var $cart = $( cart ), $cartQuantity = $( '.w-cart-quantity', $cart ), miniCartAmount = $( '.us_mini_cart_amount:first', $cart ).text(); if ( $cart.hasClass( 'opened' ) && ! $cart.hasClass( 'drop_on_click' ) && ! $cart.hasClass( 'layout_left_panel' ) && ! $cart.hasClass( 'layout_right_panel' ) ) { $cart.removeClass( 'opened' ); } if ( miniCartAmount !== _undefined ) { miniCartAmount = String( miniCartAmount ).match( /\d+/g ); $cartQuantity.html( miniCartAmount > 0 ? miniCartAmount : '0' ); $cart[ miniCartAmount > 0 ? 'removeClass' : 'addClass' ]( 'empty' ); } else { // fallback in case our action wasn't fired somehow var total = 0; $( '.quantity', $cart ).each( ( _, quantity ) => { var matches = String( quantity.innerText ).match( /\d+/g ); if ( matches ) { total += parseInt( matches[0], 10 ); } } ); $cartQuantity.html( total > 0 ? total : '0' ); $cart[ total > 0 ? 'removeClass' : 'addClass' ]( 'empty' ); } } ); }, /** * Show notifications after adding product to cart. * * @event handler * @param {Event} e * @param {{} fragments * @param {node} $button */ showNotification: function( e, fragments, _, $button ) { if ( $ush.isUndefined( e ) ) { return; } const self = this; // Update cart element self.updateCart(); const $notice = self.$cartNotification; // NOTE: Get product name in product page or popup when available. const theProductName = $button .closest( '.product, .w-popup-box-content' ) .find( '.woocommerce-loop-product__title, .w-post-elm.post_title' ) .first() .text(); $( '.product-name', $notice ).html( `"${theProductName}"` ); // Skip show message if ( $notice.hasClass( 'skip_message' ) ) { $notice.removeClass( 'skip_message' ); return; } $notice.addClass( 'shown' ); $notice.on( 'mouseenter', () => { $notice.removeClass( 'shown' ); } ); $ush.timeout( () => { $notice .removeClass( 'shown' ) .off( 'mouseenter' ); }, 3000 ); }, /** * Handler when remove a item. * * @event handler * @param {Event} e */ removeCartItem: function( e ) { var $item = $( e.target ) .closest( '.cart_item' ) .addClass( 'change_process' ); // If the element is the last, then delete the table for correct operation `cart.js:update_wc_div` if ( ! $item.siblings( '.cart_item:not(.change_process)' ).length ) { $( '.w-cart-table', $us.$canvas ).remove(); } }, /** * Check and set quantity. * * @event handler * @param {Event} e */ changeCartQuantity: function( e ) { if ( $us.usbPreview() ) { return; } const self = this; const $input = $( e.target ); const isGroupTable = $input.closest( '.cart' ).hasClass( 'grouped_form' ); const max = $ush.parseInt( $input.attr( 'max' ) ) || -1; const min = $ush.parseInt( $input.attr( 'min' ) ) || ( isGroupTable ? 0 : 1 ); var value = $ush.parseInt( $input.val() ); if ( $input.is( ':disabled' ) ) { return; } if ( min >= value ) { value = min; } if ( max > 1 && value >= max ) { value = max; } if ( value != $input.val() ) { $input.val( value ); } $input .siblings( 'input.plus:first' ) .prop( 'disabled', ( max > 0 && value >= max ) ); $input .siblings( 'input.minus:first' ) .prop( 'disabled', ( value <= min ) ); // If the event type is `initControls` then this is the // first init when loading the document if ( e.type == 'initControls' ) { return; } // Add a flag that there was a change in the quantity to the cart elements $( 'input[name=us_cart_quantity]', $us.$canvas ).val( true ); // Update the cart by means of WooCommerce if ( ! $( '.w-cart-table', $us.$canvas ).hasClass( 'processing' ) ) { self.__updateCartForm_long( self._updateCartForm.bind( self ) ); } else { self._updateCartForm(); } }, /** * Decreasing quantity item in cart. * * @event handler * @param {Event} e */ minusCartQuantity: function( e ) { const self = this; var $target = $( e.target ), $input = $target.siblings( 'input.qty:first' ); if ( ! $input.length ) { return; } const step = $ush.parseInt( $input.attr( 'step' ) || 1 ); $input // Update quantity .val( $ush.parseInt( $input.val() ) - step ) .trigger( 'change' ); }, /** * Increasing quantity item in cart. * * @event handler * @param {Event} e */ plusCartQuantity: function( e ) { const self = this; var $target = $( e.target ), $input = $target.siblings( 'input.qty:first' ); if ( ! $input.length ) { return; } const step = $ush.parseInt( $input.attr( 'step' ) || 1 ); $input .val( $ush.parseInt( $input.val() ) + step ) .trigger( 'change' ); }, /** * Update the cart form by means of WooCommerce * Note: The code is moved to a separate function since `debounced` * must be initialized before calling * * @param {function} fn The function to be executed * @type debounced */ __updateCartForm_long: $ush.debounce( $ush.fn, /* wait */50 ), /** * Update the cart form by means of WooCommerce. */ _updateCartForm: function() { const self = this; // Abort previous cart update request if ( typeof ( lastUpdateCartRequest || {} ).abort === 'function' ) { lastUpdateCartRequest.abort(); } // Initialize cart update $( '.w-cart-table > button[name=update_cart]', $us.$canvas ) .removeAttr( 'disabled' ) .trigger( 'click' ); }, /** * Update cart totals. * * @event handler */ updatedCartTotals: function() { const self = this; // Reset last active request if ( lastUpdateCartRequest ) { lastUpdateCartRequest = _undefined; } // Removing animated class if any element had it var wooElementClasses = [ 'w-cart-shipping', 'w-cart-table', 'w-cart-totals', 'w-checkout-billing', 'w-checkout-order-review', 'w-checkout-payment', 'w-wc-coupon-form', ]; for ( const i in wooElementClasses ) { $( `.${wooElementClasses[i]}.us_animate_this`, $us.$canvas ).removeClass( 'us_animate_this' ); } // Shipping element sync after totals update const $shipping = $( '.w-cart-shipping .shipping', $us.$canvas ); if ( ! $shipping.length ) { return; } $shipping.html( $( '.w-cart-totals .shipping:first', $us.$canvas ).html() ); }, /** * Enter the coupon code in the field. * * @event handler * @param {Event} e */ couponCodeChange: function( e ) { // Transit value to the cart form to add a coupon by WooCommerce logic $( '.w-cart-table, form.checkout_coupon:first', $us.$canvas ) .find( 'input[name=coupon_code]' ) .val( e.target.value ); }, /** * Enter a coupon code. * * @event handler * @param {Event} e */ enterCouponCode: function( e ) { if ( e.keyCode === $ush.ENTER_KEYCODE ) { $( e.target ) .trigger( 'change' ) .siblings( 'button:first' ) .trigger( 'click' ); } }, /** * Click on the "Apply Coupon" button. * * @event handler * @param {Event} e */ applyCouponCode: function( e ) { // Stop event (Important on the checkout page) e.stopPropagation(); e.preventDefault(); // Initialize coupon additions using WooCommerce logic $( '.w-cart-table, form.checkout_coupon:first', $us.$canvas ) .find( 'button[name=apply_coupon]' ) .trigger( 'click' ); // Clear input field $( e.target ).closest( '.w-wc-coupon-form' ).find( 'input:first' ).val( '' ); }, /** * Coupon form display switch. * * @param {Event} e */ couponDisplaySwitch: function( e ) { const $coupon = $( '.w-wc-coupon-form', $us.$canvas ); if ( ! $coupon.length ) { return; } // Add a class if the coupon is applied if ( e.type.indexOf( 'applied_coupon' ) > -1 && ! $( '.woocommerce-error', $us.$canvas ).length ) { $coupon.addClass( 'coupon_applied' ); } // Remove a class if all coupons were removed if ( e.type.indexOf( 'removed_coupon' ) > -1 && $( '.woocommerce-remove-coupon', $us.$canvas ).length <= 1 ) { $coupon.removeClass( 'coupon_applied' ); } }, /** * Move notifications to `[wc_notices...]`. * * @event handler * @param {Event} e * @param {String} err_html The value is needed for checkout_error. */ moveNotifications: function( e, err_html ) { const self = this; // Do not proceed with notices adjustment if there are no US Cart / Checkout elements on the page if ( ! self.$notice.length ) { var $cartTotals = $( '.w-cart-totals', $us.$canvas ), $checkoutPayment = $( '.w-checkout-payment', $us.$canvas ); if ( ! $cartTotals.length && ! $checkoutPayment.length ) { return; } } // Get notice var $message; if ( e.type === 'checkout_error' && err_html ) { $message = $( err_html ); } else { $message = $( '.woocommerce-error, .woocommerce-message', $us.$canvas ); } // Show notification if ( $message.length > 0 ) { self.$notice.html( $message.clone() ); } $message.remove(); // Remove NoticeGroup if ( e.type === 'checkout_error' ) { $( '.woocommerce-NoticeGroup-checkout' ).remove(); } }, }); // Cart element $.extend( WooCommerce.prototype, { /** * "Add to cart" via Ajax. * * @event handler * @param {Event} e */ ajaxAddToCart: function( e ) { const self = this; const $button = $( e.currentTarget ); const $form = $button.closest( 'form.cart' ); // External/Affiliate product if ( $form.is( '[method=get]' ) ) { return; } e.preventDefault(); // Add text wrapper and preloader if ( $( '.g-preloader', $button ).length === 0 ) { $button.html( `