Appearance
Filtering
Columns with filterable: true show a filter in the filter row below the header. The kind of filter depends on the column type or on filterType.
Source
vue
<template>
<div class="demo">
<div class="demo-toolbar">
<input v-model="search" type="search" placeholder="Search in all columns…" />
<button @click="filters = [{ column: 'salary', value: { operator: 'greaterThan', value: 100000 } }]">
Salary > 100.000
</button>
<button @click="filters = []; search = ''">Clear filters</button>
</div>
<Datagrid
:columns="columns"
:rows="rows"
:options="options"
v-model:globalFilter="search"
v-model:columnFilter="filters"
/>
<code class="demo-log">{{ JSON.stringify(filters) }}</code>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Datagrid, type ColumnConfig, type GridOptions } from "@datagrid/vue-ui";
import { createEmployees, type Employee } from "./data";
const rows = createEmployees(1000);
const search = ref("");
const filters = ref<Array<{ column: string | number; value: unknown }>>([]);
const columns: Array<ColumnConfig<Employee>> = [
{ field: "firstName", text: "Text" },
{ field: "department", text: "Set filter", filterType: "set" },
{ field: "salary", text: "Number", type: "number" },
{ field: "startDate", text: "Date", type: "date" },
{ field: "remote", text: "Boolean", type: "boolean" },
{
field: "rating",
text: "Custom (≥ value)",
type: "number",
// custom filter function
filter: (value, filterValue) => value >= Number(filterValue),
},
];
const options: GridOptions<Employee> = {
defaultColumn: { filterable: true, sortable: true, flex: 1, minWidth: 140 },
};
</script>tsx
import { useState } from "react";
import {
Datagrid,
type ColumnConfig,
type FilterModel,
type GridOptions,
} from "@datagrid/react-ui";
import { createEmployees, type Employee } from "../data";
const rows = createEmployees(1000);
const columns: Array<ColumnConfig<Employee>> = [
{ field: "firstName", text: "Text" },
{ field: "department", text: "Set filter", filterType: "set" },
{ field: "salary", text: "Number", type: "number" },
{ field: "startDate", text: "Date", type: "date" },
{ field: "remote", text: "Boolean", type: "boolean" },
{
field: "rating",
text: "Custom (≥ value)",
type: "number",
// custom filter function
filter: (value, filterValue) => value >= Number(filterValue),
},
];
const options: GridOptions<Employee> = {
defaultColumn: { filterable: true, sortable: true, flex: 1, minWidth: 140 },
};
export default function Filtering() {
const [search, setSearch] = useState("");
const [filters, setFilters] = useState<FilterModel>([]);
return (
<div className="demo">
<div className="demo-toolbar">
<input
value={search}
onChange={(e) => setSearch(e.target.value)}
type="search"
placeholder="Search in all columns…"
/>
<button onClick={() => setFilters([{ column: "salary", value: { operator: "greaterThan", value: 100000 } }])}>
Salary > 100.000
</button>
<button
onClick={() => {
setFilters([]);
setSearch("");
}}
>
Clear filters
</button>
</div>
<Datagrid
columns={columns}
rows={rows}
options={options}
globalFilter={search}
onGlobalFilterChange={(value) => setSearch(String(value ?? ""))}
columnFilter={filters}
onColumnFilterChange={setFilters}
/>
<code className="demo-log">{JSON.stringify(filters)}</code>
</div>
);
}Filter types
| Filter type | Used for | Floating filter | Default operator |
|---|---|---|---|
text | text columns (default) | input | contains (case insensitive) |
number | type: "number" | input with shorthand syntax | equals |
date | type: "date" | input with shorthand syntax | equals (same day) |
boolean | type: "boolean" | select: all / yes / no | equals |
set | filterType: "set" | button showing the selected values | value in list |
The funnel button next to the input opens the filter popup with operators and a second condition (combined with and / or). Set filters show a searchable list of all distinct values of the column.
Operators
- text: contains, does not contain, equals, does not equal, starts with, ends with, blank, not blank
- number / date: equals, does not equal, less than, less than or equal, greater than, greater than or equal, between, blank, not blank
Restrict the operators of a column with filterParams.operators:
ts
{ field: "age", type: "number", filterParams: { operators: ["greaterThan", "lessThan"] } }Shorthand syntax
Number and date inputs understand comparison operators:
| Input | Meaning |
|---|---|
10 | equals 10 |
>10, >=10, <10, <=10 | comparison |
!=10 or <>10 | does not equal |
10..20 or 10-20 | between 10 and 20 (inclusive) |
>2024-01-01 | date after January 1st 2024 |
Filter options per column
filterParams adapts the filter of a column to your data:
values– fixed values of a set filter in a meaningful order (e.g. the steps of a workflow) instead of the sorted distinct values. Values which don't occur in the rows are listed as well.operators– restricts the operators of the filter popup; the first one is used for the input of the floating filter (here: the title filter uses starts with).debounce– delay in ms until the input is applied,0filters on every keystroke (useful for small client side data sets).
Set filters show the formatted values, so the status codes are displayed as "To do", "In progress", … The grid starts with a filter which hides finished tasks.
Source
vue
<template>
<div class="demo">
<Datagrid :columns="columns" :rows="rows" :options="options" v-model:columnFilter="filters" />
<code class="demo-log">{{ JSON.stringify(filters) }}</code>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Datagrid, type ColumnConfig, type GridOptions } from "@datagrid/vue-ui";
import { createTasks, type Task } from "./data";
const rows = createTasks(300);
// initially hide finished tasks
const filters = ref<Array<{ column: string | number; value: unknown }>>([
{ column: "status", value: ["todo", "in progress", "review"] },
]);
const statusLabels: Record<string, string> = {
todo: "To do",
"in progress": "In progress",
review: "Review",
done: "Done",
};
const columns: Array<ColumnConfig<Task>> = [
{
field: "title",
text: "Title",
flex: 2,
// only two operators, "startsWith" is the default
filterParams: { operators: ["startsWith", "contains"] },
},
{
field: "status",
text: "Status",
filterType: "set",
// fixed values in workflow order instead of the sorted distinct values
filterParams: { values: ["todo", "in progress", "review", "done"] },
// the set filter shows the formatted values
valueFormatter: ({ value }) => statusLabels[value] ?? value,
},
{
field: "priority",
text: "Priority",
filterType: "set",
filterParams: { values: ["high", "medium", "low"] },
},
{
field: "estimate",
text: "Estimate (h)",
type: "number",
// applied immediately while typing
filterParams: { operators: ["greaterThan", "lessThan", "inRange"], debounce: 0 },
},
{ field: "project", text: "Project", filterType: "set" },
];
const options: GridOptions<Task> = {
statusBar: { items: ["rows"] },
defaultColumn: { filterable: true, sortable: true, flex: 1, minWidth: 110 },
};
</script>tsx
import { useState } from "react";
import { Datagrid, type ColumnConfig, type FilterModel, type GridOptions } from "@datagrid/react-ui";
import { createTasks, type Task } from "../data";
const rows = createTasks(300);
const statusLabels: Record<string, string> = {
todo: "To do",
"in progress": "In progress",
review: "Review",
done: "Done",
};
const columns: Array<ColumnConfig<Task>> = [
{
field: "title",
text: "Title",
flex: 2,
// only two operators, "startsWith" is the default
filterParams: { operators: ["startsWith", "contains"] },
},
{
field: "status",
text: "Status",
filterType: "set",
// fixed values in workflow order instead of the sorted distinct values
filterParams: { values: ["todo", "in progress", "review", "done"] },
// the set filter shows the formatted values
valueFormatter: ({ value }) => statusLabels[value] ?? value,
},
{
field: "priority",
text: "Priority",
filterType: "set",
filterParams: { values: ["high", "medium", "low"] },
},
{
field: "estimate",
text: "Estimate (h)",
type: "number",
// applied immediately while typing
filterParams: { operators: ["greaterThan", "lessThan", "inRange"], debounce: 0 },
},
{ field: "project", text: "Project", filterType: "set" },
];
const options: GridOptions<Task> = {
statusBar: { items: ["rows"] },
defaultColumn: { filterable: true, sortable: true, flex: 1, minWidth: 110 },
};
export default function FilterOptions() {
// initially hide finished tasks
const [filters, setFilters] = useState<FilterModel>([
{ column: "status", value: ["todo", "in progress", "review"] },
]);
return (
<div className="demo">
<Datagrid
columns={columns}
rows={rows}
options={options}
columnFilter={filters}
onColumnFilterChange={setFilters}
/>
<code className="demo-log">{JSON.stringify(filters)}</code>
</div>
);
}Filter values
Filters are set by the user, by binding the filter model (v-model:columnFilter in Vue, columnFilter + onColumnFilterChange in React) or with the API. The buttons below set different filter models:
Source
vue
<template>
<div class="demo">
<div class="demo-toolbar">
<button v-for="preset in presets" :key="preset.label" @click="filters = preset.filters">
{{ preset.label }}
</button>
<button @click="filters = []">Clear</button>
</div>
<Datagrid :columns="columns" :rows="rows" :options="options" v-model:columnFilter="filters" />
<code class="demo-log">{{ JSON.stringify(filters) }}</code>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Datagrid, type ColumnConfig, type GridOptions } from "@datagrid/vue-ui";
import { createEmployees, type Employee } from "./data";
type FilterModel = Array<{ column: string | number; value: unknown }>;
const rows = createEmployees(1000);
const filters = ref<FilterModel>([]);
const presets: Array<{ label: string; filters: FilterModel }> = [
{
// primitive values use the default operator of the filter type
label: "Engineering in Berlin",
filters: [
{ column: "department", value: ["Engineering"] },
{ column: "city", value: "Berlin" },
],
},
{
label: "Salary 60–80k",
filters: [{ column: "salary", value: { operator: "inRange", value: 60000, valueTo: 80000 } }],
},
{
// shorthand syntax of number and date filters
label: "Started 2022 or later",
filters: [{ column: "startDate", value: ">=2022-01-01" }],
},
{
// two conditions combined with "or"
label: "Name starts with A or B",
filters: [
{
column: "firstName",
value: {
join: "or",
conditions: [
{ operator: "startsWith", value: "A" },
{ operator: "startsWith", value: "B" },
],
},
},
],
},
];
const columns: Array<ColumnConfig<Employee>> = [
{ field: "firstName", text: "First name" },
{ field: "lastName", text: "Last name" },
{ field: "department", text: "Department", filterType: "set" },
{ field: "city", text: "City" },
{ field: "salary", text: "Salary", type: "number" },
{ field: "startDate", text: "Start date", type: "date" },
];
const options: GridOptions<Employee> = {
statusBar: { items: ["rows"] },
defaultColumn: { filterable: true, sortable: true, flex: 1, minWidth: 130 },
};
</script>tsx
import { useState } from "react";
import { Datagrid, type ColumnConfig, type FilterModel, type GridOptions } from "@datagrid/react-ui";
import { createEmployees, type Employee } from "../data";
const rows = createEmployees(1000);
const presets: Array<{ label: string; filters: FilterModel }> = [
{
// primitive values use the default operator of the filter type
label: "Engineering in Berlin",
filters: [
{ column: "department", value: ["Engineering"] },
{ column: "city", value: "Berlin" },
],
},
{
label: "Salary 60–80k",
filters: [{ column: "salary", value: { operator: "inRange", value: 60000, valueTo: 80000 } }],
},
{
// shorthand syntax of number and date filters
label: "Started 2022 or later",
filters: [{ column: "startDate", value: ">=2022-01-01" }],
},
{
// two conditions combined with "or"
label: "Name starts with A or B",
filters: [
{
column: "firstName",
value: {
join: "or",
conditions: [
{ operator: "startsWith", value: "A" },
{ operator: "startsWith", value: "B" },
],
},
},
],
},
];
const columns: Array<ColumnConfig<Employee>> = [
{ field: "firstName", text: "First name" },
{ field: "lastName", text: "Last name" },
{ field: "department", text: "Department", filterType: "set" },
{ field: "city", text: "City" },
{ field: "salary", text: "Salary", type: "number" },
{ field: "startDate", text: "Start date", type: "date" },
];
const options: GridOptions<Employee> = {
statusBar: { items: ["rows"] },
defaultColumn: { filterable: true, sortable: true, flex: 1, minWidth: 130 },
};
export default function FilterPresets() {
const [filters, setFilters] = useState<FilterModel>([]);
return (
<div className="demo">
<div className="demo-toolbar">
{presets.map((preset) => (
<button key={preset.label} onClick={() => setFilters(preset.filters)}>
{preset.label}
</button>
))}
<button onClick={() => setFilters([])}>Clear</button>
</div>
<Datagrid columns={columns} rows={rows} options={options} columnFilter={filters} onColumnFilterChange={setFilters} />
<code className="demo-log">{JSON.stringify(filters)}</code>
</div>
);
}A filter value can be:
ts
// primitive: default operator of the filter type
{ column: "name", value: "ann" }
{ column: "age", value: ">30" }
// condition
{ column: "age", value: { operator: "inRange", value: 20, valueTo: 30 } }
// combined conditions
{
column: "name",
value: {
join: "or",
conditions: [
{ operator: "startsWith", value: "A" },
{ operator: "startsWith", value: "B" },
],
},
}
// set filter: array or { values }
{ column: "country", value: { values: ["Germany", "Austria"] } }
{ column: "country", value: ["Germany", "Austria"] }Custom filter function
A filter function decides for each row if it is visible. It receives the cell value, the filter value and the row:
ts
{
field: "tags",
filterable: true,
filter: (value, filterValue, row) => value.includes(filterValue),
}The filter value can be of any type, e.g. when used with a custom filter component.
Custom filter components
When the built-in inputs don't fit, replace the floating filter with your own component and combine it with a filter function. The filter value can be anything – here an object { min, max } for the salary range and the minimum number of stars for the rating. Calling setValue(undefined) removes the filter.
The slot #filter-<columnId> receives value, setValue(value), column and api. Alternatively set a component with filterComponent in the column definition, it gets modelValue and column and emits update:modelValue.
filterRenderers contains a render function per column id, it receives value, setValue(value), column and api.
Source
vue
<template>
<Datagrid :columns="columns" :rows="rows" :options="options">
<!-- range filter: the filter value is an object { min, max } -->
<template #filter-salary="{ value, setValue }">
<div class="fc-range">
<input type="number" placeholder="min" :value="value?.min" @input="setValue(range(value, 'min', $event))" />
<input type="number" placeholder="max" :value="value?.max" @input="setValue(range(value, 'max', $event))" />
</div>
</template>
<!-- star filter: the filter value is the minimum rating -->
<template #filter-rating="{ value, setValue }">
<div class="fc-stars">
<button
v-for="n in 5"
:key="n"
type="button"
:class="{ active: value != null && n <= value }"
:title="`at least ${n} stars`"
@click="setValue(value === n ? undefined : n)"
>
★
</button>
</div>
</template>
</Datagrid>
</template>
<script setup lang="ts">
import { Datagrid, formatters, type ColumnConfig, type GridOptions } from "@datagrid/vue-ui";
import { createEmployees, type Employee } from "./data";
type Range = { min?: number; max?: number };
const rows = createEmployees(500);
// returns the new filter value, undefined removes the filter
function range(value: Range | undefined, key: keyof Range, event: Event): Range | undefined {
const text = (event.target as HTMLInputElement).value;
const next = { ...value, [key]: text === "" ? undefined : Number(text) };
return next.min == null && next.max == null ? undefined : next;
}
const columns: Array<ColumnConfig<Employee>> = [
{ field: "firstName", text: "First name" },
{ field: "lastName", text: "Last name" },
{
field: "salary",
text: "Salary",
type: "number",
minWidth: 190,
valueFormatter: formatters.currency("EUR", { maximumFractionDigits: 0 }),
// custom filter functions decide how the filter value is applied
filter: (value: number, filter: Range) =>
(filter.min == null || value >= filter.min) && (filter.max == null || value <= filter.max),
},
{
field: "rating",
text: "Rating",
type: "number",
minWidth: 130,
filter: (value: number, minRating: number) => value >= minRating,
},
];
const options: GridOptions<Employee> = {
statusBar: { items: ["rows"] },
defaultColumn: { filterable: true, sortable: true, flex: 1, minWidth: 110 },
};
</script>
<style>
.fc-range {
display: flex;
gap: 4px;
width: 100%;
}
.fc-range input {
width: 50%;
min-width: 0;
height: 26px;
padding: 0 6px;
border: 1px solid var(--ut-input-border-color);
border-radius: 4px;
background: var(--ut-input-background);
color: inherit;
font: inherit;
}
.fc-stars button {
padding: 0 1px;
font-size: 18px;
line-height: 1;
color: var(--ut-border-color);
cursor: pointer;
}
.fc-stars button.active {
color: #f5a524;
}
</style>tsx
import {
Datagrid,
formatters,
type ColumnConfig,
type DatagridProps,
type GridOptions,
} from "@datagrid/react-ui";
import { createEmployees, type Employee } from "../data";
type Range = { min?: number; max?: number };
const rows = createEmployees(500);
// returns the new filter value, undefined removes the filter
function range(value: Range | undefined, key: keyof Range, text: string): Range | undefined {
const next = { ...value, [key]: text === "" ? undefined : Number(text) };
return next.min == null && next.max == null ? undefined : next;
}
const columns: Array<ColumnConfig<Employee>> = [
{ field: "firstName", text: "First name" },
{ field: "lastName", text: "Last name" },
{
field: "salary",
text: "Salary",
type: "number",
minWidth: 190,
valueFormatter: formatters.currency("EUR", { maximumFractionDigits: 0 }),
// custom filter functions decide how the filter value is applied
filter: (value: number, filter: Range) =>
(filter.min == null || value >= filter.min) && (filter.max == null || value <= filter.max),
},
{
field: "rating",
text: "Rating",
type: "number",
minWidth: 130,
filter: (value: number, minRating: number) => value >= minRating,
},
];
const options: GridOptions<Employee> = {
statusBar: { items: ["rows"] },
defaultColumn: { filterable: true, sortable: true, flex: 1, minWidth: 110 },
};
const filterRenderers: DatagridProps<Employee>["filterRenderers"] = {
// range filter: the filter value is an object { min, max }
salary: ({ value, setValue }) => {
const current = value as Range | undefined;
return (
<div className="fc-range">
<input
type="number"
placeholder="min"
value={current?.min ?? ""}
onChange={(e) => setValue(range(current, "min", e.target.value))}
/>
<input
type="number"
placeholder="max"
value={current?.max ?? ""}
onChange={(e) => setValue(range(current, "max", e.target.value))}
/>
</div>
);
},
// star filter: the filter value is the minimum rating
rating: ({ value, setValue }) => (
<div className="fc-stars">
{[1, 2, 3, 4, 5].map((n) => (
<button
key={n}
type="button"
className={value != null && n <= (value as number) ? "active" : undefined}
title={`at least ${n} stars`}
onClick={() => setValue(value === n ? undefined : n)}
>
★
</button>
))}
</div>
),
};
export default function FilterCustom() {
return (
<>
<Datagrid columns={columns} rows={rows} options={options} filterRenderers={filterRenderers} />
<style>{`
.fc-range {
display: flex;
gap: 4px;
width: 100%;
}
.fc-range input {
width: 50%;
min-width: 0;
height: 26px;
padding: 0 6px;
border: 1px solid var(--ut-input-border-color);
border-radius: 4px;
background: var(--ut-input-background);
color: inherit;
font: inherit;
}
.fc-stars button {
padding: 0 1px;
font-size: 18px;
line-height: 1;
color: var(--ut-border-color);
cursor: pointer;
}
.fc-stars button.active {
color: #f5a524;
}
`}</style>
</>
);
}Computed columns
Filters work on the value of a column, so columns with a valueGetter can be filtered like any other column: Total pay is a number filter on salary + bonus (the number is compared, not the formatted currency text), Seniority a set filter on a category computed from the start date. Computed columns need an id, which is also used in the filter model.
Source
vue
<template>
<div class="demo">
<div class="demo-toolbar">
<button @click="filters = [{ column: 'totalPay', value: '>120000' }]">Total pay > 120.000</button>
<button @click="filters = [{ column: 'seniority', value: ['Veteran'] }]">Veterans</button>
<button @click="filters = [{ column: 'name', value: 'anna m' }]">Name contains “anna m”</button>
<button @click="filters = []">Clear</button>
</div>
<Datagrid :columns="columns" :rows="rows" :options="options" v-model:columnFilter="filters" />
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Datagrid, formatters, type ColumnConfig, type GridOptions } from "@datagrid/vue-ui";
import { createEmployees, TODAY, type Employee } from "./data";
const rows = createEmployees(500);
const filters = ref<Array<{ column: string | number; value: unknown }>>([]);
const years = (data: Employee) =>
Math.floor((Date.parse(TODAY) - Date.parse(data.startDate)) / (365.25 * 86400000));
const columns: Array<ColumnConfig<Employee>> = [
// computed columns need an id, the filters use the value of the valueGetter
{ id: "name", text: "Name", valueGetter: ({ data }) => `${data.firstName} ${data.lastName}` },
{
id: "totalPay",
text: "Total pay",
type: "number",
valueGetter: ({ data }) => data.salary + data.bonus,
// the number filter compares numbers, not the formatted text
valueFormatter: formatters.currency("EUR", { maximumFractionDigits: 0 }),
},
{ id: "years", text: "Years", type: "number", valueGetter: ({ data }) => years(data) },
{
id: "seniority",
text: "Seniority",
filterType: "set",
valueGetter: ({ data }) => {
const value = years(data);
return value < 3 ? "New" : value < 10 ? "Experienced" : "Veteran";
},
},
];
const options: GridOptions<Employee> = {
statusBar: { items: ["rows"] },
defaultColumn: { filterable: true, sortable: true, flex: 1 },
};
</script>tsx
import { useState } from "react";
import { Datagrid, formatters, type ColumnConfig, type FilterModel, type GridOptions } from "@datagrid/react-ui";
import { createEmployees, TODAY, type Employee } from "../data";
const rows = createEmployees(500);
const years = (data: Employee) =>
Math.floor((Date.parse(TODAY) - Date.parse(data.startDate)) / (365.25 * 86400000));
const columns: Array<ColumnConfig<Employee>> = [
// computed columns need an id, the filters use the value of the valueGetter
{ id: "name", text: "Name", valueGetter: ({ data }) => `${data.firstName} ${data.lastName}` },
{
id: "totalPay",
text: "Total pay",
type: "number",
valueGetter: ({ data }) => data.salary + data.bonus,
// the number filter compares numbers, not the formatted text
valueFormatter: formatters.currency("EUR", { maximumFractionDigits: 0 }),
},
{ id: "years", text: "Years", type: "number", valueGetter: ({ data }) => years(data) },
{
id: "seniority",
text: "Seniority",
filterType: "set",
valueGetter: ({ data }) => {
const value = years(data);
return value < 3 ? "New" : value < 10 ? "Experienced" : "Veteran";
},
},
];
const options: GridOptions<Employee> = {
statusBar: { items: ["rows"] },
defaultColumn: { filterable: true, sortable: true, flex: 1 },
};
export default function FilterComputed() {
const [filters, setFilters] = useState<FilterModel>([]);
return (
<div className="demo">
<div className="demo-toolbar">
<button onClick={() => setFilters([{ column: "totalPay", value: ">120000" }])}>Total pay > 120.000</button>
<button onClick={() => setFilters([{ column: "seniority", value: ["Veteran"] }])}>Veterans</button>
<button onClick={() => setFilters([{ column: "name", value: "anna m" }])}>Name contains “anna m”</button>
<button onClick={() => setFilters([])}>Clear</button>
</div>
<Datagrid
columns={columns}
rows={rows}
options={options}
columnFilter={filters}
onColumnFilterChange={setFilters}
/>
</div>
);
}Quick filter
The quick filter (global filter) searches all columns. Every word must be found in at least one column ("anna berlin" finds rows containing both words). It uses the formatted cell text, so users find what they see.
Source
vue
<template>
<div class="demo">
<div class="demo-toolbar">
<input v-model="search" type="search" placeholder="Try “anna berlin” or “senior sales”" />
</div>
<Datagrid :columns="columns" :rows="rows" :options="options" v-model:globalFilter="search" />
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Datagrid, formatters, type ColumnConfig, type GridOptions } from "@datagrid/vue-ui";
import { createEmployees, type Employee } from "./data";
const rows = createEmployees(1000);
const search = ref("");
const columns: Array<ColumnConfig<Employee>> = [
{ field: "firstName", text: "First name" },
{ field: "lastName", text: "Last name" },
{ field: "role", text: "Role" },
{ field: "department", text: "Department" },
{ field: "city", text: "City" },
// the quick filter searches the formatted text, "85,000" finds this cell
{ field: "salary", text: "Salary", valueFormatter: formatters.currency("EUR", { maximumFractionDigits: 0 }) },
];
const options: GridOptions<Employee> = {
// row count "12 of 1,000" while filtered
statusBar: { items: ["rows"] },
defaultColumn: { flex: 1, sortable: true },
};
</script>tsx
import { useState } from "react";
import { Datagrid, formatters, type ColumnConfig, type GridOptions } from "@datagrid/react-ui";
import { createEmployees, type Employee } from "../data";
const rows = createEmployees(1000);
const columns: Array<ColumnConfig<Employee>> = [
{ field: "firstName", text: "First name" },
{ field: "lastName", text: "Last name" },
{ field: "role", text: "Role" },
{ field: "department", text: "Department" },
{ field: "city", text: "City" },
// the quick filter searches the formatted text, "85,000" finds this cell
{ field: "salary", text: "Salary", valueFormatter: formatters.currency("EUR", { maximumFractionDigits: 0 }) },
];
const options: GridOptions<Employee> = {
// row count "12 of 1,000" while filtered
statusBar: { items: ["rows"] },
defaultColumn: { flex: 1, sortable: true },
};
export default function FilterQuick() {
const [search, setSearch] = useState("");
return (
<div className="demo">
<div className="demo-toolbar">
<input
value={search}
onChange={(e) => setSearch(e.target.value)}
type="search"
placeholder="Try “anna berlin” or “senior sales”"
/>
</div>
<Datagrid columns={columns} rows={rows} options={options} globalFilter={search} onGlobalFilterChange={(value) => setSearch(String(value ?? ""))} />
</div>
);
}vue
<input v-model="search" type="search" />
<Datagrid v-model:globalFilter="search" ... />tsx
const [search, setSearch] = useState("");
<input value={search} onChange={(e) => setSearch(e.target.value)} type="search" />
<Datagrid globalFilter={search} onGlobalFilterChange={(value) => setSearch(String(value ?? ""))} ... />Replace the matching logic with the grid option filterFn:
ts
const options: GridOptions<Person> = {
filterFn: (rows, columns, search) => rows.filter((row) => fuzzyMatch(row.data, search)),
};Tree data
Filters are applied to all levels of tree data. A parent row stays visible if one of its children matches.
Try the buttons: filtering the type "vue" keeps the folders src and components, because they contain Vue files, although the folders themselves don't match.
Source
vue
<template>
<div class="demo">
<div class="demo-toolbar">
<button @click="filters = [{ column: 'type', value: ['vue'] }]">Vue files</button>
<button @click="filters = [{ column: 'size', value: '>2000' }]">Larger than 2 KB</button>
<button @click="filters = [{ column: 'modified', value: '<2024-02-01' }]">Modified before February</button>
<button @click="filters = []">Clear</button>
</div>
<Datagrid
:columns="columns"
:rows="rows"
:options="options"
v-model:columnFilter="filters"
@ready="(api) => api.expandAll()"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Datagrid, type ColumnConfig, type GridOptions } from "@datagrid/vue-ui";
import { createFileTree, type FileNode } from "./data";
const rows = createFileTree();
const filters = ref<Array<{ column: string | number; value: unknown }>>([]);
const columns: Array<ColumnConfig<FileNode>> = [
{ field: "name", text: "Name", width: 240 },
{ field: "type", text: "Type", filterType: "set" },
{
field: "size",
text: "Size",
type: "number",
valueFormatter: ({ value }) => (value ? `${(value / 1024).toFixed(1)} KB` : ""),
},
{ field: "modified", text: "Modified", type: "date" },
];
const options: GridOptions<FileNode> = {
// parent rows stay visible if one of their children matches
defaultColumn: { filterable: true, sortable: true, flex: 1 },
};
</script>tsx
import { useState } from "react";
import { Datagrid, type ColumnConfig, type FilterModel, type GridOptions } from "@datagrid/react-ui";
import { createFileTree, type FileNode } from "../data";
const rows = createFileTree();
const columns: Array<ColumnConfig<FileNode>> = [
{ field: "name", text: "Name", width: 240 },
{ field: "type", text: "Type", filterType: "set" },
{
field: "size",
text: "Size",
type: "number",
valueFormatter: ({ value }) => (value ? `${(value / 1024).toFixed(1)} KB` : ""),
},
{ field: "modified", text: "Modified", type: "date" },
];
const options: GridOptions<FileNode> = {
// parent rows stay visible if one of their children matches
defaultColumn: { filterable: true, sortable: true, flex: 1 },
};
export default function FilterTree() {
const [filters, setFilters] = useState<FilterModel>([]);
return (
<div className="demo">
<div className="demo-toolbar">
<button onClick={() => setFilters([{ column: "type", value: ["vue"] }])}>Vue files</button>
<button onClick={() => setFilters([{ column: "size", value: ">2000" }])}>Larger than 2 KB</button>
<button onClick={() => setFilters([{ column: "modified", value: "<2024-02-01" }])}>Modified before February</button>
<button onClick={() => setFilters([])}>Clear</button>
</div>
<Datagrid
columns={columns}
rows={rows}
options={options}
columnFilter={filters}
onColumnFilterChange={setFilters}
onReady={(api) => api.expandAll()}
/>
</div>
);
}API
ts
api.setColumnFilter("age", ">30");
api.getColumnFilter("age"); // { column, value }
api.clearColumnFilter("age");
api.setColumnFilters([{ column: "a", value: "x" }, { column: "b", value: 1 }]); // replaces all filters
api.getColumnFilters();
api.setGlobalFilter("berlin");
api.clearFilters(); // column filters and quick filter
api.getDistinctValues("country"); // values for set filtersOptions
| Option | Description |
|---|---|
filterable (column) | shows the filter |
filterType (column) | "text", "number", "date", "set" or "boolean" |
filterParams.operators (column) | available operators, the first one is the default |
filterParams.values (column) | values of the set filter (array or function), default: distinct values of the column |
filterParams.debounce (column) | debounce time of the input in ms (default 300) |
filter (column) | custom filter function |
filterComponent (column) | custom filter component |
filterFn (grid) | custom quick filter function |
filterRowHeight (grid) | height of the filter row |