Table Component Updated the Sort Logic
This commit is contained in:
+127
-26
@@ -37,6 +37,8 @@ const id = `table-${Math.random().toString(36).slice(2, 9)}`;
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<div data-badges></div>
|
||||||
|
|
||||||
<!-- 🖥️ Desktop Table -->
|
<!-- 🖥️ Desktop Table -->
|
||||||
<div
|
<div
|
||||||
class={cn(
|
class={cn(
|
||||||
@@ -154,6 +156,7 @@ const id = `table-${Math.random().toString(36).slice(2, 9)}`;
|
|||||||
const table = document.getElementById(id);
|
const table = document.getElementById(id);
|
||||||
|
|
||||||
const searchInput = table.querySelector("[data-search]");
|
const searchInput = table.querySelector("[data-search]");
|
||||||
|
const badgeContainer = table.querySelector("[data-badges]");
|
||||||
const sortHeaders = table.querySelectorAll("[data-sort-key]");
|
const sortHeaders = table.querySelectorAll("[data-sort-key]");
|
||||||
const tbody = table.querySelector("[data-body]");
|
const tbody = table.querySelector("[data-body]");
|
||||||
const mobileBody = table.querySelector("[data-mobile-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
|
let rowsPerPage = pagignations
|
||||||
? parseInt(pageSizeSelector.value)
|
? parseInt(pageSizeSelector.value)
|
||||||
: 9999;
|
: 9999;
|
||||||
let sort = {};
|
let sort = {}; // { key: "asc" | "desc" }
|
||||||
let totalRows = data.length;
|
let totalRows = data.length;
|
||||||
let currentData = [...data];
|
let currentData = [...data];
|
||||||
|
|
||||||
@@ -196,7 +199,89 @@ const id = `table-${Math.random().toString(36).slice(2, 9)}`;
|
|||||||
return "";
|
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) {
|
function renderRows(dataset) {
|
||||||
tbody.innerHTML = "";
|
tbody.innerHTML = "";
|
||||||
mobileBody.innerHTML = "";
|
mobileBody.innerHTML = "";
|
||||||
@@ -343,26 +428,23 @@ const id = `table-${Math.random().toString(36).slice(2, 9)}`;
|
|||||||
}
|
}
|
||||||
|
|
||||||
// sort locally if needed
|
// sort locally if needed
|
||||||
if (Object.keys(sort).length !== 0) {
|
const sortKeys = Object.keys(sort);
|
||||||
for (const key in sort) {
|
if (sortKeys.length !== 0) {
|
||||||
const colKey = key;
|
// stable multi-sort — apply keys in reverse so the first sort key has priority
|
||||||
const colSortOrder = sort[key];
|
sortKeys.reverse().forEach((k) => {
|
||||||
if (colKey) {
|
const colKey = k;
|
||||||
currentData.sort((a, b) => {
|
const colSortOrder = sort[k];
|
||||||
const aVal = String(a[colKey] ?? "");
|
currentData.sort((a, b) => {
|
||||||
const bVal = String(b[colKey] ?? "");
|
const aVal = String(a[colKey] ?? "");
|
||||||
if (aVal < bVal)
|
const bVal = String(b[colKey] ?? "");
|
||||||
return colSortOrder === "asc" ? -1 : 1;
|
if (aVal < bVal) return colSortOrder === "asc" ? -1 : 1;
|
||||||
if (aVal > bVal)
|
if (aVal > bVal) return colSortOrder === "asc" ? 1 : -1;
|
||||||
return colSortOrder === "asc" ? 1 : -1;
|
return 0;
|
||||||
return 0;
|
});
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// paginate
|
// paginate
|
||||||
|
|
||||||
renderRows(paginated);
|
renderRows(paginated);
|
||||||
renderPagination(totalRows);
|
renderPagination(totalRows);
|
||||||
}
|
}
|
||||||
@@ -432,10 +514,12 @@ const id = `table-${Math.random().toString(36).slice(2, 9)}`;
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 🧭 Sorting
|
// 🧭 Sorting
|
||||||
sortHeaders.forEach((th, index) => {
|
sortHeaders.forEach((th) => {
|
||||||
th.addEventListener("click", () => {
|
th.addEventListener("click", () => {
|
||||||
const key = th.dataset.sortKey;
|
const key = th.dataset.sortKey;
|
||||||
|
if (!key) return;
|
||||||
|
|
||||||
|
// compute new sort order
|
||||||
let sortOrder = "asc";
|
let sortOrder = "asc";
|
||||||
if (sort.hasOwnProperty(key)) {
|
if (sort.hasOwnProperty(key)) {
|
||||||
sortOrder = sort[key] === "asc" ? "desc" : "asc";
|
sortOrder = sort[key] === "asc" ? "desc" : "asc";
|
||||||
@@ -444,13 +528,30 @@ const id = `table-${Math.random().toString(36).slice(2, 9)}`;
|
|||||||
sort[key] = sortOrder;
|
sort[key] = sortOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
th.querySelector(".sort-indicator")?.setAttribute(
|
// update header icon (single header)
|
||||||
"data-icon",
|
sortHeaders.forEach((h) => {
|
||||||
sortOrder === "asc"
|
const ind = h.querySelector(".sort-indicator");
|
||||||
? "solar:sort-up-bold-duotone"
|
if (!ind) return;
|
||||||
: "solar:sort-down-bold-duotone",
|
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();
|
loadData();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user