Formatting Cell Values
Formatters, getters/setters, computed columns
By default a cell shows the raw value of its field. Often you want something nicer — $49.99 instead of 49.99, Mar 5, 2024 instead of a timestamp, or a value computed from several fields. This chapter covers the display.formatter and value groups.
The key distinction in this chapter: "value" vs "display".
- The value is the real data stored on the row — the number
49.99, a timestamp, a boolean.- The display is the text the user reads in the cell —
"$49.99","Mar 5, 2024","Active".A
formatterchanges only the display. The value underneath is untouched, so sorting, filtering and export still work on the real number/date. Keep this separation in mind and the rest of the chapter is straightforward.
formatter — change how a value is displayed
formatter receives the cell value and the whole row, and returns the string to display. It only affects display — the underlying data is unchanged. The function runs for each cell as it is drawn.
toLocaleString()/toLocaleDateString()(used in the examples below) are built-in JavaScript methods that format numbers and dates according to the user's locale — adding thousands separators (50,000) or local date styles. They are standard JS, not part of EliteGrid.
Currency
{
field: 'salary',
header: 'Salary',
display: {
formatter: (value) => `$${Number(value).toLocaleString()}`,
},
}
// 50000 → "$50,000"
Dates
{
field: 'joinDate',
header: 'Joined',
display: {
formatter: (value) =>
value ? new Date(value as string).toLocaleDateString() : '—',
},
}
Booleans as friendly text
{
field: 'active',
header: 'Status',
display: {
formatter: (value) => (value ? '✓ Active' : '✗ Inactive'),
},
}
Using other fields in the format
Because the formatter also gets the row, you can combine fields:
{
field: 'firstName',
header: 'Full Name',
display: {
formatter: (value, row) => `${row.firstName} ${row.lastName}`,
},
}
exportFormatter — a different format for CSV
Sometimes the pretty display format is bad for spreadsheets (e.g. $50,000 breaks number columns in Excel). Provide a separate exportFormatter used only when exporting to CSV:
{
field: 'salary',
header: 'Salary',
display: {
formatter: (value) => `$${Number(value).toLocaleString()}`, // on screen
exportFormatter: (value) => String(value), // in CSV → 50000
},
}
The value group — custom data access
So far we've assumed each column maps to a simple top-level key like row.salary. The value group is for the two cases where that isn't true:
- Nested data — the value lives several levels deep, e.g.
row.user.profile.name. - Computed columns — there is no stored field at all; the value is calculated from others, e.g.
price × quantity.
A getter is a function that reads a value out of a row. A setter is a function that writes one back in. You only need a setter if the column is also editable.
| Property | Type | Meaning |
|---|---|---|
getter |
(row) => unknown |
Read the value (for nested or computed data) |
setter |
(row, value) => TData |
Write the value back (for nested edits) |
Reading nested data with getter
If your row looks like { user: { profile: { name: 'Ada' } } }:
{
field: 'name', // still needs a unique field id
header: 'Name',
value: {
getter: (row) => row.user.profile.name,
},
}
Computed columns
A column that doesn't map to a stored field at all — e.g. a full name or a total:
{
field: 'total',
header: 'Total',
value: {
getter: (row) => row.price * row.quantity,
},
display: {
formatter: (value) => `$${Number(value).toFixed(2)}`,
},
}
Writing back nested data with setter
When an editable column reads nested data, give it a setter so edits land in the right place. The setter must return a brand-new row object with the change applied — it should not modify the existing one in place. That is what the ... (spread) syntax below does: it copies every existing property, then overrides the one that changed. This "copy, don't mutate" rule (called immutability) is how the grid reliably detects that a row changed and re-renders only what's needed.
{
field: 'name',
header: 'Name',
value: {
getter: (row) => row.user.profile.name,
setter: (row, value) => ({
...row,
user: {
...row.user,
profile: { ...row.user.profile, name: value as string },
},
}),
},
edit: { enabled: true, type: 'text' },
}
Pairing formatter with cellClass
display also has a cellClass option for conditional styling (colour-coding a status, highlighting an outlier value) — covered in Chapter 02. It's independent of formatter, so the two are commonly used together on the same column: formatter decides what text is shown, cellClass decides how it looks.
{
field: 'status',
header: 'Status',
display: {
formatter: (value) => (value ? 'Active' : 'Inactive'),
cellClass: (value) => (value ? 'cell-active' : 'cell-inactive'),
},
}
Changing one never affects the other — restyle a column without touching its text, or reword it without touching its colours.
How the pieces fit together
For one cell, the grid runs these in order:
value.getter(or the rawfield) → gets the underlying valuedisplay.formatter→ turns it into display textdisplay.cellClass→ decides which CSS class(es) the cell gets, independently of the text- On edit commit:
edit.parser→ converts the typed string back to data, thenvalue.setter(or the field) → stores it - On CSV export:
display.exportFormatter(falls back toformatter)