Skip to content

Filter Events

Filter events let application code react after visible rows change, adjust the rows that will be trimmed, or cancel local evaluation and delegate a request to a backend.

A normal Core filter run emits these filter-plugin events in order:

OrderEventCancelableImportant runtime detail
1beforefilterapplyYescollection, full filterItems, source, and columns
2beforefiltertrimmedYesFilter model plus physical itemsToFilter indexes
3afterfilterapplyNomultiFilterItems, original source, and first-condition collection

Canceling beforefilterapply stops evaluation. Canceling beforefiltertrimmed stops the calculated filter trim from being installed. beforetrimmed and aftertrimmed are general grid events and can also run for grouping, tree visibility, or application-owned trims.

The Core filter plugin writes its calculated trim through the data provider, so its normal apply path does not emit the public beforetrimmed and aftertrimmed element events. Those events wrap calls through the grid’s general setTrimmed() method; do not include them when reasoning about the filter-plugin sequence.

filterconfigchanged is different: it reports a new grid filter configuration and is not a per-run completion event.

afterfilterapply.detail.source is the original local source, not the filtered result. Read the provider-backed visible models with getVisibleSource():

grid.addEventListener('afterfilterapply', async (event) => {
const visibleRows = await grid.getVisibleSource();
const detail = (
event as CustomEvent<{
multiFilterItems: Record<string, unknown[]>;
}>
).detail;
console.log('Active filters', detail.multiFilterItems);
console.log('Visible rows', visibleRows.length);
});

The cast is intentional. The runtime Core plugin emits afterfilterapply, but the generated Core element event map in some current packages does not declare that event yet, so TypeScript may otherwise infer only the base Event type. Applications can also augment HTMLRevoGridElementEventMap centrally with the runtime detail type.

Use beforefiltertrimmed when the application needs final control over physical row indexes:

grid.addEventListener('beforefiltertrimmed', (event) => {
// Keep physical row 0 visible even when ordinary conditions reject it.
delete event.detail.itemsToFilter[0];
});

Use this hook sparingly. A custom filter predicate or canonical AST condition is easier to persist and transport when the requirement is ordinary business logic.

Prevent beforefilterapply to stop local trimming, then load rows from the server. Guard the source replacement so reapplying the same active model does not create a request loop:

let loadedSignature = '';
let requestId = 0;
grid.addEventListener('beforefilterapply', async (event) => {
const detail = (
event as CustomEvent<{
filterItems: Record<string, unknown[]>;
filterAst?: unknown;
executionContext?: unknown;
}>
).detail;
const transportState = detail.filterAst
? {
filterAst: detail.filterAst,
executionContext: detail.executionContext,
}
: { filterItems: detail.filterItems };
const signature = JSON.stringify(transportState, (_key, value) =>
value instanceof Set ? [...value] : value,
);
if (signature === loadedSignature) return;
event.preventDefault();
const activeRequest = ++requestId;
const response = await fetch('/api/orders/filter', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: signature,
});
const rows = await response.json();
if (activeRequest !== requestId) return;
loadedSignature = signature;
grid.source = rows;
});

With AdvanceFilterPlugin, set filter.external: true when the backend always owns filtering. The plugin then prevents local trimming itself while still emitting the same beforefilterapply payload. Your handler only needs to send the model and install the latest response; it should not emit afterfilterapply, because that event remains a local-filter completion signal.

grid.filter = { external: true };
grid.addEventListener('beforefilterapply', async (event) => {
const { filterAst, executionContext } = event.detail;
grid.source = await loadOrders({ filterAst, executionContext });
});

In production, also handle request failures and abort stale network work. The Pro Pagination, Infinity Scroll, and Server-Side Grouping plugins already implement this delegation flow.

Prefer Pro’s canonical filterAst for new remote integrations. Compatible filterItems can contain runtime values such as the selection filter’s excluded-value Set; the fallback above converts sets to arrays, but applications should still define and validate their complete wire contract. See Filter State and Presets.

AdvanceFilterPlugin emits canonical events in addition to the Core lifecycle:

EventDetailUse it for
filterastchangefilterAst, executionContext, origin, projectablePersisting or transporting the effective canonical tree
filterasterrorAttempted AST, diagnostics, and originShowing validation errors without applying an invalid tree

For a local Pro run, filterastchange is emitted before beforefilterapply. Pro also adds filterAst, executionContext, and temporal transport context to the Core before/after event details at runtime. Origins distinguish config, api, ui, quickFilter, and clear changes.

grid.addEventListener('filterastchange', (event) => {
console.log(event.detail.origin, event.detail.filterAst);
});
grid.addEventListener('filterasterror', (event) => {
console.error(event.detail.diagnostics);
});

Changing grid.quickFilter wraps the local filter run with two Pro events:

beforequickfilterapply
→ filterastchange
→ beforefilterapply
→ beforefiltertrimmed
→ afterfilterapply
afterquickfilterapply

beforequickfilterapply is cancelable. Its detail and afterquickfilterapply contain the normalized quickFilter, source, and columns. Remote Pro data plugins cancel the local quick-filter run and emit the completion event after the latest remote response wins.

  • Do not treat aftertrimmed as filter-only; other grid features also trim rows.
  • Do not read the original source field as the filtered result; call getVisibleSource().
  • Do not perform a remote request without preventDefault() or local filtering will run too.
  • Do not assign remote rows without a loop guard and stale-request protection.
  • Do not mutate beforequickfilterapply.detail.quickFilter expecting it to replace the property value; observe it or cancel and assign a new grid.quickFilter value.
  • Do not assume generated element typings expose every plugin-emitted runtime field. Narrow or augment event detail types where needed.