| 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 |
|