Data grid

<ema-grid> is the workhorse of the system: client or server data, pagination, search, per-column filters, grouping with aggregates, drill-down details, inline editing, full keyboard navigation, column resize / show-hide, icon and badge cells, and proper two-axis scrolling with a sticky header.

Client-side grid (all features)

200 rows generated in the browser. Try everything:

Server-side grid

Set .source to an async function instead of .data. The grid sends a query object (page, page size, sort, filters, search, groupBy) on every change, shows a loading overlay, ignores stale responses, and renders whatever the server returns. This demo simulates an API with 350ms latency over 5,000 records.

Column definition

PropertyTypeDescription
keystringRow field to display (required)
labelstringHeader text
width / minWidthnumberInitial / minimum pixel width (drag header edge to resize)
sortablebooleanDefault true. Click header: asc → desc → off
filter'text' | 'select' | 'number' | 'date'Adds a control to the filter row with a type-specific operator menu (see table below)
optionsarrayChoices for select filters / editors (defaults to distinct values)
type'text' | 'number' | 'date' | 'badge' | 'bool'Built-in formatting
badgeMapobject{value: variant} for type:'badge'
cellIconstring | fn(row)Icon rendered before the value
iconstringIcon in the header
renderfn(value, row) → htmlFull custom cell renderer (overrides type)
editableboolean | {type, options}Enables inline editing (text / number / select editor)
groupablebooleanAppears in the toolbar Group menu
aggregate'sum' | 'avg'Shown in group header rows
align'left' | 'center' | 'right'Cell alignment
hidden / resizablebooleanInitial visibility / allow resizing (default true)

Advanced column filters

Every filter cell has an operator button (the small symbol on the left). Click it to choose the comparison; the available operators depend on the column's filter type. "Between" shows a second range input; "Is empty / not empty" applies immediately without a value.

Filter typeOperators
textContains ~ · Does not contain !~ · Equals = · Not equals · Starts with ^ · Ends with $ · Is empty · Is not empty !∅
numberEquals = · Not equals · Greater > · Greater or equal · Less < · Less or equal · Between (range) · Is empty / not empty
dateOn date = · Before < · After > · Between (range, inclusive) · Is empty / not empty — uses native date pickers
selectIs = · Is not — options from col.options or distinct values

Filter state is structured — each active filter is { op, value, value2, type }. In server mode the same objects arrive in query.filters, so your backend can map operators to SQL/queries. For mock backends, reuse the grid's own matcher:

grid.source = async ({ filters, ...q }) => {
  let rows = DB;
  for (const [key, f] of Object.entries(filters)) {
    rows = rows.filter((r) => EMA.gridFilterMatch(f, r[key]));   // honors op/value/value2/type
  }
  // …sort, paginate, return {rows, total}
};

Grid attributes & properties

AttributeDescription
grid-titleToolbar title
heightMax body height, enables vertical scrolling with sticky header (e.g. 440px, 60vh)
page-size / page-sizesInitial size / selector options (default 10,25,50,100)
selectableCheckbox selection column
stripedZebra rows
exportableCSV export button in the toolbar
no-searchHide the toolbar search box
keyRow identity field (default id) — used for selection, expansion and dirty tracking
Property / methodDescription
.columnsColumn definitions (see above)
.dataRow array — client mode
.sourceasync (query) ⇒ {rows, total} — server mode. Query: {page, pageSize, sort, filters, search, groupBy}
.detail(row) ⇒ html drill-down renderer; adds the expand column
.actions[{name, icon, label, danger}] action icon buttons per row → action event
.groupBy / .page / .pageSizeRead or set grid state
.refresh()Re-query (server) or re-render (client)
.getSelectedRows() / .clearSelection()Selection helpers
.exportCsv(filename?)Download visible columns as CSV

Events

Eventevent.detail
cell-edit{row, key, oldValue, value} — fired after an inline edit commits
row-click / row-activate{row} — click / Enter on a non-editable cell
row-expand{row, expanded}
selection-change{rows}
action{action, row}
filter-change{filters} — map of key → {op, value, value2, type}
page-change / sort-change / search-change / group-change / columns-change / column-resizeState change notifications
load-error{error} — server source threw

Keyboard reference

KeyAction
Move cell focus
Enter / F2Edit focused cell (if editable), otherwise row-activate
Enter (while editing)Commit and move down
Tab / Shift+Tab (while editing)Commit and edit next / previous cell
EscCancel edit
Home / EndFirst / last column (Ctrl+ → first / last row)
PageUp / PageDownPrevious / next page
SpaceToggle row selection