1/** 2 * KN Product Variation Frontend Scripts 3 * 4 * @package KN Product Variation 5 * @category Frontend 6 * @author IP Austria 7 */ 8 9(function($) { 10 'use strict'; 11 12 // Get the add-to-cart form for a product (works with default WooCommerce and Elementor). 13 function getCartFormForProduct(productId) { 14 if (!productId) return $(); 15 // Form containing add-to-cart hidden input for this product 16 var $input = $('input[name="add-to-cart"][value="' + productId + '"]'); 17 if ($input.length) return $input.closest('form'); 18 // Form inside same product block as our variations wrapper 19 var $wrapper = $('#kn-product-variations-' + productId); 20 if ($wrapper.length) { 21 var $form = $wrapper.closest('.product').find('form'); 22 if ($form.length) return $form.first(); 23 } 24 // Fallback: any form.cart 25 return $('form.cart').first(); 26 } 27 28 // Put or remove kn_variation_index in the product's add-to-cart form so cart receives it (price + variation name). 29 function syncVariationToCartForm(productId, variationIndex) { 30 var $form = getCartFormForProduct(productId); 31 var $existing = $form.find('input[name="kn_variation_index"], #kn_variation_index_input'); 32 if ($existing.length) { 33 $existing.val(variationIndex !== undefined && variationIndex !== null && variationIndex >= 0 ? variationIndex : ''); 34 } else { 35 $form.find('input[name="kn_variation_index"]').remove(); 36 if (variationIndex !== undefined && variationIndex !== null && variationIndex >= 0) { 37 $form.append($('<input>').attr({ type: 'hidden', name: 'kn_variation_index', value: variationIndex })); 38 } 39 } 40 } 41 42 // Helper: get variation index from radio (data-variation-index or value). 43 function getRadioVariationIndex($radio) { 44 var index = $radio.data('variation-index'); 45 if (index === undefined || index === null) { 46 var val = $radio.val(); 47 if (val !== undefined && val !== '') index = parseInt(val, 10); 48 } 49 return (index >= 0) ? index : null; 50 } 51 52 // Ensure at least one variation is selected (run on ready, after delay, and on load so we win over late scripts that uncheck). 53 function ensureFirstVariationSelected() { 54 // Sync any already-checked variation to the form. 55 $('.kn-variation-radio:checked').each(function() { 56 var productId = $(this).data('product-id'); 57 var index = getRadioVariationIndex($(this)); 58 if (productId !== undefined && index !== null) { 59 syncVariationToCartForm(productId, index); 60 } 61 }); 62 // If a wrapper has no checked radio, select the first one. 63 $('.kn-product-variations-wrapper').each(function() { 64 var $wrap = $(this); 65 var productId = $wrap.data('product-id'); 66 var $checked = $wrap.find('.kn-variation-radio:checked'); 67 if (!$checked.length) { 68 var $first = $wrap.find('.kn-variation-radio').first(); 69 if ($first.length) { 70 $first.prop('checked', true); 71 $first.closest('.kn-variation-item').addClass('selected'); 72 $first.closest('.kn-variation-label').addClass('selected'); 73 var index = getRadioVariationIndex($first); 74 if (productId !== undefined && index !== null) { 75 syncVariationToCartForm(productId, index); 76 } 77 $first.trigger('change'); 78 } 79 } 80 }); 81 } 82 83 $(document).ready(function() { 84 ensureFirstVariationSelected(); 85 // Re-apply after short delay (in case DOM was not fully ready or widgets render late). 86 setTimeout(ensureFirstVariationSelected, 100); 87 setTimeout(ensureFirstVariationSelected, 500); 88 // Re-apply on window load so we win over any script that unchecks after full load (e.g. Elementor). 89 $(window).on('load', ensureFirstVariationSelected); 90 91 // Handle radio button selection/deselection 92 $(document).on('change', '.kn-variation-radio', function() { 93 const $radio = $(this); 94 const productId = $radio.data('product-id'); 95 const variationIndex = getRadioVariationIndex($radio); 96 const $label = $radio.closest('.kn-variation-label'); 97 const $item = $radio.closest('.kn-variation-item'); 98 const $info = $('.kn-selected-variation-info'); 99 100 // If radio is checked, select it 101 if ($radio.is(':checked') && variationIndex !== null) { 102 // Update selected state visually (support both plugin template and add-to-cart widget) 103 $('.kn-variation-label').removeClass('selected'); 104 $('.kn-variation-item').removeClass('selected'); 105 $label.addClass('selected'); 106 $item.addClass('selected'); 107 108 // Get variation data via AJAX 109 $.ajax({ 110 url: knProductVariationFrontend.ajax_url, 111 type: 'POST', 112 data: { 113 action: 'kn_get_variation_data', 114 nonce: knProductVariationFrontend.nonce, 115 product_id: productId, 116 index: variationIndex 117 }, 118 success: function(response) { 119 if (response.success && response.data) { 120 const variation = response.data.variation; 121 122 // Show selected variation info 123 $info.find('.kn-selected-variation-name').text(variation.name);
124 $info.find('.kn-selected-variation-price').html(response.data.price_html); 125 $info.fadeIn(300); 126 127 // Trigger custom event for other scripts 128 $(document).trigger('kn_variation_selected', [variation, response.data]); 129 130 // Keep variation in add-to-cart form so it is sent (normal and AJAX add to cart) 131 syncVariationToCartForm(productId, variationIndex); 132 133 // Update add to cart button if exists 134 const $addToCart = $('button.single_add_to_cart_button, .single_add_to_cart_button'); 135 if ($addToCart.length) { 136 $addToCart.data('variation-index', variationIndex); 137 $addToCart.data('variation-price', response.data.price); 138 } 139 } else { 140 console.error('Error loading variation data'); 141 } 142 }, 143 error: function() { 144 console.error('Error loading variation data'); 145 } 146 }); 147 } else { 148 // Radio is unchecked, deselect 149 $label.removeClass('selected'); 150 $info.fadeOut(300); 151 syncVariationToCartForm(productId, null); 152 153 // Trigger custom event for deselection 154 $(document).trigger('kn_variation_deselected'); 155 156 // Clear add to cart button data 157 const $addToCart = $('button.single_add_to_cart_button, .single_add_to_cart_button'); 158 if ($addToCart.length) { 159 $addToCart.removeData('variation-index'); 160 $addToCart.removeData('variation-price'); 161 } 162 } 163 }); 164 165 // Before ANY add-to-cart form submits (including "Kaufe jetzt" / Buy now), add variation so cart gets name + price. 166 $(document).on('submit', 'form', function() { 167 var $form = $(this); 168 if (!$form.find('input[name="add-to-cart"]').length) return; 169 var productId = $form.find('input[name="add-to-cart"]').val(); 170 var $checked = $form.closest('.product').find('.kn-variation-radio:checked'); 171 if (!$checked.length && productId) $checked = $('#kn-product-variations-' + productId).find('.kn-variation-radio:checked'); 172 if ($checked.length) { 173 var index = getRadioVariationIndex($checked); 174 if (index !== null) syncVariationToCartForm(productId, index); 175 } 176 }); 177 178 // On click of add-to-cart / buy-now button (including "Kaufe jetzt für"), sync variation into form so cart gets name + price. 179 $(document).on('click', 'form:has(input[name="add-to-cart"]) button[type="submit"], form:has(input[name="add-to-cart"]) input[type="submit"], .single_add_to_cart_button', function() { 180 var $btn = $(this); 181 var $form = $btn.closest('form'); 182 if (!$form.length || !$form.find('input[name="add-to-cart"]').length) return; 183 var productId = $form.find('input[name="add-to-cart"]').val(); 184 if (!productId) productId = $form.closest('.product').find('.kn-product-variations-wrapper').attr('data-product-id'); 185 if (!productId) return; 186 var $checked = $('#kn-product-variations-' + productId).find('.kn-variation-radio:checked'); 187 if ($checked.length) { 188 var index = getRadioVariationIndex($checked); 189 if (index !== null) syncVariationToCartForm(productId, index); 190 } 191 }); 192 193 // Handle label click (for better UX - allow toggle) 194 $(document).on('click', '.kn-variation-label', function(e) { 195 // Don't interfere with radio button clicks 196 if ($(e.target).is('input[type="radio"]')) { 197 return; 198 } 199 200 const $label = $(this); 201 const $radio = $label.find('input[type="radio"]'); 202 203 if ($radio.length) { 204 // If already checked, uncheck it (allow deselection) 205 if ($radio.is(':checked')) { 206 $radio.prop('checked', false).trigger('change'); 207 } else { 208 // Uncheck all others first, then check this one 209 $('.kn-variation-radio').not($radio).prop('checked', false); 210 $radio.prop('checked', true).trigger('change'); 211 } 212 } 213 }); 214 }); 215 216})(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.