Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /**
- * WooCommerce Ajax Add to Cart
- *
- * This module handles adding products to cart via AJAX using the WooCommerce Store API.
- * It also updates the mini cart after a successful addition.
- */
- class AddToCart {
- constructor() {
- this.cartButtonSelector = '.cartButton';
- this.miniCartSelector = '.widget_shopping_cart_content';
- this.miniCartItemSelector = '.cart_list .mini_cart_item';
- this.cartCountSelector = '.mini_cart_counter .counter_span';
- this.isRequestPending = false;
- this.initElements();
- this.init();
- }
- /**
- * Initialize the module
- */
- initElements() {
- this.cartButtons = document.querySelectorAll('.cartButton');
- this.cartContainer = document.querySelector(this.cartContainerSelector);
- this.miniCartItems = document.querySelectorAll(this.miniCartItemSelector);
- }
- init() {
- if (this.cartButtons.length > 0) {
- this.attachEventListeners();
- }
- }
- /**
- * Attach click event listeners to cart buttons
- */
- attachEventListeners() {
- this.cartButtons.forEach(button => {
- button.addEventListener('click', this.handleAddToCart.bind(this));
- });
- }
- /**
- * Handle add to cart button click
- *
- * @param {Event} event - Click event
- */
- handleAddToCart(event) {
- event.preventDefault();
- if (this.isRequestPending) {
- return;
- }
- const button = event.currentTarget;
- const productInfo = this.getProductInfo(button);
- if (!productInfo) {
- this.showNotification('Error', 'Product information not found', 'error');
- return;
- }
- this.addToCart(productInfo, button);
- }
- /**
- * Get product information from the hidden input
- *
- * @param {Element} button - The clicked cart button
- * @returns {Object|null} - Product information or null if not found
- */
- getProductInfo(button) {
- const productContainer = button.closest('.product-actions') || button.parentNode;
- if (!productContainer) return null;
- const productInfoEl = productContainer.querySelector('.listingInfo');
- if (!productInfoEl) return null;
- return {
- id: productInfoEl.dataset.productId,
- quantity: 1, // Default quantity
- sku: productInfoEl.dataset.productSku,
- title: productInfoEl.dataset.productTitle,
- slug: productInfoEl.dataset.productSlug,
- price: productInfoEl.dataset.productPrice,
- type: productInfoEl.dataset.productType,
- };
- }
- /**
- * Add product to cart via WooCommerce Store API
- *
- * @param {Object} product - Product information
- * @param {Element} button - The clicked button element
- */
- async addToCart(product, button) {
- console.log('this is addToCart', product);
- this.isRequestPending = true;
- this.setButtonLoading(button, true);
- // If it's a variable product, we would need variation_id too
- if (product.type === 'variable') {
- this.showNotification('Info', 'Please select product options on the product page', 'info');
- this.setButtonLoading(button, false);
- this.isRequestPending = false;
- return;
- }
- // WooCommerce Store API endpoint for adding to cart
- const apiUrl = '/wp-json/wc/store/v1/cart/add-item';
- const requestData = {
- id: parseInt(product.id, 10),
- quantity: parseInt(product.quantity, 10) || 1
- };
- try {
- const response = await fetch(apiUrl, {
- method: 'POST',
- credentials: 'same-origin',
- headers: {
- 'Content-Type': 'application/json',
- 'Nonce': ajaxInfo.security.security_code || ''
- },
- body: JSON.stringify(requestData)
- });
- if (!response.ok) {
- const errorData = await response.json().catch(() => ({}));
- throw new Error(errorData.message || `HTTP error! Status: ${response.status}`);
- }
- const data = await response.json();
- console.log('Add to cart response:', data);
- // Show success notification
- this.showNotification('Success', `"${product.title || 'Product'}" has been added to your cart.`, 'success');
- // Update mini cart and cart count
- await this.updateMiniCart();
- this.updateCartCount(data.items_count || 0);
- } catch (error) {
- console.error('Error adding to cart:', error);
- this.showNotification('Error', 'Could not add item to cart. Please try again.', 'error');
- } finally {
- this.setButtonLoading(button, false);
- this.isRequestPending = false;
- }
- }
- /**
- * Update the mini cart contents
- */
- async updateMiniCart() {
- console.log('this is updateMiniCart');
- const miniCart = document.querySelector(this.miniCartSelector);
- if (!miniCart) return;
- // Fetch updated mini cart HTML
- await fetch('/wp-json/wc/store/v1/cart', {
- method: 'GET',
- credentials: 'same-origin',
- headers: {
- 'Content-Type': 'application/json',
- 'Nonce': ajaxInfo.security.security_code || ''
- }
- })
- .then(response => response.json())
- .then(data => {
- console.log('this is data from updateMiniCart: ', data);
- // Update mini cart total
- const totalEl = miniCart.querySelector('.woocommerce-mini-cart__total strong');
- if (totalEl) {
- totalEl.textContent = this.formatMoney(data.totals.total_price);
- }
- // Update mini cart items
- this.updateMiniCartItems(data.items);
- })
- .catch(error => {
- console.error('Error updating mini cart:', error);
- });
- }
- /**
- * Update items in the mini cart
- *
- * @param {Array} items - Cart items from the API
- */
- updateMiniCartItems(items) {
- console.log('this is updateMiniCartItems');
- const miniCartItemsList = document.querySelector('.cart-list');
- if (!miniCartItemsList) return;
- // Clear current items
- miniCartItemsList.innerHTML = '';
- if (items.length === 0) {
- const emptyMessage = document.createElement('li');
- emptyMessage.className = 'mini-cart-empty';
- emptyMessage.textContent = 'Your cart is empty';
- miniCartItemsList.appendChild(emptyMessage);
- return;
- }
- // Add new items
- items.forEach(item => {
- const itemElement = document.createElement('li');
- itemElement.className = 'woocommerce-mini-cart-item';
- itemElement.dataset.itemKey = item.key;
- itemElement.innerHTML = `
- <div class="attachment-woocommerce_thumbnail">
- <img src="${item.images[0]?.src || ''}" alt="${item.name}">
- </div>
- <div class="mini-cart-item-details">
- <h4>${item.name}</h4>
- <div class="mini-cart-item-price">
- ${item.quantity} × ${this.formatMoney(item.prices.price)}
- </div>
- </div>
- <a href="#" class="remove remove_from_cart_button" aria-label="Remove ${item.name} from cart" data-product_id="${item.id}" data-cart_item_key="${item.key}" data-product_sku="${item.sku}" data-success_message="'${item.name}' has been removed from your cart">×</a>
- `;
- miniCartItemsList.appendChild(itemElement);
- });
- // Add remove button listeners
- this.attachRemoveItemListeners();
- }
- /**
- * Attach event listeners to remove buttons in mini cart
- */
- attachRemoveItemListeners() {
- console.log('this is attachRemoveItemListeners');
- // Remove existing listeners to prevent conflicts
- const removeButtons = document.querySelectorAll('.remove_from_cart_button');
- removeButtons.forEach(button => {
- // Clone node to remove all event listeners
- const newButton = button.cloneNode(true);
- button.parentNode.replaceChild(newButton, button);
- });
- // Add fresh listeners
- const freshButtons = document.querySelectorAll('.remove_from_cart_button');
- freshButtons.forEach(button => {
- button.addEventListener('click', (e) => {
- e.preventDefault(); // Prevent default link behavior
- const itemKey = e.currentTarget.dataset.cartItemKey;
- if (itemKey) {
- this.removeCartItem(itemKey, e.currentTarget);
- }
- });
- });
- }
- /**
- * Remove an item from the cart
- *
- * @param {string} itemKey - Cart item key
- * @param {Element} button - Remove button element
- */
- removeCartItem(itemKey, button) {
- console.log('this is removeCartItem');
- const apiUrl = `/wp-json/wc/store/v1/cart/items/${itemKey}`;
- // Fetch cart items
- fetch(apiUrl, {
- method: 'DELETE',
- credentials: 'same-origin',
- headers: {
- 'Content-Type': 'application/json',
- 'Nonce': ajaxInfo.security.security_code || ''
- },
- body: JSON.stringify({
- key: itemKey
- })
- })
- .then(response => {
- console.log('removeCartItem response: ', response);
- if (!response.ok) {
- throw new Error(`HTTP error! Status: ${response.status}`);
- }
- return response.json();
- })
- .then(data => {
- console.log('removeCartItem response: ', data);
- // Update cart visually
- const itemElement = button.closest('.woocommerce-mini-cart-item');
- if (itemElement) {
- itemElement.remove();
- }
- // Update cart count
- //this.updateCartCount(data.items_count || 0);
- // Update totals
- const totalEl = document.querySelector('.woocommerce-mini-cart__total');
- if (totalEl) {
- totalEl.textContent = this.formatMoney(data.totals.total_price);
- }
- // Show empty message if needed
- if (data.items.length === 0) {
- const miniCartItemsList = document.querySelector('.woocommerce-mini-cart');
- if (miniCartItemsList) {
- const emptyMessage = document.createElement('li');
- emptyMessage.className = 'mini-cart-empty';
- emptyMessage.textContent = 'Your cart is empty';
- miniCartItemsList.appendChild(emptyMessage);
- }
- }
- })
- .catch(error => {
- console.error('Error removing item:', error);
- this.showNotification('Error', 'Could not remove item. Please try again.', 'error');
- });
- }
- /**
- * Update the cart item count display
- *
- * @param {number} count - New cart count
- */
- updateCartCount(count) {
- const cartCountElements = document.querySelectorAll(this.cartCountSelector);
- cartCountElements.forEach(element => {
- element.textContent = count.toString();
- // Add animation class
- element.classList.add('cart-count-updated');
- // Remove animation class after animation completes
- setTimeout(() => {
- element.classList.remove('cart-count-updated');
- }, 1000);
- });
- }
- /**
- * Set button to loading state
- *
- * @param {Element} button - Button element
- * @param {boolean} isLoading - Whether button is in loading state
- */
- setButtonLoading(button, isLoading) {
- if (isLoading) {
- button.classList.add('loading');
- button.setAttribute('disabled', 'disabled');
- // Store original text and set loading text
- button.dataset.originalText = button.innerHTML;
- button.innerHTML = '<span class="spinner">Adding...</span>';
- } else {
- button.classList.remove('loading');
- button.removeAttribute('disabled');
- // Restore original text
- if (button.dataset.originalText) {
- button.innerHTML = button.dataset.originalText;
- }
- }
- }
- /**
- * Show a notification
- *
- * @param {string} title - Notification title
- * @param {string} message - Notification message
- * @param {string} type - Notification type (success, error, info)
- */
- showNotification(title, message, type = 'info') {
- // Check if the site uses WooCommerce's built-in notices
- const noticesContainer = document.querySelector('.woocommerce-notices-wrapper');
- if (noticesContainer) {
- // Clear existing notices
- noticesContainer.innerHTML = '';
- // Create new notice
- const notice = document.createElement('div');
- notice.className = `woocommerce-${type}`;
- notice.innerHTML = message;
- noticesContainer.appendChild(notice);
- // Scroll to notice if not in viewport
- if (!this.isElementInViewport(noticesContainer)) {
- noticesContainer.scrollIntoView({ behavior: 'smooth' });
- }
- } else {
- // Create a custom notification if WooCommerce notices wrapper doesn't exist
- const notification = document.createElement('div');
- notification.className = `ajax-notification ajax-notification-${type}`;
- notification.innerHTML = `
- <div class="ajax-notification-content">
- <h4>${title}</h4>
- <p>${message}</p>
- </div>
- <button class="ajax-notification-close">×</button>
- `;
- document.body.appendChild(notification);
- // Show with animation
- setTimeout(() => {
- notification.classList.add('ajax-notification-visible');
- }, 10);
- // Add close button functionality
- const closeBtn = notification.querySelector('.ajax-notification-close');
- if (closeBtn) {
- closeBtn.addEventListener('click', () => {
- notification.classList.remove('ajax-notification-visible');
- setTimeout(() => {
- notification.remove();
- }, 300);
- });
- }
- // Auto-remove after 5 seconds for success messages
- if (type === 'success') {
- setTimeout(() => {
- notification.classList.remove('ajax-notification-visible');
- setTimeout(() => {
- notification.remove();
- }, 300);
- }, 5000);
- }
- }
- }
- /**
- * Check if an element is in the viewport
- *
- * @param {Element} element - Element to check
- * @returns {boolean} - Whether element is in viewport
- */
- isElementInViewport(element) {
- const rect = element.getBoundingClientRect();
- return (
- rect.top >= 0 &&
- rect.left >= 0 &&
- rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
- rect.right <= (window.innerWidth || document.documentElement.clientWidth)
- );
- }
- /**
- * Format money value
- *
- * @param {number|string} amount - Amount to format
- * @returns {string} - Formatted amount
- */
- formatMoney(amount) {
- // Use the site's currency symbol and format from WooCommerce if available
- if (window.woocommerce_price_format && window.woocommerce-Price-currencySymbol) {
- const formatted = parseFloat(amount).toFixed(2);
- const formattedWithSymbol = window.woocommerce_price_format
- .replace('%1$s', window.woocommerce_currency_symbol)
- .replace('%2$s', formatted);
- return formattedWithSymbol;
- }
- // Fallback formatting
- return '$' + parseFloat(amount).toFixed(2);
- }
- }
- // Export for use in other modules
- export default AddToCart;
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement