ajaxtable.js (7805B)
1 class AjaxTable { 2 constructor(apiEndpoint, id_prefix, tableHeadersId, tableBodyId, searchBarId, pageSizeSelectorId, paginationContainerId) { 3 this.apiEndpoint = apiEndpoint; 4 this.tableHeadersId = tableHeadersId; 5 this.tableBodyId = tableBodyId; 6 this.id_prefix = id_prefix; 7 this.searchBarId = searchBarId; 8 this.pageSizeSelectorId = pageSizeSelectorId; 9 this.paginationContainerId = paginationContainerId; 10 11 this.currentPage = 1; 12 this.currentPageSize = 10; // Default page size 13 this.currentSearchQuery = ''; 14 this.currentSortField = null; 15 this.currentSortOrder = null; 16 17 this.initEventListeners(); 18 this.fetchData({ page: 1, page_size: 10 }); 19 } 20 21 initEventListeners() { 22 const debouncedSearchData = this.debounce(this.searchData.bind(this), 300); 23 document.getElementById(this.searchBarId).addEventListener('keyup', debouncedSearchData); 24 25 document.getElementById(this.pageSizeSelectorId).addEventListener('change', () => { 26 this.currentPageSize = document.getElementById(this.pageSizeSelectorId).value; 27 this.currentPage = 1; // Reset to first page on page size change 28 this.fetchData(); 29 }); 30 31 document.addEventListener('htmx:afterRequest', (evt) => { 32 this.fetchData(); 33 }); 34 35 document.getElementById(this.tableBodyId).addEventListener('click', event => { 36 if (event.target.classList.contains('delete-button')) { 37 const itemId = event.target.dataset.itemId; 38 this.confirmDelete(this.apiEndpoint, itemId); 39 } 40 }); 41 42 this.sortTableByHeader(); 43 } 44 45 confirmDelete(apiEndpoint, itemId) { 46 var instance = M.Modal.getInstance(document.getElementById(this.id_prefix + 'deleteConfirmationModal')); 47 48 // Set up the delete confirmation button's event listener 49 var confirmButton = document.getElementById(this.id_prefix + 'confirmDeleteButton'); 50 confirmButton.onclick = () => { 51 this.performDeletion(apiEndpoint, itemId); 52 instance.close(); 53 }; 54 55 // Open the modal 56 instance.open(); 57 } 58 59 performDeletion(apiEndpoint, itemId) { 60 const deleteUrl = `${apiEndpoint}${itemId}`; 61 62 fetch(deleteUrl, { method: 'DELETE' }) 63 .then(response => { 64 if(response.ok) { 65 this.handleDeletionSuccess(itemId); 66 } else { 67 console.error('Deletion failed'); 68 } 69 }) 70 .catch(error => console.error('Error:', error)); 71 } 72 73 handleDeletionSuccess(itemId) { 74 const rowToRemove = document.getElementById('itemRow-' + itemId); 75 if (rowToRemove) { 76 rowToRemove.remove(); 77 } else { 78 this.fetchData(); // Refresh the table data 79 } 80 } 81 82 // Function to fetch data and update the table 83 fetchData() { 84 const queryParams = { 85 page: this.currentPage, 86 page_size: this.currentPageSize, 87 query: this.currentSearchQuery 88 }; 89 90 if (this.currentSortField && this.currentSortOrder) { 91 queryParams.sort_by = this.currentSortField; 92 queryParams.sort_order = this.currentSortOrder; 93 } 94 95 const url = new URL(this.apiEndpoint, window.location.origin); 96 Object.keys(queryParams).forEach(key => url.searchParams.append(key, queryParams[key])); 97 98 fetch(url, { headers: { 'Accept': 'text/html' } }) 99 .then(response => { 100 if (!response.ok) { 101 throw new Error(`HTTP error! status: ${response.status}`); 102 } 103 104 // Extract custom headers before processing the text 105 const totalPages = response.headers.get('X-total-pages'); 106 const currentPage = response.headers.get('X-current-page'); 107 this.updatePagination(parseInt(totalPages, 10)); 108 109 return response.text(); 110 }) 111 .then(html => { 112 const tableBody = document.getElementById(this.tableBodyId); 113 tableBody.innerHTML = html; 114 htmx.process(tableBody); // Reinitialize htmx for new content 115 }) 116 .catch(error => { 117 console.error('Fetch error:', error); 118 }); 119 } 120 121 updatePagination(totalPages) { 122 console.log(totalPages) 123 const container = document.getElementById(this.paginationContainerId); 124 container.innerHTML = ''; 125 126 // Create back button 127 const createBackButton = () => { 128 const li = document.createElement('li'); 129 li.className = this.currentPage === 1 ? 'disabled' : 'waves-effect'; 130 const a = document.createElement('a'); 131 const icon = document.createElement('i'); 132 icon.className = 'material-icons'; 133 icon.textContent = 'chevron_left'; 134 a.appendChild(icon); 135 a.onclick = () => { 136 if (this.currentPage > 1) { 137 this.currentPage -= 1; 138 this.fetchData({ page: this.currentPage - 1, page_size: this.currentPageSize }); 139 } 140 }; 141 li.appendChild(a); 142 return li; 143 }; 144 container.appendChild(createBackButton()); 145 146 // Create page buttons 147 for (let i = 1; i <= totalPages; i++) { 148 const li = document.createElement('li'); 149 li.className = i === this.currentPage ? 'active' : 'waves-effect'; 150 const a = document.createElement('a'); 151 a.textContent = i; 152 a.onclick = () => { 153 this.currentPage = i; 154 this.fetchData({ page: i, page_size: this.currentPageSize }); 155 }; 156 li.appendChild(a); 157 container.appendChild(li); 158 } 159 160 // Create forward button 161 const createForwardButton = () => { 162 const li = document.createElement('li'); 163 li.className = this.currentPage === totalPages ? 'disabled' : 'waves-effect'; 164 const a = document.createElement('a'); 165 const icon = document.createElement('i'); 166 icon.className = 'material-icons'; 167 icon.textContent = 'chevron_right'; 168 a.appendChild(icon); 169 a.onclick = () => { 170 if (this.currentPage < totalPages) { 171 this.currentPage += 1; 172 this.fetchData({ page: this.currentPage + 1, page_size: this.currentPageSize }); 173 } 174 }; 175 li.appendChild(a); 176 return li; 177 }; 178 container.appendChild(createForwardButton()); 179 } 180 181 debounce(func, delay) { 182 let debounceTimer; 183 return function() { 184 const context = this; 185 const args = arguments; 186 clearTimeout(debounceTimer); 187 debounceTimer = setTimeout(() => func.apply(context, args), delay); 188 }; 189 } 190 191 searchData() { 192 this.currentSearchQuery = document.getElementById(this.searchBarId).value; 193 this.currentPage = 1; 194 this.fetchData(); 195 } 196 197 sortTableByHeader() { 198 document.querySelectorAll(`#${this.tableHeadersId} th`).forEach(header => { 199 header.addEventListener('click', () => { 200 this.currentSortField = header.getAttribute(this.id_prefix + 'data-field'); 201 this.currentSortOrder = header.getAttribute(this.id_prefix + 'data-order') === 'asc' ? 'desc' : 'asc'; 202 header.setAttribute(this.id_prefix + 'data-order', this.currentSortOrder); 203 this.fetchData(); 204 }); 205 }); 206 } 207 }