편집 규칙과 수식
셀 잠금, 시간 편집기, 고를 수 없는 옵션, 중복 값 검증, 셀 수식, 변경 취소 이벤트, 전체 검사.
이 데모의 코드
import { createGrid, type ColumnDef, type Row } from '@freegridjs/core';
import { button, separator, text, toggle, tr, type DemoContext } from '../ui';
export default function mount(ctx: DemoContext) {
const { lang } = ctx;
const h = (en: string, ko: string) => tr(lang, { en, ko });
const data: Row[] = Array.from({ length: 30 }, (_, i) => ({
sku: `SKU-${String(100 + i)}`,
name: [h('Pen', '펜'), h('Notebook', '노트'), h('Stapler', '스테이플러'), h('Marker', '마커'), h('Tape', '테이프')][i % 5],
qty: 1 + ((i * 7) % 12),
price: 2 + ((i * 13) % 40),
// a formula: the cell shows the result
total: i % 3 === 0 ? '=ROUND(qty*price*1.1, 0)' : '=qty*price',
pickup: ['09:30', '13:00', '16:45'][i % 3],
status: ['open', 'hold', 'closed'][i % 3],
locked: i % 4 === 3,
}));
// a duplicate to find with validate()
data[5].sku = data[2].sku;
const columns: ColumnDef[] = [
{ field: 'sku', header: 'SKU', width: 110, editable: true, unique: true, required: true },
{ field: 'name', header: h('Item', '품목'), width: 120, editable: true },
// locked rows cannot be edited
{ field: 'qty', header: h('Qty', '수량'), type: 'number', width: 90, editable: ({ row }) => !row.locked,
cellClass: ({ row }) => (row.locked ? 'demo-locked' : undefined) },
{ field: 'price', header: h('Price', '단가'), type: 'number', width: 90, editable: ({ row }) => !row.locked },
{ field: 'total', header: h('Total (formula)', '금액 (수식)'), type: 'number', format: '#,##0', width: 140, editable: true, formulas: true },
{ field: 'pickup', header: h('Pickup', '수령 시각'), width: 110, editable: true, editor: { type: 'time' } },
{ field: 'status', header: h('Status', '상태'), width: 120, editable: true,
editor: { type: 'select', options: [
{ value: 'open', label: h('Open', '진행') },
{ value: 'hold', label: h('On hold', '보류') },
{ value: 'closed', label: h('Closed (admin only)', '종료 (관리자 전용)'), disabled: true },
] } },
];
const grid = createGrid(ctx.host, { columns, data, locale: lang });
let blockNegative = true;
grid.on('beforeCellChange', e => {
if (blockNegative && typeof e.newValue === 'number' && e.newValue < 0) {
e.cancel();
ctx.log(h(`beforeCellChange → refused ${e.newValue} in ${e.colId}`, `beforeCellChange → ${e.colId}의 ${e.newValue} 거부`));
}
});
grid.on('cellValueChanged', e => ctx.log(`cellValueChanged → ${e.colId}: ${String(e.oldValue)} → ${String(e.newValue)}`));
grid.on('validationFailed', e => ctx.log(`validationFailed → ${e.colId}: ${e.message}`));
text(ctx, h('Gray rows are locked. Total holds formulas like =qty*price: edit it to see the formula. Closed is a disabled option.',
'회색 행은 수정할 수 없습니다. 금액 칸에는 =qty*price 같은 수식이 들어 있어 편집하면 수식이 보입니다. "종료"는 고를 수 없는 옵션입니다.'));
separator(ctx);
button(ctx, h('Check all rows (validate)', '전체 검사 (validate)'), () => {
const bad = grid.validate();
ctx.log(bad.length ? h(`${bad.length} problem(s):`, `문제 ${bad.length}건:`) : h('all rows are valid', '모든 행이 올바릅니다'));
for (const b of bad) ctx.log(` ${h('row', '행')} ${b.rowIndex + 1} · ${b.colId}: ${b.message}`);
});
toggle(ctx, h('Refuse negative numbers', '음수 입력 막기'), true, on => { blockNegative = on; });
return grid;
}