# Filter Selection
URL: https://pro.rv-grid.com/guides/data-filter/filter-selection/
Source: src/content/docs/guides/data-filter/filter-selection.mdx
Description: An advanced filtering plugin for RevoGrid with custom filter types like slider and selection.
The **Advanced Filtering Plugin** for RevoGrid extends the grid's capabilities by introducing powerful filtering options, making data management more efficient and flexible. This plugin includes new filter types, such as **slider** and **selection**, which provide intuitive ways for users to interact with and refine data views.

Selection filter options are rendered in a virtualized nested `revo-grid`, so large custom option lists stay fast while keeping the same search, select-all-visible, checkbox toggling, sorting, and filter semantics.

The live example also enables the optional expression editor. Open a selection filter popup and click **Expression** to type selection expressions such as `in ("Apple 🍎", "Orange 🍊")` or `not in ("Banana 🍌", "Lemon 🍋")`.

- **Custom Filter Types**: Enables the use of **slider** and **selection** filters, allowing users to quickly narrow down data based on specific criteria.
- **Virtualized Selection List**: Selection popups use a nested grid viewport, so hundreds or thousands of options only render a small visible row window.
- **Flexible and Extensible**: This plugin demonstrates the potential for creating custom plugins, encouraging developers to expand the grid's functionality further.
- **Automatic Theme Support**: The plugin adapts to light or dark themes based on user settings.

- **Custom Filter Types**: Enables the use of **slider** and **selection** filters, allowing users to quickly narrow down data based on specific criteria.
- **Virtualized Selection List**: Selection options render through a nested `revo-grid` with row virtualization.
- **Custom Option Templates**: Selection options can render custom content next to the checkbox, such as icons, badges, or formatted labels.
- **Grouped Selection Options**: Selection options can be grouped by fields returned from `selection.getItems`.
- **Flexible and Extensible**: This plugin demonstrates the potential for creating custom plugins, encouraging developers to expand the grid's functionality further.
- **Automatic Theme Support**: The plugin adapts to light or dark themes based on user settings.

### Filter Selection Options

- **sortDirection**: The default sort direction, can be 'asc' or 'desc' or 'none'
- **quickSearchFiltering**: Controls whether typing in the selection popup search input also filters grid rows. Defaults to `true`. Set to `false` to only narrow the popup option list.
- **quickSearchFilter**: Custom matcher for selection popup quick search. Use it when option `value` fields are IDs but users should search by rendered `label` or item metadata. It accepts one matcher for every selection-filter column or a record keyed by column `prop`.
- **sourceRowTypes**: Row stores used by the default selection option loader. Defaults to all row stores. Set to `['rgRow']` to ignore pinned rows such as `rowPinStart`.
- **getItems**: A custom source for selection values. It accepts either one loader function for every selection-filter column or a record keyed by column `prop`.
- **itemTemplate**: Custom renderer for option content next to the plugin-owned checkbox. It accepts one renderer for every selection-filter column or a record keyed by column `prop`.
- **grouping**: Optional grouping for the nested selection option grid. It accepts one `GroupingOptions` object or a record keyed by column `prop`.
- **plugins**: Optional plugins for the nested selection option grid. It accepts one plugin array or a record keyed by column `prop`.
- **gridSettings**: Optional nested selection grid settings, such as `theme`, `rowSize`, `frameSize`, `columnTypes`, `additionalData`, `readonly`, `range`, or `useClipboard`. `source`, `columns`, and `grouping` are controlled by the selection filter renderer.
- **cascadeOptions.enabled**: Enables context-aware selection options that apply active filters from other columns and ignore the current column filter.
- **selectionTitle**: Optional title shown above the selection filter options. Omit it to render no selection title.
- **selectionSearchPlaceholder**: Placeholder text for the selection filter search input. Defaults to `Search...`.
- **sliderTitle**: The title of the slider filter
- **expressions**: Enables the optional expression editor in the popup. Selection expressions such as `is "Apple 🍎"`, `in ("Apple 🍎", "Orange 🍊")`, and `not in ("Banana 🍌")` compile into the same hidden selection filter model when the column has option metadata.

Use `defineAdvancedFilterConfig` when you only need to override part of the advanced filter localization. It fills the default filter names so you do not need to pass them manually.

```typescript
import { defineAdvancedFilterConfig } from '@revolist/revogrid-pro';

grid.filter = defineAdvancedFilterConfig({
    localization: {
        captions: {
            popupHeaderColumnName: (column) =>
                column.headerAlias || column.name || String(column.prop),
        },
    },
});
```

```typescript
grid.filter = {
    expressions: {
        enabled: true,
        placeholder: 'in ("Apple 🍎", "Orange 🍊")',
    },
    localization: {
        captions: {
            selectionTitle: 'Selection',
            selectionSearchPlaceholder: 'Search values...',
            sliderTitle: 'Slider',
            expressionButton: 'Expression',
            expressionTitle: 'Filter expression',
            expressionInvalid: 'Fix the expression before applying.',
        },
    },
    selection: {
        sortDirection: 'asc', // default sort direction, can be 'asc' or 'desc' or 'none'
        quickSearchFiltering: true, // default: selection popup search also filters grid rows
        quickSearchFilter: ({ search, label, item }) =>
            `${label} ${item.family ?? ''}`.toLowerCase().includes(search),
        getItems: async (prop) => {
            if (prop !== 'name') {
                return [];
            }

            return [
                { value: 'apple', label: 'Apple', family: 'Tree fruit', color: 'Red' },
                { value: 'banana', label: 'Banana', family: 'Tropical', color: 'Yellow' },
                { value: 'orange', label: 'Orange', family: 'Citrus', color: 'Orange' },
            ];
        },
        grouping: {
            name: {
                props: ['family', 'color'],
                expandedAll: true,
            },
        },
    },
};
```

### Usage Example

```typescript
import { AdvanceFilterPlugin } from '@revolist/revogrid-pro';
    // ...
grid.columns = [
    {
        // ...
        filter: ['string', 'selection']
    }
];
grid.plugins = [AdvanceFilterPlugin];
grid.filter = {
    localization: {
        captions: {
            selectionTitle: 'Selection',
        },
    },
    selection: {
        sortDirection: 'asc', // default sort direction, can be 'asc' or 'desc' or 'none'
        quickSearchFiltering: true, // default: search narrows options and filters grid rows
    },
};
```

### Virtualized Selection Popup

The selection popup keeps the search row fixed at the top and renders option rows through an internal `revo-grid`.

- Search still matches normalized option `value`.
- Select-all only affects currently searched and visible option rows.
- Checked state still means the value is included; unchecked values are stored in the selection filter's excluded-value `Set`.
- Opening and closing a fully selected popup does not create an empty selection filter, so `FilterHeaderPlugin` keeps showing `All`.
- Group rows are visual only; option checkboxes still belong to leaf option rows.

The old `.filter-list li` DOM shape is not part of the public API. Use the visible option behavior or `.filter-list-option` when tests need to interact with option rows.

### Quick Search Filtering

By default, the selection popup search input does two things:

- narrows the option rows shown inside the popup
- applies a hidden `quickSearch` filter to the grid rows

This preserves the existing behavior for users who expect the grid to narrow while they type. Set `selection.quickSearchFiltering` to `false` when search should only help users find values in the popup and should not change the grid rows until they check or uncheck selection values.

```typescript
grid.filter = {
    selection: {
        quickSearchFiltering: false,
    },
};
```

When selection values are IDs but the popup renders names, use `selection.quickSearchFilter` to match the text users see. The matcher receives normalized `search`, normalized option `value`, display `label`, original `item`, and `columnProp`. If `quickSearchFiltering` remains enabled, grid rows are filtered by the matched option values, so row data can still store IDs.

```typescript
grid.filter = {
    selection: {
        getItems: {
            ownerId: () => [
                { value: 'usr-1', label: 'Ana Silva', email: 'ana@example.com' },
                { value: 'usr-2', label: 'Maks Doe', email: 'maks@example.com' },
            ],
        },
        quickSearchFilter: {
            ownerId: ({ search, label, item }) =>
                `${label} ${item.email}`.toLowerCase().includes(search),
        },
    },
};
```

### Context-aware cascading options

Enable context-aware selection options with `selection.cascadeOptions.enabled`.

- For column **X**, options are built from rows matching all active filters **except filters for X**.
- This keeps current-column values reversible while still narrowing related column options.
- The feature is opt-in, and default behavior is unchanged when omitted.

```typescript
grid.filter = {
    selection: {
        cascadeOptions: {
            enabled: true,
        },
    },
};
```

### Custom List Source

By default, the selection filter builds its checkbox list from the current grid data. Use `filter.selection.getItems` when the list should come from somewhere else, for example:

- server-side or infinite-scroll datasets
- a curated allow-list of accepted values
- normalized values that differ from the rendered cell text

Default lists are rebuilt when the popup opens. If a cell value is edited, rows are removed, or `grid.source` is replaced, the next selection popup reflects the latest column values. Values that no longer exist in the source are not shown unless you provide them through a custom `selection.getItems` loader.

When pinned rows should not contribute checkbox values, keep the default loader and restrict its row stores:

```typescript
grid.filter = {
    selection: {
        sourceRowTypes: ['rgRow'],
    },
};
```

The loader receives the current column `prop` and may return data synchronously or asynchronously.

```typescript
grid.filter = {
    selection: {
        getItems: async (prop) => {
            if (prop !== 'name') {
                return [];
            }

            const response = await fetch('/api/filter-options/names');
            const items = await response.json();

            return items.map((item: { id: string; title: string }) => ({
                value: item.id.toLowerCase(),
                label: item.title,
            }));
        },
    },
};
```

If only some columns need custom values, pass a record keyed by column prop:

```typescript
grid.filter = {
    selection: {
        getItems: {
            name: async () => [
                { value: 'apple', label: 'Apple' },
                { value: 'banana', label: 'Banana' },
            ],
            category: () => [
                { value: 'fresh', label: 'Fresh' },
                { value: 'frozen', label: 'Frozen' },
            ],
        },
    },
};
```

Keep `value` aligned with the value used during filter comparison. The built-in selection filter compares lower-cased string values, so custom lists should usually provide lower-cased `value` fields.

### Custom Option Templates

Use `selection.itemTemplate` to render badges, icons, or richer labels inside the selection popup. The checkbox remains controlled by the filter plugin; the template only replaces the content next to it.

```typescript
grid.filter = {
    selection: {
        getItems: {
            availability: () => [
                { value: 'in stock', label: 'In stock', tone: 'green' },
                { value: 'backorder', label: 'Backorder', tone: 'amber' },
                { value: 'seasonal', label: 'Seasonal', tone: 'indigo' },
            ],
        },
        itemTemplate: {
            availability: (h, { item, label, checked }) =>
                h('span', {
                    class: `availability-badge availability-badge--${item.tone}`,
                    'data-selected': String(checked),
                }, label),
        },
    },
};
```

`itemTemplate` receives:

- `columnProp`: Current column property.
- `item`: Original item returned by `selection.getItems` or the default source loader.
- `value`: Normalized value used by selection filtering.
- `label`: Display label.
- `checked`: Whether the option is currently included in the result.

### Grouped Option Rows

Selection option rows can be grouped by fields on the option item. This is useful for large curated lists where users need a hierarchy such as family and color, region and country, or category and status.

```typescript
grid.filter = {
    selection: {
        grouping: {
            name: {
                props: ['family', 'color'],
                expandedAll: true,
            },
        },
        getItems: {
            name: () => [
                { value: 'apple', label: 'Apple', family: 'Tree fruit', color: 'Red' },
                { value: 'pear', label: 'Pear', family: 'Tree fruit', color: 'Green' },
                { value: 'lemon', label: 'Lemon', family: 'Citrus', color: 'Yellow' },
            ],
        },
    },
};
```

`selection.grouping` can also be a single `GroupingOptions` object when every selection-filter column should use the same grouping configuration.

### Nested List Grid Plugins and Settings

The selection list is a nested grid, and you can pass plugins or grid settings to that nested grid when you need additional rendering behavior.

```typescript
import { AdvanceFilterPlugin } from '@revolist/revogrid-pro';

class SelectionListPlugin {
    constructor(selectionGrid) {
        selectionGrid.setAttribute('data-selection-list-plugin', 'mounted');
    }
}

grid.plugins = [AdvanceFilterPlugin];
grid.filter = {
    selection: {
        plugins: {
            name: [SelectionListPlugin],
        },
        gridSettings: {
            name: {
                theme: 'compact',
                rowSize: 32,
                frameSize: 2,
                hideAttribution: true,
            },
        },
    },
};
```

`selection.plugins` and `selection.gridSettings` accept either a global value or a record keyed by column `prop`.

The filter list owns `source`, `columns`, and `grouping` so filtering, checkbox state, and virtualization stay consistent. Use `selection.grouping` for grouped option rows instead of `gridSettings.grouping`.

### Predefined Selection Values

Selection filters store unchecked values as an excluded-value `Set`. To predefine a selection filter, add a hidden `selection` filter with values that should be excluded.

```typescript
grid.filter = {
    multiFilterItems: {
        name: [
            {
                id: 0,
                type: 'selection',
                value: new Set(['banana', 'lemon']),
                relation: 'and',
                hidden: true,
            },
        ],
    },
};
```

The filter-selection demo uses this pattern for the Fruit column, so one or two fruit values are already unchecked when the demo loads.

### Customization and Extensibility

This plugin highlights the extensibility of RevoGrid’s plugin system, showcasing how developers can build and integrate advanced features tailored to specific needs. The flexibility of RevoGrid plugins enables comprehensive data manipulation and display control, making it an ideal choice for complex data-driven applications.
For a deeper dive into plugin development, refer to the RevoGrid Plugin Documentation.
