Skip to content

Validate user data input

For more advanced validation requirements, you can use a custom plugin that intercepts user edits and maintains a cache of invalid cells. This approach allows for dynamic validation rules that can be applied at runtime.

With the CellValidatePlugin, you can enforce validation rules when data is entered into the grid.

For instance, you could define validation rules directly in your column definitions:

// Define columns
const columns: ColumnRegular[] = [
{
name: 'đź’° Price',
prop: 'price',
validate: (value: number) => {
// Highlight prices outside the range of 10 and 20 but don't apply values
return value >= 10 && value <= 20;
}
},
];

The validation function also receives the full row model as a second parameter, allowing you to validate a cell against values in other columns:

const columns: ColumnRegular[] = [
{
name: 'LCS Fee',
prop: 'lcsFee',
validate: (value: any, model?: any) => {
// âś… Now you can access other column values!
if (model?.expired) return false; // Prevent fee setting for expired services
return value != null && value !== '' && !isNaN(value);
}
},
{
name: 'Expired',
prop: 'expired',
// ... other column properties
}
];

In this example, the validate function checks that the price falls within the range of 10 to 20. If the entered value does not meet these criteria, the cell will be marked as invalid, similar to the first approach.

Source code
TypeScript ts
import { defineCustomElements } from '@revolist/revogrid/loader';
import type { ColumnRegular } from '@revolist/revogrid';
import {
  CellValidatePlugin,
  ColumnStretchPlugin,
  EventManagerPlugin,
} from '@revolist/revogrid-pro';
import { currentTheme } from '../composables/useRandomData';
import { inputValidationSource } from './validate-input.data';

defineCustomElements();

const columns: ColumnRegular[] = [
  {
    name: 'đź’° Price',
    prop: 'price',
    validate: (value: number) => value >= 10 && value <= 20,
  },
];

export function load(parentSelector: string) {
  const grid = document.createElement('revo-grid');
  const { isDark } = currentTheme();

  grid.source = inputValidationSource;
  grid.columns = columns;
  grid.plugins = [
    ColumnStretchPlugin,
    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);
}
Vue vue
<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,
} from '@revolist/revogrid-pro';
import { currentThemeVue } from '../composables/useRandomData';
import { inputValidationSource } from './validate-input.data';

const { isDark } = currentThemeVue();

const columns: ColumnRegular[] = [
  {
    name: 'đź’° Price',
    prop: 'price',
    validate: (value: number) => value >= 10 && value <= 20,
  },
];

const source = inputValidationSource;
const plugins = [
  ColumnStretchPlugin,
  EventManagerPlugin,
  CellValidatePlugin,
];
const additionalData = {
  stretch: 'all',
  eventManager: {
    applyEventsToSource: true,
  },
};
</script>
React tsx
import React from 'react';
import { RevoGrid, type ColumnRegular } from '@revolist/react-datagrid';
import {
  CellValidatePlugin,
  ColumnStretchPlugin,
  EventManagerPlugin,
} from '@revolist/revogrid-pro';
import { currentTheme } from '../composables/useRandomData';
import { inputValidationSource } from './validate-input.data';

const { isDark } = currentTheme();

export default function ValidateInput() {
  const columns: ColumnRegular[] = [
    {
      name: 'đź’° Price',
      prop: 'price',
      validate: (value: number) => value >= 10 && value <= 20,
    },
  ];

  return (
    <RevoGrid
      style={{ height: '320px' }}
      theme={isDark() ? 'darkMaterial' : 'material'}
      columns={columns}
      source={inputValidationSource}
      plugins={[
        ColumnStretchPlugin,
        EventManagerPlugin,
        CellValidatePlugin,
      ]}
      additionalData={{
        stretch: 'all',
        eventManager: {
          applyEventsToSource: true,
        },
      }}
      hideAttribution
      range
    />
  );
}
Angular ts
import { Component, NO_ERRORS_SCHEMA } from '@angular/core';
import { RevoGrid } from '@revolist/angular-datagrid';
import {
  CellValidatePlugin,
  ColumnStretchPlugin,
  EventManagerPlugin,
} from '@revolist/revogrid-pro';
import { currentTheme } from '../composables/useRandomData';
import { inputValidationSource } from './validate-input.data';

@Component({
  selector: 'validate-input-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 ValidateInputGridComponent {
  source = inputValidationSource;
  columns = [
    {
      name: 'đź’° Price',
      prop: 'price',
      validate: (value: number) => value >= 10 && value <= 20,
    },
  ];
  plugins = [
    ColumnStretchPlugin,
    EventManagerPlugin,
    CellValidatePlugin,
  ];
  additionalData = {
    stretch: 'all',
    eventManager: {
      applyEventsToSource: true,
    },
  };
  theme = currentTheme().isDark() ? 'darkMaterial' : 'material';
}

This example demonstrates how validation functions can access the full row model, allowing for cross-column validation logic. Try editing the “LCS Fee” column for rows where “Expired” is “Yes” - the validation will prevent you from setting a fee for expired services.

Source code
TypeScript ts
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);
}
Vue vue
<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>
React tsx
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
    />
  );
}
Angular ts
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';
}

The CellValidatePlugin also allows you to define additional properties in your grid columns, which can be validated using the same rules you apply in your plugin. This provides flexibility in how you manage and enforce validation across your grid.

Cell validation in RevoGrid can be implemented in various ways to ensure data integrity and provide users with a smooth experience. Whether you choose to use simple cell properties for basic validation or develop a custom plugin for more complex scenarios, RevoGrid offers the tools you need to validate data effectively.

By applying these validation techniques, you can create a robust data grid that not only captures user input but also enforces data quality standards.