DocsCore FeaturesFiltering
Chapter 04

Filtering

Text/number/date/boolean filters, custom filters

Filtering is on by default. Each column header shows a small funnel icon; clicking it opens a popover where the user picks an operator (like contains, greater than, between) and types a value. This chapter explains how to configure filter types and behaviour.

Jargon check — three words you'll see throughout this chapter:

  • Filter — a rule that hides rows that don't match, leaving only the ones you care about. "Show only rows where Salary is greater than 50,000."
  • Operator — the kind of comparison: contains, equals, greater than, between, and so on. Different data types offer different operators (you can't say a name is "greater than" another in a useful way).
  • Popover — the little floating panel that appears anchored to the funnel icon when you click it. It holds the operator dropdown and the value input(s).

Grid-level filtering options

These go in the filtering group:

Property Type Default Meaning
enabled boolean true Turn filtering on/off for the whole grid
caseSensitive boolean false Match upper/lower case exactly
debounceMs number 300 Wait time (ms) before applying while typing
iconPosition 'left' | 'right' 'right' Where the funnel icon sits
showIconOnHover boolean false Only show the icon on hover
filterIcon VNode Replace the default funnel icon
activeFilterIcon VNode Icon shown when a filter is active
popoverPlacement 'bottom' | 'top' | 'auto' 'auto' Where the popover opens
const grid = createGrid<User>({
  columns,
  data,
  filtering: {
    enabled: true,
    caseSensitive: false,
    debounceMs: 300,
    popoverPlacement: 'auto',
  },
})

What is debounceMs? "Debouncing" means waiting until the user stops doing something before reacting. While someone types "ada" into a text filter, you don't want to re-filter on a, then ad, then ada — that is three passes over the data for one search. With debounceMs: 300, the grid waits 300 milliseconds after the last keystroke before filtering. Lower it for snappier feedback on small datasets; raise it if filtering large data feels janky.

What is caseSensitive? "Case" means upper- vs lower-case letters. When false (the default), searching "ADA" also matches "ada" and "Ada" — usually what users expect. Set it to true only when the distinction genuinely matters (e.g. case-sensitive codes or IDs).

What is popoverPlacement? It controls where the filter popover opens relative to the funnel icon. 'auto' (default) is smart: it normally opens below, but flips above when there isn't enough room at the bottom of the screen. Use 'top' or 'bottom' only if you want to force one side.

Turn filtering off entirely

filtering: { enabled: false }

Filter types — the filter group on a column

The most important filter setting is type, because it changes which operators the user sees and how values are compared.

Property Type Default Meaning
enabled boolean inherits grid Allow filtering this column
type 'text' | 'number' | 'date' | 'boolean' 'text' Which filter UI/operators to use
customFilter (value, row) => boolean Your own match logic
columns: [
  { field: 'name',   header: 'Name',   filter: { type: 'text' } },
  { field: 'salary', header: 'Salary', filter: { type: 'number' } },
  { field: 'joined', header: 'Joined', filter: { type: 'date' } },
  { field: 'active', header: 'Active', filter: { type: 'boolean' } },
]

Operators available per type

  • text: contains, notContains, equals, notEquals, startsWith, endsWith, isEmpty, isNotEmpty
  • number: equals, notEquals, greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual, between, isEmpty, isNotEmpty
  • date: equals, notEquals, before, after, between, isEmpty, isNotEmpty
  • boolean: equals, isEmpty, isNotEmpty

between shows two inputs (from / to). The grid validates that from ≤ to and shows an error otherwise.

Disable filtering for one column

{ field: 'avatar', header: '', filter: { enabled: false } }

Custom filters with customFilter

When the built-in operators aren't enough, write your own match function. A customFilter runs once per row. It receives the cell value and the whole row, and returns a boolean: true keeps the row, false hides it. Because it gets the whole row, you can base the decision on other fields too — something the built-in operators can't do.

Example — a "VIP" column that matches when salary is high and the user is active:

{
  field: 'salary',
  header: 'Salary',
  filter: {
    type: 'number',
    customFilter: (value, row) =>
      Number(value) > 150_000 && (row as User).active,
  },
}

Customizing the filter icon

Filter icons are typed as VNode, so build them with Vue's h() render function — any icon library, SVG, or emoji works:

import { h } from 'vue'

filtering: {
  filterIcon: h('span', {}, '⏷'),          // normal state
  activeFilterIcon: h('span', {}, '🔍'),    // when a filter is applied
  showIconOnHover: true,                    // keep headers clean until hover
}

Controlling filters from code (the API)

The active filters are described by a filter model — an object keyed by column field, where each value is { type, operator, value } (plus valueTo for between). Reading or setting this object is how you save, restore, or preset filters from code.

// Set the entire filter model at once
api.setFilterModel({
  name: { type: 'text', operator: 'contains', value: 'ada' },
  salary: { type: 'number', operator: 'greaterThan', value: 50000 },
})

// Set/clear a single column's filter
api.setColumnFilter('name', {
  type: 'text',
  operator: 'startsWith',
  value: 'A',
})
api.clearColumnFilter('name')

// Read current filters
const filters = api.getFilterModel()

// Clear everything
api.clearFilters()

Reacting to filter changes

events: {
  onFilterChange: (model) => {
    console.log('Filters changed:', model)
  },
}

When a filter matches nothing

If a filter returns zero rows, the grid shows a "No matching results" empty state. You can customize that message — see Chapter 09 · Appearance.


Presetting filters on load

To open the grid already filtered — from a saved view, a URL, or a "My active users" shortcut button — set the model in onReady (or any time after) with api.setFilterModel:

const grid = createGrid<User>({
  columns,
  data,
  events: {
    onReady: (api) => {
      const params = new URLSearchParams(window.location.search)
      const dept = params.get('department')
      if (dept) {
        api.setColumnFilter('department', {
          type: 'text',
          operator: 'equals',
          value: dept,
        })
      }
    },
  },
})

Combine this with onFilterChange (writing the model back to the URL or localStorage) for the same "restore on reload" pattern used for sorting in Chapter 03.


Filtering a computed column

Just like sorting, filtering a value.getter column filters on whatever the getter returns, not the raw row — so you can filter on a value that isn't a real field:

{
  field: 'fullName',
  header: 'Name',
  value: {
    getter: (row) => `${row.firstName} ${row.lastName}`,
  },
  filter: { type: 'text' },   // filters against the computed full name
}

If the built-in contains/equals operators aren't precise enough for a computed value, reach for customFilter instead — it receives the getter's output as value and the full row as the second argument, so you can inspect the original fields too:

{
  field: 'fullName',
  header: 'Name',
  value: { getter: (row) => `${row.firstName} ${row.lastName}` },
  filter: {
    customFilter: (value, row) =>
      String(value).toLowerCase().startsWith((row as User).firstName.toLowerCase()),
  },
}

Live example

A text filter, a number filter, and a date filter on three different columns — click a header's funnel icon to open its filter. All three combine with AND, so narrowing one column narrows the visible rows for the others too. Written as defineComponent + h(), per the note in Chapter 01.

import { defineComponent, h } from 'vue'
import { createGrid, Grid } from '@elitegrid/vue'

interface Book {
  id: number
  title: string
  author: string
  price: number
  published: string
}

const books: Book[] = [
  { id: 1, title: 'The Pragmatic Programmer', author: 'Hunt & Thomas', price: 44.99, published: '1999-10-30' },
  { id: 2, title: 'Clean Code', author: 'Robert Martin', price: 39.99, published: '2008-08-01' },
  { id: 3, title: 'Refactoring', author: 'Martin Fowler', price: 54.99, published: '1999-07-08' },
  { id: 4, title: 'Design Patterns', author: 'Gang of Four', price: 59.99, published: '1994-10-21' },
  { id: 5, title: 'The Mythical Man-Month', author: 'Fred Brooks', price: 34.99, published: '1975-01-01' },
  { id: 6, title: 'Domain-Driven Design', author: 'Eric Evans', price: 49.99, published: '2003-08-30' },
  { id: 7, title: 'Effective Java', author: 'Joshua Bloch', price: 54.99, published: '2001-05-08' },
  { id: 8, title: "You Don't Know JS", author: 'Kyle Simpson', price: 29.99, published: '2014-12-27' },
  { id: 9, title: 'Working Effectively with Legacy Code', author: 'Michael Feathers', price: 47.99, published: '2004-09-22' },
  { id: 10, title: 'Continuous Delivery', author: 'Humble & Farley', price: 52.99, published: '2010-08-06' },
]

const grid = createGrid<Book>({
  columns: [
    { field: 'title', header: 'Title', size: { flex: 2 }, filter: { type: 'text' } },
    { field: 'author', header: 'Author', size: { flex: 1.5 }, filter: { type: 'text' } },
    { field: 'price', header: 'Price', filter: { type: 'number' } },
    { field: 'published', header: 'Published', filter: { type: 'date' } },
  ],
  data: books,
  filtering: { enabled: true },
})

export default defineComponent({
  setup() {
    return () => h('div', { style: 'height:440px' }, [h(Grid, { grid })])
  },
})

Common filtering mistakes

Symptom Cause Fix
Typing feels laggy on a large dataset debounceMs too low, or a heavy customFilter running on every keystroke Raise debounceMs, or move filtering to the server with a dataSource (see Chapter 14)
Filter box shows a value but nothing is filtered You called setColumnFilter with a type that doesn't match the column's filter.type Make sure the model's type matches the column config exactly
"Between" filter never matches value/valueTo swapped, or from > to The grid validates from ≤ to in its own UI — if you're setting the model from code, validate it yourself too
Filtering a formatted column doesn't match what's on screen Filters run against the raw value, not the display.formatter output Filter against the underlying value/getter, or add a customFilter that mirrors the display logic

Next: 05 · Pagination

Was this page helpful?