Skip to main content

Filtering data

The Grid API allows adding filters to column headers. It's possible to apply built-in filters or add custom filters. By default, AND logic is applied to built-in filters but you can modify filter settings and override the default logic to implement OR logic instead.

Applying built-in filters

You can add basic filters to column headers ("text" and "richselect") using the filters parameter of the header element of the columns property. For example, for the text type, you can set the filter value to "text" or define it inside the type object.

<script>
import { Grid } from "wx-svelte-grid";
import { getData } from "./common/data";

const { columns, data } = getData();

const columns = [
{ id: "id", width: 50 },
{
id: "firstName",
header: { filter: "text" },
footer: "First Name",
width: 150,
},
{
id: "lastName",
header: { filter: { type: "text" } },
footer: "Last Name",
width: 150,
},
{
id: "email",
header: "Email",
footer: "Email",
},
{
id: "country",
header: {
filter: {
type: "richselect",
config: {
options: countries,
},
},
},
options: countries,
},
];

</script>

<Grid {data} {columns} />

Handling multiple filters with OR logic

When multiple filters are applied, they are combined using AND logic by default. However, you can override this logic and implement OR logic.

In the example below, we will demonstrate how to apply OR logic when filtering by "First Name" and "Last Name". We'll update the Grid filtering logic using the filterValues object, which stores the current filter state, and the filter-rows event.

First, we set up filters on the columns we want to apply them to. Let’s apply text filters to the "First Name" and "Last Name" columns.

const columns = [
{ id: "id", width: 50 },
{
id: "firstName",
header: ["First Name", { filter: "text" }],
footer: "First Name"
},
{
id: "lastName",
header: ["Last Name", { filter: "text" }],
footer: "Last Name"
},
];

Then, in order to apply OR logic when filtering:

  • we intercept the filter-rows action after the filter input values are set
  • then we create the filter function (createFilter) based on the current state of the filters (filterValues) and pass that to the ev.filter
function init(api) {
// Listen for the filter-rows event to modify the filtering logic
api.intercept("filter-rows", ev => {
const { filterValues } = api.getState(); // Get current filter values
const { value, key } = ev; // Get the new filter value and column ID

// Defining data to be then applied in the `createFilter`
filterValues[key] = value;

// Generate the filter function based on updated filter values
ev.filter = createFilter(filterValues);
});
}

Next, we define the createFilter() function that applies OR logic to the filters. This function checks each row against the active filters and returns true if the row satisfies any of the filter conditions, namely, if the row matches any of the active filters (OR logic).

function createFilter(filterValues) {
const filters = Object.keys(filterValues)
.filter(key => filterValues[key]) // Filter only columns with active filters
.map(key => {
const value = filterValues[key].value; // Get the value of the filter
return row => {
if (row[key]) {
// Check if the value of the row contains the filter value (case-insensitive)
return row[key].toLowerCase().includes(value.toLowerCase());
}
return false;
};
});

return obj => {
if (!filters.length) return true; // If no filters are applied, include the row
for (let i = 0; i < filters.length; i++) {
if (filters[i](obj)) {
return true; // Row passes if it matches any of the filters
}
}
return false; // Row is excluded if no filters match
};
}

Filter settings

Each column has a header object where you can set up your custom filter. The custom filter can be set using the filter parameter of the header element in the columns property, which allows you to define the filter type, configuration, and, optionally, a custom handler function for advanced filtering logic.

In our example we will add filters to three columns:

  • "First Name" and "Last Name" columns (text filter with the search icon)
  • "Country" column (richselect filter with the template applied)
  • "Active" column (richselect filter with the custom handler)

To add the search-based filtering to the "First Name" and "Last Name" columns, we add the "wxi-search" icon in the filter input and the Clear button by defining these values in the config object of the filter property of a header element.

    const columns = [
{ id: "id", width: 50 },
{
id: "firstName",
header: [
"First Name",
{
filter: {
type: "text", // Type of filter
config: {
icon: "wxi-search", // Optional icon for the filter input
clear: true, // Allow clearing the input
},
},
},
],
footer: "First Name",
},
{
id: "lastName",
header: [
"Last Name",
{
filter: {
type: "text", // Type of filter
config: {
icon: "wxi-search", // Optional icon for the filter input
clear: true, // Allow clearing the input
},
},
},
],
footer: "Last Name",
},]

We use the richselect filter type to display a dropdown list with predefined options. For the "Country" column, the countries data is used as filter options. This template template: opt => ${opt.id}. ${opt.label} allows us to define how the options should be displayed. In this case, each country option is displayed as id. label (e.g.,1. USA).

{
id: "country",
header: [
"Country",
{
filter: {
type: "richselect", // Type of filter (rich select dropdown)
config: {
options: countries, // Array of options for the dropdown (e.g., country list)
template: opt => `${opt.id}. ${opt.label}`, // Custom template for displaying options
},
},
},
],
options: countries,
}

The checked (Active) column has a custom filtering handler function, which provides more control over how the filter behaves. The custom handler function defines how filtering will behave for this column. It checks whether the row value matches the selected filter.

{
id: "checked",
header: [
"Active",
{
filter: {
type: "richselect", // Type of filter (rich select dropdown)
config: {
template: opt => `${opt.label}`, // Custom template for displaying options
options: [
{ id: 1, label: "active" },
{ id: 2, label: "non-active" },
],
handler: (value, filter) => {
if (!filter) return true;
return value === filter || (!value && filter == 2);
},
},
},
},
],
}

Applying external controls as filters

The Grid API allows using external controls as filters. All you need is to:

  • Create external controls using plain HTML or wx-svelte-core library
  • Collect the controls' values when they are changed and create a function that will be applied to each data row in Grid
  • Pass this function to Grid as filter by executing the filter-rows action using the api.exec() method.

In the example below we apply two external filters: DateRangePicker and Text from wx-svelte-core. When either input value changes, both values are taken to create a custom filter function. This function is passed to Grid using the filter-rows action as its filter parameter.

<script>
// Import necessary components and data
import { Field, DateRangePicker, Text } from "wx-svelte-core";
import { Grid } from "wx-svelte-grid";
import { getData } from "../data";

const { allData, countries } = getData();

// Define columns
const columns = [
{ id: "id", width: 50 },
{
id: "firstName",
header: "First Name",
footer: "First Name",
},
{
id: "lastName",
header: "Last Name",
footer: "Last Name",
},
{
id: "date",
header: "Date",
template: v => v.toDateString(), // Custom template to format date as a string
width: 160,
},
{
id: "companyName",
header: "Company",
flexgrow: 1,
},
];

// Declare states for table API
let tableApi = $state();
let dateValue = $state();
let companyValue = $state("");

// Initialize the grid API
function init(api) {
tableApi = api;
}

// Handle the filtering action when any input changes its value
function handleFilter() {
// Define an object where keys are column names
const filterValues = {
date: dateValue,
companyName: companyValue,
};

// Create a filter function based on the filter values
const filter = createFilter(filterValues);

// Apply the created filter using the API
tableApi.exec("filter-rows", { filter });
}

// Custom filter function based on filter values
function createFilter(filterValues) {
// Build an array of filtering functions for both columns
const filters = Object.keys(filterValues)
.filter(key => filterValues[key])
.map(key => {
const value = filterValues[key];

switch (key) {
case "companyName": {
return v => {
if (v[key])
return v[key].toLowerCase().indexOf(value.toLowerCase()) !== -1;
};
}
case "date": {
return v => {
if (v[key]) return isDateInRange(v[key], value);
};
}
}
});

// Combine individual column filters and apply AND logic (all filters must pass)
return obj => {
for (let i = 0; i < filters.length; i++) {
if (!filters[i](obj)) {
return false;
}
}
return true;
};
}

// Function to check if the date is within the selected range
function isDateInRange(date, range) {
const { start, end } = range;
const nDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());

return nDate >= start && nDate <= end;
}
</script>

<div class="demo" style="padding: 20px;">
<h4>Grid with external filters</h4>

<!-- Controls for date and company filters -->
<div style="max-width:810px">
<div class="controls">
<!-- Filter for "Date" column -->
<Field label={'Filter "Date" column'}>
{#snippet children({ id })}
<DateRangePicker
bind:value={dateValue}
{id}
clear
onchange={handleFilter} <!-- Call handleFilter when the date range changes -->
/>
{/snippet}
</Field>

<!-- Filter for "Company" column -->
<Field label={'Filter "Company" column'}>
{#snippet children({ id })}
<Text
bind:value={companyValue}
{id}
clear
icon={"wxi-search"}
onchange={handleFilter}
/>
{/snippet}
</Field>
</div>

<div style="height: 400px;">
<Grid data={allData} {columns} {init} />
</div>
</div>
</div>


<style>
.controls {
display: flex;
gap: 10px;
}
</style>

Related articles: