PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.87
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.87
51.1.87 51.1.86 51.1.84 51.1.85 51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 All 41 releases
king-addons / includes / widgets / Woo_Product_Variations / script.js

script.js in King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder 51.1.87, at includes/widgets/Woo_Product_Variations/script.js

327 lines 12.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Woo Product Variations widget behavior.
3 *
4 * Enhances variation selects with swatches and keeps WooCommerce's
5 * variation resolver in sync when the form lives in a split builder widget.
6 */
7 (function ($) {
8 "use strict";
9
10 const INIT_FLAG = "kaWooVariationsInit";
11
12 const initVariations = (wrap) => {
13 if (!wrap || !wrap.dataset) return;
14 if (wrap.dataset[INIT_FLAG] === "1") return;
15
16 const form = wrap.querySelector('form.variations_form');
17 if (!form) return;
18 wrap.dataset[INIT_FLAG] = "1";
19
20 let map = {};
21 try {
22 map = wrap.dataset.swatchesMap ? JSON.parse(wrap.dataset.swatchesMap) : {};
23 } catch (e) {
24 map = {};
25 }
26
27 const unavailableBehavior = map.unavailable || 'disable';
28 const showSelected = map.show_selected === 'yes';
29 const shape = map.shape || 'square';
30 const attrMap = (map.map) || {};
31 const source = map.source || 'auto';
32
33 const selects = form.querySelectorAll('select');
34 const updateCallbacks = [];
35 const runAll = () => updateCallbacks.forEach((cb) => cb());
36
37 // Woo leaves out-of-stock options enabled in the native select when
38 // "hide out of stock" is off. Hide/disable would then be a no-op,
39 // so also read the variation JSON Woo already prints on the form.
40 let productVariations = [];
41 try {
42 const raw = form.getAttribute('data-product_variations');
43 productVariations = raw && raw !== 'false' ? JSON.parse(raw) : [];
44 } catch (e) {
45 productVariations = [];
46 }
47
48 const priceRoots = () => document.querySelectorAll('.ka-woo-product-price');
49 priceRoots().forEach((el) => {
50 if (!el.dataset.kaOriginalPrice) {
51 el.dataset.kaOriginalPrice = el.innerHTML;
52 }
53 });
54
55 const setAtcNotice = (hide) => {
56 document.querySelectorAll('.ka-woo-product-atc__notice').forEach((el) => {
57 el.hidden = !!hide;
58 });
59 };
60
61 const applyVariationPrice = (variation) => {
62 priceRoots().forEach((el) => {
63 if (!variation || !variation.price_html) {
64 if (el.dataset.kaOriginalPrice) {
65 el.innerHTML = el.dataset.kaOriginalPrice;
66 }
67 return;
68 }
69 const discount = el.querySelector('.ka-woo-product-price__discount');
70 const discountHtml = discount ? discount.outerHTML : '';
71 el.innerHTML = variation.price_html + discountHtml;
72 });
73 };
74
75 const ensureWcVariationForm = () => {
76 if (!window.jQuery || typeof window.jQuery.fn.wc_variation_form !== 'function') {
77 return false;
78 }
79 const $form = window.jQuery(form);
80 if (!$form.data('wc_variation_form')) {
81 $form.wc_variation_form();
82 }
83 return true;
84 };
85
86 const findMatchedVariation = () => {
87 if (!Array.isArray(productVariations) || !productVariations.length) {
88 return null;
89 }
90 const selected = {};
91 let hasEmpty = false;
92 form.querySelectorAll('select[name^="attribute_"]').forEach((sel) => {
93 selected[sel.name] = sel.value;
94 if (!sel.value) {
95 hasEmpty = true;
96 }
97 });
98 if (hasEmpty) {
99 return null;
100 }
101 return productVariations.find((variation) => {
102 const attrs = variation.attributes || {};
103 return Object.keys(attrs).every((key) => {
104 const want = attrs[key];
105 return !want || want === selected[key];
106 });
107 }) || null;
108 };
109
110 const applyFallbackVariation = () => {
111 if (window.jQuery && window.jQuery(form).data('wc_variation_form')) {
112 return;
113 }
114 const match = findMatchedVariation();
115 const varInput = form.querySelector('input.variation_id, input[name="variation_id"]');
116 const single = form.querySelector('.single_variation');
117 if (match) {
118 if (varInput) {
119 varInput.value = String(match.variation_id || '');
120 }
121 if (single) {
122 single.innerHTML = (match.price_html ? '<div class="woocommerce-variation-price">' + match.price_html + '</div>' : '')
123 + (match.availability_html ? '<div class="woocommerce-variation-availability">' + match.availability_html + '</div>' : '');
124 }
125 applyVariationPrice(match);
126 setAtcNotice(true);
127 } else {
128 if (varInput) {
129 varInput.value = '0';
130 }
131 if (single) {
132 single.innerHTML = '';
133 }
134 applyVariationPrice(null);
135 setAtcNotice(false);
136 }
137 };
138
139 const wcReady = ensureWcVariationForm();
140 if (window.jQuery) {
141 const $form = window.jQuery(form);
142 $form.on('found_variation', function (event, variation) {
143 applyVariationPrice(variation);
144 setAtcNotice(!!(variation && variation.variation_id));
145 });
146 $form.on('reset_data hide_variation', function () {
147 applyVariationPrice(null);
148 setAtcNotice(false);
149 });
150 $form.on('woocommerce_update_variation_values woocommerce_variation_has_changed reset_data found_variation hide_variation', runAll);
151 }
152 if (!wcReady) {
153 form.addEventListener('change', applyFallbackVariation);
154 applyFallbackVariation();
155 if (window.jQuery) {
156 window.jQuery(function () {
157 if (ensureWcVariationForm()) {
158 const selected = form.querySelector('select[name^="attribute_"]');
159 if (selected && selected.value) {
160 window.jQuery(selected).trigger('change');
161 }
162 } else {
163 applyFallbackVariation();
164 }
165 });
166 }
167 }
168
169 if (wrap.dataset.swatches !== 'yes') {
170 return;
171 }
172
173 const attributeMatches = (variationAttr, selected) => {
174 if (!variationAttr) {
175 return true;
176 }
177 return variationAttr === selected;
178 };
179
180 const valueIsAvailable = (attrName, val) => {
181 if (!Array.isArray(productVariations) || !productVariations.length) {
182 const opt = form.querySelector(`select[name="${attrName}"] option[value="${val}"]`);
183 return !(opt && opt.disabled);
184 }
185 return productVariations.some((variation) => {
186 if (!variation || variation.is_in_stock === false) {
187 return false;
188 }
189 const attrs = variation.attributes || {};
190 if (!attributeMatches(attrs[attrName], val)) {
191 return false;
192 }
193 return Array.from(selects).every((other) => {
194 if (other.name === attrName || !other.value) {
195 return true;
196 }
197 return attributeMatches(attrs[other.name], other.value);
198 });
199 });
200 };
201
202 selects.forEach((select) => {
203 const attrName = select.name;
204 const swatchesInfo = attrMap[attrName];
205 if (!swatchesInfo) {
206 return;
207 }
208
209 const container = document.createElement('div');
210 container.className = 'ka-woo-variations__swatches';
211
212 const selectedText = document.createElement('div');
213 selectedText.className = 'ka-woo-variations__selected';
214
215 const options = Array.from(select.options).filter((opt) => opt.value !== '');
216 options.forEach((opt) => {
217 const btn = document.createElement('button');
218 btn.type = 'button';
219 btn.className = 'ka-woo-variations__swatch';
220 if (shape === 'round') {
221 btn.classList.add('ka-woo-variations__swatch--round');
222 }
223 btn.dataset.value = opt.value;
224
225 const info = swatchesInfo[opt.value] || {};
226
227 if (source === 'text') {
228 const span = document.createElement('span');
229 span.className = 'ka-woo-variations__swatch-text';
230 span.textContent = info.label || opt.text;
231 btn.appendChild(span);
232 } else if (info.image) {
233 const img = document.createElement('img');
234 img.src = info.image;
235 img.alt = info.label || opt.text;
236 img.className = 'ka-woo-variations__swatch-img';
237 btn.appendChild(img);
238 } else if (info.color) {
239 const span = document.createElement('span');
240 span.className = 'ka-woo-variations__swatch-color';
241 span.style.background = info.color;
242 btn.appendChild(span);
243 } else {
244 const span = document.createElement('span');
245 span.className = 'ka-woo-variations__swatch-text';
246 span.textContent = info.label || opt.text;
247 btn.appendChild(span);
248 }
249
250 btn.addEventListener('click', () => {
251 if (!valueIsAvailable(attrName, opt.value)) {
252 return;
253 }
254 select.value = opt.value;
255 select.dispatchEvent(new Event('change', { bubbles: true }));
256 if (window.jQuery) {
257 window.jQuery(select).trigger('check_variations');
258 window.jQuery(select).trigger('change');
259 }
260 applyFallbackVariation();
261 runAll();
262 });
263
264 container.appendChild(btn);
265 });
266
267 select.style.display = 'none';
268 select.insertAdjacentElement('afterend', container);
269 if (showSelected) {
270 container.insertAdjacentElement('afterend', selectedText);
271 }
272
273 const updateStates = () => {
274 const current = select.value;
275 Array.from(container.children).forEach((btn) => {
276 const val = btn.dataset.value;
277 const opt = select.querySelector(`option[value="${val}"]`);
278 const unavailable = !valueIsAvailable(attrName, val) || !!(opt && opt.disabled);
279 btn.classList.toggle('is-active', current === val);
280 btn.classList.toggle('is-disabled', unavailable);
281 if (unavailable && unavailableBehavior === 'hide') {
282 btn.style.display = 'none';
283 } else {
284 btn.style.display = '';
285 }
286 });
287 if (showSelected) {
288 const label = (swatchesInfo[current] && swatchesInfo[current].label) || current || '';
289 const prefix = select.getAttribute('aria-label') || '';
290 selectedText.textContent = label ? `${prefix}: ${label}` : '';
291 }
292 };
293
294 select.addEventListener('change', runAll);
295 updateCallbacks.push(updateStates);
296 });
297
298 runAll();
299
300 const resetBtn = form.querySelector('.reset_variations');
301 if (resetBtn) {
302 resetBtn.addEventListener('click', () => {
303 setTimeout(() => {
304 runAll();
305 applyFallbackVariation();
306 }, 50);
307 });
308 }
309 };
310
311 const initAll = (root) => {
312 const ctx = root && root.querySelectorAll ? root : document;
313 ctx.querySelectorAll(".ka-woo-variations").forEach(initVariations);
314 };
315
316 document.addEventListener("DOMContentLoaded", () => initAll(document));
317
318 $(window).on("elementor/frontend/init", function () {
319 elementorFrontend.hooks.addAction(
320 "frontend/element_ready/woo_product_variations.default",
321 function ($scope) {
322 initAll($scope && $scope[0] ? $scope[0] : document);
323 }
324 );
325 });
326 })(jQuery);
327