Searching values
Searching values
Search data across Handsontable using the built-in API methods of the Search plugin, and implement your own search UI.
Overview
The Search plugin lets you scan all cells in the grid and get back a list of matches. Enable it by setting the search option to true or to a configuration object.
Once enabled, the plugin exposes the query(queryStr) method. Call it with a search string whenever the user types. By default, the search is case-insensitive and matches partial cell values.
How query() works
Calling query(queryStr, [callback], [queryMethod]) does two things:
- Iterates over every cell in the grid and tests each one using the
queryMethod. - After each test, calls the
callbackto update cell metadata (isSearchResult).
It returns an array of result objects - one for each matching cell:
| Property | Type | Description |
|---|---|---|
row | number | Visual row index of the matching cell |
col | number | Visual column index of the matching cell |
data | string|number|null | Value of the matching cell |
After calling query(), call hot.render() to refresh visual highlighting.
Search result class
After query() runs, every cell where isSearchResult === true automatically receives the CSS class htSearchResult. You can replace this class in two ways:
- At initialization: set
search: { searchResultClass: 'my-class' } - Programmatically: call
hot.getPlugin('search').setSearchResultClass('my-class')
Custom queryMethod
The queryMethod function determines whether the query string matches a cell value. It is called once per cell during every query() call.
Signature:
function queryMethod(query, value, cellProperties) { // return true for a match, false otherwise}| Parameter | Type | Description |
|---|---|---|
query | string | The search string passed to query() |
value | string|number|null | The cell value (from getDataAtCell()) |
cellProperties | object | The cell’s metadata object (includes locale, type, and other cell options) |
The built-in default performs a case-insensitive, locale-aware substring match:
function defaultQueryMethod(query, value, cellProperties) { if (query === undefined || query === null || query.length === 0) { return false; } if (value === undefined || value === null) { return false; }
return value.toString().toLocaleLowerCase(cellProperties.locale) .indexOf(query.toLocaleLowerCase(cellProperties.locale)) !== -1;}You can set a custom query method in three ways:
- At initialization:
search: { queryMethod: myQueryMethod } - Programmatically:
hot.getPlugin('search').setQueryMethod(myQueryMethod) - Per
query()call:searchPlugin.query(queryStr, callback, myQueryMethod)(applies to that call only)
Custom result callback
The callback function is called for every cell during a query() run, whether or not the cell matches. It is responsible for updating cell metadata so the renderer knows which cells to highlight.
Signature:
function callback(instance, row, col, data, testResult) { // update cell metadata based on testResult}| Parameter | Type | Description |
|---|---|---|
instance | Handsontable | The Handsontable instance |
row | number | Visual row index |
col | number | Visual column index |
data | string|number|null | The cell value |
testResult | boolean | true if the cell matches the query, false otherwise |
The built-in default sets the isSearchResult flag on each cell’s metadata:
function defaultCallback(instance, row, col, data, testResult) { instance.getCellMeta(row, col).isSearchResult = testResult;}If you override the callback to add custom logic (for example, to count results), call the default behavior manually so that cell highlighting still works.
You can set a custom callback in three ways:
- At initialization:
search: { callback: myCallback } - Programmatically:
hot.getPlugin('search').setCallback(myCallback) - Per
query()call:searchPlugin.query(queryStr, myCallback)(applies to that call only)
Per-cell queryMethod and callback
Both queryMethod and callback can be overridden for individual cells, columns, or rows using Handsontable’s cascading configuration model. Set a search object directly in a cell, columns, or rows entry:
handsontable({ data: myData, search: true, columns: [ {}, // Column 1: exact match only, everything else uses the global queryMethod { search: { queryMethod(queryStr, value) { return queryStr.toString() === value.toString(); } } } ]});You can also set it programmatically on a specific cell using setCellMeta():
hot.setCellMeta(row, col, 'search', { queryMethod(queryStr, value) { return queryStr.toString() === value.toString(); }});Per-cell settings take precedence over the plugin-level queryMethod and callback. Only queryMethod and callback support per-cell overrides - searchResultClass does not.
Simplest use case
The example below:
- Enables the
Searchplugin by settingsearchtotrue - Listens for
keyupevents on a search input - Calls
query()on each keystroke and re-renders the grid to apply highlighting
import Handsontable from 'handsontable/base';import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.registerAllModules();
const data = [ ['Tesla', 2017, 'black', 'black'], ['Nissan', 2018, 'blue', 'blue'], ['Chrysler', 2019, 'yellow', 'black'], ['Volvo', 2020, 'yellow', 'gray'],];
const container = document.querySelector('#example1');const hot = new Handsontable(container, { data, colHeaders: true, // enable the `Search` plugin search: true, height: 'auto', autoWrapRow: true, autoWrapCol: true, licenseKey: 'non-commercial-and-evaluation',});
const searchField = document.querySelector('#search_field');
// add a search input listenersearchField.addEventListener('keyup', (event) => { // get the `Search` plugin's instance const search = hot.getPlugin('search'); // use the `Search` plugin's `query()` method const queryResult = search.query(event.target.value);
console.log(queryResult); hot.render();});import Handsontable from 'handsontable/base';import { registerAllModules } from 'handsontable/registry';import { Search } from 'handsontable/plugins';
// Register all Handsontable's modules.registerAllModules();
const data: (string | number)[][] = [ ['Tesla', 2017, 'black', 'black'], ['Nissan', 2018, 'blue', 'blue'], ['Chrysler', 2019, 'yellow', 'black'], ['Volvo', 2020, 'yellow', 'gray'],];
const container = document.querySelector('#example1')!;
const hot = new Handsontable(container, { data, colHeaders: true, // enable the `Search` plugin search: true, height: 'auto', autoWrapRow: true, autoWrapCol: true, licenseKey: 'non-commercial-and-evaluation',});
const searchField = document.querySelector('#search_field')!;
// add a search input listenersearchField.addEventListener('keyup', (event) => { // get the `Search` plugin's instance const search: Search = hot.getPlugin('search'); // use the `Search` plugin's `query()` method const queryResult = search.query((event.target as HTMLInputElement).value);
console.log(queryResult);
hot.render();});<div class="example-controls-container"> <div class="controls"> <input id="search_field" type="search" placeholder="Search"> </div></div><div id="example1"></div>Custom search result class
You can style search results with a custom CSS class, using the Search plugin’s searchResultClass option.
The example below highlights search results with a pink background and red text. To do this, it:
- Defines a custom CSS class called
my-class, scoped to.ht-theme-main .handsontable - Enables the
Searchplugin with a configuration object - Sets
searchResultClassto'my-class'
import Handsontable from 'handsontable/base';import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.registerAllModules();
const data = [ ['Tesla', 2017, 'black', 'black'], ['Nissan', 2018, 'blue', 'blue'], ['Chrysler', 2019, 'yellow', 'black'], ['Volvo', 2020, 'yellow', 'gray'],];
const container = document.querySelector('#example2');const hot = new Handsontable(container, { data, colHeaders: true, // enable the `Search` plugin search: { // add your custom CSS class searchResultClass: 'my-class', }, height: 'auto', autoWrapRow: true, autoWrapCol: true, licenseKey: 'non-commercial-and-evaluation',});
const searchField = document.querySelector('#search_field2');
searchField.addEventListener('keyup', (event) => { const search = hot.getPlugin('search'); const queryResult = search.query(event.target.value);
console.log(queryResult); hot.render();});import Handsontable from 'handsontable/base';import { registerAllModules } from 'handsontable/registry';import { Search } from 'handsontable/plugins';
// Register all Handsontable's modules.registerAllModules();
const data: (string | number)[][] = [ ['Tesla', 2017, 'black', 'black'], ['Nissan', 2018, 'blue', 'blue'], ['Chrysler', 2019, 'yellow', 'black'], ['Volvo', 2020, 'yellow', 'gray'],];
const container = document.querySelector('#example2')!;
const hot = new Handsontable(container, { data, colHeaders: true, // enable the `Search` plugin search: { // add your custom CSS class searchResultClass: 'my-class', }, height: 'auto', autoWrapRow: true, autoWrapCol: true, licenseKey: 'non-commercial-and-evaluation',});
const searchField = document.querySelector('#search_field2')!;
searchField.addEventListener('keyup', (event) => { const search: Search = hot.getPlugin('search'); const queryResult = search.query((event.target as HTMLInputElement).value);
console.log(queryResult); hot.render();});.ht-theme-main .handsontable .my-class { background: pink; color: red;}<div class="example-controls-container"> <div class="controls"> <input id="search_field2" type="search" placeholder="Search"> </div></div><div id="example2"></div>Custom query method
You can replace the built-in substring search with a custom query method, using the queryMethod option.
The example below searches only for exact matches. To do this, it:
- Defines a custom query method called
onlyExactMatchthat uses strict equality (===) - Enables the
Searchplugin with a configuration object - Sets
queryMethodtoonlyExactMatch
import Handsontable from 'handsontable/base';import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.registerAllModules();
const data = [ ['Tesla', 2017, 'black', 'black'], ['Nissan', 2018, 'blue', 'blue'], ['Chrysler', 2019, 'yellow', 'black'], ['Volvo', 2020, 'white', 'gray'],];
// define your custom query methodfunction onlyExactMatch(queryStr, value) { return queryStr.toString() === value.toString();}
const container = document.querySelector('#example3');const hot = new Handsontable(container, { data, colHeaders: true, // enable the `Search` plugin search: { // add your custom query method queryMethod: onlyExactMatch, }, height: 'auto', autoWrapRow: true, autoWrapCol: true, licenseKey: 'non-commercial-and-evaluation',});
const searchField = document.querySelector('#search_field3');
searchField.addEventListener('keyup', (event) => { const search = hot.getPlugin('search'); // use the `Search`'s `query()` method const queryResult = search.query(event.target.value);
console.log(queryResult); hot.render();});import Handsontable from 'handsontable/base';import { registerAllModules } from 'handsontable/registry';import { Search } from 'handsontable/plugins';
// Register all Handsontable's modules.registerAllModules();
const data: (string | number)[][] = [ ['Tesla', 2017, 'black', 'black'], ['Nissan', 2018, 'blue', 'blue'], ['Chrysler', 2019, 'yellow', 'black'], ['Volvo', 2020, 'white', 'gray'],];
// define your custom query methodfunction onlyExactMatch(queryStr, value) { return queryStr.toString() === value.toString();}
const container = document.querySelector('#example3')!;
const hot = new Handsontable(container, { data, colHeaders: true, // enable the `Search` plugin search: { // add your custom query method queryMethod: onlyExactMatch, }, height: 'auto', autoWrapRow: true, autoWrapCol: true, licenseKey: 'non-commercial-and-evaluation',});
const searchField = document.querySelector('#search_field3')!;
searchField.addEventListener('keyup', (event) => { const search: Search = hot.getPlugin('search'); // use the `Search`'s `query()` method const queryResult = search.query((event.target as HTMLInputElement).value);
console.log(queryResult);
hot.render();});<div class="example-controls-container"> <div class="controls"> <input id="search_field3" type="search" placeholder="Search"> </div></div><div id="example3"></div>Custom callback
You can add a custom callback function, using the Search plugin’s callback option.
The example below displays the number of matching search results. To do this, it:
- Defines a custom callback function called
searchResultCounterthat counts matches and calls the default callback to preserve highlighting - Enables the
Searchplugin with a configuration object - Sets
callbacktosearchResultCounter
import Handsontable from 'handsontable/base';import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.registerAllModules();
let searchResultCount = 0;const data = [ ['Tesla', 2017, 'black', 'black'], ['Nissan', 2018, 'blue', 'blue'], ['Chrysler', 2019, 'yellow', 'black'], ['Volvo', 2020, 'white', 'gray'],];
// define your custom callback functionfunction searchResultCounter(instance, row, column, value, result) { const DEFAULT_CALLBACK = function (instance, row, col, _data, testResult) { instance.getCellMeta(row, col).isSearchResult = testResult; };
DEFAULT_CALLBACK.apply(this, [instance, row, column, value, result]);
if (result) { searchResultCount++; }}
const container = document.querySelector('#example4');const hot = new Handsontable(container, { data, colHeaders: true, // enable the `Search` plugin search: { // add your custom callback function callback: searchResultCounter, }, height: 'auto', autoWrapRow: true, autoWrapCol: true, licenseKey: 'non-commercial-and-evaluation',});
const searchField = document.querySelector('#search_field4');const output = document.querySelector('#output');
searchField.addEventListener('keyup', (event) => { searchResultCount = 0;
const search = hot.getPlugin('search'); const queryResult = search.query(event.target.value);
console.log(queryResult); output.innerText = `${searchResultCount} results`; hot.render();});import Handsontable from 'handsontable/base';import { registerAllModules } from 'handsontable/registry';import { Search } from 'handsontable/plugins';
// Register all Handsontable's modules.registerAllModules();
let searchResultCount = 0;
const data: (string | number)[][] = [ ['Tesla', 2017, 'black', 'black'], ['Nissan', 2018, 'blue', 'blue'], ['Chrysler', 2019, 'yellow', 'black'], ['Volvo', 2020, 'white', 'gray'],];
// define your custom callback functionfunction searchResultCounter( instance: Handsontable.Core, row: number, column: number, value: Handsontable.CellValue, result: boolean): void { const DEFAULT_CALLBACK = function (instance, row, col, _data, testResult) { instance.getCellMeta(row, col).isSearchResult = testResult; };
DEFAULT_CALLBACK.apply(this, [instance, row, column, value, result]);
if (result) { searchResultCount++; }}
const container = document.querySelector('#example4')!;
const hot = new Handsontable(container, { data, colHeaders: true, // enable the `Search` plugin search: { // add your custom callback function callback: searchResultCounter, }, height: 'auto', autoWrapRow: true, autoWrapCol: true, licenseKey: 'non-commercial-and-evaluation',});
const searchField = document.querySelector('#search_field4')!;const output = document.querySelector('#output')!;
searchField.addEventListener('keyup', (event) => { searchResultCount = 0;
const search: Search = hot.getPlugin('search'); const queryResult = search.query((event.target as HTMLInputElement).value);
console.log(queryResult); (output as HTMLElement).innerText = `${searchResultCount} results`; hot.render();});<div class="example-controls-container"> <div class="controls"> <input id="search_field4" type="search" placeholder="Search"> </div> <output class="console" id="output">0 results</output></div><div id="example4"></div>Related API reference
Configuration options
Plugins