Select editor and cascading lists
A drop-down of fixed choices with labels, disabled options and a placeholder; options from the row make dependent lists, and dependsOn clears a child value that no longer fits.
Live demoSelect and cascading lists
Country → city: the city list follows the country and is cleared when it no longer fits.
Code for this demo
import { createGrid, type ColumnDef, type Row } from '@freegridjs/core';
import { makeRows } from '../data';
import { button, separator, text, tr, type DemoContext } from '../ui';
const CITIES: Record<string, string[]> = { Korea: ['Seoul', 'Busan'], Japan: ['Tokyo', 'Osaka'], France: ['Paris', 'Lyon'] };
const DISTRICTS: Record<string, string[]> = { Seoul: ['Gangnam', 'Mapo', 'Jongno'], Busan: ['Haeundae', 'Suyeong'], Tokyo: ['Shibuya', 'Shinjuku'], Osaka: ['Namba', 'Umeda'], Paris: ['Le Marais', 'Montmartre'], Lyon: ['Presqu’île'] };
export default function mount(ctx: DemoContext) {
const h = (en: string, ko: string) => tr(ctx.lang, { en, ko });
const countries = Object.keys(CITIES);
const data = makeRows(150).map((r, i) => {
const country = countries[i % 3];
const city = CITIES[country][i % 2] ?? CITIES[country][0];
return { ...r, country, city, district: i % 4 ? DISTRICTS[city][0] : null, status: ['open', 'hold', 'open'][i % 3] };
});
const grid = createGrid(ctx.host, {
columns: [
{ field: 'name', header: h('Name', '이름'), width: 140 },
{ field: 'status', header: h('Status', '상태'), width: 140, editable: true, editor: { type: 'select', options: [
{ value: 'open', label: h('Open', '진행') }, { value: 'hold', label: h('On hold', '보류') }, { value: 'closed', label: h('Closed (admin)', '종료(관리자)'), disabled: true }] } },
{ field: 'country', header: h('Country', '국가'), width: 120, editable: true, editor: { type: 'select', options: countries } },
{ field: 'city', header: h('City', '도시'), width: 120, editable: true, dependsOn: ['country'],
editor: { type: 'select', placeholder: h('City…', '도시 선택'), options: (row: Row) => CITIES[row.country] ?? [] } },
{ field: 'district', header: h('District', '구'), width: 140, editable: true, dependsOn: ['city'],
editor: { type: 'select', placeholder: h('District…', '구 선택'), options: (row: Row) => DISTRICTS[row.city] ?? [] } },
],
data, locale: ctx.lang,
});
grid.on('cellValueChanged', e => ctx.log(`#${e.row.id} ${e.colId}: ${String(e.oldValue)} → ${String(e.newValue)}`));
text(ctx, h('Change a country: a city that does not fit is cleared, and its district too. Ctrl+Z brings all back at once.',
'국가를 바꿔 보세요. 맞지 않는 도시와 그 구가 비워지고, Ctrl+Z 한 번으로 모두 돌아옵니다.'));
separator(ctx);
button(ctx, h('Undo', '실행 취소'), () => grid.undo());
return grid;
}editor: { type: 'select', options } opens the browser's own drop-down with fixed choices. Options can be plain values or { value, label, disabled }; the cell shows the label and stores the value. For long lists use the combo editor; for a few choices shown at once, the radio editor.
Basic use
{ field: 'status', editable: true, editor: {
type: 'select',
placeholder: 'Choose…', // an empty first choice, shown for blank cells
options: [
{ value: 'open', label: 'Open' },
{ value: 'hold', label: 'On hold' },
{ value: 'closed', label: 'Closed (admin only)', disabled: true }, // shown, cannot be chosen or pasted
],
} }Options
| Name | Type | Description |
|---|---|---|
options | SelectOption[] | (row) => SelectOption[] | The choices. A function of the row makes a list that depends on other fields of the row (it is called each time the editor opens). |
placeholder | string | Adds an empty first choice with this text. A blank cell opens on it (instead of on the first option), and choosing it saves null. |
dependsOn (column) | string[] | Fields this column's options come from. When one of them changes (edit, paste, fill), a value no longer in the options is cleared in the same undo step. |
Cascading lists (country → city → district)
const CITIES = { KR: ['Seoul', 'Busan'], JP: ['Tokyo', 'Osaka'] };
const DISTRICTS = { Seoul: ['Gangnam', 'Mapo'], Busan: ['Haeundae'], Tokyo: ['Shibuya'], Osaka: ['Namba'] };
const columns = [
{ field: 'country', editable: true, editor: { type: 'select', options: Object.keys(CITIES) } },
{ field: 'city', editable: true, dependsOn: ['country'],
editor: { type: 'select', placeholder: 'City…', options: row => CITIES[row.country] ?? [] } },
// clearing a city clears its district too: the chain follows on
{ field: 'district', editable: true, dependsOn: ['city'],
editor: { type: 'select', placeholder: 'District…', options: row => DISTRICTS[row.city] ?? [] } },
];- Changing the country to JP clears a Seoul city and its district; one Ctrl+Z brings all three back.
- A value that still fits stays: changing KR → KR (or to a country that also has the city) keeps it.
- Clearing follows paste and fill as well, so a pasted country column never leaves wrong cities behind.
- Without dependsOn the list still follows the row, but old values are kept — use that when the child may stay as is.
Paste and filters
- Pasting accepts a value or a label in any case; a value not in the options (or disabled) is skipped and counted.
- A select column gets a list filter (set filter) that shows the option labels in option order, in the column menu and the filter row.
Questions about this page
How do I make a city list that depends on the country?
Give the city column options: row => CITIES[row.country] and dependsOn: ['country']. When the country changes, a city that is not in the new list is cleared in the same undo step.