Advanced Filter Builder (Enterprise)

Column filters answer "show me rows where this column matches". They cannot answer "region is EMEA and (revenue is above 1M or the account is strategic)", because a per-column filter has nowhere to put the parentheses.

The advanced filter in apex-grid-enterprise is a nested AND / OR query over any number of columns, available both as a visual builder element and as plain JSON you can construct yourself.

Added in enterprise 0.7.0.

The visual builder

<apex-grid-filter-builder> is a companion element: point it at a grid and it reads that grid's columns to offer the right operators per column type.

import 'apex-grid-enterprise/define';

const grid = document.createElement('apex-grid-enterprise');
grid.columns = columns;
grid.data = rows;

const builder = document.createElement('apex-grid-filter-builder');
builder.grid = grid;

document.body.append(builder, grid);

The builder's Apply button calls grid.applyAdvancedFilter(model) and Clear calls grid.clearAdvancedFilter(), so you do not have to wire either. Both also fire apex-advanced-filter-changed, which bubbles and composes, with the current model in event.detail.model:

builder.addEventListener('apex-advanced-filter-changed', (e) => {
  localStorage.setItem('grid-filter', JSON.stringify(e.detail.model));
});

The whole builder UI is localized through the same locale surface as the rest of the grid.

Applying a model directly

You do not need the element. The model is plain JSON, so an app that builds queries its own way can hand one straight to the grid:

grid.applyAdvancedFilter({
  kind: 'group',
  join: 'and',
  children: [
    { kind: 'condition', column: 'region', operator: 'equals', value: 'EMEA' },
    {
      kind: 'group',
      join: 'or',
      children: [
        { kind: 'condition', column: 'revenue', operator: 'greaterThan', value: 1_000_000 },
        { kind: 'condition', column: 'strategic', operator: 'true' },
      ],
    },
  ],
});

grid.advancedFilterModel   // the model currently applied, or null
grid.clearAdvancedFilter()

The model

TypeShape
AdvancedFilterModelThe root. Always an AdvancedFilterGroup
AdvancedFilterGroup{ kind: 'group', join: 'and' | 'or', children: AdvancedFilterNode[] }
AdvancedFilterCondition{ kind: 'condition', column: string, operator: string, value?: string | number | boolean }
AdvancedFilterNodeEither of the two above. Groups nest to any depth

value is omitted for unary operators such as empty or true.

operator is an operand name from the table for that column's type, so a number column offers greaterThan and a string column offers contains. These are the grid's existing operand tables, the same ones the built-in column filters use, rather than a second vocabulary to learn. Two helpers expose them if you are building your own UI:

import { operatorsForType, defaultOperator } from 'apex-grid-enterprise';

operatorsForType('number')   // [{ name, label, unary }, …]
defaultOperator('string')    // the operator a new condition starts on

Because the model is plain JSON it round-trips through localStorage, a URL, or your own state persistence without a serializer.

How it evaluates

The filter runs client-side, through the data pipeline's filter hook, so nothing in the grid core changed to support it and it composes with sort, pagination and grouping exactly as a column filter does.

Two consequences worth knowing:

  • While a model is active, it owns column filtering. It replaces the built-in filter rather than combining with it. Clearing restores whatever was there before, including an app-provided dataPipelineConfiguration.filter hook, which the grid saves when the advanced filter is applied.
  • It filters the rows the grid has. With the infinite or server-side row model the server owns filtering, so push the query through filterModel instead.

The evaluator itself is pure and DOM-free, and exported, so you can run the same query server-side or in a test:

import { filterRows, isEmptyModel, emptyAdvancedFilter } from 'apex-grid-enterprise';

const matched = filterRows(rows, model, columns);
isEmptyModel(model)        // true for a group with no conditions
emptyAdvancedFilter()      // a fresh empty root group

Live demo