reuse form

This commit is contained in:
antv
2026-07-01 16:57:34 +07:00
parent 4e4d03d62a
commit f1858f8f0f
34 changed files with 1273 additions and 681 deletions
+223
View File
@@ -0,0 +1,223 @@
export class AjaxTable {
constructor(options = {}) {
this.formId = options.formId || 'filterForm';
this.tableContainerSelector = options.tableContainerSelector || '#userTableContainer';
this.spinnerId = options.spinnerId || 'loadingSpinner';
this.searchInputId = options.searchInputId || 'searchInput';
this.btnClearSearchId = options.btnClearSearchId || 'btnClearSearch';
this.btnClearFiltersId = options.btnClearFiltersId || 'btnClearFilters';
this.flagSendFilterId = options.flagSendFilterId || 'flagSendFilter';
this.monthFilterId = options.monthFilterId || 'monthFilter';
this.skeletonColumns = options.skeletonColumns || 7;
this.isLoading = false;
this.searchDebounceTimer = null;
this.init();
}
init() {
this.form = document.getElementById(this.formId);
this.spinner = document.getElementById(this.spinnerId);
this.searchInput = document.getElementById(this.searchInputId);
this.btnClearSearch = document.getElementById(this.btnClearSearchId);
this.btnClearFilters = document.getElementById(this.btnClearFiltersId);
this.flagSendFilter = document.getElementById(this.flagSendFilterId);
this.monthFilter = document.getElementById(this.monthFilterId);
if (this.form) {
this.form.addEventListener('submit', (e) => {
e.preventDefault();
this.submitForm();
});
}
if (this.searchInput) {
this.searchInput.addEventListener('input', () => {
this.toggleClearSearchBtn();
clearTimeout(this.searchDebounceTimer);
this.searchDebounceTimer = setTimeout(() => {
this.submitForm();
}, 500);
});
}
if (this.btnClearSearch) {
this.btnClearSearch.addEventListener('click', () => {
if (this.searchInput) {
this.searchInput.value = '';
this.toggleClearSearchBtn();
this.submitForm();
}
});
}
if (this.flagSendFilter) {
this.flagSendFilter.addEventListener('change', () => {
this.submitForm();
});
}
if (this.btnClearFilters) {
this.btnClearFilters.addEventListener('click', () => {
if (this.searchInput) this.searchInput.value = '';
if (this.flagSendFilter) this.flagSendFilter.value = '';
if (this.monthFilter) {
this.monthFilter.value = new Date().toISOString().slice(0, 7);
}
this.toggleClearSearchBtn();
this.submitForm();
});
}
// Intercept pagination links
document.addEventListener('click', (e) => {
if (this.isLoading) {
e.preventDefault();
return;
}
const link = e.target.closest(`${this.tableContainerSelector} nav a`);
if (link) {
e.preventDefault();
this.loadUrl(link.href);
}
});
// Browser history handling
window.addEventListener('popstate', () => {
this.loadUrl(window.location.href, false);
});
this.toggleClearSearchBtn();
}
toggleClearSearchBtn() {
if (this.searchInput && this.btnClearSearch) {
if (this.searchInput.value.trim() !== '') {
this.btnClearSearch.classList.remove('hidden');
} else {
this.btnClearSearch.classList.add('hidden');
}
}
}
submitForm() {
if (!this.form) return;
const url = this.form.action + '?' + new URLSearchParams(new FormData(this.form)).toString();
this.loadUrl(url);
}
getSkeletonHtml() {
let html = '';
for (let i = 0; i < 10; i++) {
html += `
<tr class="animate-pulse h-[69px]">
<td class="px-6 py-4"><div class="h-4 bg-gray-200 rounded w-16"></div></td>
<td class="px-6 py-4"><div class="h-4 bg-gray-200 rounded w-48"></div></td>
<td class="px-6 py-4"><div class="h-4 bg-gray-200 rounded w-12"></div></td>
<td class="px-6 py-4"><div class="h-4 bg-gray-200 rounded w-12"></div></td>
<td class="px-6 py-4"><div class="h-4 bg-gray-200 rounded w-16"></div></td>
<td class="px-6 py-4"><div class="h-6 bg-gray-200 rounded-full w-20"></div></td>
<td class="px-6 py-4"><div class="h-8 bg-gray-200 rounded w-20 ml-auto"></div></td>
</tr>`;
}
return html;
}
async loadUrl(url, updateHistory = true) {
if (this.isLoading) return;
this.isLoading = true;
if (this.spinner) {
this.spinner.classList.remove('hidden');
}
let formElements = [];
if (this.form) {
formElements = this.form.querySelectorAll('input, select, button');
formElements.forEach(el => el.disabled = true);
}
const tableContainer = document.querySelector(this.tableContainerSelector);
const tableBody = tableContainer ? tableContainer.querySelector('tbody') : null;
if (tableBody) {
tableBody.innerHTML = this.getSkeletonHtml();
}
try {
const [res] = await Promise.all([
fetch(url, { headers: { 'X-Requested-With': 'XMLHttpRequest' } }),
new Promise(resolve => setTimeout(resolve, 200)) // smooth transition
]);
if (!res.ok) throw new Error('Network response was not ok');
const contentType = res.headers.get('content-type');
const updateDOM = async () => {
if (contentType && contentType.includes('application/json')) {
const data = await res.json();
const titleEl = document.getElementById('pageTitle');
if (titleEl && data.title !== undefined) {
titleEl.innerHTML = data.title;
}
const statsEl = document.getElementById('statsGrid');
if (statsEl && data.stats !== undefined) {
statsEl.innerHTML = data.stats;
}
if (tableContainer && data.table !== undefined) {
tableContainer.innerHTML = data.table;
}
} else {
const html = await res.text();
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const currentTitle = document.getElementById('pageTitle');
const newTitle = doc.getElementById('pageTitle');
if (currentTitle && newTitle) currentTitle.innerHTML = newTitle.innerHTML;
const currentGrid = document.getElementById('statsGrid');
const newGrid = doc.getElementById('statsGrid');
if (currentGrid && newGrid) currentGrid.innerHTML = newGrid.innerHTML;
const newTable = doc.querySelector(this.tableContainerSelector);
if (tableContainer && newTable) tableContainer.innerHTML = newTable.innerHTML;
}
};
if (document.startViewTransition) {
document.startViewTransition(updateDOM);
} else {
await updateDOM();
}
if (updateHistory) {
window.history.pushState({}, '', url);
}
const newTableContainer = document.querySelector(this.tableContainerSelector);
if (newTableContainer) {
newTableContainer.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
} catch (err) {
console.error('Error fetching data:', err);
window.location.reload();
} finally {
this.isLoading = false;
if (this.spinner) {
this.spinner.classList.add('hidden');
}
if (this.form) {
formElements.forEach(el => el.disabled = false);
}
this.toggleClearSearchBtn();
}
}
}