FreeGridv1.15.1English

유효성 검사와 변경 추적

required, unique, validate로 잘못된 값을 막고, 저장 전 validate()로 전체를 검사하며, getModifiedRows, 실행 취소, commitChanges로 변경을 관리합니다.

실행 예제유효성 검사와 편집 규칙

셀 잠금, 중복·빈 값 검사, 시간 편집기, 고를 수 없는 옵션, 행 단위 편집, 변경 취소 이벤트, 전체 검사.

이 데모의 코드
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),
    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 },
    // worked out from the row; not editable
    { id: 'total', header: h('Total', '금액'), type: 'number', format: '#,##0', width: 120, valueGetter: r => r.qty * r.price },
    { 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 },
      ] } },
  ];
  // editType 'row': Tab stays in the row, Enter or another row saves it, Escape puts the row back
  const grid = createGrid(ctx.host, { columns, data, locale: lang, editType: 'row' });

  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('Rows are edited as a whole: Tab stays in the row, Enter saves it, Escape puts it back. Gray rows are locked. SKU must be unique. Closed is a disabled option.',
    '행 단위로 편집합니다. Tab은 같은 행 안에서 이동하고, Enter는 행 저장, Esc는 행 전체 되돌리기입니다. 회색 행은 수정할 수 없고, SKU는 중복될 수 없습니다. "종료"는 고를 수 없는 옵션입니다.'));
  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; });
  grid.on('rowEditStop', e => ctx.log(`rowEditStop → ${e.committed ? h('saved', '저장') : h('put back', '되돌림')} ${e.changes.map(c => c.colId).join(', ')}`));
  return grid;
}

컬럼에 규칙을 주면 잘못된 값이 그리드에 들어오지 않습니다. required는 빈 값을, unique는 다른 행에 이미 있는 값을 거부하고(빈 값은 허용), validate(value, row)는 메시지를 돌려주어 음수 수량이나 @ 없는 이메일 주소처럼 그 밖의 값을 거부합니다. 편집하다 거부되면 값을 고치거나 Esc로 취소할 때까지 편집기에 메시지가 남아 있고, validationFailed 이벤트가 옵니다. 규칙에 맞지 않는 값을 붙여넣으면 그 셀은 건너뛰고 paste 이벤트의 skipped에 셉니다.

불러오거나 가져오거나 코드로 바꾼 데이터도 규칙에 어긋날 수 있습니다. 저장하기 전에 validate()를 부르세요. 편집 가능한 모든 셀을 검사해 규칙에 맞지 않는 셀을 행, 컬럼, 메시지와 함께 돌려주므로, 첫 문제로 스크롤하거나 목록으로 보여 주면 됩니다.

const grid = createGrid(el, {
  columns: [
    { field: 'code', editable: true, unique: true, required: true },
    { field: 'name', editable: true, required: true },
    { field: 'qty', type: 'number', editable: true, validate: v => v >= 0 || 'Must be 0 or more' },
    { field: 'email', editable: true, validate: (v, row) => !v || v.includes('@') || 'Not an e-mail address' },
  ],
  data,
});
grid.on('validationFailed', e => toast(e.message));

// before saving: every cell that breaks a rule
const problems = grid.validate(); // [{ row, rowIndex, colId, message }]
if (problems.length) grid.scrollToRow(problems[0].rowIndex);

저장할 행처럼 그리드 일부만 검사하려면 다음을 보세요: 일부 행만 검사하기.

변경 추적과 실행 취소

편집, 붙여넣기, 행 추가와 삭제는 모두 기록됩니다. 바뀐 셀에는 모서리에 작은 표시가 생기고, Ctrl+Z와 Ctrl+Y로 실행 취소와 다시 실행을 할 수 있으며, 서버에 무엇을 보낼지도 그리드가 알고 있습니다. getModifiedRows()는 추가·수정·삭제된 행을, getChanges()는 바뀐 셀마다 이전 값과 새 값을 돌려줍니다. 서버에 저장한 뒤 commitChanges()를 부르면 지금 행이 새 기준이 되고 표시가 지워집니다. 반대로 revertChanges()는 모든 변경을 되돌립니다.

grid.addRows([{ name: 'New' }], 0);   // insert at the top
grid.removeSelectedRows();
grid.undo(); grid.redo();              // Ctrl+Z / Ctrl+Y
grid.hasChanges();                     // true

// send only what changed
const { created, updated, deleted } = grid.getModifiedRows();
await save({ created, updated, deleted, cells: grid.getChanges() });
grid.commitChanges();                  // the saved state is the new baseline
// grid.revertChanges();               // or put everything back

applyTransaction으로 들어온 변경(실시간 데이터)은 편집이 아니므로 기록되지 않고 실행 취소할 수도 없습니다. "조회 → 편집 → 저장" 화면이라면 saveChanges()가 변경을 보내고 서버가 받아들이면 확정까지 해 줍니다. 서버 예제를 보세요.

옵션, 메서드, 이벤트

이름타입설명
required / uniqueboolean컬럼: 빈 값을 거부합니다 / 다른 행에 이미 있는 값을 거부합니다(빈 값은 허용).
validate(value, row)true | string컬럼: 메시지를 돌려주면 값을 거부합니다. 편집기가 열린 채 메시지를 보여 줍니다.
grid.validateCell(row, colId)string | null셀 하나가 규칙에 어긋나는 이유, 맞으면 null.
grid.validate({ rows?, colIds?, changedOnly? }){ row, rowIndex, colId, message }[]편집 가능한 모든 셀을 규칙으로 검사합니다. 모두 맞으면 빈 배열.
validationFailed{ rowIndex, colId, value, message } (event)입력한 값이 거부됐습니다(규칙에 맞지 않는 붙여넣기 값은 paste 이벤트에 셉니다).
getModifiedRows() / getChanges() / hasChanges()object추가·수정·삭제된 행, 바뀐 셀마다 이전 값과 새 값, 바뀐 것이 있는지 여부.
commitChanges() / revertChanges()void지금 행을 새 기준으로 삼거나(저장 뒤) 모든 변경을 되돌립니다.
addRows(rows, index?) / removeRows(rows | fn) / removeSelectedRows()Row[]행을 추가하거나 삭제합니다. 둘 다 변경 내역에 남고 실행 취소할 수 있습니다.
undo() / redo() / canUndo() / canRedo()void / boolean편집·붙여넣기·행 변경의 실행 취소 기록(Ctrl+Z / Ctrl+Y).

이 페이지에 대한 질문

저장하기 전에 값을 검사하려면?

컬럼에 required, unique, validate를 주세요. 규칙에 맞지 않는 입력은 거부되고, 저장 전에 grid.validate()를 부르면 잘못된 셀 목록을 돌려줍니다.