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 / widgets / Testimonials_Wall / 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/widgets/Testimonials_Wall/script.js

368 lines 15.1 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 debounce = (fn, wait) => {
5 let timer;
6 return (...args) => {
7 window.clearTimeout(timer);
8 timer = window.setTimeout(() => fn.apply(null, args), wait);
9 };
10 };
11
12 const initTestimonialsWall = (root) => {
13 const scope = root instanceof HTMLElement ? root : document;
14 const wrappers = scope.querySelectorAll('.king-addons-testimonials-wall');
15
16 wrappers.forEach((wrapper) => {
17 if (wrapper.dataset.twInit === 'yes') {
18 return;
19 }
20 wrapper.dataset.twInit = 'yes';
21
22 const grid = wrapper.querySelector('.king-addons-testimonials-wall__grid');
23 if (!grid) {
24 return;
25 }
26
27 const items = Array.from(grid.querySelectorAll('.king-addons-testimonials-wall__item'));
28 const loadMoreBtn = wrapper.querySelector('.king-addons-testimonials-wall__load-more-btn');
29 const searchInput = wrapper.querySelector('.king-addons-testimonials-wall__search-input');
30 const ratingSelect = wrapper.querySelector('.king-addons-testimonials-wall__rating-select');
31 const chipButtons = Array.from(wrapper.querySelectorAll('.king-addons-testimonials-wall__filter-chip'));
32 const categoryButtons = chipButtons.filter((btn) => btn.dataset.filter !== undefined);
33 const ratingButtons = chipButtons.filter((btn) => btn.dataset.rating !== undefined);
34
35 const isEditor = window.elementorFrontend && typeof elementorFrontend.isEditMode === 'function'
36 ? elementorFrontend.isEditMode()
37 : false;
38 const allowMasonry = wrapper.dataset.layout === 'masonry'
39 && (!isEditor || wrapper.dataset.editorMasonry === 'yes');
40 const readMoreEnabled = wrapper.dataset.readMore === 'yes';
41 const skeletonEnabled = wrapper.dataset.skeleton === 'yes';
42 const readMoreText = wrapper.dataset.readMoreText || 'Read more';
43 const readLessText = wrapper.dataset.readLessText || 'Read less';
44
45 wrapper.classList.toggle('is-masonry', allowMasonry);
46
47 const state = {
48 category: '*',
49 rating: 'all',
50 search: '',
51 visibleCount: Math.min(parseInt(wrapper.dataset.initial || items.length, 10), items.length),
52 perLoad: Math.max(parseInt(wrapper.dataset.perLoad || 1, 10), 1),
53 loading: false,
54 };
55
56 const updateReadMoreLineHeight = () => {
57 if (!readMoreEnabled) {
58 return;
59 }
60 const sampleText = wrapper.querySelector('.king-addons-testimonials-wall__text');
61 if (!sampleText) {
62 return;
63 }
64 const styles = window.getComputedStyle(sampleText);
65 let lineHeight = parseFloat(styles.lineHeight);
66 if (Number.isNaN(lineHeight)) {
67 const fontSize = parseFloat(styles.fontSize) || 16;
68 lineHeight = fontSize * 1.6;
69 }
70 wrapper.style.setProperty('--ka-tw-text-line-height', `${lineHeight}px`);
71 };
72
73 const scheduleReadMoreLineHeight = debounce(updateReadMoreLineHeight, 120);
74
75 items.forEach((item, index) => {
76 item.dataset.index = String(index);
77 if (!item.dataset.search) {
78 item.dataset.search = (item.textContent || '').trim();
79 }
80 });
81
82 if (readMoreEnabled) {
83 const lineClamp = parseInt(wrapper.dataset.readMoreLines || '4', 10);
84 if (!Number.isNaN(lineClamp) && lineClamp > 0) {
85 wrapper.style.setProperty('--ka-tw-read-more-lines', String(lineClamp));
86 }
87 updateReadMoreLineHeight();
88 window.addEventListener('resize', scheduleReadMoreLineHeight);
89 }
90
91 const setActiveButton = (buttons, active) => {
92 buttons.forEach((button) => {
93 const isActive = button === active;
94 button.classList.toggle('is-active', isActive);
95 button.setAttribute('aria-pressed', isActive ? 'true' : 'false');
96 });
97 };
98
99 const matchesCategory = (item) => {
100 if (!state.category || state.category === '*') {
101 return true;
102 }
103 const categories = (item.dataset.categories || '').split(' ').filter(Boolean);
104 return categories.includes(state.category);
105 };
106
107 const matchesRating = (item) => {
108 if (!state.rating || state.rating === 'all') {
109 return true;
110 }
111 const ratingValue = parseInt(item.dataset.rating || '0', 10);
112 const threshold = parseInt(state.rating, 10);
113 return ratingValue >= threshold;
114 };
115
116 const matchesSearch = (item) => {
117 if (!state.search) {
118 return true;
119 }
120 const haystack = (item.dataset.search || '').toLowerCase();
121 return haystack.includes(state.search);
122 };
123
124 const clearHideTimer = (item) => {
125 if (item._twHideTimer) {
126 window.clearTimeout(item._twHideTimer);
127 item._twHideTimer = null;
128 }
129 };
130
131 const showItem = (item) => {
132 clearHideTimer(item);
133 if (!item.classList.contains('is-hidden')) {
134 item.setAttribute('aria-hidden', 'false');
135 return;
136 }
137 item.classList.remove('is-hidden');
138 item.classList.remove('is-fading-out');
139 item.classList.add('is-revealing');
140 item.setAttribute('aria-hidden', 'false');
141 window.requestAnimationFrame(() => {
142 item.classList.remove('is-revealing');
143 });
144 };
145
146 const hideItem = (item, immediate = false) => {
147 clearHideTimer(item);
148 if (item.classList.contains('is-hidden')) {
149 item.setAttribute('aria-hidden', 'true');
150 return;
151 }
152 if (immediate) {
153 item.classList.add('is-hidden');
154 item.classList.remove('is-fading-out');
155 item.setAttribute('aria-hidden', 'true');
156 return;
157 }
158 item.classList.add('is-fading-out');
159 item.setAttribute('aria-hidden', 'true');
160 item._twHideTimer = window.setTimeout(() => {
161 item.classList.add('is-hidden');
162 item.classList.remove('is-fading-out');
163 item._twHideTimer = null;
164 }, 200);
165 };
166
167 const updateLoadMore = (totalMatches) => {
168 const total = (typeof totalMatches === 'number') ? totalMatches : items.length;
169 const hasMore = total > 0 && state.visibleCount < total;
170
171 wrapper.classList.toggle('is-complete', !hasMore);
172 if (loadMoreBtn) {
173 loadMoreBtn.disabled = !hasMore;
174 }
175 };
176
177 const reflowMasonry = () => {
178 if (!allowMasonry) {
179 items.forEach((item) => {
180 item.style.gridRowEnd = 'auto';
181 });
182 return;
183 }
184 const rowHeight = parseFloat(getComputedStyle(grid).getPropertyValue('--ka-tw-row')) || 8;
185 items.forEach((item) => {
186 if (item.classList.contains('is-hidden')) {
187 item.style.gridRowEnd = 'auto';
188 return;
189 }
190 const height = item.getBoundingClientRect().height;
191 const span = Math.ceil(height / rowHeight);
192 item.style.gridRowEnd = `span ${span}`;
193 });
194 };
195
196 const updateReadMoreVisibility = () => {
197 if (!readMoreEnabled) {
198 return;
199 }
200 items.forEach((item) => {
201 if (item.classList.contains('is-hidden')) {
202 return;
203 }
204 const text = item.querySelector('.king-addons-testimonials-wall__text');
205 const button = item.querySelector('.king-addons-testimonials-wall__read-more');
206 if (!text || !button) {
207 return;
208 }
209 const isExpanded = item.classList.contains('is-expanded');
210 if (isExpanded) {
211 button.style.display = '';
212 button.textContent = readLessText;
213 button.setAttribute('aria-expanded', 'true');
214 return;
215 }
216
217 const isOverflowing = text.scrollHeight > text.clientHeight + 2;
218 button.style.display = isOverflowing ? '' : 'none';
219 button.textContent = readMoreText;
220 button.setAttribute('aria-expanded', 'false');
221 });
222 };
223
224 const applyFilters = () => {
225 let totalMatches = 0;
226
227 items.forEach((item) => {
228 const matches = matchesCategory(item) && matchesRating(item) && matchesSearch(item);
229 if (matches) {
230 totalMatches += 1;
231 if (totalMatches <= state.visibleCount) {
232 showItem(item);
233 } else {
234 hideItem(item, true);
235 }
236 } else {
237 hideItem(item, false);
238 }
239 });
240
241 wrapper.classList.toggle('is-empty', totalMatches === 0);
242 updateLoadMore(totalMatches);
243 updateReadMoreVisibility();
244 window.requestAnimationFrame(reflowMasonry);
245 };
246
247 if (categoryButtons.length) {
248 wrapper.addEventListener('click', (event) => {
249 const button = event.target.closest('.king-addons-testimonials-wall__filter-chip');
250 if (!button || button.dataset.filter === undefined) {
251 return;
252 }
253 state.category = button.dataset.filter || '*';
254 setActiveButton(categoryButtons, button);
255 applyFilters();
256 });
257 }
258
259 if (ratingButtons.length) {
260 wrapper.addEventListener('click', (event) => {
261 const button = event.target.closest('.king-addons-testimonials-wall__filter-chip');
262 if (!button || button.dataset.rating === undefined) {
263 return;
264 }
265 state.rating = button.dataset.rating || 'all';
266 setActiveButton(ratingButtons, button);
267 applyFilters();
268 });
269 }
270
271 if (ratingSelect) {
272 ratingSelect.addEventListener('change', () => {
273 state.rating = ratingSelect.value || 'all';
274 applyFilters();
275 });
276 }
277
278 if (searchInput) {
279 const onSearch = debounce(() => {
280 state.search = (searchInput.value || '').trim().toLowerCase();
281 applyFilters();
282 }, 250);
283 searchInput.addEventListener('input', onSearch);
284 }
285
286 if (loadMoreBtn) {
287 loadMoreBtn.addEventListener('click', () => {
288 if (state.loading) {
289 return;
290 }
291 state.loading = true;
292 const delay = skeletonEnabled ? 350 : 0;
293 const loadText = loadMoreBtn.dataset.loadText || loadMoreBtn.textContent;
294 const loadingText = loadMoreBtn.dataset.loadingText || loadText;
295
296 if (skeletonEnabled) {
297 wrapper.classList.add('is-loading');
298 }
299 loadMoreBtn.textContent = loadingText;
300 loadMoreBtn.disabled = true;
301
302 window.setTimeout(() => {
303 state.visibleCount = Math.min(state.visibleCount + state.perLoad, items.length);
304 applyFilters();
305 wrapper.classList.remove('is-loading');
306 loadMoreBtn.textContent = loadText;
307 state.loading = false;
308 }, delay);
309 });
310 }
311
312 if (readMoreEnabled) {
313 wrapper.addEventListener('click', (event) => {
314 const button = event.target.closest('.king-addons-testimonials-wall__read-more');
315 if (!button) {
316 return;
317 }
318 const item = button.closest('.king-addons-testimonials-wall__item');
319 if (!item) {
320 return;
321 }
322 const expanded = item.classList.toggle('is-expanded');
323 button.setAttribute('aria-expanded', expanded ? 'true' : 'false');
324 button.textContent = expanded ? readLessText : readMoreText;
325 window.requestAnimationFrame(reflowMasonry);
326 });
327 }
328
329 if (allowMasonry) {
330 if ('ResizeObserver' in window) {
331 const resizeObserver = new ResizeObserver(() => reflowMasonry());
332 resizeObserver.observe(grid);
333 items.forEach((item) => resizeObserver.observe(item));
334 } else {
335 window.addEventListener('resize', () => reflowMasonry());
336 }
337
338 const images = grid.querySelectorAll('img');
339 images.forEach((img) => {
340 if (img.complete) {
341 return;
342 }
343 img.addEventListener('load', () => reflowMasonry(), { once: true });
344 });
345 }
346
347 applyFilters();
348 });
349 };
350
351 if (window.elementorFrontend && elementorFrontend.hooks) {
352 $(window).on('elementor/frontend/init', () => {
353 elementorFrontend.hooks.addAction(
354 'frontend/element_ready/king-addons-testimonials-wall.default',
355 ($scope) => {
356 initTestimonialsWall($scope[0]);
357 }
358 );
359 });
360 }
361
362 if (document.readyState === 'loading') {
363 document.addEventListener('DOMContentLoaded', () => initTestimonialsWall(document));
364 } else {
365 initTestimonialsWall(document);
366 }
367 })(jQuery);
368