PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.84
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.84
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 / widgets / Woo_Products_Grid / script.js

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

537 lines 20.0 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 setGridVars = (grid) => {
5 grid.style.setProperty('--ka-grid-desktop', grid.dataset.colsDesktop || 4);
6 grid.style.setProperty('--ka-grid-tablet', grid.dataset.colsTablet || 3);
7 grid.style.setProperty('--ka-grid-mobile', grid.dataset.colsMobile || 2);
8 grid.style.setProperty('--ka-masonry-row', '8px');
9 grid.style.setProperty('--ka-masonry-gap', '16px');
10 };
11
12 const appendItems = (grid, html, replace = false) => {
13 const track = grid.querySelector('.ka-woo-products-grid__track') || grid;
14 const temp = document.createElement('div');
15 temp.innerHTML = html;
16 const items = temp.querySelectorAll('.ka-woo-products-grid__item');
17 if (replace) {
18 track.innerHTML = '';
19 }
20 items.forEach((item) => track.appendChild(item));
21 return items.length;
22 };
23
24 const reflowMasonry = (grid) => {
25 if (grid.dataset.layoutType !== 'masonry') return;
26 const styles = getComputedStyle(grid);
27 const rowHeight = parseFloat(styles.getPropertyValue('--ka-masonry-row')) || 8;
28 const gap = parseFloat(styles.getPropertyValue('--ka-masonry-gap')) || 16;
29 const items = grid.querySelectorAll('.ka-woo-products-grid__item');
30 items.forEach((item) => {
31 // The container's row gap is zero, so the spacing between cards has
32 // to come out of the span itself.
33 const span = Math.ceil((item.getBoundingClientRect().height + gap) / rowHeight);
34 item.style.gridRowEnd = `span ${span}`;
35 });
36 };
37
38 const setupMasonry = (grid) => {
39 if (grid.dataset.layoutType !== 'masonry') return;
40 if (typeof ResizeObserver === 'undefined') return;
41 const resizeObserver = new ResizeObserver(() => reflowMasonry(grid));
42 resizeObserver.observe(grid);
43 (grid.querySelector('.ka-woo-products-grid__track') || grid).querySelectorAll('img').forEach((img) => {
44 if (img.complete) {
45 reflowMasonry(grid);
46 } else {
47 img.addEventListener('load', () => reflowMasonry(grid));
48 }
49 });
50 reflowMasonry(grid);
51 };
52
53 const setupSlider = (grid) => {
54 if (grid.dataset.layoutType !== 'slider') return;
55 const parent = grid.parentElement;
56 if (!parent) return;
57 const track = grid.querySelector('.ka-woo-products-grid__track') || grid;
58 const existingNav = parent.querySelector('.ka-woo-products-grid__slider-nav');
59 if (existingNav) {
60 existingNav.remove();
61 }
62 const slides = Array.from(track.children);
63 if (!slides.length) return;
64
65 grid.classList.add('ka-woo-products-grid--slider');
66 slides.forEach((slide) => {
67 slide.classList.add('ka-woo-products-grid__slide');
68 });
69
70 let index = 0;
71 const loop = grid.dataset.sliderLoop === 'true';
72 const autoplay = grid.dataset.sliderAutoplay === 'true';
73 const speed = parseInt(grid.dataset.sliderAutoplaySpeed || '5000', 10);
74 const skin = grid.dataset.sliderSkin || 'arrows';
75 let timer;
76 let isDragging = false;
77 let startX = 0;
78 let currentX = 0;
79
80 const dots = [];
81
82 const update = () => {
83 const offset = index * 100;
84 track.style.transform = `translateX(-${offset}%)`;
85 dots.forEach((dot, idx) => {
86 dot.classList.toggle('is-active', idx === index);
87 });
88 };
89
90 const go = (dir) => {
91 const last = slides.length - 1;
92 let next = index + dir;
93 if (next > last) {
94 next = loop ? 0 : last;
95 } else if (next < 0) {
96 next = loop ? last : 0;
97 }
98 if (next === index && !loop) return;
99 index = next;
100 update();
101 };
102
103 const nav = document.createElement('div');
104 nav.className = 'ka-woo-products-grid__slider-nav';
105 const dotsWrap = document.createElement('div');
106 dotsWrap.className = 'ka-woo-products-grid__slider-dots';
107 slides.forEach((_, idx) => {
108 const dot = document.createElement('button');
109 dot.type = 'button';
110 dot.className = 'ka-woo-products-grid__slider-dot';
111 dot.addEventListener('click', () => {
112 index = idx;
113 update();
114 resetAutoplay();
115 });
116 dotsWrap.appendChild(dot);
117 dots.push(dot);
118 });
119
120 const controls = document.createElement('div');
121 controls.className = 'ka-woo-products-grid__slider-controls';
122 const prev = document.createElement('button');
123 prev.type = 'button';
124 prev.className = 'ka-woo-products-grid__slider-btn ka-woo-products-grid__slider-btn--prev';
125 prev.textContent = '‹';
126 prev.addEventListener('click', () => {
127 go(-1);
128 resetAutoplay();
129 });
130 const next = document.createElement('button');
131 next.type = 'button';
132 next.className = 'ka-woo-products-grid__slider-btn ka-woo-products-grid__slider-btn--next';
133 next.textContent = '›';
134 next.addEventListener('click', () => {
135 go(1);
136 resetAutoplay();
137 });
138
139 if (skin === 'arrows' || skin === 'both') {
140 controls.appendChild(prev);
141 controls.appendChild(next);
142 }
143 if (skin === 'dots' || skin === 'both') {
144 nav.appendChild(dotsWrap);
145 }
146 if (controls.children.length) {
147 nav.appendChild(controls);
148 }
149 parent.appendChild(nav);
150
151 const stopAutoplay = () => {
152 if (timer) {
153 window.clearInterval(timer);
154 timer = undefined;
155 }
156 };
157 const startAutoplay = () => {
158 if (!autoplay) return;
159 stopAutoplay();
160 timer = window.setInterval(() => go(1), speed);
161 };
162 const resetAutoplay = () => {
163 stopAutoplay();
164 startAutoplay();
165 };
166
167 grid.addEventListener('mouseenter', stopAutoplay);
168 grid.addEventListener('mouseleave', startAutoplay);
169
170 const getPointerX = (event) => (event.touches && event.touches.length ? event.touches[0].clientX : event.clientX);
171
172 const onPointerDown = (event) => {
173 isDragging = true;
174 startX = getPointerX(event);
175 currentX = startX;
176 track.style.transition = 'none';
177 event.preventDefault();
178 };
179
180 const onPointerMove = (event) => {
181 if (!isDragging) return;
182 currentX = getPointerX(event);
183 };
184
185 const onPointerUp = () => {
186 if (!isDragging) return;
187 const delta = currentX - startX;
188 track.style.transition = '';
189 if (Math.abs(delta) > 40) {
190 if (delta < 0) {
191 go(1);
192 } else {
193 go(-1);
194 }
195 resetAutoplay();
196 } else {
197 update();
198 }
199 isDragging = false;
200 };
201
202 track.addEventListener('mousedown', onPointerDown);
203 track.addEventListener('mousemove', onPointerMove);
204 track.addEventListener('mouseup', onPointerUp);
205 track.addEventListener('mouseleave', () => {
206 if (isDragging) {
207 onPointerUp();
208 }
209 });
210 track.addEventListener('touchstart', onPointerDown, { passive: true });
211 track.addEventListener('touchmove', onPointerMove, { passive: true });
212 track.addEventListener('touchend', onPointerUp);
213 track.addEventListener('touchcancel', onPointerUp);
214
215 update();
216 startAutoplay();
217 };
218
219 const buildFormData = (grid, page) => {
220 const boolVal = (value) => (value === 'true' || value === '1' ? '1' : '');
221 const form = new FormData();
222 form.append('action', 'ka_products_grid');
223 form.append('nonce', grid.dataset.nonce || '');
224 form.append('page', page);
225 form.append('per_page', grid.dataset.perPage || '8');
226 form.append('order', grid.dataset.order || 'DESC');
227 form.append('orderby', grid.dataset.orderby || 'date');
228 form.append('show_rating', boolVal(grid.dataset.showRating));
229 form.append('show_excerpt', boolVal(grid.dataset.showExcerpt));
230 form.append('excerpt_length', grid.dataset.excerptLength || '15');
231 form.append('show_badge', boolVal(grid.dataset.showBadge));
232 form.append('show_best_badge', boolVal(grid.dataset.showBestBadge));
233 form.append('show_custom_badge', boolVal(grid.dataset.showCustomBadge));
234 form.append('custom_badge_text', grid.dataset.customBadgeText || '');
235 form.append('show_brand', boolVal(grid.dataset.showBrand));
236 form.append('show_sku', boolVal(grid.dataset.showSku));
237 form.append('card_layout', grid.dataset.cardLayout || 'classic');
238 // Keep the listing scoped to the category or tag being browsed - the
239 // server cannot work that out from an admin-ajax request.
240 if (grid.dataset.archiveTaxonomy && grid.dataset.archiveTerm) {
241 form.append('archive_taxonomy', grid.dataset.archiveTaxonomy);
242 form.append('archive_term', grid.dataset.archiveTerm);
243 }
244
245 const filters = grid.dataset.filters ? grid.dataset.filters : '';
246 if (filters) {
247 form.append('filters', filters);
248 }
249 return form;
250 };
251
252 const fetchPage = (grid, page, replace = false) =>
253 fetch(grid.dataset.ajaxUrl, {
254 method: 'POST',
255 body: buildFormData(grid, page),
256 }).then((res) => res.json());
257
258 const bindLightbox = (grid, rebind = false) => {
259 if (!rebind && grid.dataset.lightboxBound === 'true') return;
260 grid.dataset.lightboxBound = 'true';
261 const overlayId = 'ka-woo-products-grid-lightbox';
262 let overlay = document.getElementById(overlayId);
263 if (!overlay) {
264 overlay = document.createElement('div');
265 overlay.id = overlayId;
266 overlay.className = 'ka-woo-products-grid__lightbox';
267 overlay.innerHTML = `
268 <button class="ka-woo-products-grid__lightbox-close" type="button" aria-label="Close">×</button>
269 <figure class="ka-woo-products-grid__lightbox-figure">
270 <img src="" alt="" />
271 <figcaption></figcaption>
272 </figure>
273 `;
274 document.body.appendChild(overlay);
275 }
276 const imgEl = overlay.querySelector('img');
277 const captionEl = overlay.querySelector('figcaption');
278 const closeBtn = overlay.querySelector('.ka-woo-products-grid__lightbox-close');
279
280 const close = () => {
281 overlay.classList.remove('is-open');
282 document.body.classList.remove('ka-woo-products-grid--lock');
283 };
284
285 closeBtn.addEventListener('click', close);
286 overlay.addEventListener('click', (e) => {
287 if (e.target === overlay) {
288 close();
289 }
290 });
291 document.addEventListener('keyup', (e) => {
292 if (e.key === 'Escape') {
293 close();
294 }
295 });
296
297 grid.addEventListener('click', (e) => {
298 const link = e.target.closest('.ka-woo-products-grid__thumb');
299 if (!link || !grid.contains(link)) return;
300 const full = link.dataset.full || link.href;
301 if (!full) return;
302 e.preventDefault();
303 if (imgEl) {
304 imgEl.src = full;
305 }
306 if (captionEl) {
307 captionEl.textContent = link.dataset.caption || '';
308 }
309 overlay.classList.add('is-open');
310 document.body.classList.add('ka-woo-products-grid--lock');
311 });
312 };
313
314 const handleAjax = (grid) => {
315 // Sorting and filtering are wired up further down, and every grid needs
316 // them regardless of how it paginates. Bailing out here for "none" and
317 // "numbers" - the two default choices - meant the Sorting widget and
318 // the faceted filters silently did nothing on most grids.
319 const paginationType = grid.dataset.paginationType || 'none';
320 const isIncremental = 'load_more' === paginationType || 'infinite' === paginationType;
321
322 const parent = grid.parentElement;
323 if (!parent) return;
324 const btn = isIncremental ? parent.querySelector('.ka-woo-products-grid__load-more') : null;
325 let page = parseInt(grid.dataset.page || '1', 10);
326 const max = () => parseInt(grid.dataset.maxPages || '1', 10);
327 let sentinelObserver;
328 let sentinelEl;
329 let isProcessing = false;
330
331 const setButtonState = (state) => {
332 if (!btn) return;
333 const labelEl = btn.querySelector('.ka-woo-products-grid__load-more-label');
334 const spinner = btn.querySelector('.ka-woo-products-grid__spinner');
335 const defaultText = btn.dataset.defaultText || btn.textContent || '';
336 const loadingText = btn.dataset.loadingText || 'Loading…';
337 if (state === 'loading') {
338 btn.disabled = true;
339 if (labelEl) labelEl.textContent = loadingText;
340 spinner?.classList.add('is-visible');
341 btn.classList.add('is-loading');
342 } else if (state === 'ready') {
343 btn.disabled = false;
344 if (labelEl) labelEl.textContent = defaultText;
345 spinner?.classList.remove('is-visible');
346 btn.classList.remove('is-loading');
347 } else if (state === 'done') {
348 spinner?.classList.remove('is-visible');
349 btn.classList.remove('is-loading');
350 btn.remove();
351 }
352 };
353
354 const cleanupSentinel = () => {
355 if (sentinelObserver && sentinelEl) {
356 sentinelObserver.unobserve(sentinelEl);
357 }
358 if (sentinelEl) {
359 sentinelEl.remove();
360 }
361 };
362
363 const process = (replace = false) => {
364 if (isProcessing) return;
365 if (page >= max() && !replace) {
366 setButtonState('done');
367 cleanupSentinel();
368 return;
369 }
370 isProcessing = true;
371 setButtonState('loading');
372 fetchPage(grid, replace ? 1 : page + 1, replace)
373 .then((res) => {
374 if (!res || !res.success || !res.data) {
375 setButtonState('done');
376 cleanupSentinel();
377 return;
378 }
379 const added = appendItems(grid, res.data.html || '', replace);
380 const maxPages = res.data.max_pages ? parseInt(res.data.max_pages, 10) : max();
381 grid.dataset.maxPages = maxPages;
382 if (replace) {
383 page = 1;
384 grid.dataset.page = '1';
385 } else {
386 page += 1;
387 grid.dataset.page = page;
388 }
389 if (page >= maxPages || added === 0) {
390 setButtonState('done');
391 cleanupSentinel();
392 } else {
393 setButtonState('ready');
394 }
395 reflowMasonry(grid);
396 setupSlider(grid);
397 bindLightbox(grid, true);
398 })
399 .catch(() => {
400 setButtonState('ready');
401 })
402 .finally(() => {
403 isProcessing = false;
404 });
405 };
406
407 if (btn) {
408 btn.addEventListener('click', () => process(false));
409 }
410
411 if ('infinite' === paginationType && typeof IntersectionObserver !== 'undefined') {
412 sentinelEl = document.createElement('div');
413 sentinelEl.className = 'ka-woo-products-grid__sentinel';
414 parent.appendChild(sentinelEl);
415 sentinelObserver = new IntersectionObserver(
416 (entries) => {
417 entries.forEach((entry) => {
418 if (entry.isIntersecting) {
419 process(false);
420 }
421 });
422 },
423 { rootMargin: '200px' }
424 );
425 sentinelObserver.observe(sentinelEl);
426 }
427
428 const applyFilters = (filters) => {
429 if (filters && typeof filters === 'object') {
430 grid.dataset.filters = JSON.stringify(filters);
431 } else {
432 grid.dataset.filters = '';
433 }
434 grid.dataset.page = '1';
435 process(true);
436 };
437
438 const applySorting = (orderby, order) => {
439 if (orderby) {
440 grid.dataset.orderby = orderby;
441 }
442 if (order) {
443 grid.dataset.order = order;
444 }
445 grid.dataset.page = '1';
446 process(true);
447 };
448
449 window.addEventListener('kaWooGridApplyFilters', (event) => {
450 const detail = event.detail || {};
451 if (detail.queryId && detail.queryId !== grid.dataset.queryId) {
452 return;
453 }
454 applyFilters(detail.filters || {});
455 });
456
457 document.addEventListener('kingaddons:filters:apply', (event) => {
458 const detail = event.detail || {};
459 if (!detail.queryId || detail.queryId !== grid.dataset.queryId) {
460 return;
461 }
462 applyFilters(detail.filters || {});
463 });
464
465 document.addEventListener('kingaddons:filters:reset', (event) => {
466 const detail = event.detail || {};
467 if (!detail.queryId || detail.queryId !== grid.dataset.queryId) {
468 return;
469 }
470 applyFilters({});
471 });
472
473 document.addEventListener('kingaddons:sorting:apply', (event) => {
474 const detail = event.detail || {};
475 if (detail.queryId && detail.queryId !== grid.dataset.queryId) {
476 return;
477 }
478 applySorting(detail.orderby, detail.order);
479 });
480 };
481
482 const initGrid = (grid) => {
483 if (grid.dataset.kngBound === 'true') return;
484 grid.dataset.kngBound = 'true';
485 setGridVars(grid);
486 setupMasonry(grid);
487 setupSlider(grid);
488 bindLightbox(grid);
489 handleAjax(grid);
490 };
491
492 const initAll = (context) => {
493 const root = context && context.querySelectorAll ? context : document;
494 root.querySelectorAll('.ka-woo-products-grid').forEach((grid) => initGrid(grid));
495 };
496
497 const observeNewGrids = () => {
498 if (!('MutationObserver' in window)) return;
499 const observer = new MutationObserver((mutations) => {
500 mutations.forEach((mutation) => {
501 mutation.addedNodes.forEach((node) => {
502 if (!(node instanceof HTMLElement)) return;
503 if (node.classList.contains('ka-woo-products-grid')) {
504 initGrid(node);
505 }
506 node.querySelectorAll?.('.ka-woo-products-grid').forEach((grid) => initGrid(grid));
507 });
508 });
509 });
510 observer.observe(document.body, { childList: true, subtree: true });
511 };
512
513 document.addEventListener('DOMContentLoaded', () => {
514 initAll(document);
515 observeNewGrids();
516 });
517
518 const onElementorReady = () => {
519 if (!window.elementorFrontend || !window.elementorFrontend.hooks) {
520 return;
521 }
522 window.elementorFrontend.hooks.addAction('frontend/element_ready/woo_products_grid.default', (scope) => {
523 const root = scope && scope[0] ? scope[0] : scope;
524 initAll(root || document);
525 });
526 };
527
528 if (document.readyState === 'complete') {
529 onElementorReady();
530 } else {
531 window.addEventListener('DOMContentLoaded', onElementorReady);
532 }
533 })();
534
535
536
537