demo D125
Presentation mode in htmx
Scale the whole grid up for a room, then come back
grid.presentation.start() · nudge() · stop()
This grid scales up to be read across a room, then returns to its normal layout without losing the reader's place. It takes a grid built for a desk into a meeting, with a nudge control to fine-tune the size, so a dense table is legible from the back.
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="presentation-mode-table" data-lattice-grid>
<thead>
<tr>
<th data-field="account">Account</th>
<th data-field="service">Service</th>
<th data-field="resource">Resource</th>
<th data-field="region">Region</th>
<th data-field="change">Change</th>
<th data-field="cost">Monthly cost</th>
</tr>
</thead>
<tbody>
<tr data-id="1"><td>Legacy (Do Not Decommission)</td><td>Relational Database</td><td>legacy-nat-delete-after-friday</td><td>us-east-1</td><td>-0.0468</td><td>31612.55</td></tr>
<tr data-id="2"><td>Cost Optimisation Programme</td><td>Elastic Compute</td><td>tmp-jumpbox-03</td><td>us-east-1</td><td>0.1143</td><td>6109.09</td></tr>
<tr data-id="3"><td>Data & Insights</td><td>Block Storage</td><td>test-api-terraform-imported-by-hand</td><td>us-east-1</td><td>0.1082</td><td>42.69</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('presentation-mode-table'), {
selection: 'multiple',
// Saved views are the slides the deck steps through.
views: {
saved: [
{
id: 'prod-spend',
name: 'Production spend',
state: {
version: 1,
filters: {
op: 'and',
conditions: [{ col: 'environment', op: 'in', value: ['prod', 'prod-2', 'definitely-prod'] }],
},
sort: [{ col: 'cost', dir: 'desc' }],
group: [],
},
},
{
id: 'biggest-movers',
name: 'Biggest movers',
state: { version: 1, filters: null, sort: [{ col: 'change', dir: 'desc' }], group: [] },
},
{
id: 'by-service',
name: 'By service',
state: { version: 1, filters: null, sort: [{ col: 'cost', dir: 'desc' }], group: ['service'] },
},
],
},
toolPanel: {
side: 'left',
panels: ['columns', 'filters', 'views', 'quick'],
exportName: 'lattice-demo',
actions: ['restore', 'maximise'],
},
columns: [
{ field: 'account', title: 'Account', filter: { type: 'set' } },
{ field: 'service', title: 'Service', filter: { type: 'set' } },
{ field: 'resource', title: 'Resource', layout: { flex: 1, min: 200, max: 320 } },
{ field: 'region', title: 'Region', filter: { type: 'set' } },
{ field: 'environment', title: 'Env', filter: { type: 'set' },
cell: { decoration: 'pill', variant: { map: {
prod: 'danger', 'prod-2': 'danger', 'definitely-prod': 'danger',
staging: 'warning', untagged: 'warning', test: 'info',
dev: 'success', 'not-prod': 'neutral',
} } } },
{ field: 'change', title: 'Change', type: 'number',
layout: { width: 140, min: 140 }, format: { style: 'percent', decimals: 1 } },
{ field: 'cost', title: 'Monthly cost', type: 'number', layout: { width: 170 },
format: { style: 'currency', currency: 'USD', decimals: 2 }, total: 'sum' },
],
});
// Presentation is driven from the grid rather than set in config, and it takes
// over the viewport, so call it once the grid exists rather than on arrival.
// Present the grid as it stands, full screen at 1.5x. Escape comes back.
grid.presentation.start({ scale: 1.5 });
// Or present the saved views as a slide deck: arrow keys, space and
// Page Up/Down step through them.
grid.presentation.start({ scale: 1.5, views: ['prod-spend', 'biggest-movers', 'by-service'] });
// Arm a spotlight on the Monthly cost column. It paints while presenting and
// dims every cell outside that column.
grid.presentation.setSpotlight({ colIds: ['cost'] });
// Escape leaves full screen but leaves the presentation running, so stop it
// yourself to put the grid back.
grid.presentation.stop();
</script>
Scaling a grid up for a room, then handing control back
Presentation mode enlarges the whole grid for viewing at a distance, then returns it to its normal layout without losing the reader’s place. A developer reaches for it when a JavaScript data grid built for a desk gets walked into a meeting: a dashboard cast to a conference screen, a review of figures on a shared monitor, a status board glanced at from across a room. Rather than a separate zoomed view, Lattice Grid scales the existing rows and columns in place, so column widths, sort order, and any applied filters stay exactly as the presenter left them. The demo is driven through grid.presentation.start(), nudge(), and stop(): start switches to the enlarged layout, nudge steps the focus forward or back a row so a presenter can advance through figures from a clicker or the keyboard, and stop restores the working layout. Because the scaling changes font size and row height rather than re-rendering the data, the switch is immediate even on a grid holding tens of thousands of rows, and virtual scrolling continues to render only the rows in view, so a large table stays as responsive at presentation scale as at desk scale. Keyboard focus carries across the switch, so a screen reader user does not lose their position when the layout changes size.
How do I make a data grid readable from across a room?
Call grid.presentation.start() to switch the grid into an enlarged layout that keeps the current data, sort, and filters in place, then nudge() to step through rows without leaving the keyboard. grid.presentation.stop() returns the grid to its normal size. The scaling only changes row height and font size, so it works the same way regardless of how many rows the grid is holding.