Lattice Grid Buy a licence

demo D62

An application-level filter in htmx

A host predicate composed with the grid’s own, for permissions

hostFilter: { active, passes }

This grid composes an application-level predicate with the user's own filters, so certain rows stay hidden no matter what a reader selects. It enforces a scope like a region or a role at the grid level, without pre-slicing the data before it arrives.

Building…
Loading a live grid…

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="host-filter-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 &amp; 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('host-filter-table'), {
    toolPanel: { side: 'left', panels: ['filters', 'columns'], actions: ['restore'], exportName: 'lattice-demo' },
    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' },
    ],
    // The application's own predicate, composed with whatever the user filters.
    // Rows it refuses never reach the row model: not in the totals, not in an
    // export, and not reachable by clearing the filters. `passes` is handed the
    // grid's row wrapper, so the record itself is `row.data`. In a real
    // deployment this reads a session rather than a constant.
    hostFilter: {
      active: () => true,
      passes: (row) => row.data.environment !== 'prod' && row.data.environment !== 'prod-2',
    },
  });
</script>

Composing a host-supplied filter with the grid’s own

Some rows need to stay hidden regardless of what a user selects in the filter menu: a support agent scoped to one region, a viewer role without access to archived records, a tenant boundary in a multi-tenant table. Rather than pre-slicing the dataset before it reaches the grid, Lattice Grid accepts a hostFilter: { active, passes } option that runs alongside whatever the user configures. The active flag turns the predicate on or off, and passes receives each row and returns a boolean, exactly like a permissions check written outside the grid. Both filters apply to the same row model, combining with AND: a row must clear both to render.

This is the pattern to reach for whenever filtering needs to encode something the user cannot override, as distinct from set filters or the filter builder, which are user-facing. Because passes runs during the grid’s own filter pass rather than as a separate pre-processing step, it benefits from the same virtual scrolling the rest of the grid uses: only rows that clear the host predicate and any user conditions get measured and rendered, so a 200,000-row dataset with a permissions boundary costs no more to scroll than one without it. The predicate re-evaluates whenever active changes or the data updates, keeping the boundary consistent as rows are added or edited.

How do you hide rows a user is not allowed to see in a JavaScript data grid?

Set hostFilter.active to true and supply a passes(row) function that returns false for any row the current user should not see. Lattice Grid combines this with the user’s own filters using AND, so the restriction holds regardless of what conditions someone sets in the filter menu, and it re-runs automatically as data or permissions change.