/** * Frontend JavaScript for Composable Products * * @package WC_Composable_Product */ (function($) { 'use strict'; const ComposableProduct = { /** * Initialize */ init: function() { this.bindEvents(); }, /** * Bind events */ bindEvents: function() { const self = this; // Handle checkbox changes $(document).on('change', '.composable-product-checkbox', function() { self.handleCheckboxChange($(this)); }); // Handle add to cart button $(document).on('click', '.composable-add-to-cart', function(e) { e.preventDefault(); self.addToCart($(this)); }); }, /** * Handle checkbox change * * @param {jQuery} $checkbox Checkbox element */ handleCheckboxChange: function($checkbox) { const $container = $checkbox.closest('.wc-composable-product-selector'); const selectionLimit = parseInt($container.data('selection-limit')); const pricingMode = $container.data('pricing-mode'); const $checked = $container.find('.composable-product-checkbox:checked'); // Enforce selection limit if ($checked.length > selectionLimit) { $checkbox.prop('checked', false); this.showMessage($container, wcComposableProduct.i18n.max_items, 'error'); return; } // Update visual state $checkbox.closest('.composable-product-item').toggleClass('selected', $checkbox.is(':checked')); // Update total price if in sum mode if (pricingMode === 'sum') { this.updateTotalPrice($container); } // Clear messages this.clearMessages($container); }, /** * Update total price * * @param {jQuery} $container Container element */ updateTotalPrice: function($container) { const $checked = $container.find('.composable-product-checkbox:checked'); let total = 0; $checked.each(function() { const price = parseFloat($(this).data('price')); if (!isNaN(price)) { total += price; } }); const currencySymbol = $container.find('.total-price').data('currency'); $container.find('.calculated-total').text(currencySymbol + total.toFixed(2)); }, /** * Add to cart * * @param {jQuery} $button Button element */ addToCart: function($button) { const $container = $button.closest('.wc-composable-product-selector'); const $checked = $container.find('.composable-product-checkbox:checked'); const productId = $button.data('product-id'); // Validate selection if ($checked.length === 0) { this.showMessage($container, wcComposableProduct.i18n.min_items, 'error'); return; } // Collect selected product IDs const selectedProducts = []; $checked.each(function() { selectedProducts.push($(this).val()); }); // Disable button $button.prop('disabled', true).addClass('loading'); // Add to cart via AJAX $.ajax({ url: wc_add_to_cart_params.wc_ajax_url.toString().replace('%%endpoint%%', 'add_to_cart'), type: 'POST', data: { product_id: productId, quantity: 1, composable_products: selectedProducts }, success: function(response) { if (response.error) { this.showMessage($container, response.error, 'error'); } else { // Trigger WooCommerce event $(document.body).trigger('added_to_cart', [response.fragments, response.cart_hash, $button]); // Show success message this.showMessage($container, 'Product added to cart!', 'success'); // Reset selection setTimeout(function() { $checked.prop('checked', false); $container.find('.composable-product-item').removeClass('selected'); this.updateTotalPrice($container); }.bind(this), 1500); } }.bind(this), error: function() { this.showMessage($container, 'An error occurred. Please try again.', 'error'); }.bind(this), complete: function() { $button.prop('disabled', false).removeClass('loading'); } }); }, /** * Show message * * @param {jQuery} $container Container element * @param {string} message Message text * @param {string} type Message type (error, success) */ showMessage: function($container, message, type) { const $messages = $container.find('.composable-messages'); const $message = $('