Skip to content

Filtering Plugin

The Filtering plugin adds column header filters with text search, dropdown options, and custom filter panels. It supports both local filtering for small datasets and async handlers for server-side filtering on large datasets.

import '@toolbox-web/grid/features/filtering';
import { queryGrid } from '@toolbox-web/grid';
import '@toolbox-web/grid/features/filtering';
const grid = queryGrid('tbw-grid');
grid.gridConfig = {
columns: [
{ field: 'name', header: 'Name', filterable: true },
{ field: 'status', header: 'Status', filterable: true },
{ field: 'email', header: 'Email', filterable: true },
],
features: { filtering: { debounceMs: 300 } },
};
grid.rows = data;
Debounce (ms) Filter input debounce delay
Case sensitive Match case when filtering

Type in column header filter inputs to filter rows. The plugin debounces input to avoid excessive re-filtering while typing.

The filterPanelRenderer option lets you replace the default filter panel with custom UI. When the renderer returns undefined, the default panel is used for that column.

Your custom renderer receives a params object with:

Properties

PropertyTypeDescription
fieldstringThe field being filtered
columnColumnConfigColumn configuration
uniqueValuesunknown[]All unique values for this field
excludedValuesSet<unknown>Currently excluded values (set filter)
searchTextstringCurrent search text
currentFilterFilterModel?Active filter model for this field (if any) — use to pre-populate custom UI

Methods

MethodSignatureDescription
applySetFilter(excluded: unknown[], valueTo?: unknown) => voidApply a set filter; optional valueTo stores metadata alongside the filter
applyTextFilter(op: FilterOperator, val: string | number, valueTo?: string | number) => voidApply a text/number/date filter with operator
clearFilter() => voidClear the filter for this field
closePanel() => voidClose the filter panel
import { queryGrid } from '@toolbox-web/grid';
const grid = queryGrid('tbw-grid');
grid.gridConfig = {
features: {
filtering: {
filterPanelRenderer: (container, params) => {
// Custom panel only for 'status' column
if (params.field !== 'status') return undefined;
const activeValue = params.currentFilter?.value;
container.innerHTML = `
<div style="padding: 8px;">
<label><input type="radio" name="status" value="all" ${!activeValue ? 'checked' : ''}> All</label>
${params.uniqueValues
.map((v) => `<label><input type="radio" name="status" value="${v}" ${v === activeValue ? 'checked' : ''}> ${v}</label>`)
.join('')}
</div>
`;
container.querySelectorAll('input').forEach((input) => {
input.addEventListener('change', () => {
if (input.value === 'all') {
params.clearFilter();
} else {
const excluded = params.uniqueValues.filter((v) => v !== input.value);
params.applySetFilter(excluded);
}
params.closePanel();
});
});
},
},
},
};

The Filtering plugin automatically displays appropriate filter UIs based on column type:

Column TypeFilter UIDescription
numberRange SliderDual-thumb slider with min/max inputs. Includes a “Blank” checkbox to filter rows with no value.
dateDate Range PickerFrom/to date inputs with range selection. Includes a “Show only blank” checkbox for empty dates.
(other)Checkbox SetStandard multi-select with search. Rows with null/undefined/empty values appear as a (Blank) entry.

Configure bounds and step via filterParams on the column:

const columns = [
{
field: 'salary',
header: 'Salary',
type: 'number',
filterParams: { min: 50000, max: 150000, step: 5000 },
},
{
field: 'hireDate',
header: 'Hire Date',
type: 'date',
filterParams: { min: '2015-01-01', max: '2025-12-31' },
},
];

If filterParams is not provided, the plugin auto-detects min/max from the data.

For large or server-side datasets, the default local filtering may not be suitable:

  • Not all unique values are available locally for the filter panel
  • Filtering should happen on the backend rather than in the browser

Use valuesHandler and filterHandler to customize this behavior:

grid.gridConfig = {
columns: [...],
features: {
filtering: {
// Fetch unique values for a column from the server
valuesHandler: async (field, column) => {
const response = await fetch(`/api/distinct-values?field=${field}`);
return response.json(); // Returns: ['Engineering', 'Marketing', ...]
},
// Apply filters on the server
// FilterModel: { field, type, operator: 'notIn', value: excludedValues[] }
filterHandler: async (filters, currentRows) => {
const response = await fetch('/api/data', {
method: 'POST',
body: JSON.stringify({ filters }),
});
return response.json();
},
},
},
};

Handler signatures:

HandlerSignatureReturns
valuesHandler(field: string, column: ColumnConfig) => Promise<T[]>Unique values for filter
filterHandler(filters: FilterModel[], rows: T[]) => T[] | Promise<T[]>Filtered rows

When valuesHandler is provided, the filter panel shows a loading indicator while fetching values. When filterHandler is provided, filter application bypasses the local processRows hook.

You can disable filtering grid-wide using gridConfig.filterable:

grid.gridConfig = {
filterable: false, // Disables ALL filtering, even on filterable columns
features: { filtering: true },
};

This is useful for toggling filtering on/off at runtime without removing the plugin.

OptionTypeDefaultDescription
debounceMsnumber300Debounce delay for filter input
caseSensitivebooleanfalseCase-sensitive string matching
trimInputbooleantrueTrim whitespace from filter input
useWorkerbooleantrueUse Web Worker for datasets >1000 rows
trackColumnStatebooleanfalseInclude filter state in column state persistence
filterPanelRendererFilterPanelRenderer-Custom filter panel renderer
valuesHandlerFilterValuesHandler-Async handler to fetch unique filter values
filterHandlerFilterHandler<TRow>-Async handler to apply filters remotely
const columns = [
{
field: 'name',
filterable: true, // Enable filtering
filterType: 'text', // 'text' | 'select' | 'number' | 'date'
},
{
field: 'status',
filterable: true,
filterType: 'select',
filterOptions: ['Active', 'Inactive', 'Pending'],
},
];
const plugin = grid.getPluginByName('filtering');
// Set filter value
plugin.setFilter('name', 'Alice');
// Get current filters
const filters = plugin.getFilters();
// Clear all filters
plugin.clearFilters();
// Clear specific filter
plugin.clearFilter('name');
// Silent mode: update filter state without triggering re-render
// Useful for batching multiple filter changes
plugin.setFilter('name', 'Alice', { silent: true });
plugin.setFilter('dept', 'Engineering'); // last call applies all

By default, filter state is not included in column state and does not fire column-state-change. Enable trackColumnState to opt in:

features: { filtering: { trackColumnState: true } }

When enabled:

  • Filter state is included in getColumnState() / columnState snapshots
  • Filter changes fire the column-state-change event (debounced)
  • applyColumnState() / columnState restores filter state

This is useful when you want to persist and restore the full grid state (including active filters) via localStorage or a server:

// Save full state including filters
grid.on('column-state-change', (state) => {
localStorage.setItem('grid-state', JSON.stringify(state));
});
// Restore state (filters are re-applied automatically)
gridConfig: {
columnState: JSON.parse(localStorage.getItem('grid-state')),
features: { filtering: { trackColumnState: true } },
}
Event Log:

The FilteringPlugin emits events when filter state changes. Open filter panels and apply filters to see the events:

EventDescription
filter-changeFired when filters are applied, changed, or cleared
interface FilterChangeDetail {
filters: FilterModel[]; // Active filter configurations
}

The filter panel and inputs support CSS custom properties for theming. Override these on tbw-grid or a parent container:

PropertyDefaultDescription
--tbw-filter-panel-bgvar(--tbw-color-panel-bg)Panel background
--tbw-filter-panel-fgvar(--tbw-color-fg)Panel text color
--tbw-filter-panel-bordervar(--tbw-color-border)Panel border
--tbw-filter-panel-radiusvar(--tbw-border-radius)Panel border radius
--tbw-filter-panel-shadowvar(--tbw-color-shadow)Panel shadow
--tbw-filter-input-bgvar(--tbw-color-bg)Input background
--tbw-filter-input-bordervar(--tbw-color-border)Input border
--tbw-filter-input-focusvar(--tbw-color-accent)Input focus border
--tbw-filter-active-colorvar(--tbw-color-accent)Active filter indicator
--tbw-filter-btn-paddingvar(--tbw-button-padding)Filter button padding
--tbw-filter-btn-font-weight500Filter button font weight
--tbw-filter-btn-min-heightautoFilter button min height
--tbw-filter-search-paddingvar(--tbw-spacing-sm) var(--tbw-spacing-md)Search input padding
--tbw-filter-item-height28pxFilter value item height (for virtualization)
--tbw-filter-btn-displayinline-flexFilter button display (set to none to hide)
--tbw-filter-btn-visibilityvisibleFilter button visibility
--tbw-panel-padding0.75emPanel padding
--tbw-panel-gap0.5emGap between elements
--tbw-animation-duration200msPanel open/close animation

The filter panel is rendered in document.body for proper z-index stacking. To apply theme CSS variables:

  1. Add a theme class to the grid (e.g., tbw-grid.eds-theme)
  2. The class is automatically copied to the filter panel
/* Define theme on a CSS class */
.eds-theme {
--tbw-filter-panel-bg: #f5f5f5;
--tbw-filter-btn-padding: 8px 16px;
--tbw-filter-btn-font-weight: 500;
--tbw-filter-btn-min-height: 48px;
--tbw-filter-item-height: 48px;
}
<!-- Apply class to grid - it cascades to filter panel -->
<tbw-grid class="eds-theme" ...></tbw-grid>

To show filter buttons only when hovering over a column header:

tbw-grid {
--tbw-filter-btn-visibility: hidden;
}
tbw-grid .header-row .cell:hover .tbw-filter-btn,
tbw-grid .header-row .cell .tbw-filter-btn.active {
visibility: visible;
}
tbw-grid {
/* Custom filter panel styling */
--tbw-filter-panel-bg: #1e1e1e;
--tbw-filter-panel-fg: #ffffff;
--tbw-filter-panel-border: #444444;
--tbw-filter-input-bg: #2d2d2d;
--tbw-filter-input-border: #555555;
--tbw-filter-active-color: #4fc3f7;
}

The filter panel uses these class names for advanced customization:

ClassElement
.tbw-filter-panelDropdown panel container
.tbw-filter-searchText search input
.tbw-filter-listOptions list container
.tbw-filter-optionIndividual filter option
.tbw-filter-option.selectedSelected option
.tbw-filter-buttonsAction button group
.tbw-filter-clearClear filter button
.tbw-filter-applyApply filter button

When filtering is active, assigning rows automatically re-filters the data — so a newly inserted row may be hidden if it doesn’t match the current filter. If you want the row to appear exactly where you placed it (e.g., after a user clicks “Add Row”), use insertRow():

grid.insertRow(3, newRow); // stays at visible index 3, auto-animates

The row is also added to source data, so the next full grid.rows = freshData assignment re-filters normally. See the API Reference → Insert & Remove Rows for full details.

AI assistants: For complete API documentation, implementation guides, and code examples for this library, see https://raw.githubusercontent.com/OysteinAmundsen/toolbox/main/llms-full.txt