PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.78
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.78
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 51.1.45 51.1.46 51.1.47 51.1.49 All 37 releases
king-addons / includes / extensions / Woo_Builder / script.js

script.js in King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder 51.1.78, at includes/extensions/Woo_Builder/script.js

415 lines 17.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 (function (factory) {
2 if (typeof window.jQuery === 'undefined') {
3 return;
4 }
5 factory(window.jQuery);
6 })(function ($) {
7 'use strict';
8
9 const $rulesContainer = $('#ka-woo-rules');
10 if (!$rulesContainer.length || typeof $.fn.select2 === 'undefined') {
11 return;
12 }
13
14 const restRoot = (window.wpApiSettings && window.wpApiSettings.root) ? window.wpApiSettings.root : '';
15 const restNonce = (window.wpApiSettings && window.wpApiSettings.nonce) ? window.wpApiSettings.nonce : '';
16 const noValueTypes = ['always', 'all_products', 'cart', 'checkout', 'my_account', 'is_shop'];
17
18 const productTypeOptions = [
19 {id: 'simple', text: 'Simple'},
20 {id: 'variable', text: 'Variable'},
21 {id: 'grouped', text: 'Grouped'},
22 {id: 'external', text: 'External/Affiliate'}
23 ];
24
25 const endpoints = {
26 product_in: {url: restRoot + 'kingaddons/v1/ajaxselect2/getPostsByPostType/', params: {query_slug: 'product'}},
27 product_cat_in: {url: restRoot + 'kingaddons/v1/ajaxselect2/getTaxonomies/', params: {query_slug: 'product_cat'}},
28 product_cat_archive_in: {url: restRoot + 'kingaddons/v1/ajaxselect2/getTaxonomies/', params: {query_slug: 'product_cat'}},
29 product_tag_archive_in: {url: restRoot + 'kingaddons/v1/ajaxselect2/getTaxonomies/', params: {query_slug: 'product_tag'}},
30 product_tag_in: {url: restRoot + 'kingaddons/v1/ajaxselect2/getTaxonomies/', params: {query_slug: 'product_tag'}},
31 products: {url: restRoot + 'kingaddons/v1/ajaxselect2/getPostsByPostType/', params: {query_slug: 'product'}},
32 product_categories: {url: restRoot + 'kingaddons/v1/ajaxselect2/getTaxonomies/', params: {query_slug: 'product_cat'}},
33 product_cat_archives: {url: restRoot + 'kingaddons/v1/ajaxselect2/getTaxonomies/', params: {query_slug: 'product_cat'}},
34 product_tags: {url: restRoot + 'kingaddons/v1/ajaxselect2/getTaxonomies/', params: {query_slug: 'product_tag'}},
35 };
36
37 const getPlaceholder = () => {
38 if (window.KingAddonsWooBuilder && window.KingAddonsWooBuilder.labels && window.KingAddonsWooBuilder.labels.valuePlaceholder) {
39 return window.KingAddonsWooBuilder.labels.valuePlaceholder;
40 }
41 return '';
42 };
43
44 const initRule = ($rule, idx) => {
45 const $type = $rule.find('.ka-woo-rule-type');
46 const $values = $rule.find('.ka-woo-rule-values');
47
48 $type.attr('name', 'ka_woo_rule_type[' + idx + ']');
49 $values.attr('name', 'ka_woo_rule_values[' + idx + '][]');
50
51 const typeVal = $type.val() || '';
52
53 if (noValueTypes.includes(typeVal)) {
54 if ($values.hasClass('select2-hidden-accessible')) {
55 $values.select2('destroy');
56 }
57 $values.prop('disabled', true).val(null);
58 return;
59 }
60
61 $values.prop('disabled', false);
62
63 if ($values.hasClass('select2-hidden-accessible')) {
64 $values.select2('destroy');
65 }
66
67 const ajaxConfig = endpoints[typeVal] || null;
68 const select2Options = {
69 width: '100%',
70 allowClear: true,
71 placeholder: getPlaceholder(),
72 };
73
74 if (ajaxConfig && restRoot) {
75 select2Options.ajax = {
76 url: ajaxConfig.url,
77 dataType: 'json',
78 delay: 120,
79 headers: restNonce ? {'X-WP-Nonce': restNonce} : {},
80 data: function (params) {
81 const query = Object.assign({}, ajaxConfig.params || {});
82 if (params && params.term) {
83 query.s = params.term;
84 }
85 if ($values.data('selected-ids')) {
86 query.ids = $values.data('selected-ids');
87 }
88 return query;
89 },
90 processResults: function (data) {
91 return data;
92 }
93 };
94 } else if (typeVal === 'product_type_in' || typeVal === 'product_types') {
95 select2Options.data = productTypeOptions;
96 }
97
98 $values.select2(select2Options);
99
100 // Clear pre-set data-selected-ids after init to avoid duplicate queries.
101 $values.removeData('selected-ids');
102 };
103
104 const rebuildIndexes = () => {
105 $rulesContainer.find('.ka-woo-rule').each((idx, el) => initRule($(el), idx));
106 };
107
108 $rulesContainer.on('click', '.ka-woo-remove-rule', function () {
109 if ($rulesContainer.find('.ka-woo-rule').length <= 1) {
110 return;
111 }
112 $(this).closest('.ka-woo-rule').remove();
113 rebuildIndexes();
114 });
115
116 $('#ka-woo-add-rule').on('click', function () {
117 const $first = $rulesContainer.find('.ka-woo-rule').first();
118 if (!$first.length) {
119 return;
120 }
121 const $clone = $first.clone();
122 $clone.find('option').prop('selected', false);
123 $clone.find('.ka-woo-rule-values').empty();
124 $clone.appendTo($rulesContainer);
125 rebuildIndexes();
126 });
127
128 $rulesContainer.on('change', '.ka-woo-rule-type', function () {
129 const $rule = $(this).closest('.ka-woo-rule');
130 initRule($rule, $rulesContainer.find('.ka-woo-rule').index($rule));
131 });
132
133 $rulesContainer.find('.ka-woo-rule-values').each(function () {
134 const existingValues = $(this).val();
135 if (existingValues && existingValues.length) {
136 $(this).data('selected-ids', Array.isArray(existingValues) ? existingValues.join(',') : existingValues);
137 }
138 });
139
140 rebuildIndexes();
141 });
142
143 (function () {
144 'use strict';
145
146 const defaultPanels = ['#customer_details', '.woocommerce-checkout-payment', '.woocommerce-checkout-review-order'];
147
148 const resolvePanels = (container) => {
149 const customSelectors = Array.from(container.querySelectorAll('.ka-woo-checkout-step')).map((step) => step.dataset.target || '');
150 const mapped = customSelectors.some(Boolean) ? customSelectors : defaultPanels;
151 return mapped.map((selector, idx) => ({ selector, idx }));
152 };
153
154 const togglePanels = (stepsMap, activeIndex, scroll) => {
155 stepsMap.forEach((item, idx) => {
156 if (!item.selector) {
157 return;
158 }
159 document.querySelectorAll(item.selector).forEach((el) => {
160 if (idx === activeIndex) {
161 el.classList.remove('ka-woo-hidden');
162 } else {
163 el.classList.add('ka-woo-hidden');
164 }
165 });
166 });
167
168 if (scroll && scroll === 'yes') {
169 const targetSelector = stepsMap[activeIndex] ? stepsMap[activeIndex].selector : null;
170 const target = targetSelector ? document.querySelector(targetSelector) : null;
171 if (target) {
172 target.scrollIntoView({ behavior: 'smooth', block: 'start' });
173 }
174 }
175 };
176
177 const initSteps = () => {
178 document.querySelectorAll('.ka-woo-checkout-steps--pro').forEach((container) => {
179 const steps = Array.from(container.querySelectorAll('.ka-woo-checkout-step'));
180 if (!steps.length) {
181 return;
182 }
183 let current = 0;
184 const scrollTop = container.dataset.scrollTop || 'no';
185 const stepsMap = resolvePanels(container);
186 const wrapper = container.parentElement;
187 const enableNav = wrapper?.dataset.enableNav === 'true';
188 const navPrev = wrapper?.querySelector('.ka-woo-checkout-steps__prev');
189 const navNext = wrapper?.querySelector('.ka-woo-checkout-steps__next');
190 const form = document.querySelector('form.checkout');
191 const submitButton = form ? form.querySelector('button[type="submit"]') : null;
192 const errorClass = 'ka-woo-checkout-step--error';
193 const serverErrors = document.querySelector('.woocommerce-NoticeGroup-checkout, .woocommerce-error');
194
195 const validateCurrent = () => {
196 const panelSelector = stepsMap[current] ? stepsMap[current].selector : null;
197 if (!panelSelector || !form) {
198 return true;
199 }
200 const panel = document.querySelector(panelSelector);
201 if (!panel) {
202 return true;
203 }
204 const fields = panel.querySelectorAll('input, select, textarea');
205 let valid = true;
206 fields.forEach((field) => {
207 if (typeof field.reportValidity === 'function') {
208 if (!field.reportValidity()) {
209 valid = false;
210 }
211 }
212 });
213 return valid;
214 };
215
216 const setActive = (idx) => {
217 current = idx;
218 steps.forEach((step, i) => step.classList.toggle('is-active', i === current));
219 togglePanels(stepsMap, current, scrollTop);
220 if (enableNav && navPrev && navNext) {
221 navPrev.disabled = current === 0;
222 const isLast = current >= steps.length - 1;
223 navNext.dataset.isLast = isLast ? 'true' : 'false';
224 const defaultNext = container.dataset.nextText || navNext.textContent;
225 const submitText = container.dataset.submitText || navNext.textContent;
226 navNext.textContent = isLast ? submitText : defaultNext;
227 navNext.classList.toggle('is-submit', isLast);
228 if (submitButton) {
229 submitButton.disabled = !isLast;
230 }
231 }
232 steps.forEach((step) => step.classList.remove(errorClass));
233 };
234
235 steps.forEach((step, idx) => {
236 step.addEventListener('click', () => setActive(idx));
237 });
238
239 if (enableNav && navPrev && navNext) {
240 navPrev.addEventListener('click', () => {
241 setActive(Math.max(0, current - 1));
242 });
243 navNext.addEventListener('click', (e) => {
244 if (!validateCurrent()) {
245 e.preventDefault();
246 return;
247 }
248 if (navNext.dataset.isLast === 'true') {
249 if (submitButton) {
250 submitButton.click();
251 } else if (form) {
252 form.dispatchEvent(new Event('submit', { cancelable: true, bubbles: true }));
253 }
254 return;
255 }
256 setActive(Math.min(steps.length - 1, current + 1));
257 });
258 }
259
260 setActive(0);
261
262 // React to Woo checkout errors: highlight step containing error.
263 document.body.addEventListener('checkout_error', () => {
264 const errorField = document.querySelector('.woocommerce-error, .woocommerce-invalid');
265 if (!errorField) {
266 return;
267 }
268 const errorStepIdx = stepsMap.findIndex((map) => map.selector && errorField.closest(map.selector));
269 if (errorStepIdx >= 0) {
270 steps[errorStepIdx]?.classList.add(errorClass);
271 setActive(errorStepIdx);
272 errorField.scrollIntoView({ behavior: 'smooth', block: 'center' });
273 }
274 });
275
276 // React to Woo AJAX errors (server-side validation)
277 if (serverErrors) {
278 const observer = new MutationObserver(() => {
279 const err = document.querySelector('.woocommerce-error, .woocommerce-invalid');
280 if (!err) {
281 return;
282 }
283 const idx = stepsMap.findIndex((map) => map.selector && err.closest(map.selector));
284 if (idx >= 0) {
285 steps[idx]?.classList.add(errorClass);
286 setActive(idx);
287 err.scrollIntoView({ behavior: 'smooth', block: 'center' });
288 }
289 });
290 observer.observe(serverErrors.parentElement || document.body, { childList: true, subtree: true });
291 }
292 });
293 };
294
295 const initSticky = () => {
296 const items = document.querySelectorAll('[data-ka-sticky="true"]');
297 items.forEach((el) => {
298 const offset = parseInt(el.dataset.kaStickyOffset || '20', 10);
299 const breakpoint = parseInt(el.dataset.kaStickyBreakpoint || '768', 10);
300 const footer = document.querySelector('.site-footer, footer');
301
302 const applySticky = () => {
303 if (window.innerWidth < breakpoint) {
304 el.classList.remove('ka-woo-sticky-ready');
305 el.style.top = '';
306 el.style.maxHeight = '';
307 el.style.overflow = '';
308 return;
309 }
310 let available = window.innerHeight - offset - 20;
311 if (footer) {
312 const rect = footer.getBoundingClientRect();
313 if (rect.top > 0) {
314 available = Math.min(available, rect.top - offset - 10);
315 }
316 }
317 el.classList.add('ka-woo-sticky-ready');
318 el.style.top = offset + 'px';
319 if (available > 100) {
320 el.style.maxHeight = available + 'px';
321 el.style.overflow = 'auto';
322 }
323 };
324
325 const ro = new ResizeObserver(() => applySticky());
326 ro.observe(document.body);
327 if (footer) {
328 const fo = new ResizeObserver(() => applySticky());
329 fo.observe(footer);
330 }
331 // Adjust on content changes inside sticky container
332 const contentObserver = new MutationObserver(() => applySticky());
333 contentObserver.observe(el, { childList: true, subtree: true });
334 applySticky();
335 window.addEventListener('resize', applySticky, { passive: true });
336 });
337 };
338
339 const initPayments = () => {
340 document.querySelectorAll('.ka-woo-checkout-payment').forEach((wrapper) => {
341 const icons = wrapper.dataset.kaIcons ? JSON.parse(wrapper.dataset.kaIcons) : {};
342 const placeorder = wrapper.dataset.kaPlaceorder ? JSON.parse(wrapper.dataset.kaPlaceorder) : {};
343 const accordion = wrapper.dataset.kaAccordion === 'true';
344 const methods = wrapper.querySelectorAll('.wc_payment_methods .wc_payment_method');
345 const orderButton = wrapper.querySelector('.place-order button[type="submit"]') || document.querySelector('.place-order button[type="submit"]');
346
347 const applyIcons = () => {
348 methods.forEach((method) => {
349 const input = method.querySelector('input[name="payment_method"]');
350 const label = input ? wrapper.querySelector('label[for="' + input.id + '"]') : null;
351 if (!input || !label) {
352 return;
353 }
354 const gid = input.value;
355 if (icons && icons[gid] && !label.querySelector('.ka-woo-payment-icon')) {
356 const img = document.createElement('img');
357 img.src = icons[gid];
358 img.alt = gid;
359 img.className = 'ka-woo-payment-icon';
360 label.appendChild(img);
361 }
362 });
363 };
364
365 const updatePlaceOrder = () => {
366 if (!orderButton) return;
367 const selected = wrapper.querySelector('input[name="payment_method"]:checked');
368 if (!selected) return;
369 const gid = selected.value;
370 if (placeorder && placeorder[gid]) {
371 orderButton.textContent = placeorder[gid];
372 }
373 };
374
375 const toggleAccordion = () => {
376 if (!accordion) return;
377 methods.forEach((method) => {
378 const input = method.querySelector('input[name="payment_method"]');
379 const box = method.querySelector('.payment_box');
380 const active = input && input.checked;
381 method.classList.toggle('is-active', !!active);
382 if (box) {
383 box.style.display = active ? '' : 'none';
384 }
385 });
386 };
387
388 methods.forEach((method) => {
389 const input = method.querySelector('input[name="payment_method"]');
390 if (input) {
391 input.addEventListener('change', () => {
392 updatePlaceOrder();
393 toggleAccordion();
394 });
395 }
396 });
397
398 applyIcons();
399 updatePlaceOrder();
400 toggleAccordion();
401 });
402 };
403
404 document.addEventListener('DOMContentLoaded', () => {
405 initSteps();
406 initSticky();
407 initPayments();
408 });
409 })();
410
411
412
413
414
415