Table Component Updated the Sort Logic
This commit is contained in:
+117
-16
@@ -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, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
// ---------- 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();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user