Skip to content

Datagrid prefs

Column preferences, persisted the hypermedia way — two wires, zero new JavaScript beyond what already ships:

  1. WidthsinstallDatagrid() mirrors every committed resize into any input[data-hc-datagrid-width="<col>"] before dispatching hc:datagridcolumnresize; a small form autosaves it on the debounced event. The server persists per user and renders the remembered widths back as inline widths + data-resized on later loads — the width is a server-rendered fact.
  2. Order — the datagrid-columns chooser upgraded with installSortable(): checkbox serialization follows DOM order, and the server honors the submitted cols= sequence as the column order.

Covered by the versioning policy.

Drag the Name or Status resize grip (or focus it and press the arrow keys). The committed width lands in the hidden inputs, the debounced form posts them, and the status line answers with what a real server would persist:

NameStatusOwner
Ingest pipelineActiveAda
Billing exportPendingAlan

Resize a column to save its width.

<!-- In the grid: resizable headers must name their column —
installDatagrid() skips a data-resizable header without data-col. -->
<th class="hc-datagrid__headcell" data-resizable data-col="name"
scope="col">Name</th>
<th class="hc-datagrid__headcell" data-resizable data-col="status"
scope="col">Status</th>
<form action="/prefs/columns" method="post"
data-hx-post="/prefs/columns"
data-hx-trigger="hc:datagridcolumnresize from:body delay:500ms"
data-hx-include="this"
data-hx-target="#prefs-status" data-hx-swap="innerHTML">
<input type="hidden" name="w-name" data-hc-datagrid-width="name">
<input type="hidden" name="w-status" data-hc-datagrid-width="status">
<div id="prefs-status" role="status" class="hc-sr-only"></div>
</form>

The mirror happens before the event dispatch, so the event-triggered request always serializes the fresh value. Mirrored inputs are found in the grid’s closest <form>, else document-wide.

<fieldset class="hc-popover__body" id="cols-fields" data-hc-sortable>
<div class="hc-item">
<button type="button" class="hc-button" data-variant="ghost"
data-hc-sortable-handle aria-label="Reorder Name"></button>
<label class="hc-checkbox-label">
<input class="hc-checkbox" type="checkbox" name="cols" value="name" checked>
Name
</label>
</div>
<!-- …one sortable row per column… -->
</fieldset>

Each row is a direct child of the sortable fieldset, and the handle sits beside the label, never inside it — interactive content inside a <label> is invalid HTML, and clicking the handle would toggle the checkbox. Drag (or grab with the keyboard) to reorder — Apply serializes the checkboxes in DOM order, and the upgraded datagrid-columns contract renders the columns in that sequence.

POST /prefs/columns (w-<col> pairs):

CaseResponse (200)
any w-<col> setthe role="status" fragment (e.g. “Saved — Name 220px”); persisted per user
invalid valueclamped or ignored server-side, still 200 — a width is a preference, never an error
no-JSnative POST → 303 PRG

Rendering back is the other half: on later loads the server renders remembered widths as style="inline-size: <w>px; max-inline-size: <w>px" + data-resized on the column’s header and cells — print/export and other sessions agree with the screen.

Without JavaScript there is no client resize — and the server-rendered widths still apply. The order chooser stays a real GET form; without the sortable upgrade the checkboxes still choose the set.

  • Saves announce through the role="status" region — never silent.
  • Sortable handles are real buttons with installSortable()’s keyboard grammar (Space/Enter grabs, arrows move, Escape cancels).
  • Column resize itself is keyboard-accessible (the grip is a focusable separator) — so the whole prefs loop is.
  • datagrid-columns — the chooser this recipe upgrades with ordering.
  • sortable — the reorder behavior and its keyboard grammar.
  • autosave — the same debounced-persistence idea for whole forms.
  • Datagrid — column resize and the mirrored-input hook.