demo D23
Durations in htmx
Elapsed time that sorts numerically and reads as 3h 12m
type: 'duration'
This grid holds elapsed time that reads as '3h 12m' but sorts by actual length, so '1h 5m' comes before '10h 0m'. It fixes the trap of storing durations as text, giving you readable cells and correct ordering at once.
This is the htmx version. A server-rendered table is upgraded in place, so the page is readable before any script runs and gains the full grid when the module loads, which suits a progressively enhanced app.
The configuration
<link rel="stylesheet" href="https://cdn.latticegrid.dev/lattice-grid@1.107.0/lattice-grid.min.css">
<!-- A real, server-rendered table: readable with JavaScript off. data-field on
each header names the column that cell becomes, and data-id on each row is
the key the grid carries it under. -->
<table id="durations-table" data-lattice-grid>
<thead>
<tr>
<th data-field="pipeline">Pipeline</th>
<th data-field="stage">Stage</th>
<th data-field="elapsed">Elapsed</th>
<th data-field="queued">Queued</th>
<th data-field="cpu">CPU time</th>
<th data-field="records">Records</th>
</tr>
</thead>
<tbody>
<tr data-id="1"><td>ingest</td><td>validate</td><td>2611343</td><td>159325</td><td>5094401</td><td>3360749</td></tr>
<tr data-id="2"><td>archive</td><td>publish</td><td>889055</td><td>84386</td><td>830759</td><td>249878</td></tr>
<tr data-id="3"><td>ingest</td><td>validate</td><td>11569</td><td>70750</td><td>11906</td><td>1880377</td></tr>
<!-- the rest of the rows, rendered by your server -->
</tbody>
</table>
<script type="module">
import { hydrateTable } from 'https://cdn.latticegrid.dev/lattice-grid@1.107.0/modules/htmx.esm.min.js';
// The markup carries the rows; a server-rendered table's column types are
// declared in the config hydrateTable takes. The grid replaces the table
// where it stood.
const grid = hydrateTable(document.getElementById('durations-table'), {
toolPanel: {
side: 'left',
panels: ['columns', 'filters', 'views', 'quick'],
actions: ['undo', 'redo', 'export', 'restore', 'maximise'],
exportName: 'lattice-demo',
},
columns: [
{ field: 'pipeline', title: 'Pipeline', filter: { type: 'set' }, layout: { width: 130, pin: 'start' } },
{ field: 'stage', title: 'Stage', filter: { type: 'set' }, layout: { width: 130 } },
{
field: 'outcome', title: 'Outcome', filter: { type: 'set' }, layout: { width: 120 },
cell: { decoration: 'pill', variant: { map: { ok: 'success', retried: 'warning', failed: 'danger' } } },
},
// Milliseconds in, "3h 12m" out. The value stays a number, so the sort is
// numeric, the range filter takes numbers and the total is a sum.
{ field: 'elapsed', title: 'Elapsed', type: 'duration', layout: { width: 140 }, total: 'sum' },
{ field: 'queued', title: 'Queued', type: 'duration', layout: { width: 140 }, total: 'sum' },
{ field: 'cpu', title: 'CPU time', type: 'duration', layout: { width: 140 }, total: 'sum' },
{
id: 'share', title: 'CPU / elapsed', type: 'number', layout: { width: 150 },
format: { style: 'percent', decimals: 0 },
value: { deps: ['elapsed', 'cpu'], pure: true, compute: (d) => (d.elapsed ? d.cpu / d.elapsed : null) },
},
{ field: 'records', title: 'Records', type: 'number', layout: { width: 130 }, format: { notation: 'compact' }, total: 'sum' },
],
});
</script>
Formatting and sorting elapsed time correctly
Elapsed time is a poor fit for the number or date types: a raw second count is unreadable in a cell, and a formatted string like “3h 12m” sorts alphabetically rather than by duration, so “1h 5m” ends up after “10h 0m”. Lattice Grid’s type: 'duration' column stores the underlying value as seconds (or milliseconds, if you prefer) and keeps the comparator numeric while the renderer shows the short form a person actually reads, so a column of build times, session lengths or SLA countdowns sorts by true elapsed time even though every cell displays as hours and minutes rather than a number. A developer reaches for this type wherever a JavaScript data grid needs to show a span rather than a point: task duration, video length, uptime, time-to-resolution. Because the comparator works on the stored numeric value rather than the rendered string, sorting a column of durations costs the same as sorting any other numeric column, with no string-parsing step inserted per row, and it stays correct at any row count the grid virtualises.
The renderer also accepts a threshold so unusually long or short durations can carry a variant, letting a duration column double as an at-a-glance SLA indicator without a separate formatting rule.
How do you sort a duration column correctly in a data grid?
Store the duration as a plain number of seconds or milliseconds and let the grid’s type: 'duration' comparator sort on that value, rather than sorting the formatted string. Sorting the display text (“3h 12m”, “45m”) puts short-looking strings ahead of longer numeric durations; sorting the underlying seconds keeps the order numerically correct regardless of how compact the rendered label is.