PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.44
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.44
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 51.1.45 All 40 releases
king-addons / includes / extensions / Woo_Builder / script.js

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

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