Lattice Grid Buy a licence

developer guide

JavaScript Data Grid Filtering

Filtering runs from the header menu and from code through the same condition tree, so what a reader builds by clicking is exactly what your application can set, read back and store. The operators cover text, numbers, dates, blanks and lists of values, combined with and and or to any depth.

Developer guideSorting, filtering and find › JavaScript Data Grid Filtering

Filtering

There are two filters and they are separate on purpose. The quick filter is one string matched across every readable column. The filter set is a structured condition tree.

Quick filter

grid.filters.quick('singapore');
grid.filters.quick('');          // clear

A condition tree

grid.filters.set({
  op: 'and',
  conditions: [
    { col: 'region', op: 'in', value: ['EMEA', 'APAC'] },
    { col: 'utilisation', op: 'gt', value: 0.9 },
    { op: 'or', conditions: [
      { col: 'slaBreached', op: 'eq', value: true },
      { col: 'margin', op: 'lt', value: 0 },
    ]},
  ],
});

That structure is a published wire protocol, not an internal detail. It is what grid.state.get() serialises, what a saved view carries, and what you can send to a server to evaluate the same filter against the full dataset. A remote source hands it straight to your backend.

Operators are per family: text has contains, startsWith, matches; numbers and dates have between, gt, lte; multi-value columns have containsAny, containsAll, containsNone. blank and notBlank work everywhere.

Filters your application owns: where

A condition tree can only test what is in a column. Plenty of real filters cannot be written that way - whether this user may see the row, whether you hold an exchange rate for its currency, whether it came back from your last search call. Those go in as named predicates, and they compose with everything above.

A permission filter and a toggle, side by side

grid.filters.where('visibleToMe', row => row.owner === me, { pinned: true });
grid.filters.where('rateKnown', row => rates.has(row.ccy), { deps: ['ccy'] });

grid.filters.where();              // ['visibleToMe', 'rateKnown']
grid.filters.where('rateKnown', null);   // remove just that one
grid.filters.reapply('rateKnown');       // the rate table arrived late

There is deliberately no "a filter is present" flag. That flag is a second piece of state describing the first, and the two drift: the classic symptom is a grid that filters while the UI insists it is not, or insists it is filtering while every row passes. Here, registering a predicate is what puts it in force, and removing it is what takes it out.

Three options shape one. deps names the columns the predicate reads, exactly as value.deps does for a computed column: the verdict is then cached per row and re-run when one of those columns changes on that row, not when an unrelated one does. Leave it off and the predicate is assumed to read the whole row, so it runs every pass and can never be stale. pinned makes a predicate survive filters.clear(), which is what you want for permissions and tenant scoping and not much else. condition gives the predicate a declarative twin that is pushed to the source while the function stays as the residual, so a pushdown engine narrows the fetch instead of your code filtering a page.

Migrating from AG Grid's external filter: its three pieces become two. isExternalFilterPresent() goes away, because registration is presence. doesExternalFilterPass(node) becomes the predicate itself. onFilterChanged() becomes deps where the grid can watch the change for you, and reapply(name?) where it cannot.

What travels in a saved view is the name, not the function. state.get() carries where: string[]; your predicates are your code and the grid will not pretend it can serialise them. Applying a view that names a predicate you have not registered reports the skip instead of quietly showing a wider row set, and never removes a predicate the view did not mention.