Validate user data input
The CellValidatePlugin validates edits before they are written to the grid source. Use it when the grid should enforce spreadsheet-style rules such as required fields, numeric ranges, enum membership, cross-field limits, and approval gates.
Install it together with EventManagerPlugin so edit events are collected through the Pro event pipeline:
import { CellValidatePlugin, EventManagerPlugin,} from '@revolist/revogrid-pro';
grid.plugins = [ EventManagerPlugin, CellValidatePlugin,];
grid.eventManager = { applyEventsToSource: true,};Strict and soft validation
Section titled “Strict and soft validation”By default validation is strict. If validate(value, model) returns false, the edit is rejected and the source keeps the previous value.
const columns: ColumnRegular[] = [ { name: 'Price', prop: 'price', validate: (value, model) => { const price = Number(value); return Number.isFinite(price) && price >= 50 && price <= 1200; }, validationTooltip: (value) => `Price ${value} must be between 50 and 1200`, },];Set softValidation: true when invalid values should still be saved and marked visually. This is useful for imported data cleanup flows where users need to see and fix invalid rows in place. Pair it with validationRenderer when you want saved invalid values to stay visible after the edit.
{ prop: 'discount', softValidation: true, ...validationRenderer(), validate: (value, model) => Number(value) <= Number(model.price) * 0.3, validationTooltip: (value, model) => `Discount cannot exceed 30% of price (${Math.round(Number(model.price) * 0.3)})`,}Candidate row model
Section titled “Candidate row model”Both validate(value, model) and validationTooltip(value, model) receive the row model. During strict edit validation, the model includes the pending row patch, so cross-field validation works for single edits and pasted batches. If the edit is rejected, the tooltip renderer receives the rejected value and candidate model, which lets the message explain what failed even though the source was not changed.
{ name: 'Approved', prop: 'approved', validate: (value, model) => { const netPrice = Number(model.price) - Number(model.discount ?? 0); return netPrice <= 900 || value === true; }, validationTooltip: () => 'High-value rows require approval',}validationTooltip also receives the column as a third argument: validationTooltip(value, model, column). Use that when one shared message resolver needs column metadata.
Interactive example
Section titled “Interactive example”This demo exposes the rules as controls. Try strict mode to reject invalid edits, soft mode to save invalid data with markers, and the seed/reset actions to simulate cleanup workflows.
Source code
import { defineCustomElements } from '@revolist/revogrid/loader';
import {
CellValidatePlugin,
ColumnStretchPlugin,
EventManagerPlugin,
AFTER_SOURCE_EVENT,
TooltipPlugin,
} from '@revolist/revogrid-pro';
import { currentTheme } from '../composables/useRandomData';
import {
collectValidationIssues,
createInvalidValidationRows,
createValidationColumns,
createValidationRows,
defaultValidationRules,
findRejectedEdit,
getValidationColumnIndex,
type RejectedEdit,
type ValidationRuleState,
type ValidationRow,
} from './validate-input.shared';
import {
VALIDATION_DEMO_ACTION_EVENT,
VALIDATION_DEMO_OPTIONS_CHANGE_EVENT,
createValidationDemoToolbar,
type ValidationDemoActionEvent,
type ValidationDemoOptionsChangeEvent,
} from './validate-input-toolbar';
defineCustomElements();
export function load(parentSelector: string) {
const root = document.createElement('div');
const grid = document.createElement('revo-grid');
const { isDark } = currentTheme();
let rules: ValidationRuleState = { ...defaultValidationRules };
let rows: ValidationRow[] = createValidationRows();
let lastRejected: RejectedEdit | undefined;
root.className = `validation-demo ${isDark() ? 'is-dark' : ''}`;
const toolbar = createValidationDemoToolbar({
rules,
issues: collectValidationIssues(rows, rules),
lastRejected,
});
const updateToolbar = () => {
toolbar.state = {
rules,
issues: collectValidationIssues(rows, rules),
lastRejected,
};
};
const applyRules = () => {
grid.columns = createValidationColumns(rules);
void grid.updateColumns?.(grid.columns);
updateToolbar();
void grid.refresh?.();
};
const setRows = (nextRows: ValidationRow[]) => {
rows = nextRows;
grid.source = rows;
lastRejected = undefined;
updateToolbar();
};
grid.columns = createValidationColumns(rules);
grid.source = rows;
grid.plugins = [
ColumnStretchPlugin,
TooltipPlugin,
EventManagerPlugin,
CellValidatePlugin,
];
Object.assign(grid, {
stretch: 'all',
eventManager: {
applyEventsToSource: true,
},
});
grid.theme = isDark() ? 'darkMaterial' : 'material';
grid.hideAttribution = true;
grid.range = true;
grid.className = 'validation-demo-grid cell-border';
toolbar.addEventListener(VALIDATION_DEMO_OPTIONS_CHANGE_EVENT, (event) => {
rules = (event as ValidationDemoOptionsChangeEvent).detail;
lastRejected = undefined;
grid.dispatchEvent(new CustomEvent(AFTER_SOURCE_EVENT));
applyRules();
});
toolbar.addEventListener(VALIDATION_DEMO_ACTION_EVENT, (event) => {
const action = (event as ValidationDemoActionEvent).detail;
if (action === 'seed-invalid') {
setRows(createInvalidValidationRows());
return;
}
if (action === 'reset-rows') {
setRows(createValidationRows());
return;
}
const issue = collectValidationIssues(rows, rules)[0];
if (!issue) {
return;
}
const columnIndex = getValidationColumnIndex(issue.prop);
void grid.scrollToCoordinate?.({ y: issue.rowIndex, x: columnIndex });
void grid.setCellsFocus?.(
{ y: issue.rowIndex, x: columnIndex },
{ y: issue.rowIndex, x: columnIndex },
);
});
grid.addEventListener('gridedit', (event) => {
const rejected = findRejectedEdit((event as CustomEvent).detail, rules);
setTimeout(() => {
lastRejected = event.defaultPrevented ? rejected : undefined;
rows = grid.source as ValidationRow[];
updateToolbar();
}, 0);
});
root.append(toolbar, grid);
document.querySelector(parentSelector)?.appendChild(root);
return () => root.remove();
}
<template>
<div :class="['validation-demo', { 'is-dark': isDark }]">
<validation-input-demo-toolbar
ref="toolbar"
@validation-demo-options-change="updateRules"
@validation-demo-action="handleToolbarAction"
/>
<RevoGrid
ref="grid"
class="validation-demo-grid cell-border"
:columns="columns"
:source="source"
:plugins="plugins"
:additionalData="additionalData"
:theme="isDark ? 'darkMaterial' : 'material'"
stretch="all"
hide-attribution
range
@gridedit="handleGridEdit"
/>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, reactive, ref, watch } from 'vue';
import RevoGrid from '@revolist/vue3-datagrid';
import {
CellValidatePlugin,
ColumnStretchPlugin,
EventManagerPlugin,
AFTER_SOURCE_EVENT,
TooltipPlugin,
} from '@revolist/revogrid-pro';
import { currentThemeVue } from '../composables/useRandomData';
import {
collectValidationIssues,
createInvalidValidationRows,
createValidationColumns,
createValidationRows,
defaultValidationRules,
findRejectedEdit,
getValidationColumnIndex,
type RejectedEdit,
type ValidationRuleState,
type ValidationRow,
} from './validate-input.shared';
import {
defineValidationDemoToolbarElement,
type ValidationDemoActionEvent,
type ValidationDemoToolbarElement,
} from './validate-input-toolbar';
defineValidationDemoToolbarElement();
const { isDark } = currentThemeVue();
const grid = ref<{ $el?: HTMLRevoGridElement } | null>(null);
const toolbar = ref<ValidationDemoToolbarElement | null>(null);
const rules = reactive({ ...defaultValidationRules });
const source = ref<ValidationRow[]>(createValidationRows());
const lastRejected = ref<RejectedEdit | undefined>();
const columns = computed(() => createValidationColumns(rules));
const issues = computed(() => collectValidationIssues(source.value, rules));
const additionalData = computed(() => ({
eventManager: {
applyEventsToSource: true,
},
}));
const plugins = [
ColumnStretchPlugin,
TooltipPlugin,
EventManagerPlugin,
CellValidatePlugin,
];
function getGridElement() {
return grid.value?.$el as HTMLRevoGridElement | undefined;
}
function syncToolbar() {
if (toolbar.value) {
toolbar.value.state = {
rules,
issues: issues.value,
lastRejected: lastRejected.value,
};
}
}
function updateRules(event: CustomEvent<ValidationRuleState>) {
lastRejected.value = undefined;
getGridElement()?.dispatchEvent(new CustomEvent(AFTER_SOURCE_EVENT));
Object.assign(rules, event.detail);
}
function seedInvalid() {
lastRejected.value = undefined;
source.value = createInvalidValidationRows();
}
function resetRows() {
lastRejected.value = undefined;
source.value = createValidationRows();
}
function focusFirstInvalid() {
const issue = issues.value[0];
const element = getGridElement();
if (!issue || !element) {
return;
}
const columnIndex = getValidationColumnIndex(issue.prop);
void element.scrollToCoordinate({ y: issue.rowIndex, x: columnIndex });
void element.setCellsFocus({ y: issue.rowIndex, x: columnIndex }, { y: issue.rowIndex, x: columnIndex });
}
function handleToolbarAction(event: ValidationDemoActionEvent) {
if (event.detail === 'seed-invalid') {
seedInvalid();
return;
}
if (event.detail === 'reset-rows') {
resetRows();
return;
}
focusFirstInvalid();
}
function handleGridEdit(event: CustomEvent<HTMLRevoGridElementEventMap['gridedit']>) {
const rejected = findRejectedEdit(event.detail, rules);
setTimeout(() => {
lastRejected.value = event.defaultPrevented ? rejected : undefined;
source.value = [...(getGridElement()?.source as ValidationRow[] ?? source.value)];
}, 0);
}
watch([rules, source, lastRejected], syncToolbar, { deep: true });
onMounted(syncToolbar);
</script>
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { RevoGrid } from '@revolist/react-datagrid';
import {
CellValidatePlugin,
ColumnStretchPlugin,
EventManagerPlugin,
AFTER_SOURCE_EVENT,
TooltipPlugin,
} from '@revolist/revogrid-pro';
import { currentTheme } from '../composables/useRandomData';
import {
collectValidationIssues,
createInvalidValidationRows,
createValidationColumns,
createValidationRows,
defaultValidationRules,
findRejectedEdit,
getValidationColumnIndex,
type RejectedEdit,
type ValidationRow,
} from './validate-input.shared';
import {
VALIDATION_DEMO_ACTION_EVENT,
VALIDATION_DEMO_OPTIONS_CHANGE_EVENT,
VALIDATION_DEMO_TOOLBAR_TAG,
defineValidationDemoToolbarElement,
type ValidationDemoActionEvent,
type ValidationDemoOptionsChangeEvent,
type ValidationDemoToolbarElement,
} from './validate-input-toolbar';
const { isDark } = currentTheme();
defineValidationDemoToolbarElement();
export default function ValidateInput() {
const gridRef = useRef<HTMLRevoGridElement>(null);
const toolbarRef = useRef<ValidationDemoToolbarElement>(null);
const [rules, setRules] = useState(defaultValidationRules);
const [source, setSource] = useState<ValidationRow[]>(() => createValidationRows());
const [lastRejected, setLastRejected] = useState<RejectedEdit | undefined>();
const columns = useMemo(() => createValidationColumns(rules), [rules]);
const plugins = useMemo(() => [
ColumnStretchPlugin,
TooltipPlugin,
EventManagerPlugin,
CellValidatePlugin,
], []);
const additionalData = useMemo(() => ({
eventManager: {
applyEventsToSource: true,
},
}), []);
const issues = collectValidationIssues(source, rules);
useEffect(() => {
if (toolbarRef.current) {
toolbarRef.current.state = { rules, issues, lastRejected };
}
}, [issues, lastRejected, rules]);
useEffect(() => {
const toolbar = toolbarRef.current;
if (!toolbar) {
return;
}
const handleOptionsChange = (event: Event) => {
setLastRejected(undefined);
gridRef.current?.dispatchEvent(new CustomEvent(AFTER_SOURCE_EVENT));
setRules((event as ValidationDemoOptionsChangeEvent).detail);
};
const handleAction = (event: Event) => {
const action = (event as ValidationDemoActionEvent).detail;
if (action === 'seed-invalid') {
setRows(createInvalidValidationRows());
return;
}
if (action === 'reset-rows') {
setRows(createValidationRows());
return;
}
focusFirstInvalid();
};
toolbar.addEventListener(VALIDATION_DEMO_OPTIONS_CHANGE_EVENT, handleOptionsChange);
toolbar.addEventListener(VALIDATION_DEMO_ACTION_EVENT, handleAction);
return () => {
toolbar.removeEventListener(VALIDATION_DEMO_OPTIONS_CHANGE_EVENT, handleOptionsChange);
toolbar.removeEventListener(VALIDATION_DEMO_ACTION_EVENT, handleAction);
};
});
const setRows = (rows: ValidationRow[]) => {
setLastRejected(undefined);
setSource(rows);
};
const handleGridEdit = (event: CustomEvent<HTMLRevoGridElementEventMap['gridedit']>) => {
const rejected = findRejectedEdit(event.detail, rules);
setTimeout(() => {
setLastRejected(event.defaultPrevented ? rejected : undefined);
setSource([...(gridRef.current?.source as ValidationRow[] ?? source)]);
}, 0);
};
const focusFirstInvalid = () => {
const issue = issues[0];
if (!issue) {
return;
}
const columnIndex = getValidationColumnIndex(issue.prop);
void gridRef.current?.scrollToCoordinate({ y: issue.rowIndex, x: columnIndex });
void gridRef.current?.setCellsFocus(
{ y: issue.rowIndex, x: columnIndex },
{ y: issue.rowIndex, x: columnIndex },
);
};
return (
<div className={`validation-demo ${isDark() ? 'is-dark' : ''}`}>
{React.createElement(VALIDATION_DEMO_TOOLBAR_TAG, { ref: toolbarRef })}
<RevoGrid
ref={gridRef}
className="validation-demo-grid cell-border"
theme={isDark() ? 'darkMaterial' : 'material'}
columns={columns}
source={source}
plugins={plugins}
stretch="all"
additionalData={additionalData}
hideAttribution
range
onGridedit={handleGridEdit}
/>
</div>
);
}
import {
AfterViewInit,
CUSTOM_ELEMENTS_SCHEMA,
Component,
ElementRef,
ViewChild,
ViewEncapsulation,
} from '@angular/core';
import { RevoGrid } from '@revolist/angular-datagrid';
import {
CellValidatePlugin,
ColumnStretchPlugin,
EventManagerPlugin,
AFTER_SOURCE_EVENT,
TooltipPlugin,
} from '@revolist/revogrid-pro';
import { currentTheme } from '../composables/useRandomData';
import {
collectValidationIssues,
createInvalidValidationRows,
createValidationColumns,
createValidationRows,
defaultValidationRules,
findRejectedEdit,
getValidationColumnIndex,
type RejectedEdit,
type ValidationIssue,
type ValidationRow,
} from './validate-input.shared';
import {
defineValidationDemoToolbarElement,
type ValidationDemoActionEvent,
type ValidationDemoOptionsChangeEvent,
type ValidationDemoToolbarElement,
} from './validate-input-toolbar';
defineValidationDemoToolbarElement();
@Component({
selector: 'validate-input-grid',
standalone: true,
imports: [RevoGrid],
template: `
<div class="validation-demo" [class.is-dark]="theme === 'darkMaterial'">
<validation-input-demo-toolbar
#toolbarRef
(validation-demo-options-change)="setRules($event)"
(validation-demo-action)="handleToolbarAction($event)"
></validation-input-demo-toolbar>
<revo-grid
#gridRef
class="validation-demo-grid cell-border"
[source]="source"
[columns]="columns"
[plugins]="plugins"
[stretch]="'all'"
[eventManager]="eventManager"
[theme]="theme"
[hideAttribution]="true"
[range]="true"
(gridedit)="handleGridEdit($event)"
></revo-grid>
</div>
`,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
encapsulation: ViewEncapsulation.None,
})
export class ValidateInputGridComponent implements AfterViewInit {
@ViewChild('gridRef', { read: ElementRef }) gridElement?: ElementRef<HTMLRevoGridElement>;
@ViewChild('toolbarRef', { read: ElementRef })
toolbarElement?: ElementRef<ValidationDemoToolbarElement>;
source: ValidationRow[] = createValidationRows();
rules = { ...defaultValidationRules };
columns = createValidationColumns(this.rules);
issues: ValidationIssue[] = collectValidationIssues(this.source, this.rules);
lastRejected?: RejectedEdit;
plugins = [
ColumnStretchPlugin,
TooltipPlugin,
EventManagerPlugin,
CellValidatePlugin,
];
eventManager = {
applyEventsToSource: true,
};
theme = currentTheme().isDark() ? 'darkMaterial' : 'material';
ngAfterViewInit() {
this.syncToolbar();
}
private getGridElement() {
return this.gridElement?.nativeElement;
}
private syncToolbar() {
if (this.toolbarElement) {
this.toolbarElement.nativeElement.state = {
rules: this.rules,
issues: this.issues,
lastRejected: this.lastRejected,
};
}
}
setRules(event: ValidationDemoOptionsChangeEvent) {
this.lastRejected = undefined;
this.getGridElement()?.dispatchEvent(new CustomEvent(AFTER_SOURCE_EVENT));
this.rules = event.detail;
this.syncRules();
}
syncRules() {
this.rules = { ...this.rules };
this.columns = createValidationColumns(this.rules);
this.issues = collectValidationIssues(this.source, this.rules);
this.syncToolbar();
void this.getGridElement()?.refresh?.();
}
seedInvalid() {
this.lastRejected = undefined;
this.source = createInvalidValidationRows();
this.issues = collectValidationIssues(this.source, this.rules);
this.syncToolbar();
}
resetRows() {
this.lastRejected = undefined;
this.source = createValidationRows();
this.issues = collectValidationIssues(this.source, this.rules);
this.syncToolbar();
}
focusFirstInvalid() {
const issue = this.issues[0];
const grid = this.getGridElement();
if (!issue || !grid) {
return;
}
const columnIndex = getValidationColumnIndex(issue.prop);
void grid.scrollToCoordinate({ y: issue.rowIndex, x: columnIndex });
void grid.setCellsFocus({ y: issue.rowIndex, x: columnIndex }, { y: issue.rowIndex, x: columnIndex });
}
handleToolbarAction(event: ValidationDemoActionEvent) {
if (event.detail === 'seed-invalid') {
this.seedInvalid();
return;
}
if (event.detail === 'reset-rows') {
this.resetRows();
return;
}
this.focusFirstInvalid();
}
handleGridEdit(event: CustomEvent<HTMLRevoGridElementEventMap['gridedit']>) {
const rejected = findRejectedEdit(event.detail, this.rules);
setTimeout(() => {
this.lastRejected = event.defaultPrevented ? rejected : undefined;
this.source = [...(this.getGridElement()?.source as ValidationRow[] ?? this.source)];
this.issues = collectValidationIssues(this.source, this.rules);
this.syncToolbar();
}, 0);
}
}
Cross-column example
Section titled “Cross-column example”The validation function can also block a cell based on another column in the same row. This example prevents fees on expired services.
Source code
import { defineCustomElements } from '@revolist/revogrid/loader';
import type { ColumnRegular } from '@revolist/revogrid';
import {
CellValidatePlugin,
ColumnStretchPlugin,
EventManagerPlugin,
TooltipPlugin,
validationRenderer,
} from '@revolist/revogrid-pro';
import { currentTheme } from '../composables/useRandomData';
import { crossColumnValidationSource } from './validate-input.data';
import { invalidCellStyle } from './validate-basic.data';
defineCustomElements();
const columns: ColumnRegular[] = [
{ name: 'ID', prop: 'id', size: 80 },
{ name: 'Service Name', prop: 'name', size: 220 },
{
name: 'LCS Fee',
prop: 'lcsFee',
...validationRenderer({
invalidProperties: () => ({
style: invalidCellStyle,
}),
}),
validationTooltip: (value: any, model?: any) => {
if (model?.expired) {
return 'Cannot set fee for expired services';
}
if (value == null || value === '' || Number.isNaN(Number(value))) {
return 'Fee must be a valid number';
}
return undefined;
},
validate: (value: any, model?: any) => {
if (model?.expired) {
return false;
}
return value != null && value !== '' && !Number.isNaN(Number(value));
},
},
{
name: 'Expired',
prop: 'expired',
size: 110,
cellTemplate: (h, { value }) =>
h(
'span',
{
style: {
color: value ? '#b91c1c' : '#15803d',
fontWeight: '600',
},
},
value ? 'Yes' : 'No',
),
},
];
export function load(parentSelector: string) {
const grid = document.createElement('revo-grid');
const { isDark } = currentTheme();
grid.source = crossColumnValidationSource;
grid.columns = columns;
grid.plugins = [
ColumnStretchPlugin,
TooltipPlugin,
EventManagerPlugin,
CellValidatePlugin,
];
Object.assign(grid, {
stretch: 'all',
eventManager: {
applyEventsToSource: true,
},
})
grid.theme = isDark() ? 'darkMaterial' : 'material';
grid.hideAttribution = true;
grid.range = true;
document.querySelector(parentSelector)?.appendChild(grid);
}
<template>
<RevoGrid
class="cell-border rounded-lg overflow-hidden"
style="min-height: 320px;"
:columns="columns"
:source="source"
:plugins="plugins"
:additionalData="additionalData"
:theme="isDark ? 'darkMaterial' : 'material'"
hide-attribution
range
/>
</template>
<script setup lang="ts">
import RevoGrid, { type ColumnRegular } from '@revolist/vue3-datagrid';
import {
CellValidatePlugin,
ColumnStretchPlugin,
EventManagerPlugin,
TooltipPlugin,
validationRenderer,
} from '@revolist/revogrid-pro';
import { currentThemeVue } from '../composables/useRandomData';
import { crossColumnValidationSource } from './validate-input.data';
import { invalidCellStyle } from './validate-basic.data';
const { isDark } = currentThemeVue();
const columns: ColumnRegular[] = [
{ name: 'ID', prop: 'id', size: 80 },
{ name: 'Service Name', prop: 'name', size: 220 },
{
name: 'LCS Fee',
prop: 'lcsFee',
...validationRenderer({
invalidProperties: () => ({
style: invalidCellStyle,
}),
}),
validationTooltip: (value: any, model?: any) => {
if (model?.expired) {
return 'Cannot set fee for expired services';
}
if (value == null || value === '' || Number.isNaN(Number(value))) {
return 'Fee must be a valid number';
}
return undefined;
},
validate: (value: any, model?: any) => {
if (model?.expired) {
return false;
}
return value != null && value !== '' && !Number.isNaN(Number(value));
},
},
{
name: 'Expired',
prop: 'expired',
size: 110,
cellTemplate: (h, { value }) =>
h(
'span',
{
style: {
color: value ? '#b91c1c' : '#15803d',
fontWeight: '600',
},
},
value ? 'Yes' : 'No',
),
},
];
const source = crossColumnValidationSource;
const plugins = [
ColumnStretchPlugin,
TooltipPlugin,
EventManagerPlugin,
CellValidatePlugin,
];
const additionalData = {
stretch: 'all',
eventManager: {
applyEventsToSource: true,
},
};
</script>
import React from 'react';
import { RevoGrid, type ColumnRegular } from '@revolist/react-datagrid';
import {
CellValidatePlugin,
ColumnStretchPlugin,
EventManagerPlugin,
TooltipPlugin,
validationRenderer,
} from '@revolist/revogrid-pro';
import { currentTheme } from '../composables/useRandomData';
import { crossColumnValidationSource } from './validate-input.data';
import { invalidCellStyle } from './validate-basic.data';
const { isDark } = currentTheme();
export default function ValidateCrossColumn() {
const columns: ColumnRegular[] = [
{ name: 'ID', prop: 'id', size: 80 },
{ name: 'Service Name', prop: 'name', size: 220 },
{
name: 'LCS Fee',
prop: 'lcsFee',
...validationRenderer({
invalidProperties: () => ({
style: invalidCellStyle,
}),
}),
validationTooltip: (value: any, model?: any) => {
if (model?.expired) {
return 'Cannot set fee for expired services';
}
if (value == null || value === '' || Number.isNaN(Number(value))) {
return 'Fee must be a valid number';
}
return undefined;
},
validate: (value: any, model?: any) => {
if (model?.expired) {
return false;
}
return value != null && value !== '' && !Number.isNaN(Number(value));
},
},
{
name: 'Expired',
prop: 'expired',
size: 110,
cellTemplate: (h, { value }) =>
h(
'span',
{
style: {
color: value ? '#b91c1c' : '#15803d',
fontWeight: '600',
},
},
value ? 'Yes' : 'No',
),
},
];
return (
<RevoGrid
style={{ height: '320px' }}
theme={isDark() ? 'darkMaterial' : 'material'}
columns={columns}
source={crossColumnValidationSource}
plugins={[
ColumnStretchPlugin,
TooltipPlugin,
EventManagerPlugin,
CellValidatePlugin,
]}
additionalData={{
stretch: 'all',
eventManager: {
applyEventsToSource: true,
},
}}
hideAttribution
range
/>
);
}
import { Component, NO_ERRORS_SCHEMA } from '@angular/core';
import { RevoGrid } from '@revolist/angular-datagrid';
import {
CellValidatePlugin,
ColumnStretchPlugin,
EventManagerPlugin,
TooltipPlugin,
validationRenderer,
} from '@revolist/revogrid-pro';
import { currentTheme } from '../composables/useRandomData';
import { crossColumnValidationSource } from './validate-input.data';
import { invalidCellStyle } from './validate-basic.data';
@Component({
selector: 'validate-cross-column-grid',
standalone: true,
imports: [RevoGrid],
template: `
<revo-grid
class="grow h-full cell-border"
[source]="source"
[columns]="columns"
[plugins]="plugins"
[stretch]="additionalData.stretch"
[eventManager]="additionalData.eventManager"
[theme]="theme"
[hideAttribution]="true"
[range]="true"
style="min-height: 320px;"
></revo-grid>
`,
// Allows Angular demos to bind RevoGrid plugin props that are not wrapper inputs.
schemas: [NO_ERRORS_SCHEMA],
})
export class ValidateCrossColumnGridComponent {
source = crossColumnValidationSource;
columns = [
{ name: 'ID', prop: 'id', size: 80 },
{ name: 'Service Name', prop: 'name', size: 220 },
{
name: 'LCS Fee',
prop: 'lcsFee',
...validationRenderer({
invalidProperties: () => ({
style: invalidCellStyle,
}),
}),
validationTooltip: (value: any, model?: any) => {
if (model?.expired) {
return 'Cannot set fee for expired services';
}
if (value == null || value === '' || Number.isNaN(Number(value))) {
return 'Fee must be a valid number';
}
return undefined;
},
validate: (value: any, model?: any) => {
if (model?.expired) {
return false;
}
return value != null && value !== '' && !Number.isNaN(Number(value));
},
},
{
name: 'Expired',
prop: 'expired',
size: 110,
cellTemplate: (h: any, { value }: any) =>
h(
'span',
{
style: {
color: value ? '#b91c1c' : '#15803d',
fontWeight: '600',
},
},
value ? 'Yes' : 'No',
),
},
];
plugins = [
ColumnStretchPlugin,
TooltipPlugin,
EventManagerPlugin,
CellValidatePlugin,
];
additionalData = {
stretch: 'all',
eventManager: {
applyEventsToSource: true,
},
};
theme = currentTheme().isDark() ? 'darkMaterial' : 'material';
}