Home/Editing/Edit Validation
Editing

Edit Validation

Rules live on the column and run at the commit gate, so inline editing, paste, fill and bulk edits all pass the same ones. What happens to a value that fails is a second decision: block it, or let it commit and keep the cell marked.

Per-column rules — declared, or a function of your own

Five columns, five kinds of rule: required, a pattern, a range, a custom function, and one that reads the row it is on. Double-click a cell and type something wrong — the editor stays open with the reason.

Try it: double-click Age and type 5  ·  clear a Name  ·  type a bad Email  ·  set Salary to 1234
ID
Full name
Email
Age
Salary ($)
Department
1
Alice Smith
alice@corp.io
31
92000
Engineering
2
Bob Johnson
bob@corp.io
44
88000
Engineering
3
Carol Williams
carol@corp.io
29
105000
Marketing
4
David Brown
david@corp.io
52
78000
Engineering
5
Emma Jones
emma@corp.io
27
82000
Design
6
Frank Miller
frank@corp.io
38
115000
Engineering
7
Grace Davis
grace@corp.io
33
65000
Sales
8
Henry Wilson
henry@corp.io
47
95000
Marketing
All records loaded
8 / 8 records

column.validate takes declarative rules, a function, or both together. Every column in this grid uses a different shape of it:

  • Full name — { required: true }. Clear it and the commit is refused.
  • Email — { required: true, pattern: /^\S+@\S+\.\S+$/ }. Two rules on one column; either one failing is enough.
  • Age — { min: 18, max: 99 }, whose messages come from GridMessages rather than from you.
  • Salary — a declared min and a rule function returning a string, which becomes the message.
  • Department — a bare function, which is shorthand for { rule }.

A rule returns true for valid, false for invalid with the default message, or a string that is the message. It receives (value, row, column), so a rule can read the rest of the row it is on — which is what makes "this field is only valid for that kind of record" expressible without a form.

The badge above the grid is (cellEditRejected), which fires with the value and the messages every time a commit is refused. It is how you log a rejection, count them, or show your own notice.

block or allow — what happens to the value that failed

The same columns and the same rules twice, one property apart. Set an Age of 5 in each: on the left the editor refuses to close and the cell keeps its number; on the right the 5 lands in the cell and the cell stays marked until you fix it.

editValidation: 'block' — the default

Full name
Age
Department
Alice Smith
31
Engineering
Bob Johnson
44
Engineering
Carol Williams
29
Marketing
David Brown
52
Engineering
4 / 8 records
In the data: Alice Smith is 31

editValidation: 'allow' — commit and mark

Full name
Age
Department
Alice Smith
31
Engineering
Bob Johnson
44
Engineering
Carol Williams
29
Marketing
David Brown
52
Engineering
4 / 8 records
In the data: Alice Smith is 31

Two grids, the same rules, one property apart:

config = { …, editValidation: 'block' | 'allow' }

Set Age to 5 on the first row of each and watch the readout under it — that line is read back out of the data, not off the screen, and the chips beside it name every output that fired for the edit.

  • 'block' (the default) — the editor refuses to close, the message stays, and the row's age is unchanged. Esc gives up; clicking away reverts. Nothing downstream — a filter, an export, an aggregate — can ever see the bad value, because it never existed. One output fires: (cellEditRejected).
  • 'allow' — the 5 commits. The cell keeps a visible invalid mark until something valid is written over it, and undo/redo carries the marks with the values. You get both outputs for that one edit — (cellEditCommit) with the value that landed, and (cellEditRejected) with the reason it should not have — so you can persist it and flag it in the same handler pair.

Choose 'allow' when a half-finished record is a real state in your product — a draft, an import that has to land before it can be cleaned. Choose 'block' whenever the data behind the grid is expected to be correct at all times, which is most of the time, and is why it is the default.

Rules live on the column

column.validate accepts { required, min, max, pattern, rule }, a bare function (shorthand for { rule }), or an array of functions. A rule returns true for valid, false for invalid with the default message, or a string which becomes the message. It receives (value, row, column) — so it can read the row's other fields.

One validated path

Validation runs at the commit gate, not inside any one editor. Every way a cell can change goes through it: inline editing, a clipboard paste, the fill handle, a bulk edit. A pasted range skips only its invalid cells and reports how many were rejected — you do not write the rule twice, and you cannot forget one of the paths.

block or allow

  • editValidation: 'block' (default) — an invalid value never enters the data.
  • editValidation: 'allow' — it commits, and the cell stays visibly marked until a valid value replaces it.

Both are shown side by side in the second example, which is the only way the difference is a single read rather than two visits to the same grid.

Accessibility and localisation

The editor input flips aria-invalid and links its message with aria-describedby; a rejection is announced politely rather than interrupting. The default messages are GridMessages keys — validationRequired, validationMin, validationMax, validationPattern — with English and Arabic built in. Strings your own rules return are yours to localise. See Localisation.

Theming the invalid state

--gc-editor-invalid-border and --gc-editor-error-bg / -color / -border style the open editor and its message; --gc-cell-invalid-bg and --gc-cell-invalid-outline style a cell marked under 'allow'.

Keyboard

KeyWith an invalid value
Enter / TabBlocked — the editor stays open showing the message, and Tab does not advance
EscCancel — the original value is restored
Click awayReverts to the original value, so the editor never traps you

Client validation is UX, not security. Always revalidate on the server before persisting — the code tab's save() shows where that goes.

Validation API

PropertyTypeDefaultDescription
column.validateColumnValidation | rule | rule[]—{ required, min, max, pattern, rule }. A bare function is shorthand for { rule }.
ValidationRule(value, row, column) => boolean | string | void—false is invalid with the default message; a string is invalid with that message. The row argument is what lets one field validate against another.
config.editValidation'block' | 'allow''block'block: an invalid value never commits. allow: it commits and the cell stays marked.
(cellEditRejected)CellEditRejectedEvent<T>—Fires on every refused — or allow-marked — commit, with the value, the messages and the mode.
(cellEditCommit)CellEditEvent<T>—Fires on every ACCEPTED commit. Under allow that includes a commit that failed its rules, so one invalid edit fires this and (cellEditRejected) — persist from here, flag from there.
validateCellValue()(col, value, row, messages?) => string[]—The rule evaluator, exported. Reuse it in your own forms or in a server-side mock so both sides run the same rules.
GridMessages.validation*stringen / ar built invalidationRequired, validationMin, validationMax, validationPattern — the default messages, per locale.
--gc-cell-invalid-bg / -outlineCSS custom propertythemeHow a cell marked under allow is painted. The open editor has its own --gc-editor-error-* tokens.