Filter Header
FilterHeaderPlugin places common filter controls directly below each column label. Use it when users need fast, always-visible filtering; the normal filter icon remains available for the complete advanced-filter popup.
Source code
// src/components/filter-header/FilterAdvanced.ts
import { defineCustomElements } from '@revolist/revogrid/loader';
import {
AdvanceFilterPlugin,
ColumnStretchPlugin,
FilterHeaderPlugin,
RowOddPlugin,
RowSelectPlugin,
SameValueMergePlugin,
} from '@revolist/revogrid-pro';
defineCustomElements();
import { currentTheme } from '../composables/useRandomData';
import { makeData } from '../composables/makeData';
const { isDark } = currentTheme();
export function load(parentSelector: string, rows = makeData(100)) {
const grid = document.createElement('revo-grid');
grid.columns = [
{
name: 'Superheroes',
children: [
{
name: 'First Name',
prop: 'firstName',
filter: true,
sortable: true,
rowSelect: true,
columnTemplate: (h) =>
h(
'span',
{ style: { fontWeight: 'bold', color: '#0066cc' } },
'Custom First Name',
),
},
{
name: 'Last Name',
prop: 'lastName',
filter: ['selection'],
sortable: true,
},
],
},
{
name: 'Params',
children: [
{
name: 'Gender',
prop: 'gender',
filter: ['selection'],
sortable: true,
},
{
name: 'Age',
prop: 'age',
filter: ['number', 'slider'],
sortable: true,
},
],
},
];
grid.plugins = [
AdvanceFilterPlugin,
FilterHeaderPlugin,
ColumnStretchPlugin,
SameValueMergePlugin,
RowSelectPlugin,
RowOddPlugin,
];
grid.stretch = 'all';
grid.filter = {
localization: {
captions: {
selectionAll: 'Any value',
},
},
slider: {
showRangeInputs: true,
},
};
grid.theme = isDark() ? 'darkMaterial' : 'material';
grid.hideAttribution = true;
grid.resize = true;
document.querySelector(parentSelector)?.appendChild(grid);
grid.source = rows;
return () => grid.remove();
}
// src/components/filter-header/FilterAdvanced.vue
<template>
<VGrid
class="grow h-full cell-border"
:theme="isDark ? 'darkMaterial' : 'material'"
:columns="columns"
:source="rows"
:plugins="plugins"
stretch="all"
:filter="filter"
style="min-height: 400px"
hide-attribution
resize
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { currentThemeVue } from '../composables/useRandomData';
import {
type ColumnGrouping,
type ColumnRegular,
VGrid,
} from '@revolist/vue3-datagrid';
import {
AdvanceFilterPlugin,
FilterHeaderPlugin,
ColumnStretchPlugin,
SameValueMergePlugin,
RowSelectPlugin,
RowOddPlugin,
} from '@revolist/revogrid-pro';
import { makeData } from '../composables/makeData';
const { isDark } = currentThemeVue();
const columns = ref<(ColumnRegular | ColumnGrouping)[]>([
{
name: 'Superheroes',
children: [
{
name: 'First Name',
prop: 'firstName',
filter: true,
sortable: true,
rowSelect: true,
columnTemplate: (h) =>
h(
'span',
{ style: { fontWeight: 'bold', color: '#0066cc' } },
'Custom First Name',
),
},
{
name: 'Last Name',
prop: 'lastName',
filter: ['selection'],
sortable: true,
},
],
},
{
name: 'Params',
children: [
{
name: 'Gender',
prop: 'gender',
filter: ['selection'],
sortable: true,
},
{
name: 'Age',
prop: 'age',
filter: ['number', 'slider'],
sortable: true,
},
],
},
]);
const plugins = [
AdvanceFilterPlugin,
FilterHeaderPlugin,
ColumnStretchPlugin,
SameValueMergePlugin,
RowSelectPlugin,
RowOddPlugin,
];
const rows = ref(makeData(100));
const filter = ref({
localization: {
captions: {
selectionAll: 'Any value',
},
},
slider: {
showRangeInputs: true,
},
});
</script>
// src/components/filter-header/FilterAdvanced.tsx
import { useMemo, useState } from 'react';
import {
RevoGrid,
type ColumnGrouping,
type ColumnRegular,
type DataType,
} from '@revolist/react-datagrid';
import {
AdvanceFilterPlugin,
ColumnStretchPlugin,
FilterHeaderPlugin,
RowOddPlugin,
RowSelectPlugin,
SameValueMergePlugin,
} from '@revolist/revogrid-pro';
import { currentTheme } from '../composables/useRandomData';
import { makeData } from '../composables/makeData';
type FilterAdvancedProps = {
rows?: DataType[];
};
function FilterAdvanced({ rows }: FilterAdvancedProps) {
const { isDark } = currentTheme();
const [source] = useState<DataType[]>(() => rows ?? makeData(100));
const filter = useMemo(
() => ({
localization: {
captions: {
selectionAll: 'Any value',
},
},
slider: {
showRangeInputs: true,
},
}),
[],
);
const plugins = useMemo(
() => [
AdvanceFilterPlugin,
FilterHeaderPlugin,
ColumnStretchPlugin,
SameValueMergePlugin,
RowSelectPlugin,
RowOddPlugin,
],
[],
);
const columns = useMemo<(ColumnRegular | ColumnGrouping)[]>(
() => [
{
name: 'Superheroes',
children: [
{
name: 'First Name',
prop: 'firstName',
filter: true,
sortable: true,
rowSelect: true,
columnTemplate: (h) =>
h(
'span',
{ style: { fontWeight: 'bold', color: '#0066cc' } },
'Custom First Name',
),
},
{
name: 'Last Name',
prop: 'lastName',
filter: ['selection'],
sortable: true,
},
],
},
{
name: 'Params',
children: [
{
name: 'Gender',
prop: 'gender',
filter: ['selection'],
sortable: true,
},
{
name: 'Age',
prop: 'age',
filter: ['number', 'slider'],
sortable: true,
},
],
},
],
[],
);
return (
<RevoGrid
plugins={plugins}
className="grow h-full cell-border"
columns={columns}
source={source}
stretch="all"
filter={filter}
hide-attribution
theme={isDark() ? 'darkMaterial' : 'material'}
style={{ minHeight: 400 }}
resize
/>
);
}
export default FilterAdvanced;
// src/components/filter-header/FilterAdvancedAngular.ts
import { Component, ViewEncapsulation } from '@angular/core';
import { RevoGrid } from '@revolist/angular-datagrid';
import {
AdvanceFilterPlugin,
ColumnStretchPlugin,
FilterHeaderPlugin,
RowOddPlugin,
RowSelectPlugin,
SameValueMergePlugin,
} from '@revolist/revogrid-pro';
import { currentTheme } from '../composables/useRandomData';
import { makeData } from '../composables/makeData';
@Component({
selector: 'filter-header-grid',
standalone: true,
imports: [RevoGrid],
template: ` <revo-grid
[plugins]="plugins"
class="grow h-full cell-border"
[columns]="columns"
[source]="source"
stretch="all"
[filter]="filter"
[hideAttribution]="true"
[theme]="theme"
[resize]="true"
style="min-height: 400px;"
></revo-grid>`,
encapsulation: ViewEncapsulation.None,
})
export class FilterHeaderGridComponent {
theme = currentTheme().isDark() ? 'darkMaterial' : 'material';
source = makeData(100);
columns = [
{
name: 'Superheroes',
children: [
{
name: 'First Name',
prop: 'firstName',
filter: true,
sortable: true,
rowSelect: true,
columnTemplate: (h) =>
h(
'span',
{ style: { fontWeight: 'bold', color: '#0066cc' } },
'Custom First Name',
),
},
{
name: 'Last Name',
prop: 'lastName',
filter: ['selection'],
sortable: true,
},
],
},
{
name: 'Params',
children: [
{
name: 'Gender',
prop: 'gender',
filter: ['selection'],
sortable: true,
},
{
name: 'Age',
prop: 'age',
filter: ['number', 'slider'],
sortable: true,
},
],
},
];
filter = {
localization: {
captions: {
selectionAll: 'Any value',
},
},
slider: {
showRangeInputs: true,
},
};
plugins = [
AdvanceFilterPlugin,
FilterHeaderPlugin,
ColumnStretchPlugin,
SameValueMergePlugin,
RowSelectPlugin,
RowOddPlugin,
];
}
Quick start
Section titled “Quick start”Register FilterHeaderPlugin, then configure filter families on the columns. It installs AdvanceFilterPlugin automatically:
import { FilterHeaderPlugin,} from '@revolist/revogrid-pro';
grid.plugins = [FilterHeaderPlugin];grid.columns = [ { name: 'Name', prop: 'name', filter: 'string' }, { name: 'Status', prop: 'status', filter: ['selection'] }, { name: 'Age', prop: 'age', filter: ['number', 'slider'] }, { name: 'Tags', prop: 'tags', filter: ['array'] },];Text columns receive a debounced input. Popup-style and registered structured filters receive a compact trigger. Slider columns receive either an inline range control or a formatted range summary.
| Column filter family | Header control |
|---|---|
| Text and number | Debounced inline input |
| Selection | Popup trigger with included-value summary |
| Date | Popup trigger with active-state indicator |
| Boolean | Popup trigger for the valueless Yes/No operators |
| Array | Popup trigger for empty/non-empty array operators |
| Slider | Inline range control or formatted range summary |
| Structured types | Popup trigger using the type’s readable summary |
Slider headers
Section titled “Slider headers”A column containing the slider filter type renders the shared range slider directly in the header. The regular filter icon remains available for opening the full advanced-filter popup.
const columns = [{ name: 'Age', prop: 'age', filter: ['number', 'slider'] }];Moving either handle applies the range immediately. Returning both handles to the full data range clears the slider filter.
Popup trigger headers
Section titled “Popup trigger headers”For selection, date, boolean, and array popup filters, the header renders a popup trigger instead of a free text input. Clicking it opens the same advanced-filter popup available from the normal filter icon.
Selection triggers show All while no values are excluded. Opening a selection popup and closing it without changes keeps the header value as All; it does not expand to every selected item.
Customize that fallback through the standard advanced-filter localization captions:
const filter = { localization: { captions: { selectionAll: 'Any value', }, },};When values are unchecked in the selection popup, the trigger shows a compact selected/total badge such as 8/9. This is the default presentation and requires no additional configuration. Hover the badge to see the full selected-value summary. By default, that summary includes the number of matching rows for each value; set hideFilterHeaderCount on the column to hide those row counts.
Date triggers become active when a date condition has a value. Boolean and array triggers become active when one of their valueless operators is applied. Array support requires the array family in the column’s filter array:
grid.columns = [{ name: 'Tags', prop: 'tags', filter: ['array'] }];The array popup offers Is empty array and Is not empty array. See Array Filter for their strict array-value behavior.
Custom header presentation
Section titled “Custom header presentation”Every popup-style filter uses the same FilterHeaderTemplateFunc contract. A column template has the highest priority:
import type { FilterHeaderTemplateFunc } from '@revolist/revogrid-pro';
const compactStatusHeader: FilterHeaderTemplateFunc = (h, { presentation }) => ( <span class={{ 'status-summary': true, active: presentation.active }}> {presentation.summary} </span>);
const columns = [{ name: 'Status', prop: 'status', filter: ['selection'], filterHeaderTemplate: compactStatusHeader,}];The grid owns the actual button, focus behavior, tooltip, keyboard activation, and accessible name. A template only renders non-interactive visual content inside that button. Use presentation.summary for compact text and presentation.details for the full human-readable meaning. Returning undefined keeps the standard text or count fallback.
A custom registered structured type may provide the same template once for every column using that type:
const temperatureFilter: StructuredFilterType = { id: 'temperatureRange', operatorIds: ['temperatureBetween'], render: context => <TemperatureEditor {...context} />, describeCondition: ({ condition }) => ({ summary: `${condition.value.min}–${condition.value.max} °C`, details: `Temperature from ${condition.value.min} to ${condition.value.max} °C`, }), headerControl: { kind: 'popup', template: (h, { presentation }) => ( <span>{presentation.summary}</span> ), },};Use kind: 'popup' when the filter needs custom badge, star, or summary content inside the grid-owned trigger. Use a headerControl resolver with kind: 'inline' when the type needs an interactive control such as a range slider. The resolver receives the same controlled body context as the popup, including aggregates and replaceConditions(), while the grid owns the outer non-sorting interaction boundary.
describeCondition is the source of truth for filter badges, grouped-filter text, header labels, and accessibility text. Keep it data-independent: it receives the condition and column configuration, not grid rows. This makes compact presentation safe for remote and very large datasets. A column-level filterHeaderTemplate overrides a type-owned popup template.
The template props also expose the column, canonical conditions, selection compatibility values, and the complete presentation object. The former top-level active and text fields remain as compatibility aliases; new code should use presentation.
Localize the surrounding accessible text with the normal filter captions:
const filter = { localization: { captions: { filterHeaderLabel: column => `Filter ${column.name ?? column.prop}`, filterHeaderSelectionSummary: (selected, total, details) => `${selected} of ${total} selected${details ? `: ${details}` : ''}`, }, },};When to use the popup instead
Section titled “When to use the popup instead”Header controls prioritize quick changes and limited space. Keep the normal popup workflow for multiple conditions, expressions, Excel-style staged selection, grouped AST editing, or any control that needs explanatory content.