I’ll build this as a strong inc/js/woocommerce.js front-end file for RX Theme: cart UX, quantity controls, AJAX events, checkout helpers, product gallery, accessibility, and safe fallbacks.
My dear friend, yes — you can create a powerful inc/js/woocommerce.js file for your RX Theme. This file will improve WooCommerce product page, shop page, cart page, checkout page, mini-cart, quantity buttons, AJAX cart updates, accessibility, sticky add-to-cart, coupon toggle, checkout validation UI, and more.
Create or replace this file:
rx-theme/inc/js/woocommerce.js
Then paste this full code:
/*!
* RX Theme WooCommerce Frontend
* File: inc/js/woocommerce.js
* Theme: RX Theme
* Author: RxHarun
*
* Purpose:
* Advanced WooCommerce frontend helper for product page, shop page,
* cart page, checkout page, mini-cart, accessibility, UX, and AJAX events.
*/
(function ($, window, document) {
'use strict';
var RXWoo = {
config: {
body: $('body'),
window: $(window),
document: $(document),
htmlBody: $('html, body'),
ajaxDelay: 350,
noticeDelay: 6000,
stickyOffset: 220,
activeClass: 'is-active',
loadingClass: 'is-loading',
openClass: 'is-open',
disabledClass: 'is-disabled',
hiddenClass: 'rx-hidden',
initializedClass: 'rx-woo-initialized'
},
init: function () {
this.cacheSelectors();
this.addBodyClasses();
this.wrapWooElements();
this.quantityButtons();
this.autoUpdateCart();
this.miniCartPanel();
this.productGallery();
this.productTabs();
this.variableProductUX();
this.stickyAddToCart();
this.shopFilters();
this.orderingEnhancement();
this.couponToggle();
this.checkoutEnhancement();
this.formEnhancement();
this.noticeEnhancement();
this.accountPageEnhancement();
this.productArchiveCards();
this.wishlistCompareSafeButtons();
this.backToShopButton();
this.accessibilityFixes();
this.bindWooEvents();
this.scrollFixes();
this.performanceSafeLazyActions();
this.config.body.addClass(this.config.initializedClass);
},
cacheSelectors: function () {
this.$body = this.config.body;
this.$window = this.config.window;
this.$document = this.config.document;
this.$cartForm = $('.woocommerce-cart-form');
this.$checkoutForm = $('form.checkout');
this.$productForm = $('form.cart');
this.$miniCart = $('.widget_shopping_cart, .woocommerce-mini-cart');
this.$noticesWrapper = $('.woocommerce-notices-wrapper');
this.$singleProduct = $('.single-product');
this.$productSummary = $('.single-product .summary');
this.$productGallery = $('.woocommerce-product-gallery');
this.$archiveProducts = $('.woocommerce ul.products');
},
addBodyClasses: function () {
if ($('.woocommerce').length) {
this.$body.addClass('rx-woocommerce-page');
}
if ($('.woocommerce-cart').length) {
this.$body.addClass('rx-woo-cart-page');
}
if ($('.woocommerce-checkout').length) {
this.$body.addClass('rx-woo-checkout-page');
}
if ($('.single-product').length) {
this.$body.addClass('rx-woo-single-product-page');
}
if ($('.woocommerce-account').length) {
this.$body.addClass('rx-woo-account-page');
}
},
wrapWooElements: function () {
$('.woocommerce-notices-wrapper').each(function () {
var $this = $(this);
if (!$this.parent().hasClass('rx-woo-notice-area')) {
$this.wrap('<div class="rx-woo-notice-area" aria-live="polite"></div>');
}
});
$('.woocommerce table.shop_table').each(function () {
var $table = $(this);
if (!$table.parent().hasClass('rx-woo-table-scroll')) {
$table.wrap('<div class="rx-woo-table-scroll"></div>');
}
});
$('.woocommerce-result-count, .woocommerce-ordering').each(function () {
var $item = $(this);
if (!$item.parent().hasClass('rx-woo-toolbar-item')) {
$item.wrap('<div class="rx-woo-toolbar-item"></div>');
}
});
if ($('.woocommerce-result-count').length && $('.woocommerce-ordering').length) {
var $count = $('.woocommerce-result-count').first();
var $ordering = $('.woocommerce-ordering').first();
if (!$count.closest('.rx-woo-shop-toolbar').length) {
$count.parent('.rx-woo-toolbar-item')
.add($ordering.parent('.rx-woo-toolbar-item'))
.wrapAll('<div class="rx-woo-shop-toolbar"></div>');
}
}
},
quantityButtons: function () {
var self = this;
$('.quantity').each(function () {
var $quantity = $(this);
var $input = $quantity.find('input.qty');
if (!$input.length || $quantity.hasClass('rx-quantity-ready')) {
return;
}
$quantity.addClass('rx-quantity-ready');
var minusText = $input.attr('aria-label')
? 'Decrease ' + $input.attr('aria-label')
: 'Decrease quantity';
var plusText = $input.attr('aria-label')
? 'Increase ' + $input.attr('aria-label')
: 'Increase quantity';
$input.before(
'<button type="button" class="rx-qty-btn rx-qty-minus" aria-label="' + self.escapeAttr(minusText) + '">−</button>'
);
$input.after(
'<button type="button" class="rx-qty-btn rx-qty-plus" aria-label="' + self.escapeAttr(plusText) + '">+</button>'
);
});
this.$document.off('click.rxQty').on('click.rxQty', '.rx-qty-btn', function (e) {
e.preventDefault();
var $button = $(this);
var $quantity = $button.closest('.quantity');
var $input = $quantity.find('input.qty');
var current = parseFloat($input.val());
var max = parseFloat($input.attr('max'));
var min = parseFloat($input.attr('min'));
var step = parseFloat($input.attr('step'));
current = isNaN(current) ? 0 : current;
max = isNaN(max) ? false : max;
min = isNaN(min) ? 0 : min;
step = isNaN(step) || step <= 0 ? 1 : step;
if ($button.hasClass('rx-qty-plus')) {
if (max && current >= max) {
return;
}
current = current + step;
if (max && current > max) {
current = max;
}
}
if ($button.hasClass('rx-qty-minus')) {
if (current <= min) {
return;
}
current = current - step;
if (current < min) {
current = min;
}
}
$input.val(self.cleanNumber(current)).trigger('change');
});
this.updateQtyButtonStates();
this.$document.off('change.rxQtyState').on('change.rxQtyState', '.quantity input.qty', function () {
self.updateQtyButtonStates();
});
},
updateQtyButtonStates: function () {
$('.quantity').each(function () {
var $quantity = $(this);
var $input = $quantity.find('input.qty');
var current = parseFloat($input.val());
var max = parseFloat($input.attr('max'));
var min = parseFloat($input.attr('min'));
current = isNaN(current) ? 0 : current;
max = isNaN(max) ? false : max;
min = isNaN(min) ? 0 : min;
$quantity.find('.rx-qty-minus').prop('disabled', current <= min);
if (max) {
$quantity.find('.rx-qty-plus').prop('disabled', current >= max);
} else {
$quantity.find('.rx-qty-plus').prop('disabled', false);
}
});
},
autoUpdateCart: function () {
var self = this;
var timer = null;
this.$document.off('change.rxCartQty').on('change.rxCartQty', '.woocommerce-cart-form input.qty', function () {
var $form = $('.woocommerce-cart-form');
var $button = $form.find('button[name="update_cart"]');
if (!$button.length) {
return;
}
$button.prop('disabled', false).removeAttr('disabled');
clearTimeout(timer);
timer = setTimeout(function () {
$form.addClass(self.config.loadingClass);
$button.trigger('click');
}, self.config.ajaxDelay);
});
},
miniCartPanel: function () {
var self = this;
if (!$('.rx-mini-cart-toggle').length && $('.cart-contents, .site-header-cart, .header-cart').length) {
$('.cart-contents, .site-header-cart > a, .header-cart > a').first().addClass('rx-mini-cart-toggle');
}
this.$document.off('click.rxMiniCart').on('click.rxMiniCart', '.rx-mini-cart-toggle', function (e) {
var $panel = $('.rx-mini-cart-panel, .widget_shopping_cart').first();
if (!$panel.length) {
return;
}
e.preventDefault();
self.$body.toggleClass('rx-mini-cart-open');
$panel.toggleClass(self.config.openClass);
if ($panel.hasClass(self.config.openClass)) {
$panel.attr('aria-hidden', 'false');
} else {
$panel.attr('aria-hidden', 'true');
}
});
this.$document.off('click.rxMiniCartClose').on('click.rxMiniCartClose', '.rx-mini-cart-close, .rx-mini-cart-overlay', function (e) {
e.preventDefault();
self.closeMiniCart();
});
this.$document.off('keyup.rxMiniCartEsc').on('keyup.rxMiniCartEsc', function (e) {
if (e.key === 'Escape') {
self.closeMiniCart();
}
});
},
closeMiniCart: function () {
this.$body.removeClass('rx-mini-cart-open');
$('.rx-mini-cart-panel, .widget_shopping_cart')
.removeClass(this.config.openClass)
.attr('aria-hidden', 'true');
},
productGallery: function () {
var self = this;
$('.woocommerce-product-gallery').each(function () {
var $gallery = $(this);
if ($gallery.hasClass('rx-gallery-ready')) {
return;
}
$gallery.addClass('rx-gallery-ready');
$gallery.find('.woocommerce-product-gallery__image a').attr({
'aria-label': 'View product image',
'data-rx-gallery-link': 'true'
});
if (!$gallery.find('.rx-gallery-counter').length) {
var total = $gallery.find('.woocommerce-product-gallery__image').length;
if (total > 1) {
$gallery.append('<div class="rx-gallery-counter" aria-live="polite">1 / ' + total + '</div>');
}
}
});
this.$document.off('click.rxGalleryThumb').on('click.rxGalleryThumb', '.flex-control-thumbs li img', function () {
var index = $(this).closest('li').index() + 1;
var total = $('.woocommerce-product-gallery__image').length;
$('.rx-gallery-counter').text(index + ' / ' + total);
});
this.$document.off('keydown.rxGallery').on('keydown.rxGallery', '.woocommerce-product-gallery', function (e) {
var $thumbs = $(this).find('.flex-control-thumbs li img');
if (!$thumbs.length) {
return;
}
var $active = $thumbs.filter('.flex-active');
var index = $thumbs.index($active);
if (e.key === 'ArrowRight') {
e.preventDefault();
$thumbs.eq(Math.min(index + 1, $thumbs.length - 1)).trigger('click');
}
if (e.key === 'ArrowLeft') {
e.preventDefault();
$thumbs.eq(Math.max(index - 1, 0)).trigger('click');
}
});
setTimeout(function () {
self.productImageZoomHint();
}, 700);
},
productImageZoomHint: function () {
var $gallery = $('.woocommerce-product-gallery');
if (!$gallery.length || $gallery.find('.rx-product-zoom-hint').length) {
return;
}
if ($gallery.find('.woocommerce-product-gallery__trigger').length) {
$gallery.append('<span class="rx-product-zoom-hint">Click image to zoom</span>');
}
},
productTabs: function () {
var self = this;
$('.woocommerce-tabs').each(function () {
var $tabs = $(this);
if ($tabs.hasClass('rx-tabs-ready')) {
return;
}
$tabs.addClass('rx-tabs-ready');
$tabs.find('.tabs li a').each(function () {
var $link = $(this);
var target = $link.attr('href');
$link.attr({
role: 'tab',
'aria-selected': $link.parent().hasClass('active') ? 'true' : 'false'
});
if (target && target.charAt(0) === '#') {
$(target).attr({
role: 'tabpanel',
tabindex: '0'
});
}
});
});
this.$document.off('click.rxTabs').on('click.rxTabs', '.woocommerce-tabs .tabs li a', function () {
var $link = $(this);
var $tabs = $link.closest('.woocommerce-tabs');
setTimeout(function () {
$tabs.find('.tabs li a').attr('aria-selected', 'false');
$tabs.find('.tabs li.active a').attr('aria-selected', 'true');
if (window.location.hash && $(window.location.hash).length) {
self.smoothScrollTo($(window.location.hash), 80);
}
}, 60);
});
},
variableProductUX: function () {
var self = this;
this.$document.off('found_variation.rxVariation').on('found_variation.rxVariation', 'form.variations_form', function (event, variation) {
var $form = $(this);
$form.addClass('rx-variation-found');
$form.removeClass('rx-variation-empty');
if (variation && variation.price_html) {
self.updateVariationPrice($form, variation.price_html);
}
if (variation && variation.availability_html) {
self.updateVariationStock($form, variation.availability_html);
}
});
this.$document.off('reset_data.rxVariation').on('reset_data.rxVariation', 'form.variations_form', function () {
var $form = $(this);
$form.removeClass('rx-variation-found');
$form.addClass('rx-variation-empty');
$form.find('.rx-selected-variation-price').remove();
$form.find('.rx-selected-variation-stock').remove();
});
this.$document.off('change.rxVariationSelect').on('change.rxVariationSelect', '.variations select', function () {
var $select = $(this);
var label = $select.closest('tr').find('label').text();
var value = $select.find('option:selected').text();
if (value) {
$select.attr('aria-label', label + ': ' + value);
}
});
this.$document.off('click.rxResetVariations').on('click.rxResetVariations', '.reset_variations', function () {
$('.rx-selected-variation-price, .rx-selected-variation-stock').remove();
});
},
updateVariationPrice: function ($form, priceHtml) {
var $summary = $form.closest('.summary');
if (!$summary.find('.rx-selected-variation-price').length) {
$form.before('<div class="rx-selected-variation-price" aria-live="polite"></div>');
}
$summary.find('.rx-selected-variation-price').html(priceHtml);
},
updateVariationStock: function ($form, stockHtml) {
var $summary = $form.closest('.summary');
if (!$summary.find('.rx-selected-variation-stock').length) {
$form.before('<div class="rx-selected-variation-stock" aria-live="polite"></div>');
}
$summary.find('.rx-selected-variation-stock').html(stockHtml);
},
stickyAddToCart: function () {
var self = this;
if (!$('.single-product').length || !$('form.cart').length) {
return;
}
if ($('.rx-sticky-add-to-cart').length) {
return;
}
var title = $('.product_title').first().text();
var price = $('.summary .price').first().html();
var image = $('.woocommerce-product-gallery__image img, .wp-post-image').first().attr('src');
var html = '';
html += '<div class="rx-sticky-add-to-cart" aria-hidden="true">';
html += '<div class="rx-sticky-product-info">';
if (image) {
html += '<img src="' + self.escapeAttr(image) + '" alt="' + self.escapeAttr(title) + '" class="rx-sticky-product-image">';
}
html += '<div class="rx-sticky-product-text">';
html += '<strong>' + self.escapeHtml(title) + '</strong>';
if (price) {
html += '<span class="rx-sticky-product-price">' + price + '</span>';
}
html += '</div>';
html += '</div>';
html += '<button type="button" class="rx-sticky-cart-button">Add to cart</button>';
html += '</div>';
this.$body.append(html);
this.$window.off('scroll.rxStickyCart').on('scroll.rxStickyCart', function () {
var scrollTop = self.$window.scrollTop();
var $sticky = $('.rx-sticky-add-to-cart');
var $cart = $('form.cart').first();
if (!$cart.length) {
return;
}
if (scrollTop > self.config.stickyOffset && !$cart.isInViewport()) {
$sticky.addClass(self.config.activeClass).attr('aria-hidden', 'false');
} else {
$sticky.removeClass(self.config.activeClass).attr('aria-hidden', 'true');
}
});
this.$document.off('click.rxStickyCart').on('click.rxStickyCart', '.rx-sticky-cart-button', function (e) {
e.preventDefault();
var $cartButton = $('form.cart .single_add_to_cart_button').first();
if ($cartButton.length) {
$cartButton.trigger('click');
} else {
self.smoothScrollTo($('form.cart').first(), 80);
}
});
},
shopFilters: function () {
var self = this;
if (!$('.woocommerce-shop, .post-type-archive-product, .tax-product_cat, .tax-product_tag').length) {
return;
}
if (!$('.rx-shop-filter-toggle').length && $('.widget_price_filter, .woocommerce-widget-layered-nav, .widget_layered_nav_filters').length) {
$('.woocommerce-products-header').after(
'<button type="button" class="rx-shop-filter-toggle" aria-expanded="false">Filter products</button>'
);
}
this.$document.off('click.rxFilterToggle').on('click.rxFilterToggle', '.rx-shop-filter-toggle', function (e) {
e.preventDefault();
var $button = $(this);
self.$body.toggleClass('rx-shop-filter-open');
var expanded = self.$body.hasClass('rx-shop-filter-open') ? 'true' : 'false';
$button.attr('aria-expanded', expanded);
});
this.$document.off('click.rxFilterClose').on('click.rxFilterClose', '.rx-shop-filter-close, .rx-filter-overlay', function (e) {
e.preventDefault();
self.$body.removeClass('rx-shop-filter-open');
$('.rx-shop-filter-toggle').attr('aria-expanded', 'false');
});
$('.widget_price_filter form').each(function () {
var $form = $(this);
if (!$form.find('.rx-price-filter-note').length) {
$form.append('<p class="rx-price-filter-note">Use the price slider and press Filter.</p>');
}
});
},
orderingEnhancement: function () {
$('.woocommerce-ordering select').each(function () {
var $select = $(this);
if (!$select.attr('aria-label')) {
$select.attr('aria-label', 'Product sorting');
}
});
this.$document.off('change.rxOrdering').on('change.rxOrdering', '.woocommerce-ordering select', function () {
$(this).closest('form').addClass('is-submitting');
});
},
couponToggle: function () {
var self = this;
$('.woocommerce-form-coupon-toggle .showcoupon').attr({
role: 'button',
'aria-expanded': 'false'
});
this.$document.off('click.rxCouponToggle').on('click.rxCouponToggle', '.showcoupon', function () {
var $button = $(this);
setTimeout(function () {
var isVisible = $('.checkout_coupon, .woocommerce-form-coupon').is(':visible');
$button.attr('aria-expanded', isVisible ? 'true' : 'false');
if (isVisible) {
self.smoothScrollTo($('.checkout_coupon, .woocommerce-form-coupon').first(), 100);
}
}, 100);
});
$('.coupon input.input-text').attr('placeholder', 'Enter coupon code');
},
checkoutEnhancement: function () {
var self = this;
if (!$('form.checkout').length) {
return;
}
$('form.checkout').attr('novalidate', 'novalidate');
$('.woocommerce-checkout-review-order-table').each(function () {
var $table = $(this);
if (!$table.closest('.rx-checkout-order-box').length) {
$table.wrap('<div class="rx-checkout-order-box"></div>');
}
});
$('#ship-to-different-address-checkbox').on('change.rxShippingAddress', function () {
var checked = $(this).is(':checked');
$('.shipping_address').attr('aria-hidden', checked ? 'false' : 'true');
}).trigger('change.rxShippingAddress');
this.$document.off('checkout_error.rxCheckout').on('checkout_error.rxCheckout', function () {
var $error = $('.woocommerce-error').first();
if ($error.length) {
self.smoothScrollTo($error, 120);
$error.attr('tabindex', '-1').focus();
}
});
this.$document.off('updated_checkout.rxCheckout').on('updated_checkout.rxCheckout', function () {
self.refreshCheckoutUI();
});
this.$document.off('change.rxPaymentMethod').on('change.rxPaymentMethod', 'input[name="payment_method"]', function () {
$('.wc_payment_method').removeClass('rx-payment-selected');
$(this).closest('.wc_payment_method').addClass('rx-payment-selected');
});
$('input[name="payment_method"]:checked').closest('.wc_payment_method').addClass('rx-payment-selected');
},
refreshCheckoutUI: function () {
$('.woocommerce-checkout-review-order-table').closest('.rx-checkout-order-box').addClass('rx-checkout-updated');
setTimeout(function () {
$('.rx-checkout-order-box').removeClass('rx-checkout-updated');
}, 600);
$('input[name="payment_method"]:checked').closest('.wc_payment_method').addClass('rx-payment-selected');
},
formEnhancement: function () {
var self = this;
$('.woocommerce form input, .woocommerce form textarea, .woocommerce form select').each(function () {
var $field = $(this);
if ($field.attr('type') === 'hidden') {
return;
}
var id = $field.attr('id');
var $label = id ? $('label[for="' + id + '"]') : $();
if ($label.length && !$field.attr('aria-label')) {
$field.attr('aria-label', $.trim($label.text().replace('*', '')));
}
});
this.$document.off('focus.rxField').on('focus.rxField', '.woocommerce form input, .woocommerce form textarea, .woocommerce form select', function () {
$(this).closest('.form-row, p').addClass('rx-field-focused');
});
this.$document.off('blur.rxField').on('blur.rxField', '.woocommerce form input, .woocommerce form textarea, .woocommerce form select', function () {
var $field = $(this);
var $row = $field.closest('.form-row, p');
$row.removeClass('rx-field-focused');
if ($field.val()) {
$row.addClass('rx-field-has-value');
} else {
$row.removeClass('rx-field-has-value');
}
});
this.$document.off('submit.rxWooForms').on('submit.rxWooForms', '.woocommerce form', function () {
$(this).addClass(self.config.loadingClass);
});
},
noticeEnhancement: function () {
var self = this;
$('.woocommerce-message, .woocommerce-info, .woocommerce-error').each(function () {
var $notice = $(this);
if ($notice.hasClass('rx-notice-ready')) {
return;
}
$notice.addClass('rx-notice-ready');
$notice.attr('role', $notice.hasClass('woocommerce-error') ? 'alert' : 'status');
if (!$notice.find('.rx-notice-close').length) {
$notice.append('<button type="button" class="rx-notice-close" aria-label="Close notice">×</button>');
}
});
this.$document.off('click.rxNoticeClose').on('click.rxNoticeClose', '.rx-notice-close', function (e) {
e.preventDefault();
$(this).closest('.woocommerce-message, .woocommerce-info, .woocommerce-error').slideUp(180, function () {
$(this).remove();
});
});
clearTimeout(window.rxWooNoticeTimer);
window.rxWooNoticeTimer = setTimeout(function () {
$('.woocommerce-message.rx-notice-ready, .woocommerce-info.rx-notice-ready').fadeOut(300);
}, self.config.noticeDelay);
},
accountPageEnhancement: function () {
if (!$('.woocommerce-account').length) {
return;
}
$('.woocommerce-MyAccount-navigation').each(function () {
var $nav = $(this);
if (!$nav.attr('aria-label')) {
$nav.attr('aria-label', 'My account navigation');
}
});
$('.woocommerce-MyAccount-navigation-link.is-active a').attr('aria-current', 'page');
if (!$('.rx-account-mobile-toggle').length && $('.woocommerce-MyAccount-navigation').length) {
$('.woocommerce-MyAccount-navigation').before(
'<button type="button" class="rx-account-mobile-toggle" aria-expanded="false">Account menu</button>'
);
}
this.$document.off('click.rxAccountToggle').on('click.rxAccountToggle', '.rx-account-mobile-toggle', function (e) {
e.preventDefault();
var $button = $(this);
var $nav = $('.woocommerce-MyAccount-navigation');
$nav.toggleClass('is-open');
$button.attr('aria-expanded', $nav.hasClass('is-open') ? 'true' : 'false');
});
},
productArchiveCards: function () {
var self = this;
$('.woocommerce ul.products li.product').each(function () {
var $product = $(this);
if ($product.hasClass('rx-product-card-ready')) {
return;
}
$product.addClass('rx-product-card-ready');
var $link = $product.find('a.woocommerce-LoopProduct-link').first();
var title = $.trim($product.find('.woocommerce-loop-product__title').text());
if ($link.length && title) {
$link.attr('aria-label', 'View product: ' + title);
}
$product.find('.button').each(function () {
var $button = $(this);
if (!$button.attr('aria-label') && title) {
$button.attr('aria-label', $button.text() + ': ' + title);
}
});
});
this.$document.off('mouseenter.rxProductCard focusin.rxProductCard').on(
'mouseenter.rxProductCard focusin.rxProductCard',
'.woocommerce ul.products li.product',
function () {
$(this).addClass('rx-card-hover');
}
);
this.$document.off('mouseleave.rxProductCard focusout.rxProductCard').on(
'mouseleave.rxProductCard focusout.rxProductCard',
'.woocommerce ul.products li.product',
function () {
$(this).removeClass('rx-card-hover');
}
);
this.$document.off('added_to_cart.rxProductCard').on('added_to_cart.rxProductCard', function () {
self.noticeEnhancement();
});
},
wishlistCompareSafeButtons: function () {
$('.yith-wcwl-add-to-wishlist a, .compare.button, .woosc-btn, .woosw-btn').each(function () {
var $button = $(this);
if (!$button.attr('aria-label')) {
$button.attr('aria-label', $.trim($button.text()) || 'Product action');
}
});
},
backToShopButton: function () {
if (!$('.woocommerce-cart').length) {
return;
}
if (!$('.rx-back-to-shop').length && $('.cart-empty.woocommerce-info').length) {
$('.return-to-shop').addClass('rx-back-to-shop');
}
},
accessibilityFixes: function () {
$('.star-rating').each(function () {
var $rating = $(this);
if (!$rating.attr('aria-label')) {
var text = $.trim($rating.text());
if (text) {
$rating.attr('aria-label', text);
}
}
});
$('.woocommerce-pagination a, .woocommerce-pagination span').each(function () {
var $item = $(this);
if ($item.hasClass('current')) {
$item.attr('aria-current', 'page');
}
});
$('.remove').each(function () {
var $remove = $(this);
if (!$remove.attr('aria-label')) {
$remove.attr('aria-label', 'Remove item');
}
});
$('.woocommerce-breadcrumb').attr('aria-label', 'Product breadcrumb');
},
bindWooEvents: function () {
var self = this;
this.$document.on('added_to_cart removed_from_cart wc_fragments_refreshed', function () {
self.closeMiniCart();
self.noticeEnhancement();
self.quantityButtons();
self.accessibilityFixes();
});
this.$document.on('updated_cart_totals updated_wc_div', function () {
$('.woocommerce-cart-form').removeClass(self.config.loadingClass);
self.quantityButtons();
self.noticeEnhancement();
self.accessibilityFixes();
});
this.$document.on('wc_fragments_loaded wc_fragments_refreshed', function () {
self.miniCartPanel();
self.noticeEnhancement();
});
this.$document.on('updated_checkout', function () {
self.refreshCheckoutUI();
self.formEnhancement();
self.noticeEnhancement();
});
this.$document.on('found_variation reset_data', function () {
self.updateQtyButtonStates();
});
},
scrollFixes: function () {
var self = this;
this.$document.off('click.rxScrollReviews').on('click.rxScrollReviews', '.woocommerce-review-link', function (e) {
var target = $('#reviews');
if (target.length) {
e.preventDefault();
self.smoothScrollTo(target, 90);
target.attr('tabindex', '-1').focus();
}
});
this.$document.off('click.rxTabHash').on('click.rxTabHash', 'a[href^="#tab-"]', function () {
var hash = $(this).attr('href');
if (hash && $(hash).length) {
setTimeout(function () {
self.smoothScrollTo($(hash), 90);
}, 80);
}
});
},
performanceSafeLazyActions: function () {
var self = this;
if ('requestIdleCallback' in window) {
window.requestIdleCallback(function () {
self.lazyProductImages();
self.markExternalProductLinks();
});
} else {
setTimeout(function () {
self.lazyProductImages();
self.markExternalProductLinks();
}, 900);
}
},
lazyProductImages: function () {
$('.woocommerce img').each(function () {
var $img = $(this);
if (!$img.attr('loading')) {
$img.attr('loading', 'lazy');
}
if (!$img.attr('decoding')) {
$img.attr('decoding', 'async');
}
});
$('.single-product .woocommerce-product-gallery img').first().attr('loading', 'eager');
},
markExternalProductLinks: function () {
var siteHost = window.location.hostname;
$('.woocommerce a[href^="http"]').each(function () {
var $link = $(this);
var linkHost = this.hostname;
if (linkHost && linkHost !== siteHost) {
$link.attr({
target: '_blank',
rel: 'noopener noreferrer'
});
}
});
},
smoothScrollTo: function ($target, offset) {
if (!$target || !$target.length) {
return;
}
offset = offset || 0;
$('html, body').stop().animate({
scrollTop: $target.offset().top - offset
}, 450);
},
cleanNumber: function (number) {
if (number % 1 === 0) {
return parseInt(number, 10);
}
return parseFloat(number.toFixed(2));
},
escapeHtml: function (text) {
if (!text) {
return '';
}
return String(text)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
},
escapeAttr: function (text) {
return this.escapeHtml(text);
}
};
$.fn.isInViewport = function () {
if (!this.length) {
return false;
}
var elementTop = this.offset().top;
var elementBottom = elementTop + this.outerHeight();
var viewportTop = $(window).scrollTop();
var viewportBottom = viewportTop + $(window).height();
return elementBottom > viewportTop && elementTop < viewportBottom;
};
$(document).ready(function () {
RXWoo.init();
});
window.RXWoo = RXWoo;
})(jQuery, window, document);
Now enqueue it in your functions.php like this:
/**
* RX Theme WooCommerce frontend script.
*/
function rx_theme_enqueue_woocommerce_script() {
if ( ! class_exists( 'WooCommerce' ) ) {
return;
}
wp_enqueue_script(
'rx-theme-woocommerce',
get_template_directory_uri() . '/inc/js/woocommerce.js',
array( 'jquery', 'wc-add-to-cart', 'wc-cart-fragments' ),
wp_get_theme()->get( 'Version' ),
true
);
}
add_action( 'wp_enqueue_scripts', 'rx_theme_enqueue_woocommerce_script' );
For better styling, you can later add CSS classes such as:
.rx-qty-btn {
cursor: pointer;
}
.rx-sticky-add-to-cart {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 9999;
display: none;
background: #fff;
box-shadow: 0 -5px 25px rgba(0,0,0,.12);
padding: 12px 20px;
}
.rx-sticky-add-to-cart.is-active {
display: flex;
align-items: center;
justify-content: space-between;
}
.rx-sticky-product-info {
display: flex;
align-items: center;
gap: 12px;
}
.rx-sticky-product-image {
width: 50px;
height: 50px;
object-fit: cover;
border-radius: 8px;
}
.rx-notice-close {
float: right;
border: 0;
background: transparent;
font-size: 20px;
cursor: pointer;
}
.rx-woo-table-scroll {
overflow-x: auto;
}
.rx-shop-filter-open .widget_price_filter,
.rx-shop-filter-open .woocommerce-widget-layered-nav {
display: block;
}
Important note: this JavaScript is safe for a normal WooCommerce theme, but some features depend on your theme HTML structure. If your header cart or filter sidebar has different class names, we can adjust those selectors later.