valueFormatter and valueParser
valueFormatter turns a value into the text shown on screen, in search, tooltips, copy and exports; valueParser turns typed or pasted text back into a value.
Live demoFormatter and parser
Prices with their row's currency; type "$ 1,200" back.
Code for this demo
import { createGrid } from '@freegridjs/core';
import { makeRows } from '../data';
import { button, separator, text, tr, type DemoContext } from '../ui';
const CURRENCIES = ['KRW', 'USD', 'JPY', 'EUR'];
export default function mount(ctx: DemoContext) {
const h = (en: string, ko: string) => tr(ctx.lang, { en, ko });
const STATUS: Record<string, string> = { A: h('Active', '진행'), P: h('Paused', '보류'), X: h('Closed', '종료') };
const data = makeRows(300).map((r, i) => ({ id: r.id, name: r.name, price: r.q1 / 10, currency: CURRENCIES[i % 4], code: 'APX'[i % 3], size: (i * 7919) % 5_000_000 }));
const grid = createGrid(ctx.host, {
columns: [
{ field: 'name', header: h('Name', '이름'), width: 160 },
{
field: 'price', header: h('Price', '가격'), type: 'number', format: '#,##0.00', width: 150, editable: true,
// the row's currency after the number
valueFormatter: p => `${p.formatted} ${p.row?.currency ?? ''}`.trim(),
// '$ 1,200', '1200원' → 1200; no digits → refused
valueParser: p => {
const digits = p.text.replace(/[^0-9.-]/g, '');
return /[0-9]/.test(digits) ? Number(digits) : undefined;
},
},
{ field: 'code', header: h('Status (code)', '상태(코드)'), width: 120, valueFormatter: p => STATUS[String(p.value)] ?? String(p.value ?? '') },
{ field: 'size', header: h('File size', '파일 크기'), type: 'number', width: 110, valueFormatter: p => (Number(p.value) >= 1e6 ? `${(Number(p.value) / 1e6).toFixed(1)} MB` : `${Math.round(Number(p.value) / 1e3)} KB`) },
],
data, locale: ctx.lang,
});
grid.on('cellValueChanged', e => ctx.log(`price stored as ${JSON.stringify(e.newValue)}`));
grid.on('validationFailed', e => ctx.log(`refused "${String(e.value ?? '')}"`));
const box = document.createElement('input');
box.type = 'search';
box.placeholder = h('Quick search: paused, usd, mb', '빠른 검색: 보류, usd, mb');
box.addEventListener('input', () => { grid.setQuickFilter(box.value); ctx.log(`"${box.value}" → ${grid.getDisplayedRowCount()} rows`); });
ctx.controls.append(box);
separator(ctx);
button(ctx, 'CSV', () => grid.exportCsv('formatted.csv'));
text(ctx, h('Type "$ 7,500" or "7500원" into a price; copy a price and paste it into another. The rows keep codes and plain numbers; sorting uses them.',
'가격에 "$ 7,500"이나 "7500원"을 입력하거나, 가격을 복사해 다른 칸에 붙여넣어 보세요. 행에는 코드와 숫자가 그대로 있고 정렬은 그 값을 씁니다.'));
return grid;
}format covers the usual number and date patterns. When the text depends on your own rules or on other fields of the row — a currency from the row, a code shown as a label, a size in KB or MB — give the column a valueFormatter. It returns the text; the value in the row stays as it is.
const STATUS = { A: 'Active', P: 'Paused', X: 'Closed' };
columns: [
{ field: 'status', valueFormatter: p => STATUS[p.value] ?? String(p.value ?? '') },
{
field: 'price', type: 'number', format: '#,##0.00',
// formatted is what format would show; the row gives the currency
valueFormatter: p => `${p.formatted} ${p.row?.currency ?? ''}`,
},
{ field: 'size', type: 'number', valueFormatter: p => (p.value >= 1e6 ? (p.value / 1e6).toFixed(1) + ' MB' : Math.round(p.value / 1e3) + ' KB') },
]| Name | Type | Description |
|---|---|---|
value | unknown | The cell value (after valueGetter or the nested field). |
formatted | string | What `format` and the locale would show; useful to add to it. |
row | Row | undefined | The row; absent where text is made without one (the set filter list, group and summary values). |
column | ColumnDef | The column definition. |
Where the text is used
| Place | Uses valueFormatter |
|---|---|
| Cells, tooltips, auto-size | yes |
| Quick search, find (Ctrl+F) | yes: "paused" finds P |
| Copy (Ctrl+C), CSV, print, PDF | yes |
| Excel | Text cells use it; number and date cells stay numbers and dates so Excel can add them up |
| Group rows and the summary | Sums and averages of the same type use it (without a row); counts and shares do not |
| Sorting and column filters | No: they compare the values, so 1,200 KRW sorts as 1200 |
valueParser: text back to a value
Once a cell shows "1,200 KRW", people type and paste text like that. valueParser turns it into the value to store. It runs for the text editor, paste, fill from text and file import. Return undefined to refuse the text: the editor stays open with the "invalid value" message, and a paste skips the cell. The column rules (required, validate, min/max) still run on the result.
{
field: 'price', type: 'number', editable: true,
valueFormatter: p => `${p.formatted} ${p.row?.currency ?? ''}`,
// '$ 7,500', '7500원', '7,500 USD' → 7500; text without digits is refused
valueParser: p => {
const digits = p.text.replace(/[^0-9.-]/g, '');
return /[0-9]/.test(digits) ? Number(digits) : undefined;
},
}| Name | Type | Description |
|---|---|---|
text | string | What was typed or pasted. |
row | Row | The row being edited (a new, empty row for lines that paste adds). |
oldValue | unknown | The value before. |
column | ColumnDef | The column definition. |
Copy and paste back
With a valueFormatter, Ctrl+C copies the text the cell shows. Give the same column a valueParser so that pasting it back, here or in another grid, gives the value again. Without a valueFormatter the clipboard keeps raw numbers and ISO dates, as before.
Editors with their own list (select, combo, radio, multiSelect) already show option labels and read them back; valueParser, when given, takes over the pasted text for them too. A valueFormatter that throws is reported once in the console and the plain text is shown.
Questions about this page
How do I show a code as a label, like A as Active, without changing the data?
Give the column valueFormatter: p => LABELS[p.value]. The cell, quick search, find, the tooltip, CSV and copy use the label; the row keeps 'A'. For a fixed list you can also use a select editor, whose option labels are shown the same way.
Why use valueFormatter instead of a renderer?
A renderer only draws the cell. valueFormatter gives text, so the same text is searched, found, copied, exported and measured for auto-size, and it keeps working with grouping and the summary.