fix(04): badge count + DnD init — use DOMContentLoaded/htmx:afterSettle
Replace htmx.onLoad (requires htmx at parse time) with native
document.addEventListener('DOMContentLoaded') + 'htmx:afterSettle'
so Sortable.js is guaranteed loaded before init runs.
Add task-count-badge-{status} wrapper IDs and updateBadges() that
recounts .task-card elements on every HTMX settle so badge counts
stay in sync after create, delete, and reorder operations.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8ac0a51969
commit
68f2ccdea3
1 changed files with 59 additions and 29 deletions
|
|
@ -38,38 +38,66 @@ templ KanbanBoard(tabloID uuid.UUID, csrfToken string, tasks []sqlc.Task) {
|
|||
@KanbanColumn(tabloID, status, grouped[status], csrfToken)
|
||||
}
|
||||
<script>
|
||||
htmx.onLoad(function(content) {
|
||||
var cols = content.querySelectorAll ? content.querySelectorAll(".sortable-column") : [];
|
||||
if (cols.length === 0 && document) {
|
||||
cols = document.querySelectorAll(".sortable-column");
|
||||
}
|
||||
cols.forEach(function(col) {
|
||||
new Sortable(col, {
|
||||
group: "kanban",
|
||||
animation: 150,
|
||||
handle: ".task-drag-handle",
|
||||
draggable: ".task-card",
|
||||
ghostClass: "bg-slate-100",
|
||||
chosenClass: "opacity-50",
|
||||
onEnd: function(evt) {
|
||||
var form = document.getElementById("reorder-form");
|
||||
form.querySelectorAll("input[name=task_id],input[name=task_col]").forEach(function(el) { el.remove(); });
|
||||
document.querySelectorAll(".sortable-column").forEach(function(c) {
|
||||
c.querySelectorAll(".task-card").forEach(function(card) {
|
||||
var tid = card.dataset.taskId;
|
||||
var inp = document.createElement("input");
|
||||
inp.type = "hidden"; inp.name = "task_id"; inp.value = tid;
|
||||
form.appendChild(inp);
|
||||
var col = document.createElement("input");
|
||||
col.type = "hidden"; col.name = "task_col"; col.value = c.dataset.status;
|
||||
form.appendChild(col);
|
||||
(function() {
|
||||
function initSortable(root) {
|
||||
var cols = root.querySelectorAll ? root.querySelectorAll(".sortable-column") : [];
|
||||
if (cols.length === 0) {
|
||||
cols = document.querySelectorAll(".sortable-column");
|
||||
}
|
||||
cols.forEach(function(col) {
|
||||
if (col._sortableInit) { return; }
|
||||
col._sortableInit = true;
|
||||
new Sortable(col, {
|
||||
group: "kanban",
|
||||
animation: 150,
|
||||
handle: ".task-drag-handle",
|
||||
draggable: ".task-card",
|
||||
ghostClass: "bg-slate-100",
|
||||
chosenClass: "opacity-50",
|
||||
onEnd: function() {
|
||||
var form = document.getElementById("reorder-form");
|
||||
form.querySelectorAll("input[name=task_id],input[name=task_col]").forEach(function(el) { el.remove(); });
|
||||
document.querySelectorAll(".sortable-column").forEach(function(c) {
|
||||
c.querySelectorAll(".task-card").forEach(function(card) {
|
||||
var inp = document.createElement("input");
|
||||
inp.type = "hidden"; inp.name = "task_id"; inp.value = card.dataset.taskId;
|
||||
form.appendChild(inp);
|
||||
var colInp = document.createElement("input");
|
||||
colInp.type = "hidden"; colInp.name = "task_col"; colInp.value = c.dataset.status;
|
||||
form.appendChild(colInp);
|
||||
});
|
||||
});
|
||||
});
|
||||
htmx.trigger(form, "submit");
|
||||
form.dispatchEvent(new Event("submit", { bubbles: true }));
|
||||
htmx.trigger(form, "submit");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function updateBadges() {
|
||||
document.querySelectorAll(".sortable-column").forEach(function(col) {
|
||||
var status = col.dataset.status;
|
||||
var count = col.querySelectorAll(".task-card").length;
|
||||
var wrapper = document.getElementById("task-count-badge-" + status);
|
||||
if (wrapper) {
|
||||
var inner = wrapper.querySelector("span") || wrapper;
|
||||
inner.textContent = String(count);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initial page load — runs after all defer scripts (htmx + sortable) have executed.
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
initSortable(document.body);
|
||||
updateBadges();
|
||||
});
|
||||
});
|
||||
|
||||
// After any HTMX swap — re-init new columns and refresh badge counts.
|
||||
document.addEventListener("htmx:afterSettle", function() {
|
||||
initSortable(document.body);
|
||||
updateBadges();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
}
|
||||
|
|
@ -80,7 +108,9 @@ templ KanbanColumn(tabloID uuid.UUID, status sqlc.TaskStatus, tasks []sqlc.Task,
|
|||
<div class="flex-shrink-0 w-72">
|
||||
<div class="bg-slate-100 rounded px-3 py-2 mb-2 flex items-center justify-between">
|
||||
<h3 class="text-sm font-semibold text-slate-700">{ TaskColumnLabels[status] }</h3>
|
||||
@ui.Badge(ui.BadgeProps{Label: strconv.Itoa(len(tasks)), Variant: ui.BadgeVariantInfo})
|
||||
<span id={ "task-count-badge-" + string(status) }>
|
||||
@ui.Badge(ui.BadgeProps{Label: strconv.Itoa(len(tasks)), Variant: ui.BadgeVariantInfo})
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="sortable-column min-h-16 space-y-2"
|
||||
|
|
|
|||
Loading…
Reference in a new issue