Filtering & paging
This chapter shows how to filter and page grid data with a SlickDataView. Filtering hides the rows that do not match a test. Paging shows one block of rows at a time. Both run on the client, inside the DataView. You need this chapter when you add a search box, a per-column filter row, or a pager. It builds on the DataView chapter; read that first if the terms items and rows are new.
Concepts
The DataView does the work
The grid renders rows and reports events. It does not filter or page. The SlickDataView filters the items, then pages the result, then feeds the visible rows to the grid. If you have no DataView, you cannot filter or page. See Providing data to the grid.
Filter first, then page
Order matters. The DataView applies the filter first. It pages the filtered rows second. So getPagingInfo() reports totalRows and totalPages for the filtered rows, not for every item. When a new filter makes the current page fall past the last row, the DataView moves you to the last page.
A filter is a predicate
A filter is one function. It receives an item and returns true to keep it:
type FilterFn = (item: any, args: any) => boolean;Return true to show the row. Return false to hide it. The second parameter, args, carries any live values you supply, such as the text from a search box.
The filter row is your own HTML
SlickGrid does not build filter inputs for you. It gives you a second header row and one event. You create the inputs. You read their values. You call the filter. This keeps the grid small and free of assumptions about your UI.
The pager is optional UI
Paging is an API on the DataView: setPagingOptions, getPagingInfo, and onPagingInfoChanged. The SlickGridPager control is ready-made UI over that API. You can also build your own pager from the same three members.
Filtering is always CSP-safe
In v6 the DataView calls your filter function directly. It never compiles code with new Function. Filtering works under a strict Content-Security-Policy with no change.
The old options inlineFilters and useCSPSafeFilter are deprecated and ignored. Do not set them. See CSP & sanitization.
Walkthrough
The steps assume a grid and a DataView, wired as in the DataView chapter.
1. Write a filter function
Set the filter with setFilter. This example keeps rows whose name contains the search text:
import { SlickGrid, SlickDataView } from 'slickgrid';
const dataView = new SlickDataView();
// ...wire the DataView to the grid and load items (see the DataView chapter)
dataView.setFilter((item, args) => {
if (!args?.text) {
return true; // no search text: keep every row
}
return item.name.toLowerCase().includes(args.text.toLowerCase());
});setFilterstores the function and refreshes once.- Read the current filter back with
getFilter. - Keep the search value in
args, not in the function body. One filter function then serves any search text.
2. Feed live values with setFilterArgs
The filter above reads args.text. Supply it from a search box:
<input id="search" type="search" placeholder="Search…">const search = document.querySelector<HTMLInputElement>('#search')!;
search.addEventListener('input', () => {
dataView.setFilterArgs({ text: search.value });
dataView.refresh();
});setFilterArgspasses extra data to the filter. It arrives as the second parameter.setFilterArgsdoes not re-run the filter on its own. Callrefreshafter it.- Read the args back with
getFilterArgs.
3. Build a per-column filter row
For a filter under each column, turn on the header row and fill it yourself.
First, set the grid options:
const options = {
showHeaderRow: true, // add a second header row for filters
headerRowHeight: 30, // its height in pixels (default 25)
explicitInitialization: true, // let us subscribe before headers render
};showHeaderRowadds the filter row. It defaults tofalse.headerRowHeightsets the row height.explicitInitializationis explained in the last part of this step.
Next, hold the filter values in one object, keyed by column id, and read them from the filter:
const columnFilters: Record<string, string> = {};
dataView.setFilter((item, args) => {
for (const columnId in args) {
const value = args[columnId];
if (value !== '') {
const col = grid.getColumns()[grid.getColumnIndex(columnId)];
if (!String(item[col.field]).toLowerCase().includes(value.toLowerCase())) {
return false;
}
}
}
return true;
});
dataView.setFilterArgs(columnFilters);- The filter tests the item against every active column value.
setFilterArgskeeps a reference tocolumnFilters. Later edits to that object are visible on the nextrefresh, so you set the args only once. CallsetFilterArgsagain only if you replace the object.
Now create an input each time a header cell renders:
grid.onHeaderRowCellRendered.subscribe((_e, args) => {
args.node.replaceChildren(); // clear the cell
const input = document.createElement('input');
input.type = 'search';
input.dataset.columnId = String(args.column.id);
input.value = columnFilters[args.column.id] ?? '';
args.node.appendChild(input);
});onHeaderRowCellRenderedfires once per column, every time the grid renders its headers.args.nodeis the filter cell for that column.args.columnis the column.- The grid rebuilds the header cells on resize and on a column change. Restore each input value from
columnFilters, or typed text is lost.
Read the inputs with one delegated listener on the header row:
grid.getHeaderRow().addEventListener('input', (e) => {
const input = e.target as HTMLInputElement;
const columnId = input.dataset.columnId;
if (columnId != null) {
columnFilters[columnId] = input.value.trim();
dataView.refresh();
}
});getHeaderRowreturns the header row container.- One listener on the container handles every input. Rebuilt inputs need no new listener. This is why delegation is better than binding each input.
Finally, initialize the grid so the subscription runs before the first render:
grid.init();- With
explicitInitialization: true, the grid does not build its headers until you callinit. Subscribe toonHeaderRowCellRenderedbefore this line. - Without it, the grid builds headers inside its constructor. A subscription added after
new SlickGrid(...)then misses that first render, and the inputs never appear.
To toggle the row later, call setHeaderRowVisibility(true). To reach one cell directly, call getHeaderRowColumn(id).
4. Turn on paging
Set the page size and current page with setPagingOptions:
dataView.setPagingOptions({ pageSize: 25, pageNum: 0 });pageSizeis the number of rows per page.pageNumis the current page, counted from0.- A
pageSizeof0shows all rows on one page. setPagingOptionsrefreshes the grid.- It fires
onBeforePagingInfoChangedfirst. Returnfalsefrom a handler to veto the change. It then firesonPagingInfoChanged.
5. Read state and react to changes
getPagingInfo returns the current paging state:
const info = dataView.getPagingInfo();
// { pageSize, pageNum, totalRows, totalPages, dataView }Move one page forward, without running past the end:
const info = dataView.getPagingInfo();
dataView.setPagingOptions({ pageNum: Math.min(info.pageNum + 1, info.totalPages - 1) });Update your own UI from onPagingInfoChanged:
dataView.onPagingInfoChanged.subscribe((_e, info) => {
label.textContent = `Page ${info.pageNum + 1} of ${info.totalPages}`;
});onPagingInfoChangedfires when the page, the size, or the total row count changes.totalRowsandtotalPagescount the filtered rows. A new filter changes them, and fires this event.
6. Use the SlickGridPager control
The built-in pager renders a ready pager and keeps itself in sync. Add a container under the grid:
<div id="myGrid" style="width:600px;height:400px;"></div>
<div id="pager" style="width:600px;height:24px;"></div>Create the pager after the grid and the DataView:
import { SlickGridPager } from 'slickgrid';
new SlickGridPager(dataView, grid, '#pager', {
showCount: true, // add a "from-to of total" count
showPageSizes: true, // show the page-size chooser
});- The constructor takes the DataView, the grid, a container (a CSS selector or an element), and options.
- The pager subscribes to
onPagingInfoChangedand redraws itself. You never update it by hand. - Its buttons call
setPagingOptionsfor you: first, previous, next, and last page. - The page-size list defaults to
All,Auto,25,50,100.Alluses one page;Autofits the page to the viewport height. Override the list with thepagingOptionsoption. showCountandshowPageSizesboth default tofalse.- Call
destroy()on the pager to remove it and unbind its listeners.
The pager needs the SlickGrid stylesheet for its layout and icons. See Controls for the full option list.
Notes and pitfalls
setFilterArgsneedsrefresh. Setting args does not re-run the filter. Follow it withdataView.refresh().setFilterrefreshes;setFilterArgsdoes not.setFilterruns the filter at once.setFilterArgsonly stores the value.- Restore inputs on header render. The grid rebuilds header cells often. Read each input value from your state in the
onHeaderRowCellRenderedhandler. - Subscribe before
init. WithexplicitInitialization, add theonHeaderRowCellRenderedhandler beforegrid.init(). Otherwise the first render has no inputs. - A row index is not an item index. In a filter or event handler, look items up through the DataView. See the DataView chapter.
totalRowsis the filtered count. Paging works on the filtered rows, so a filter can change the page count and the current page.pageSize: 0means all rows. Use it to turn paging off;totalPagesis then1.- Large datasets: hint the refresh. Before a
refreshthat does not change the filter, callsetRefreshHints({ isFilterUnchanged: true }). The DataView then skips re-running the filter. The pager does this for page changes.
See also
- DataView — items vs rows, ids, and wiring the DataView to the grid.
- DataView — API reference —
setFilter,setFilterArgs,setPagingOptions,getPagingInfo, and the paging events. - Controls — API reference — the
SlickGridPagercontrol and its options. - Grid — API reference —
showHeaderRow,onHeaderRowCellRendered,getHeaderRow, andinit. - Sorting — the other DataView transform, driven by the grid's
onSortevent. - CSP & sanitization — why filtering is CSP-safe by default.