Table Component Updated the Sort Logic

This commit is contained in:
2025-11-13 16:38:12 +05:30
parent 4bf4c73295
commit 4695e31713
+117 -16
View File
@@ -37,6 +37,8 @@ const id = `table-${Math.random().toString(36).slice(2, 9)}`;
)
}
<div data-badges></div>
<!-- 🖥️ Desktop Table -->
<div
class={cn(
@@ -154,6 +156,7 @@ const id = `table-${Math.random().toString(36).slice(2, 9)}`;
const table = document.getElementById(id);
const searchInput = table.querySelector("[data-search]");
const badgeContainer = table.querySelector("[data-badges]");
const sortHeaders = table.querySelectorAll("[data-sort-key]");
const tbody = table.querySelector("[data-body]");
const mobileBody = table.querySelector("[data-mobile-body]");
@@ -165,7 +168,7 @@ const id = `table-${Math.random().toString(36).slice(2, 9)}`;
let rowsPerPage = pagignations
? parseInt(pageSizeSelector.value)
: 9999;
let sort = {};
let sort = {}; // { key: "asc" | "desc" }
let totalRows = data.length;
let currentData = [...data];
@@ -196,7 +199,89 @@ const id = `table-${Math.random().toString(36).slice(2, 9)}`;
return "";
};
/** 🧱 Render rows */
// ---------- BADGE helpers ----------
function createOrUpdateBadge(key, order) {
// try find existing badge by data-key
const existing = badgeContainer.querySelector(
`[data-badge-key="${key}"]`,
);
const badgeHtml = badgeTemplate(key, order);
if (existing) {
// replace
existing.outerHTML = badgeHtml;
} else {
badgeContainer.insertAdjacentHTML("beforeend", badgeHtml);
}
// attach close handler
const btn = badgeContainer.querySelector(
`[data-badge-close="${key}"]`,
);
if (btn) {
btn.addEventListener("click", (e) => {
e.stopPropagation();
removeBadge(key);
});
}
}
function badgeTemplate(key, order) {
// returns HTML string for a badge; includes data-key and close button
return `
<span data-badge-key="${escapeHtml(key)}" class="wr:uppercase wr:inline-flex wr:items-center wr:py-1.5 wr:ps-3 wr:pe-2 wr:rounded-full wr:text-xs wr:font-medium wr:bg-blue-100 wr:text-blue-800 dark:bg-blue-800/30 wr:dark:text-blue-500" role="status" aria-live="polite">
<span class="wr:mr-2">${escapeHtml(key)}: ${escapeHtml(order)}</span>
<button type="button" data-badge-close="${escapeHtml(key)}" class="wr:ml-1 wr:inline-flex wr:items-center wr:justify-center wr:rounded-full wr:p-1 wr:hover:bg-blue-200 wr:focus:outline-none" aria-label="Remove ${escapeHtml(key)} sort">
<svg class="wr:w-3 wr:h-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 6 6 18"></path>
<path d="m6 6 12 12"></path>
</svg>
</button>
</span>
`;
}
function removeBadge(key) {
// remove from DOM
const el = badgeContainer.querySelector(
`[data-badge-key="${key}"]`,
);
if (el) el.remove();
// remove from sort map
if (sort.hasOwnProperty(key)) {
delete sort[key];
}
// update header indicator (if any)
const th = Array.from(sortHeaders).find(
(t) => t.dataset.sortKey === key,
);
if (th) {
const indicator = th.querySelector(".sort-indicator");
if (indicator) {
indicator.setAttribute(
"data-icon",
"solar:sort-vertical-line-duotone",
);
}
}
// reload data
currentPage = 1;
loadData();
}
function escapeHtml(str) {
return String(str)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
// ---------- ROW rendering (unchanged) ----------
function renderRows(dataset) {
tbody.innerHTML = "";
mobileBody.innerHTML = "";
@@ -343,26 +428,23 @@ const id = `table-${Math.random().toString(36).slice(2, 9)}`;
}
// sort locally if needed
if (Object.keys(sort).length !== 0) {
for (const key in sort) {
const colKey = key;
const colSortOrder = sort[key];
if (colKey) {
const sortKeys = Object.keys(sort);
if (sortKeys.length !== 0) {
// stable multi-sort — apply keys in reverse so the first sort key has priority
sortKeys.reverse().forEach((k) => {
const colKey = k;
const colSortOrder = sort[k];
currentData.sort((a, b) => {
const aVal = String(a[colKey] ?? "");
const bVal = String(b[colKey] ?? "");
if (aVal < bVal)
return colSortOrder === "asc" ? -1 : 1;
if (aVal > bVal)
return colSortOrder === "asc" ? 1 : -1;
if (aVal < bVal) return colSortOrder === "asc" ? -1 : 1;
if (aVal > bVal) return colSortOrder === "asc" ? 1 : -1;
return 0;
});
}
}
});
}
// paginate
renderRows(paginated);
renderPagination(totalRows);
}
@@ -432,10 +514,12 @@ const id = `table-${Math.random().toString(36).slice(2, 9)}`;
});
// 🧭 Sorting
sortHeaders.forEach((th, index) => {
sortHeaders.forEach((th) => {
th.addEventListener("click", () => {
const key = th.dataset.sortKey;
if (!key) return;
// compute new sort order
let sortOrder = "asc";
if (sort.hasOwnProperty(key)) {
sortOrder = sort[key] === "asc" ? "desc" : "asc";
@@ -444,13 +528,30 @@ const id = `table-${Math.random().toString(36).slice(2, 9)}`;
sort[key] = sortOrder;
}
th.querySelector(".sort-indicator")?.setAttribute(
// update header icon (single header)
sortHeaders.forEach((h) => {
const ind = h.querySelector(".sort-indicator");
if (!ind) return;
if (h.dataset.sortKey === key) {
ind.setAttribute(
"data-icon",
sortOrder === "asc"
? "solar:sort-up-bold-duotone"
: "solar:sort-down-bold-duotone",
);
} else {
ind.setAttribute(
"data-icon",
"solar:sort-vertical-line-duotone",
);
}
});
// create / update badge
createOrUpdateBadge(key, sort[key]);
// reload data
currentPage = 1;
loadData();
});
});