@egi/smart-table
v0.4.0
Published
Model-backed React data table with editing, search, selection, validation, and persistence
Maintainers
Readme
SmartTable Guide
SmartTable is a reusable model-backed React table built on PrimeReact's
DataTable. It loads rows through the configured environment, formats values
through the package's local SmartTools, and provides shared search, editing, selection,
validation, save, delete, refresh, and print surfaces.
Read this guide before adding a table or changing SmartTable behavior.
Basic Structure
Define columns and the table definition in the owning class component:
private buildColumns(): ColumnDefinition[] {
return [
{field: "code", header: this.props.t("resources.code"), type: "text", readonly: true, sortable: true, width: 185},
{field: "active", header: this.props.t("common.active"), type: "checkbox", readonly: true, width: 73}
];
}
private buildTableDef(): TableDefinition {
return {
operations: "read-only",
reloadButton: true,
search: true,
source: {model: ResourceModel, params: {orderBy: ["code"]}},
title: this.props.t("resources.title")
};
}Render it inside a container with a defined or flex-derived height:
<SmartTable
ref={this.tableRef}
columns={this.buildColumns()}
tableDef={this.buildTableDef()}
/>The source may use a generated model class or plain row objects. SmartTable
obtains the row key from source.primaryKey, then model metadata, falling back
to pk. Keep that primary key stable for the table's lifetime.
Table Capabilities
operations is required and declares the mutation UI SmartTable may expose:
"read-only", "all", one of "create", "update", or "delete", or an
array containing the required mutable operations. The special values are never
combined in an array. This is a component capability contract, not an access
check; the consumer derives it from its own policy.
The remaining table options use positive names. Omit a feature to keep it off:
search,rowCounter,rowCheckbox, and text-selection options expose standard table facilities.saveButton,cancelButton,printButton, andreloadButtonexpose standard controls. Strings customize the visible Save/Cancel label or the icon-only Print/Reload tooltip.onCreate,onSave,onPrint,onReload,onRowChange,onUpdateRows, andonSelectionChangesupply behavior independently of control visibility.topBarButtonsandbottomBarButtonsadd application-specific actions.scrollModeselects"none","standard", or"virtual"scrolling.source.windowopts a read-only virtual table into bounded server-backed ranges; omitting it preserves eager loading.columnResizeModeselects"none","fit", or"expand";columnToggleButtonexposes the visibility chooser.viewStatepersists field-keyed widths, hidden columns, and sorting in browser storage or through an application-owned async adapter.
Public refs use the SmartTableApi interface. Its save(), print(), and
reload() methods execute the same configured action as the corresponding
standard button. A custom onSave, onPrint, or onReload replaces the
built-in action and must not call the same public method recursively.
TableDefinition attribute reference
| Attribute | Required | Default | Description |
|-----------|----------|---------|-------------|
| afterLoad | No | — | Runs after loaded and transformed rows have entered table state; receives the rows and SmartTableApi. |
| afterSave | No | — | Runs after the built-in table-owned save succeeds. |
| beforeSave | No | — | Async save guard receiving changed rows, deleted rows, and the API; return false to cancel saving. |
| bottomBarButtons | No | [] | Custom actions rendered in their supplied order from right to left in the bottom toolbar. Their presence creates that toolbar. |
| cancelButton | No | false | Shows built-in Cancel when truthy. true uses the standard label; a string supplies the label. |
| columnResizeMode | No | "none" | Controls column resizing. "fit" transfers width to the next visible column; "expand" grows the scrollable table. |
| columnToggleButton | No | false | Shows the icon-only visibility chooser. true uses the localized label; a string supplies it. |
| onCreate | No | — | Handles the Create button, for example by opening a modal dialog. Requires the create operation. |
| dateConfigYear | No | Current year | Reserved for abbreviated date expansion, which is currently disabled in SmartTable editors. |
| defaultSortField | No | — | Initial client-side sort field, or initial server sort in windowed mode. Omit it for manual row-order tables. |
| defaultSortOrder | No | — | Initial client-side order: 1 ascending or -1 descending. |
| emptyMessage | No | Localized standard text | Message displayed when no rows are available. |
| headerContent | No | — | Additional React content displayed in the title bar. |
| inputRowUpdate | No | — | Async hook for adjusting the standalone inline insert row after a field update and optionally requesting the next focus field. |
| insertLine | No | false | Enables SmartTable's inline insert row. Use onCreate instead when creation belongs in a separate workflow. Requires the create operation. |
| localization | No | {} | Plain translation-key/message overrides for this table. These take precedence over environment and built-in messages. |
| onDeleteRows | No | Built-in model deletion | Replaces deletion persistence for staged saves and confirmed immediate deletion. |
| onPrint | No | Table-only browser print workflow | Replaces the built-in print action. |
| onReload | No | Reload from source | Replaces the built-in reload action. |
| onRowChange | No | — | Receives the updated row, changed field, and details containing the primary key, old/new values, resulting dirty state, and table API after an actual committed native cell or checkbox change. |
| onSave | No | Built-in bulk save | Replaces the built-in save action. |
| onSelectionChange | No | — | Receives the selected row array and API after row selection changes. |
| onUpdateRows | No | Built-in model update | Replaces persistence for changed rows while retaining validation, Save/Cancel state, inserts, deletes, beforeSave, afterSave, error handling, and reload behavior. |
| operations | Yes | — | Neutral mutation capabilities: "read-only", "all", one mutable operation, or an array of mutable operations. |
| postLoad | No | Identity | Pure transformation applied to plain loaded rows before they enter table state. |
| printButton | No | false | Shows icon-only Print in the top toolbar when truthy. A string supplies its tooltip. |
| reloadButton | No | false | Shows icon-only Reload in the top toolbar when truthy. A string supplies its tooltip. |
| recordLabels | No | {singular: "Record", plural: "Records"} | Supplies neutral record terminology. An omitted plural appends s only for English; other locales reuse the singular label. |
| rowCheckbox | No | false | Shows the row-selection checkbox column for multiple selection. It does not represent business data. |
| rowCounter | No | false | Shows the filtered row count in the bottom toolbar when present, otherwise in the top toolbar. |
| rowOrder | No | — | Configures persisted manual row ordering and optional tri-state column sorting. |
| saveButton | No | false | Shows Save in the bottom toolbar when truthy. A string supplies its label. With delete enabled and Save hidden, deletion is confirmed and persisted immediately instead of becoming pending. |
| scrollMode | No | "standard" | Selects "none", "standard", or "virtual" DataTable scrolling. |
| search | No | false | true enables client search; a non-empty field tuple enables debounced server search. |
| searchCaseSensitive | No | false | Makes search-field matching distinguish letter case. |
| selectionMode | No | — | Enables "single" or "multiple" row selection. Multiple selection normally uses rowCheckbox. |
| sortMode | No | PrimeReact default | Selects "single" or "multiple" column sorting. Row ordering requires single sorting. |
| source | Yes | — | Optional generated model, primary-key override, read parameters, and opt-in server-backed window configuration. |
| textSelection | No | false | Enables browser text selection inside table contents initially. Inputs remain selectable even when false; with the update operation, row double-click enters edit mode. |
| textSelectionButton | No | false | Shows the icon-only top-toolbar text-selection toggle. A string supplies its tooltip. |
| title | Yes | — | Localized table title. |
| titleSubtext | No | — | Secondary React content displayed below the title. |
| topBarButtons | No | [] | Custom actions rendered at the start of the top toolbar's right-hand group. |
| viewState | No | — | Persists widths, visibility, and single/multiple sorting. Storage defaults to local browser storage or can be an async external adapter. |
Column Types
Use the native type whenever it matches the data. Native types provide shared formatting, editing, validation, alignment, filtering, and accessibility.
| Type | Intended use | Important behavior |
|------|--------------|--------------------|
| text | Strings and read-only display fields | Uses a text editor when mutable. |
| number | Decimal numbers | Right-aligned, localized formatting and numeric validation. |
| int | Safe JavaScript integers | Right-aligned; rejects fractions, trailing text, and values outside the safe-integer range. |
| decimal | Exact fixed-scale decimals | Stores canonical strings and uses SmartDB normalization without conversion to JavaScript number. Plain rows require value.scale. |
| bigint | Exact integral values | Stores canonical strings and sorts them numerically without conversion to JavaScript number. |
| date | Calendar dates | Supports JavaScript Date, canonical ISO-date strings, and SmartDB PlainDate. Generated PlainDate metadata is inferred. Set editorControl: "picker" to opt into a calendar picker. |
| datetime | Date/time values | Uses local SmartTools formatting and display-format validation with relaxed day, month, and year widths. Set editorControl: "picker" to opt into a calendar and 24-hour time picker with seconds. |
| dropdown | Values from a droplist/domain | Requires source; uses shared droplist loading and labels. |
| choice | Typed finite choices | Uses value.options or a droplist source and preserves option value types. |
| autocomplete | Query-backed suggestions | Requires autoComplete. |
| reference | Stable typed identifiers with async labels | Uses a row-scoped value.query, optional initial resolve, request cancellation, and stale-result protection. |
| checkbox | Boolean values | Centered automatically; supports read-only, table-owned, and controlled modes. |
| action | Row action buttons | Configure actionButtons; normally set noPrint: true. Action columns are not listed in the visibility chooser. |
Do not render an <input>, checkbox, dropdown, date field, or action button in
render when a native column type already supports it.
render takes precedence over the native cell renderer. Reserve it for a
presentation the table cannot express, such as combining a code and description
in one display cell. A custom renderer owns its accessibility and interaction
behavior and should therefore be the exception.
ColumnDefinition attribute reference
| Attribute | Required | Default | Description |
|-----------|----------|---------|-------------|
| actionButtons | For action | [] | Row actions rendered by an action column. Give standard actions a semantic type such as "edit" or "delete". |
| ariaLabel | No | — | Produces a row-specific accessible label, especially for native checkboxes. |
| autoComplete | For autocomplete | — | Async suggestion provider receiving field, row, query, and an optional abort signal. Calls use a 150 ms trailing debounce; the loading indicator follows the active promise. |
| bodyClassName | No | — | CSS class or per-row callback forwarded to PrimeReact Column.bodyClassName. |
| bodyStyle | No | — | Fixed style object forwarded to PrimeReact Column.bodyStyle. |
| controlled | No | false | Declares that the parent owns this column's value and persistence. Commonly paired with getValue and onBlur. |
| disabled | No | false | Row-specific predicate that disables an interactive native checkbox. |
| editorControl | No | "input" | Chooses the native editing control. "picker" currently enables PrimeReact Calendar for date and datetime; the control union can grow with future column editors. |
| field | Yes | — | Unique row property name used for values, editing, validation, and sorting. |
| getValue | No | Direct row field | Derives the displayed, searched, or checkbox value without mutating the row. |
| header | Yes | — | Localized column header. |
| hideZero | No | false | Displays numeric zero as an empty value. |
| hidden | No | false | Hides the column initially. Hidden columns are absent from screen, print, and client search but remain in row data and payloads. |
| hideable | No | true | Controls whether the visibility chooser can hide or restore the column. |
| ignoreGlobalFilter | No | false | Excludes the column from client-side table search. |
| filterValue | No | Raw row value | Supplies a typed client filter value independently of display. |
| mandatory | No | false | Requires a non-empty value during inline insert and editing validation. |
| minWidth | No | 80 | Minimum width in pixels for a flexible column. |
| noPrint | No | false | Excludes the column from printed output. |
| numberFormat | No | "1.2-2" | Angular-style number pattern forwarded to SmartTools.formatNumber. |
| onBlur | No | — | Receives committed edits and native checkbox changes. |
| printWidth | No | — | Reserved compatibility attribute; current SmartTable printing does not consume it. |
| presentation | No | Plain text | Adds a storage-neutral badge or protocol-checked link; screen and print share the same content. |
| readonly | No | false | Prevents editing. Read-only checkboxes render as a tick rather than an interactive control. |
| resizable | No | true | Controls whether the column exposes its own resize handle when table resizing is enabled. |
| render | No | Native renderer | Custom cell renderer. It overrides the native renderer and owns overflow and accessibility behavior. |
| searchValue | No | Formatted display text | Supplies searchable text independently of storage and rendering. |
| serverFilterField | No | field | Maps a declared server-search field to the field understood by the adapter/server. |
| serverSortField | No | field | Declares the corresponding server-side sort field for lazy/server integrations. |
| sortValue | No | Raw row value | Supplies a typed client sort value; exact numerics use value-correct comparison. |
| sortable | No | false | Enables the PrimeReact header sort control for this field. |
| source | For dropdown | — | Droplist definition or domain code used to resolve dropdown values and labels. |
| type | Yes | — | Native column type, including exact decimal/bigint, typed choice, and async reference in addition to the legacy types. |
| value | No | Inferred from type or generated model metadata | Declares numeric storage/rounding/format, explicit temporal storage, typed choice identity, or reference lookup semantics. |
| validators | No | — | Reserved compatibility attribute; current SmartTable validation does not consume custom validators. |
| width | No | Flexible | Fixed width in pixels. |
| wrap | No | "auto" | Selects "auto", "nowrap", "word", or "anywhere" wrapping for standard value cells. |
Detailed Column Behavior
Exact numbers
Generated SmartDB metadata is authoritative. When the source model marks an
attribute with losslessNumberType: "decimal" | "bigint", SmartTable enables
exact semantics even when the compatibility column still says type: "number".
Marked values are normalized on load and after each accepted edit, remain strings
in row state and Save payloads, and are never passed through Number, parseFloat,
or parseInt. Conflicting explicit kind, precision, or scale is reported as a
column configuration error.
Semantic decimal and bigint columns also accept matching calculation objects,
such as SmartDB's Decimal, from getValue and inputRowUpdate. Cell values are
normalized before display. Changed insert-row values returned by
inputRowUpdate are localized for the editor and converted back to canonical
strings when the row is committed.
For plain rows, declare the same contract explicitly:
{
field: "amount",
header: "Amount",
type: "decimal",
value: {
format: {currency: "CHF", style: "currency"},
kind: "decimal",
precision: 20,
rounding: DecimalRoundingMode.ROUND_HALF_UP,
scale: 2
}
}All decimal.js-compatible SmartDB rounding modes are supported. Percent display
must declare whether storage is a fraction or percentage points. null and
undefined remain distinct. Ordinary number columns retain their compatibility
behavior, and primary keys remain finite safe JavaScript integers.
SmartTable imports the browser-safe SmartDB normalizers and does not depend on
decimal.js. Applications only install SmartDB's optional decimal peer when they
actually import generated decimal models or use Decimal calculation getters.
Temporal, choice, and reference semantics
Date-only columns support three storage modes:
"date"keeps the legacy JavaScriptDaterepresentation."iso-date"keeps a canonicalYYYY-MM-DDstring."plain-date"keeps a SmartDBPlainDateobject and emitsYYYY-MM-DDat JSON boundaries.
When a generated source model marks an attribute as type: "PlainDate",
SmartTable infers the third mode; duplicating column.value is unnecessary and
conflicting storage declarations are reported as configuration errors. A
model-less source opts in explicitly with
value: {kind: "date-only", storage: "plain-date"}. Canonical strings arriving
from plain or projected reads are revived through SmartDB's strict
plainDate.from contract; timestamp strings and JavaScript Date values are
not accepted as implicit PlainDate conversions.
Both generated and explicit PlainDate columns can use editorControl: "picker".
PrimeReact's temporary Date is only a UI carrier: SmartTable copies local
year/month/day components in both directions, so it never converts the calendar
day through an instant or timezone. SmartTable reaches the PlainDate runtime
through a lazy boundary, so SmartTable alone does not put the Temporal fallback
in an ordinary consumer's initial chunk. Importing SmartDB's root entry or a
generated model can still include that fallback according to SmartDB's own
browser bundle contract.
ISO-date storage is also validated and formatted without timezone conversion.
Date-time storage must
choose "instant" (an ISO value with Z or an offset), "local" (a wall-time
value without an offset), or legacy "date". String temporal storage cannot use
the Date-valued picker editor.
Typed choices compare option values with identity semantics and never stringify
numeric values. Set searchBy and sortBy to "label" when localized labels are
the intended user-facing meaning. Unknown/inactive values remain visible as their
stored value.
References persist only SmartTableValueOption.value; labels are independent.
Lookup context includes row identity, field, query, result limit, and an abort
signal. Results are keyed per row and field, and a superseded generation cannot
replace a newer one. resolve supplies labels for initially loaded identifiers;
unresolved or inaccessible identifiers remain visibly represented by the value.
fieldmust be unique within the table. It identifies the value used by the editor and PrimeReact sorting.headermust use an i18n-backed label.readonlyprevents editing. A read-only checkbox is displayed as a tick, not as an interactive disabled checkbox.sortableenables the header sort control.widthfixes the width in pixels. Withoutwidth, the column remains flexible and receives an implicit80pxminimum width; setminWidthto override that minimum.mandatoryparticipates in insert/edit validation.getValuederives the displayed, searched, or checkbox value without changing the source row.ignoreGlobalFilterexcludes a column from client-side global search.noPrintexcludes a column from printing.hiddensupplies initial visibility.hideable: falsemakes that state fixed for the mounted table;hidden: true, hideable: falseis a valid permanently hidden data field when another data column remains visible.resizable: falseremoves the column's own handle. In"fit"mode PrimeReact can still change it as the next sibling of a dragged column.wrapselects a standard value cell's wrapping mode:"auto","nowrap","word", or"anywhere". Omit it or use"auto"for the type/width-based defaults."nowrap"uses an ellipsis and supplies the complete formatted value as hover text only while clipping is present."word"prefers word boundaries but breaks an uninterrupted token when necessary."anywhere"permits a break between any characters.ariaLabelsupplies a row-specific accessible label for native checkboxes.disabledcontrols whether a native checkbox is interactive for a row.onBlurreceives committed edits and checkbox changes.bodyStyleforwards a fixedReact.CSSPropertiesobject to PrimeReact'sColumn.bodyStyle, for example{backgroundColor: "var(--surface-100)"}.bodyClassNameforwards either a CSS class string or PrimeReact's per-row(data, options) => stringcallback toColumn.bodyClassName. Prefer classes for conditional field backgrounds so hover, selection, and theme states can be handled in local SCSS.
getValue does not replace PrimeReact's sort field. Sorting still uses field,
so a sortable column should normally reference a real sortable row property.
Column resizing and visibility
Both features require the ordinary DataTable layout and are rejected when
insertLine is enabled because the standalone insert row uses a separate flex
layout. Invalid configuration is reported in the table status area and the
requested feature stays disabled.
columnResizeMode: "fit" keeps total table width stable by resizing the dragged
column and its next visible neighbor. The final visible boundary has no handle.
"expand" changes only the dragged column and grows the table into horizontal
scrolling. Width changes are captured after drag end and survive table state
updates, data reloads, and visibility changes for the mounted component.
columnToggleButton displays a keyboard-accessible chooser with a select-all
checkbox. Checking it restores every chooser column; clearing it hides every
hideable column while retaining at least one visible data column. Columns with
hideable: false remain visible. Widths are retained by field when columns are
hidden or restored. Hidden columns remain available to formatting, validation,
dirty tracking, saves, and explicitly configured server search, but client-side
search cannot match text that the user cannot see. Sorting is preserved when its
column is hidden. If that field controls optional manual row sorting, row dragging
remains unavailable until the column is restored.
Persisted view state
Persistence is opt-in and owned by SmartTable rather than PrimeReact's opaque
stateStorage payload. This keeps widths associated with column fields and includes
the visibility state that SmartTable owns itself:
const tableDef: TableDefinition = {
// ...ordinary required options
viewState: {
key: "delivery-board",
storage: "local", // "local" is also the default; "session" is available
saveDebounceMs: 250
}
};The JSON-safe SmartTableViewState payload has version: 1, columnWidths,
hiddenColumnFields, and optional single- or multi-sort metadata. Unknown versions,
unknown fields, invalid widths, non-sortable fields, and configurations that would
hide every column are ignored safely. Restoring an async value participates in the
standard DataTable loading overlay. Save operations are debounced and serialized so
an older request cannot overwrite a newer preference.
For per-user persistence in an external database, provide one stable adapter object. The server should derive the user from its authenticated session instead of trusting a client-supplied user identifier:
const userTablePreferences: SmartTableViewStateAdapter = {
load: async (key) => {
const response = await fetch(`/api/table-preferences/${encodeURIComponent(key)}`);
return response.status === 404 ? undefined : response.json();
},
save: async (key, state) => {
await fetch(`/api/table-preferences/${encodeURIComponent(key)}`, {
body: JSON.stringify(state),
headers: {"content-type": "application/json"},
method: "PUT"
});
},
remove: async (key) => {
await fetch(`/api/table-preferences/${encodeURIComponent(key)}`, {method: "DELETE"});
}
};
const tableDef: TableDefinition = {
// ...ordinary required options
viewState: {key: "delivery-board", storage: userTablePreferences}
};The adapter receives the logical table key; user scoping, transport, authorization,
and database schema remain application concerns. Change the key when the logical table
or authenticated preference scope changes. Late responses from an earlier key are
discarded. SmartTableApi.resetViewState() restores configured defaults and calls the
adapter's optional remove; without remove, it stores the default payload instead.
Load/save failures are reported in the table status area without preventing row data
from loading or being edited.
Mutation Ownership
Authorization Boundary
SmartTable must remain standalone from application authorization. Never import
or call session, resource, role, or access-check services in this component.
The consumer evaluates permissions and supplies neutral capabilities such as
operations, mutable or read-only columns, rowOrder, onCreate, and the
standard button options.
These options control what the UI exposes; they are not security checks. Every mutation must still be authorized by the server. This separation keeps SmartTable independent of application-specific permission models and prevents the shared component from acquiring feature-specific policy.
Every mutable value must have exactly one state owner.
SmartTable-owned inline editing
Use ordinary mutable columns when SmartTable should track dirty rows and save
them through the configured data adapter. Include "update" in operations and set
saveButton: true and usually cancelButton: true. The table owns the edited
row copies and Save/Cancel lifecycle.
Use beforeSave, afterSave, onUpdateRows, and onDeleteRows for focused
behavior around that lifecycle. onUpdateRows(rows, table) replaces persistence
only for existing changed rows; SmartTable still validates, runs the save guard,
persists inserts and unhandled deletes, clears pending state after success, runs
afterSave, and reloads. A rejected hook keeps the changes staged. Use onSave
instead when one application-owned operation must persist every row category
atomically. Call methods such as markDirty, deleteRow, or save through a
SmartTableApi ref when external operations genuinely need to participate in
the same table-owned edit session.
Use onRowChange(row, field, details) to observe committed native cell and
checkbox changes without replacing SmartTable's state ownership. The first
argument is the updated row record, so consumers can inspect dependent fields
without another lookup; details.rowId also exposes its configured primary key.
The details include value, previousValue, dirty, and table. Loads,
refreshes, row ordering, and imperative dirty-state methods do not emit this
hook.
Use markDirtyRows(rowIds) and clearDirtyRows(rowIds) when an external
workflow only needs to change which current persisted rows participate in the
next built-in save. These methods do not alter row values or the separate state
for inserted and deleted rows. Use markDirty(rows) instead when external row
values also need to be merged into the table.
After an application-owned operation persists existing rows, call
refreshRows(rowIds) to read only those primary keys through the configured
data adapter and replace them without disturbing table order or marking them
dirty. SmartTable keeps unrelated row references stable and adopts each returned
row as the new committed baseline. The adapter must support array values in
where as an IN predicate.
Dialog- or parent-owned editing
Use this mode when a surrounding form owns draft state and persists it with its
own Save action. Expose only the required neutral operations, omit
saveButton, and use controlled columns so the table does not expose a
competing persistence workflow.
When this mode includes the delete operation, SmartTable confirms each delete
request and persists the requested rows immediately. It does not add those rows
to the table's dirty state.
For a native checkbox whose value comes from parent state, set controlled: true
and provide getValue plus onBlur:
{
field: "assigned",
header: t("permissions.assigned"),
type: "checkbox",
controlled: true,
getValue: (_field, row) => this.state.assignedPks.includes(row.pk),
disabled: (_field, row) => !this.canAssign(row),
ariaLabel: (_field, row) => t("permissions.assignItem", {item: row.code}),
onBlur: (_field, checked, row) => this.toggleAssign(row, checked)
}A controlled checkbox delegates the update to its owner and does not mutate the table's row copy. Tables containing controlled columns disable PrimeReact cell memoization so parent-driven values and disabled states cannot become stale.
Use functional setState updates in the owning class when one checkbox update
depends on existing arrays, maps, or related flags. This prevents rapid events
from overwriting one another.
Never combine parent-derived getValue with an uncontrolled mutable checkbox.
That creates two sources of truth and can produce stale or cross-influenced
checkbox states.
Read-only Lists and Dialog CRUD
For a read-only list whose create/edit operations use dialogs:
- Mark display columns
readonly: true. - Set
operations: "read-only", oroperations: "create"when the table owns a dialog-launching create action. - Provide
onCreateto open the create dialog. - Use an
actioncolumn for edit/delete commands. - Call
reload()on the table ref after successful persistence.
Use insertLine for inline creation, or onCreate when creation belongs in a
dialog or another parent-owned workflow.
Toolbars
Standard controls use positive boolean | string options: saveButton,
cancelButton, printButton, and reloadButton. Omitted or false means
hidden. Save and Cancel use visible localized labels; Print and Reload are
icon-only. A string customizes the corresponding label or tooltip. Callbacks are
configured separately with onSave, onPrint, and onReload.
The bottom toolbar is displayed when Save, Cancel, or bottomBarButtons are
visible. Print, Reload, and rowCounter use the bottom toolbar when it exists;
otherwise they appear on the right side of the top toolbar. topBarButtons
appear before the row count. Search always remains at the right edge, with
separators between the row count, the complete button group, and search. Buttons
within the same toolbar never have separators between them.
Browser text selection is disabled for table display content by default; text
inputs remain selectable for editing. Set textSelection: true to enable it.
Set textSelectionButton to show a toggle in the top toolbar. The toggle appears
immediately left of top-toolbar Reload without a separator; a string value is
used as its tooltip.
When text selection is disabled, a row double-click first looks for an action
button with type: "edit" and executes its handler. This supports dialog-owned
editing without declaring inline update capability. If no semantic Edit action
exists and operations includes "update", double-click enters edit mode for
the clicked editable cell. When text selection is enabled, double-click retains
normal browser selection behavior and does not invoke either edit path.
Search
Set search: true for client-side search across visible, non-action
columns. Search uses the formatted value, including getValue, and skips columns
with ignoreGlobalFilter: true. Matching is case-independent by default; set
searchCaseSensitive: true to opt into case-sensitive matching.
For server-side search, set search to a non-empty tuple of model field names,
for example search: ["cd", "description"]. Input is debounced and
ordinary eager loading adds a LIKE filter to the source query. Windowed mode
passes normalized {caseSensitive, fields, term} metadata to readRange() so
the adapter owns the database predicate and must honor the requested case
behavior. Do not use derived display-only fields unless the column supplies
serverFilterField.
Sorting
Set sortable: true on each sortable column. Use:
defaultSortFieldanddefaultSortOrderfor the initial client-side sort and sort indicator.sortMode: "single"orsortMode: "multiple"for the interaction mode.source.params.orderByfor deterministic API/database ordering.
For predictable initial results, keep defaultSortField consistent with the
leading orderBy field. Sorting is currently field-based; custom sort functions
are not exposed by SmartTable.
Server-backed Windowing
scrollMode: "virtual" alone keeps mounted DOM rows bounded but deliberately
retains the eager data contract. Add source.window only for a table whose
adapter can load globally correct absolute ranges and an exact matching total:
const tableDef: TableDefinition = {
defaultSortField: "title",
defaultSortOrder: 1,
operations: "read-only",
rowCounter: true,
scrollMode: "virtual",
search: ["title", "owner"],
selectionMode: "single",
source: {
model: TaskModel,
window: {pageSize: 100, retainedPages: 3}
},
title: "Tasks",
virtualRowHeight: 28
};pageSize is required and must be from 1 through 1,000. retainedPages
defaults to five nearby pages in addition to every page intersecting the current
rendered range. The logical sparse array preserves scroll height while only
loaded page rows and a small neighboring cache are retained as objects.
The environment adapter implements the separate optional operation below.
params are the application-owned base parameters; sort is deterministic and
always ends with the stable primary key; search is present only for a non-empty
debounced server search. The adapter must apply all of them to both rows and
total from the same logical snapshot.
readRange: async (model, request) => {
const result = await api.readPage(model, {
count: request.count,
first: request.first,
params: request.params,
search: request.search,
sort: request.sort,
signal: request.signal
});
return {rows: result.rows, total: result.total};
}SmartTable aligns requests to pages, coalesces overlap, aborts obsolete
generations, ignores stale results, and evicts distant pages. Explicit reload,
source identity, sort, and search changes reset to the first range;
refreshRows() invalidates the cache and refetches the visible range. The row
counter reports the adapter total, not the number of retained rows. getData()
returns only the currently retained rows in this mode. postLoad transforms one
loaded page at a time, and afterLoad runs after that page enters table state.
The first release intentionally rejects configurations whose correct semantics require the complete result:
| Capability | Windowed support |
|------------|------------------|
| Column visibility/resizing and persisted view state | Yes |
| Reload, row counter, text selection, row actions | Yes |
| Single-row selection by stable primary key | Yes |
| Global server search and server sorting | Yes |
| Create/update/delete, Save/Cancel, manual row ordering | No |
| Checkbox select-all or multiple-row selection | No |
| Built-in browser print | No; provide onPrint |
| Client search (search: true) or client sortValue | No |
Every sortable windowed column uses serverSortField ?? field. Choice/reference
label sorting or searching requires an explicit serverSortField or
serverFilterField; SmartTable never sorts or searches only the cached subset.
The demo adapter shows one SmartDbServer translation using validated
limit: {limit, offset}, orderBy, where, and a separate exact-count command.
Manual Row Ordering
Use rowOrder only when rows have a persisted numeric sequence field and the
table loads the complete ordering scope. The canonical source order must start
with that field ascending, the default client sort must be omitted, sorting must
be single-column, and virtual scrolling is not supported.
For table-owned editing, reordered rows join the normal dirty-row Save/Cancel lifecycle:
operations: "update",
saveButton: true,
cancelButton: true,
rowOrder: {field: "sortOrder", mode: "table", step: 10}For dialog-owned or transaction-specific workflows, use external persistence:
operations: "update",
rowOrder: {
field: "sortOrder",
mode: "external",
step: 10,
columnSort: {field: "cd"},
onPersist: (orderedRows, changedRows) => persistOrder(orderedRows, changedRows)
}External mode updates optimistically and restores the previous order when the
callback fails. Each successful reorder renumbers the complete scope to
step, 2 * step, 3 * step, .... The public static
SmartTable.renumberRows() method exposes the same deterministic calculation
for individual workflows and tests.
The unsorted DataTable view is canonical: it preserves the array order returned
by source.params.orderBy, so PrimeReact reorders the same array it displays.
Sorting any column disables dragging until removable sorting is cleared. Search,
invalid cells, pending inserts or deletes, loading, and external persistence also
disable dragging. Authorized users retain a dimmed handle with a localized
reason; consumers that omit rowOrder, including read-only views, show no handle.
Tables use their ordinary behavior unless rowOrder is configured.
Set rowOrder.columnSort.field to a sortable column when users should be able
to switch between manual order and conventional column order. The removable
single-sort cycle then has three explicit states: ascending, descending, and
manual row order. Manual state displays PrimeReact's standard unsorted
up/down-arrow icon with a dimmed treatment on the configured column. Leaving
manual order for ascending column sort requires confirmation by default, but
only when the current sequence differs from descending column order. Descending
is the state immediately before manual in the tri-state cycle, so a matching
sequence is known to be easily reproducible and does not need a warning;
set warnWhenLeavingManualOrder: false to suppress it. Switching directly
between ascending and descending does not warn. Each ascending or descending
transition renumbers and persists the rows through the configured row-order
owner. Returning to the third state removes the active sort marker; the rows
remain in the last persisted sequence and dragging is re-enabled when the other
row-order preconditions are satisfied.
SmartTable disables PrimeReact body-cell memoization for row-order tables so every move handle immediately reflects sort, search, loading, and persistence state changes. Consumers do not need to force row refreshes when switching between column and manual order.
Set rowOrder.reorderable: false only when an update-capable workflow should
retain persisted tri-state column ordering but deliberately omit row-drag
handles. It is not an authorization control: when the user lacks update
permission, omit rowOrder and do not mark sequence-changing columns sortable.
Omitting the option permits reordering.
Selection and Master/Detail Screens
Use selectionMode: "single" and onSelectionChange for a master/detail table.
The callback receives the selected rows array and table handle. Use
rowCheckbox for bulk row selection and getSelection/setSelection through
the ref when needed.
Selection is separate from boolean data columns. Do not use a row-selection checkbox to represent a persisted business flag.
Loading and Transformation
SmartTable reads source.model with source.params, converts model instances
to plain row objects, and then applies postLoad if supplied. Use postLoad for
small presentation-oriented row transformations, not as a second business-state
store. Use afterLoad for notifications or owner coordination after rows are in
table state.
When source.model is null or omitted, the data adapter returns plain objects
directly. Declare source.primaryKey when their stable key is not named pk.
Conversion Tools
Date and number conversion is intentionally local rather than part of the static
environment contract. SmartTable imports the package's tools singleton
directly. It defaults to dd.MM.yyyy, dd.MM.yyyy HH:mm:ss, and de-CH and can
be configured once at application startup:
import {tools} from "@egi/smart-table";
tools.configure({dateFormat: "yyyy-MM-dd", dateTimeFormat: "yyyy-MM-dd HH:mm:ss", locale: "en-CH"});Number columns use the optional localization adapter's current locale, or en
when no adapter is configured. SmartTable date shortcuts and partial-date
expansion are currently disabled, while their SmartBrowserTools implementation
remains available. Editors instead require complete date and datetime values
matching the configured display order and separators. Days and months may use
one or two digits; years may use two or four digits, with two-digit years placed
in the current century.
Date and datetime columns use ordinary text input by default. Opt into the
PrimeReact Calendar control per column with editorControl: "picker". Picker
selection returns a Date, while typed invalid text is retained so the same
field-exit and pre-save validation messages apply.
Layout, Localization, and Accessibility
- Place the table in a container with a usable height; the wrapper fills its parent and scrolls internally.
- Keep feature-specific dimensions and appearance in the owning screen's SCSS.
- Use
widthdeliberately for checkbox/action columns and translated headers; verify longer German labels are not clipped. - Choose date/time and identifier column widths based on the application's typography, localization, and density settings.
- All titles, headers, empty messages, actions, and accessibility labels must be i18n-backed.
- Native checkbox columns are centered automatically.
- Flexible text, dropdown, and autocomplete columns wrap at word boundaries
when possible and break an uninterrupted value at the available line width
only when necessary. Fixed-width columns and
number,int,date, anddatetimevalues are no-wrap by default and use an ellipsis plus conditional complete-value hover text. Override the default withwrap: "nowrap",wrap: "word", orwrap: "anywhere". Customrendercells own their own overflow behavior. - SmartTable uses a fixed table layout so flexible no-wrap columns shrink into the available table width without shrinking below their configured or implicit minimum. SmartTable applies the sum of all fixed/minimum column widths to the table itself, producing horizontal scrolling when that sum exceeds the viewport instead of collapsing later columns.
- Use
scrollMode: "virtual"only when the surrounding container supplies a measurable height."standard"is the default scrolling behavior and"none"disables DataTable scrolling. - Use
source.windowonly with a stable primary key, deterministic server order, and an adapter whose exact total matches its row predicate.
Current Limitations
SmartTable does not currently expose:
- A generic row-expansion API.
- A typed generic
ColumnDefinition<T>contract.
Extend the shared component before implementing one of these capabilities in a single screen.
Review Checklist
Before finishing a SmartTable change, verify:
- A native column type is used wherever available.
operationslists only UI capabilities that this consumer may expose; access services remain outside SmartTable.- Every mutable value has one state owner.
- Parent-owned checkboxes are marked
controlled. - Column headers and accessibility labels are translated.
- Sortable fields exist on the row and initial sorting matches
orderBy. - Row ordering, when configured, uses a complete canonical scope and exactly one persistence owner.
- Search includes only meaningful columns.
- The model primary key is stable.
- Create, delete, Save/Cancel, and dialog actions do not provide competing flows.
- The table container has sufficient height and translated headers are visible.
- Typecheck and the production client build pass.
