PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.87
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.87
51.1.87 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 All 41 releases
king-addons / includes / widgets / Data_Table / script.js

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

386 lines 21.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 "use strict";
2 (function ($) {
3 $(window).on('elementor/frontend/init', function () {
4 elementorFrontend.hooks.addAction('frontend/element_ready/king-addons-data-table.default', function ($scope) {
5 // Add elementor handler
6 elementorFrontend.elementsHandler.addHandler(
7 elementorModules.frontend.handlers.Base.extend({
8 onInit: function onInit() {
9 const $scope = this.$element;
10 const $tableContainer = $scope.find('.king-addons-table-inner-container');
11 const $tableBody = $tableContainer.find('tbody');
12 const initialRows = $tableBody.find('tr');
13 const paginationListItems = $scope.find('.king-addons-table-custom-pagination-list-item');
14 let beforeFilter = $tableBody.find('.king-addons-table-row');
15 let value = "";
16
17 // Initialize Perfect Scrollbar if needed
18 new PerfectScrollbar($tableContainer[0], {});
19
20 // Helper: get number of items per page (as integer)
21 const getItemsPerPage = () => +$tableContainer.attr('data-rows-per-page') || 0;
22
23 /**
24 * Updates the table with the filtered set of rows for a given page index (1-based).
25 * Also optionally checks the live-search value for resetting the table if empty.
26 */
27 const displayRowsForPage = (pageIndex, checkSearch) => {
28 // Hide table while updating
29 $tableBody.hide();
30
31 const itemsPerPage = getItemsPerPage();
32 // Filter rows that should appear on the given page
33 const newRows = initialRows.filter((i) => {
34 // Original code uses index++ inside filter; equivalently use (i+1) here
35 const rowNumber = i + 1;
36 return (
37 rowNumber > itemsPerPage * (pageIndex - 1) &&
38 rowNumber <= itemsPerPage * pageIndex
39 );
40 });
41
42 // Update the table with these new rows
43 $tableBody.html(newRows);
44
45 // If checkSearch is true and the current search is empty, revert back to beforeFilter
46 if (checkSearch && value === "") {
47 $tableBody.html(beforeFilter);
48 }
49
50 $tableBody.show();
51 // Refresh the 'beforeFilter' cache
52 beforeFilter = $tableBody.find('.king-addons-table-row');
53 // Remove highlight classes if any
54 beforeFilter
55 .find('.king-addons-table-tr-before-remove')
56 .removeClass('king-addons-table-tr-before-remove');
57
58 updateEntryInfo();
59 };
60
61 /**
62 * Removes 'active' class from all pagination items,
63 * sets the correct item as active, and adjusts their visibility.
64 */
65 const setActivePaginationItem = (pageIndex) => {
66 paginationListItems.removeClass('king-addons-active-pagination-item');
67 paginationListItems.each((i, el) => {
68 // Page indices in UI are basically i+1 if user numbered them from 1..n
69 if (i + 1 === pageIndex) {
70 $(el).addClass('king-addons-active-pagination-item');
71 }
72 });
73 adjustPaginationList();
74 };
75
76 /**
77 * Called by previous/next clicks to move forward/back by `delta` pages.
78 * If the new page is out of range, does nothing.
79 */
80 const changePage = (delta) => {
81 const currentPage = +$scope.find('.king-addons-active-pagination-item').text() || 1;
82 const totalPages = paginationListItems.length;
83 const newPage = currentPage + delta;
84 if (newPage < 1 || newPage > totalPages) return;
85
86 setActivePaginationItem(newPage);
87 // For Prev/Next we do want to check if search is empty
88 displayRowsForPage(newPage, true);
89 };
90
91 /**
92 * Adjusts pagination list items to show/hide neighbors correctly.
93 */
94 const adjustPaginationList = () => {
95 const paginationIndex = $scope.find('.king-addons-active-pagination-item').index();
96 paginationListItems.each((i, el) => {
97 // Always display the first/last, and the 2 neighbors around current item
98 if (
99 i === 0 ||
100 i === paginationListItems.length - 1 ||
101 (i <= paginationIndex && i >= paginationIndex - 2)
102 ) {
103 $(el).css('display', 'flex');
104 } else {
105 $(el).css('display', 'none');
106 }
107 });
108 };
109
110 /**
111 * Updates the "Showing X to Y of Z Entries" text (if enabled).
112 */
113 const updateEntryInfo = () => {
114 if ($tableContainer.attr('data-entry-info') !== 'yes') return;
115
116 const itemsPerPage = getItemsPerPage();
117 const entryPage = +$scope.find('.king-addons-active-pagination-item').text() || 1;
118 const rowsOnPage = $tableBody.find('tr').length;
119
120 const lastEntry = itemsPerPage * entryPage - (itemsPerPage - rowsOnPage);
121 const firstEntry = lastEntry - rowsOnPage + 1;
122
123 const info = `Showing ${firstEntry} to ${lastEntry} of ${initialRows.length} Entries.`;
124 $scope.find('.king-addons-entry-info').html(info);
125 };
126
127 /**
128 * Live search logic: filters rows by matching text in any table cell.
129 */
130 const initLiveSearch = () => {
131 $scope.find(".king-addons-table-live-search").on("keyup", function () {
132 value = this.value.toLowerCase().trim();
133
134 if (value !== "") {
135 $scope.find('.king-addons-table-pagination-cont')
136 .addClass('king-addons-hide-pagination-on-search');
137 } else {
138 $scope.find('.king-addons-table-pagination-cont')
139 .removeClass('king-addons-hide-pagination-on-search');
140 }
141
142 const filteredRows = [];
143 initialRows.each((i, row) => {
144 const $row = $(row);
145 $row.find("td").each((_, cell) => {
146 const cellText = $(cell).text().toLowerCase().trim();
147 // If found a match, add row to `filteredRows` once
148 if (cellText.indexOf(value) !== -1) {
149 filteredRows.push($row);
150 return false; // break out of .each loop for this row
151 }
152 });
153 });
154
155 // If user cleared search, revert to all filtered items
156 $tableBody.html(value === "" ? beforeFilter : filteredRows);
157 updateEntryInfo();
158 });
159 };
160
161 /**
162 * Table sorting logic by clicking on <th>.
163 */
164 const initTableSorting = () => {
165 // Only if data-table-sorting is 'yes'
166 if ($tableContainer.attr('data-table-sorting') !== 'yes') return;
167
168 // Remove highlight class if user clicks outside any TH or highlight cell
169 $(window).on('click', (e) => {
170 const isTh = $(e.target).hasClass('king-addons-table-th');
171 const inTh = $(e.target).closest('.king-addons-table-th').length > 0;
172 const isActiveTd = $(e.target).hasClass('king-addons-active-td-bg-color');
173 const inActiveTd = $(e.target).closest('.king-addons-active-td-bg-color').length > 0;
174
175 if (!isTh && !inTh && !isActiveTd && !inActiveTd) {
176 $scope.find('td.king-addons-active-td-bg-color').removeClass('king-addons-active-td-bg-color');
177 }
178 });
179
180 const getCellValue = (row, idx) =>
181 $(row).children('td').eq(idx).text();
182
183 const comparer = (idx) => (a, b) => {
184 const valA = getCellValue(a, idx);
185 const valB = getCellValue(b, idx);
186 // Numeric vs. text comparison
187 if ($.isNumeric(valA) && $.isNumeric(valB)) {
188 return valA - valB;
189 }
190 return valA.toString().localeCompare(valB);
191 };
192
193 // Handle clicks on <th> for sorting
194 $scope.find('th').on('click', function () {
195 const $this = $(this);
196 const indexOfTr = $this.index();
197
198 // Highlight the column's cells
199 $scope.find('td').each(function () {
200 $(this).toggleClass(
201 'king-addons-active-td-bg-color',
202 $(this).index() === indexOfTr
203 );
204 });
205
206 // Reset the sorting icon on every TH
207 $scope.find('th .king-addons-sorting-icon').html('<i class="fas fa-sort" aria-hidden="true"></i>');
208
209 // Sort the rows
210 const table = $this.closest('table');
211 let rows = table.find('tr:gt(0)').toArray().sort(comparer(indexOfTr));
212
213 // Toggle ascending/descending
214 this.asc = !this.asc;
215 const isCustom = $scope.hasClass('king-addons-data-table-type-custom');
216
217 if ((isCustom && !this.asc) || (!isCustom && this.asc)) {
218 // Sort reversed
219 rows = rows.reverse();
220 $this.find('.king-addons-sorting-icon').html(
221 isCustom
222 ? '<i class="fas fa-sort-down" aria-hidden="true"></i>'
223 : '<i class="fas fa-sort-up" aria-hidden="true"></i>'
224 );
225 } else {
226 // Normal ascending
227 $this.find('.king-addons-sorting-icon').html(
228 isCustom
229 ? '<i class="fas fa-sort-up" aria-hidden="true"></i>'
230 : '<i class="fas fa-sort-down" aria-hidden="true"></i>'
231 );
232 }
233
234 // Re-append sorted rows
235 for (let i = 0; i < rows.length; i++) {
236 table.append(rows[i]);
237 }
238
239 // Remove leftover classes from "expanded" or appended rows
240 beforeFilter.find('.king-addons-table-tr-before-remove').each(function () {
241 $(this)
242 .closest('.king-addons-table-row')
243 .next('.king-addons-table-appended-tr')
244 .remove();
245 $(this).removeClass('king-addons-table-tr-before-remove');
246 });
247 });
248 };
249
250 /**
251 * If data-row-pagination="yes", prepend an index column (#).
252 */
253 const initRowPagination = () => {
254 if ($tableContainer.attr('data-row-pagination') !== 'yes') return;
255
256 $scope.find('.king-addons-table-head-row').prepend(
257 '<th class="king-addons-table-th-pag" style="vertical-align: middle;">#</th>'
258 );
259 initialRows.each(function (index) {
260 $(this).prepend(
261 `<td class="king-addons-table-td-pag" style="vertical-align: middle;">
262 <span style="vertical-align: middle;">${index + 1}</span>
263 </td>`
264 );
265 });
266 };
267
268 /**
269 * Export buttons logic (CSV/XLS).
270 */
271 const initTableExport = () => {
272 if (!$scope.find('.king-addons-table-export-button-cont').length) return;
273
274 const exportBtn = $scope.find('.king-addons-table-export-button-cont .king-addons-button');
275
276 const csvEscape = (value) => {
277 const text = String(value == null ? '' : value).replace(/\r?\n/g, ' ').trim();
278 if (/[",]/.test(text)) {
279 return '"' + text.replace(/"/g, '""') + '"';
280 }
281 return text;
282 };
283
284 const tableRowsToCSV = ($view) => {
285 const rows = $view.find(".king-addons-table-row");
286 const data = [];
287 rows.each((_, row) => {
288 const cols = row.querySelectorAll(".king-addons-table-text");
289 const rowData = Array.from(cols).map(cell => csvEscape(cell.innerText));
290 data.push(rowData.join(","));
291 });
292 return data.join("\r\n");
293 };
294
295 const triggerDownload = (blob, filename) => {
296 const url = URL.createObjectURL(blob);
297 const downloadLink = document.createElement("a");
298 downloadLink.download = filename;
299 downloadLink.href = url;
300 downloadLink.rel = "noopener";
301 downloadLink.style.position = "fixed";
302 downloadLink.style.left = "-9999px";
303 document.body.appendChild(downloadLink);
304 downloadLink.click();
305 setTimeout(() => {
306 URL.revokeObjectURL(url);
307 downloadLink.remove();
308 }, 1500);
309 };
310
311 exportBtn.each(function () {
312 const $btn = $(this);
313 if ($btn.hasClass('king-addons-xls')) {
314 $btn.on('click', (event) => {
315 event.preventDefault();
316 const table = $scope.find('table').get(0);
317 if (!table) {
318 return;
319 }
320 const html = '<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel"><head><meta charset="UTF-8"></head><body>' + table.outerHTML + '</body></html>';
321 triggerDownload(new Blob(['\uFEFF' + html], { type: 'application/vnd.ms-excel' }), 'export.xls');
322 });
323 } else if ($btn.hasClass('king-addons-csv')) {
324 $btn.on('click', (event) => {
325 event.preventDefault();
326 const csv = tableRowsToCSV($scope.find('.king-addons-data-table'));
327 triggerDownload(new Blob(['\uFEFF' + csv], { type: 'text/csv;charset=utf-8;' }), 'export.csv');
328 });
329 }
330 });
331 };
332
333 /**
334 * Initializes custom pagination if enabled.
335 */
336 const initCustomPagination = () => {
337 if ($tableContainer.attr('data-custom-pagination') !== 'yes') return;
338
339 // Show only the first set of rows (page 1) initially
340 const itemsPerPage = getItemsPerPage();
341 const firstPageRows = initialRows.filter((i) => i < itemsPerPage);
342 $tableBody.html(firstPageRows);
343
344 adjustPaginationList();
345
346 // When clicking on a specific page number
347 paginationListItems.on('click', function () {
348 // Remove the old active, set the new active
349 paginationListItems.removeClass('king-addons-active-pagination-item');
350 $(this).addClass('king-addons-active-pagination-item');
351 adjustPaginationList();
352
353 const pageIndex = +$(this).text();
354 // For direct page clicks, we do NOT revert to beforeFilter if search is empty
355 displayRowsForPage(pageIndex, false);
356 });
357
358 // Handle prev/next clicks
359 $scope.find('.king-addons-table-prev-next').each(function () {
360 if ($(this).hasClass('king-addons-table-custom-pagination-prev')) {
361 $(this).on('click', () => changePage(-1));
362 } else {
363 $(this).on('click', () => changePage(1));
364 }
365 });
366 };
367
368 // ---------------------------------------------------
369 // Execute initializations
370 // ---------------------------------------------------
371 initCustomPagination();
372 initLiveSearch();
373 initTableSorting();
374 initRowPagination();
375 initTableExport();
376
377 // Finally, remove "hidden" class so the table becomes visible
378 $tableContainer.removeClass('king-addons-hide-table-before-arrange');
379 updateEntryInfo();
380 },
381 }),
382 { $element: $scope }
383 );
384 });
385 });
386 })(jQuery);