Documentation
Column Filtering
Column filtering allows users to quickly find and display only the data that meets specific criteria. Simple Table provides intelligent filtering for different data types including text, numbers, dates, booleans, and enum values.
1import {SimpleTable} from "@simple-table/react";import type { Theme } from "@simple-table/react";2import { columnFilteringConfig } from "./column-filtering.demo-data";3import "@simple-table/react/styles.css";45const ColumnFilteringDemo = ({6 height = "400px",7 theme,8}: {9 height?: string | number;10 theme?: Theme;11}) => {12 return (13 <SimpleTable14 defaultHeaders={columnFilteringConfig.headers}15 rows={columnFilteringConfig.rows}16 height={height}17 theme={theme}18 />19 );20};2122export default ColumnFilteringDemo;
1<template>2 <SimpleTable3 :default-headers="columnFilteringConfig.headers"4 :rows="columnFilteringConfig.rows"5 :height="height"6 :theme="theme"7 />8</template>910<script setup lang="ts">11import {SimpleTable} from "@simple-table/vue";import type { Theme } from "@simple-table/vue";12import { columnFilteringConfig } from "./column-filtering.demo-data";13import "@simple-table/vue/styles.css";1415withDefaults(defineProps<{ height?: string | number; theme?: Theme }>(), {16 height: "400px",17});18</script>
1import { Component, Input } from "@angular/core";2import {SimpleTableComponent} from "@simple-table/angular";import type { AngularHeaderObject, Row, Theme } from "@simple-table/angular";3import { columnFilteringConfig } from "./column-filtering.demo-data";4import "@simple-table/angular/styles.css";56@Component({7 selector: "column-filtering-demo",8 standalone: true,9 imports: [SimpleTableComponent],10 template: `11 <simple-table12 [rows]="rows"13 [defaultHeaders]="headers"14 [height]="height"15 [theme]="theme"16 ></simple-table>17 `,18})19export class ColumnFilteringDemoComponent {20 @Input() height: string | number = "400px";21 @Input() theme?: Theme;2223 readonly rows: Row[] = columnFilteringConfig.rows;24 readonly headers: AngularHeaderObject[] = columnFilteringConfig.headers;25}262728// column-filtering.demo-data.ts29// Self-contained demo table setup for this example.30import type { Row, AngularHeaderObject } from "@simple-table/angular";313233export const COLUMN_FILTERING_DATA: Row[] = [34 {35 id: 1,36 name: "Bianca Rossi",37 department: "Editorial",38 role: "Senior Editor",39 salary: 82000,40 startDate: "2020-02-14",41 isActive: true,42 },43 {44 id: 2,45 name: "Axel Chen",46 department: "Production",47 role: "Art Director",48 salary: 75000,49 startDate: "2021-06-18",50 isActive: true,51 },52 {53 id: 3,54 name: "Emilia Nakamura",55 department: "Editorial",56 role: "Managing Editor",57 salary: 95000,58 startDate: "2019-04-22",59 isActive: true,60 },61 {62 id: 4,63 name: "Luca Martinez",64 department: "Marketing",65 role: "Content Strategist",66 salary: 68000,67 startDate: "2022-01-12",68 isActive: false,69 },70 {71 id: 5,72 name: "Delia Kumar",73 department: "Production",74 role: "Layout Designer",75 salary: 72000,76 startDate: "2020-09-07",77 isActive: true,78 },79 {80 id: 6,81 name: "Cian O'Sullivan",82 department: "Sales",83 role: "Sales Representative",84 salary: 65000,85 startDate: "2021-11-03",86 isActive: true,87 },88 {89 id: 7,90 name: "Amara Okafor",91 department: "Human Resources",92 role: "HR Manager",93 salary: 78000,94 startDate: "2019-08-15",95 isActive: true,96 },97 {98 id: 8,99 name: "Rowan Thompson",100 department: "Production",101 role: "Cover Designer",102 salary: 69000,103 startDate: "2021-12-20",104 isActive: false,105 },106 {107 id: 9,108 name: "Celeste Petrov",109 department: "Marketing",110 role: "PR Specialist",111 salary: 63000,112 startDate: "2022-03-08",113 isActive: true,114 },115 {116 id: 10,117 name: "Quinn Hassan",118 department: "Sales",119 role: "Sales Manager",120 salary: 89000,121 startDate: "2020-05-11",122 isActive: true,123 },124 {125 id: 11,126 name: "Isla Williams",127 department: "Editorial",128 role: "Copy Editor",129 salary: 58000,130 startDate: "2021-10-25",131 isActive: true,132 },133 {134 id: 12,135 name: "Dax Silva",136 department: "Finance",137 role: "Financial Analyst",138 salary: 64000,139 startDate: "2022-07-14",140 isActive: false,141 },142 {143 id: 13,144 name: "Maya Patel",145 department: "IT Support",146 role: "Systems Administrator",147 salary: 71000,148 startDate: "2021-03-15",149 isActive: true,150 },151 {152 id: 14,153 name: "Jordan Lee",154 department: "Quality Assurance",155 role: "QA Engineer",156 salary: 67000,157 startDate: "2020-11-08",158 isActive: true,159 },160];161162163export const DEPARTMENT_OPTIONS = [164 { label: "Editorial", value: "Editorial" },165 { label: "Production", value: "Production" },166 { label: "Marketing", value: "Marketing" },167 { label: "Sales", value: "Sales" },168 { label: "Operations", value: "Operations" },169 { label: "Human Resources", value: "Human Resources" },170 { label: "Finance", value: "Finance" },171 { label: "Legal", value: "Legal" },172 { label: "IT Support", value: "IT Support" },173 { label: "Customer Service", value: "Customer Service" },174 { label: "Research & Development", value: "Research & Development" },175 { label: "Quality Assurance", value: "Quality Assurance" },176];177178export const columnFilteringHeaders: AngularHeaderObject[] = [179 {180 accessor: "id",181 label: "ID",182 width: 80,183 type: "number",184 isSortable: true,185 filterable: true,186 },187 {188 accessor: "name",189 label: "Employee Name",190 width: "1fr",191 minWidth: 150,192 type: "string",193 isSortable: true,194 filterable: true,195 },196 {197 accessor: "department",198 label: "Department",199 width: "1fr",200 minWidth: 120,201 type: "enum",202 isSortable: true,203 filterable: true,204 enumOptions: DEPARTMENT_OPTIONS,205 },206 {207 accessor: "role",208 label: "Role",209 width: 140,210 type: "string",211 isSortable: true,212 filterable: true,213 },214 {215 accessor: "salary",216 label: "Salary",217 width: 120,218 align: "right",219 type: "number",220 isSortable: true,221 filterable: true,222 cellRenderer: ({ row }) => {223 const salary = row.salary as number;224 return `$${salary.toLocaleString()}`;225 },226 },227 {228 accessor: "startDate",229 label: "Start Date",230 width: 130,231 type: "date",232 isSortable: true,233 filterable: true,234 },235 {236 accessor: "isActive",237 label: "Active",238 width: 100,239 align: "center",240 type: "boolean",241 isSortable: true,242 filterable: true,243 },244];245246export const columnFilteringConfig = {247 headers: columnFilteringHeaders,248 rows: COLUMN_FILTERING_DATA,249} as const;250
1<script lang="ts">2 import {SimpleTable} from "@simple-table/svelte"; import type { Theme } from "@simple-table/svelte";3 import { columnFilteringConfig } from "./column-filtering.demo-data";4 import "@simple-table/svelte/styles.css";56 let { height = "400px", theme }: { height?: string | number; theme?: Theme } = $props();7</script>89<SimpleTable10 defaultHeaders={columnFilteringConfig.headers}11 rows={columnFilteringConfig.rows}12 {height}13 {theme}14/>
1import {SimpleTable} from "@simple-table/solid";import type { Theme } from "@simple-table/solid";2import { columnFilteringConfig } from "./column-filtering.demo-data";3import "@simple-table/solid/styles.css";45export default function ColumnFilteringDemo(props: {6 height?: string | number;7 theme?: Theme;8}) {9 return (10 <SimpleTable11 defaultHeaders={columnFilteringConfig.headers}12 rows={columnFilteringConfig.rows}13 height={props.height ?? "400px"}14 theme={props.theme}15 />16 );17}
1import { SimpleTableVanilla } from "simple-table-core";2import type { Theme } from "simple-table-core";3import { columnFilteringConfig } from "./column-filtering.demo-data";4import "simple-table-core/styles.css";56export function renderColumnFilteringDemo(7 container: HTMLElement,8 options?: { height?: string | number; theme?: Theme }9): SimpleTableVanilla {10 const table = new SimpleTableVanilla(container, {11 defaultHeaders: columnFilteringConfig.headers,12 rows: columnFilteringConfig.rows,13 height: options?.height ?? "400px",14 theme: options?.theme,15 });16 return table;17}181920// column-filtering.demo-data.ts21// Self-contained demo table setup for this example.22import type { Row } from "simple-table-core";23import type { HeaderObject } from "simple-table-core";242526export const COLUMN_FILTERING_DATA: Row[] = [27 {28 id: 1,29 name: "Bianca Rossi",30 department: "Editorial",31 role: "Senior Editor",32 salary: 82000,33 startDate: "2020-02-14",34 isActive: true,35 },36 {37 id: 2,38 name: "Axel Chen",39 department: "Production",40 role: "Art Director",41 salary: 75000,42 startDate: "2021-06-18",43 isActive: true,44 },45 {46 id: 3,47 name: "Emilia Nakamura",48 department: "Editorial",49 role: "Managing Editor",50 salary: 95000,51 startDate: "2019-04-22",52 isActive: true,53 },54 {55 id: 4,56 name: "Luca Martinez",57 department: "Marketing",58 role: "Content Strategist",59 salary: 68000,60 startDate: "2022-01-12",61 isActive: false,62 },63 {64 id: 5,65 name: "Delia Kumar",66 department: "Production",67 role: "Layout Designer",68 salary: 72000,69 startDate: "2020-09-07",70 isActive: true,71 },72 {73 id: 6,74 name: "Cian O'Sullivan",75 department: "Sales",76 role: "Sales Representative",77 salary: 65000,78 startDate: "2021-11-03",79 isActive: true,80 },81 {82 id: 7,83 name: "Amara Okafor",84 department: "Human Resources",85 role: "HR Manager",86 salary: 78000,87 startDate: "2019-08-15",88 isActive: true,89 },90 {91 id: 8,92 name: "Rowan Thompson",93 department: "Production",94 role: "Cover Designer",95 salary: 69000,96 startDate: "2021-12-20",97 isActive: false,98 },99 {100 id: 9,101 name: "Celeste Petrov",102 department: "Marketing",103 role: "PR Specialist",104 salary: 63000,105 startDate: "2022-03-08",106 isActive: true,107 },108 {109 id: 10,110 name: "Quinn Hassan",111 department: "Sales",112 role: "Sales Manager",113 salary: 89000,114 startDate: "2020-05-11",115 isActive: true,116 },117 {118 id: 11,119 name: "Isla Williams",120 department: "Editorial",121 role: "Copy Editor",122 salary: 58000,123 startDate: "2021-10-25",124 isActive: true,125 },126 {127 id: 12,128 name: "Dax Silva",129 department: "Finance",130 role: "Financial Analyst",131 salary: 64000,132 startDate: "2022-07-14",133 isActive: false,134 },135 {136 id: 13,137 name: "Maya Patel",138 department: "IT Support",139 role: "Systems Administrator",140 salary: 71000,141 startDate: "2021-03-15",142 isActive: true,143 },144 {145 id: 14,146 name: "Jordan Lee",147 department: "Quality Assurance",148 role: "QA Engineer",149 salary: 67000,150 startDate: "2020-11-08",151 isActive: true,152 },153];154155156export const DEPARTMENT_OPTIONS = [157 { label: "Editorial", value: "Editorial" },158 { label: "Production", value: "Production" },159 { label: "Marketing", value: "Marketing" },160 { label: "Sales", value: "Sales" },161 { label: "Operations", value: "Operations" },162 { label: "Human Resources", value: "Human Resources" },163 { label: "Finance", value: "Finance" },164 { label: "Legal", value: "Legal" },165 { label: "IT Support", value: "IT Support" },166 { label: "Customer Service", value: "Customer Service" },167 { label: "Research & Development", value: "Research & Development" },168 { label: "Quality Assurance", value: "Quality Assurance" },169];170171export const columnFilteringHeaders: HeaderObject[] = [172 {173 accessor: "id",174 label: "ID",175 width: 80,176 type: "number",177 isSortable: true,178 filterable: true,179 },180 {181 accessor: "name",182 label: "Employee Name",183 width: "1fr",184 minWidth: 150,185 type: "string",186 isSortable: true,187 filterable: true,188 },189 {190 accessor: "department",191 label: "Department",192 width: "1fr",193 minWidth: 120,194 type: "enum",195 isSortable: true,196 filterable: true,197 enumOptions: DEPARTMENT_OPTIONS,198 },199 {200 accessor: "role",201 label: "Role",202 width: 140,203 type: "string",204 isSortable: true,205 filterable: true,206 },207 {208 accessor: "salary",209 label: "Salary",210 width: 120,211 align: "right",212 type: "number",213 isSortable: true,214 filterable: true,215 cellRenderer: ({ row }) => {216 const salary = row.salary as number;217 return `$${salary.toLocaleString()}`;218 },219 },220 {221 accessor: "startDate",222 label: "Start Date",223 width: 130,224 type: "date",225 isSortable: true,226 filterable: true,227 },228 {229 accessor: "isActive",230 label: "Active",231 width: 100,232 align: "center",233 type: "boolean",234 isSortable: true,235 filterable: true,236 },237];238239export const columnFilteringConfig = {240 headers: columnFilteringHeaders,241 rows: COLUMN_FILTERING_DATA,242} as const;243
Basic Implementation
Column filtering is enabled by adding the filterable: true property to individual column headers. Each column can be independently configured for filtering based on its data type.
Filter Configuration
| Property | Required | Description | Example |
|---|---|---|---|
Property | Required | Description | Example |
HeaderObject.filterableboolean | Optional | Enable filtering for a specific column. Each column can be independently configured for filtering based on its data type. Simple Table provides intelligent filtering with different operators for each data type. | |
HeaderObject.filterOperatorsFilterOperator[] | Optional | Restrict which filter operators appear in a column's filter dropdown. Only operators valid for the column's type are honored, and they appear in the order provided. When omitted, all operators for the column type are shown. Has no effect on enum columns, which use a checkbox value picker instead of an operator dropdown. |
Limiting Filter Operators
By default, each filterable column exposes every operator available for its data type. Use the filterOperators property to hide or limit the operators shown in a column's filter dropdown. This is useful when only certain comparisons make sense for a column — for example, restricting a text column to contains and equals.
- Operators appear in the order you provide them.
- Operators that aren't valid for the column's
typeare ignored. - If the column's default operator is excluded, the first allowed operator becomes the default selection.
- Enum columns are unaffected, since they use a checkbox value picker instead of an operator dropdown.
External Filtering
For advanced use cases, you can handle filtering externally - perfect for server-side filtering, API integration, or custom filtering logic. This demo shows how to manage filtering completely outside the table component with diverse data types and locations.
External Filter Status: No filters applied
1import { useState, useMemo } from "react";2import {SimpleTable} from "@simple-table/react";import type { Theme, TableFilterState } from "@simple-table/react";3import { externalFilterConfig, matchesFilter } from "./external-filter.demo-data";4import "@simple-table/react/styles.css";56const ExternalFilterDemo = ({7 height = "400px",8 theme,9}: {10 height?: string | number;11 theme?: Theme;12}) => {13 const [filters, setFilters] = useState<TableFilterState>({});1415 const filteredData = useMemo(() => {16 const filterEntries = Object.entries(filters);17 if (filterEntries.length === 0) return externalFilterConfig.rows;1819 return externalFilterConfig.rows.filter((row) =>20 filterEntries.every(([accessor, filter]) =>21 matchesFilter(row[accessor as keyof typeof row] as any, filter)22 )23 );24 }, [filters]);2526 return (27 <SimpleTable28 defaultHeaders={externalFilterConfig.headers}29 rows={filteredData}30 onFilterChange={setFilters}31 externalFilterHandling32 columnResizing33 height={height}34 theme={theme}35 />36 );37};3839export default ExternalFilterDemo;
1<script setup lang="ts">2import { ref, computed } from "vue";3import {SimpleTable} from "@simple-table/vue";import type { Theme, TableFilterState } from "@simple-table/vue";4import { externalFilterConfig, matchesFilter } from "./external-filter.demo-data";5import "@simple-table/vue/styles.css";67const props = withDefaults(defineProps<{ height?: string | number; theme?: Theme }>(), {8 height: "400px",9});1011const filters = ref<TableFilterState>({});1213const filteredRows = computed(() => {14 const entries = Object.entries(filters.value);15 if (entries.length === 0) return externalFilterConfig.rows;1617 return externalFilterConfig.rows.filter((row) =>18 entries.every(([accessor, filter]) =>19 matchesFilter(row[accessor as keyof typeof row] as any, filter)20 )21 );22});2324function handleFilterChange(newFilters: TableFilterState) {25 filters.value = newFilters;26}27</script>2829<template>30 <SimpleTable31 :default-headers="externalFilterConfig.headers"32 :rows="filteredRows"33 :external-filter-handling="true"34 :column-resizing="true"35 :height="props.height"36 :theme="props.theme"37 :on-filter-change="handleFilterChange"38 />39</template>
1import { Component, Input } from "@angular/core";2import {SimpleTableComponent} from "@simple-table/angular";import type { AngularHeaderObject, Row, TableFilterState, Theme } from "@simple-table/angular";3import { externalFilterConfig, matchesFilter } from "./external-filter.demo-data";4import "@simple-table/angular/styles.css";56@Component({7 selector: "external-filter-demo",8 standalone: true,9 imports: [SimpleTableComponent],10 template: `11 <simple-table12 [rows]="filteredRows"13 [defaultHeaders]="headers"14 [externalFilterHandling]="true"15 [columnResizing]="true"16 [height]="height"17 [theme]="theme"18 [onFilterChange]="handleFilterChange"19 ></simple-table>20 `,21})22export class ExternalFilterDemoComponent {23 @Input() height: string | number = "400px";24 @Input() theme?: Theme;2526 readonly headers: AngularHeaderObject[] = externalFilterConfig.headers;27 private filters: TableFilterState = {};2829 handleFilterChange = (newFilters: TableFilterState) => {30 this.filters = newFilters;31 };3233 get filteredRows(): Row[] {34 const entries = Object.entries(this.filters);35 if (entries.length === 0) return externalFilterConfig.rows as Row[];3637 return (externalFilterConfig.rows as Row[]).filter((row) =>38 entries.every(([accessor, filter]) =>39 matchesFilter(row[accessor] as any, filter)40 )41 );42 }43}444546// external-filter.demo-data.ts47// Self-contained demo table setup for this example.48import type { AngularHeaderObject, TableFilterState } from "@simple-table/angular";495051type CellValue = string | number | boolean | null | undefined;5253export function matchesFilter(54 value: CellValue,55 filter: TableFilterState[string]56): boolean {57 const { operator } = filter;5859 switch (operator) {60 case "equals":61 return value === filter.value;62 case "notEquals":63 return value !== filter.value;64 case "contains":65 return String(value).toLowerCase().includes(String(filter.value).toLowerCase());66 case "notContains":67 return !String(value).toLowerCase().includes(String(filter.value).toLowerCase());68 case "startsWith":69 return String(value).toLowerCase().startsWith(String(filter.value).toLowerCase());70 case "endsWith":71 return String(value).toLowerCase().endsWith(String(filter.value).toLowerCase());72 case "greaterThan":73 return Number(value) > Number(filter.value);74 case "lessThan":75 return Number(value) < Number(filter.value);76 case "greaterThanOrEqual":77 return Number(value) >= Number(filter.value);78 case "lessThanOrEqual":79 return Number(value) <= Number(filter.value);80 case "between":81 return (82 filter.values != null &&83 Number(value) >= Number(filter.values[0]) &&84 Number(value) <= Number(filter.values[1])85 );86 case "in":87 return filter.values != null && filter.values.includes(value);88 case "notIn":89 return filter.values != null && !filter.values.includes(value);90 case "isEmpty":91 return value == null || value === "";92 case "isNotEmpty":93 return value != null && value !== "";94 default:95 return true;96 }97}9899const DEPARTMENT_OPTIONS = [100 { label: "AI Research", value: "AI Research" },101 { label: "UX Design", value: "UX Design" },102 { label: "DevOps", value: "DevOps" },103 { label: "Marketing", value: "Marketing" },104 { label: "Engineering", value: "Engineering" },105 { label: "Product", value: "Product" },106 { label: "Sales", value: "Sales" },107];108109const LOCATION_OPTIONS = [110 { label: "San Francisco", value: "San Francisco" },111 { label: "Tokyo", value: "Tokyo" },112 { label: "Lagos", value: "Lagos" },113 { label: "Mexico City", value: "Mexico City" },114 { label: "Kolkata", value: "Kolkata" },115 { label: "Stockholm", value: "Stockholm" },116 { label: "Dubai", value: "Dubai" },117 { label: "Milan", value: "Milan" },118 { label: "Seoul", value: "Seoul" },119 { label: "Austin", value: "Austin" },120 { label: "London", value: "London" },121 { label: "Moscow", value: "Moscow" },122];123124export const externalFilterData = [125 { id: 1, name: "Dr. Elena Vasquez", age: 42, email: "elena.vasquez@techcorp.com", salary: 145000, department: "AI Research", active: true, location: "San Francisco" },126 { id: 2, name: "Kai Tanaka", age: 29, email: "k.tanaka@techcorp.com", salary: 95000, department: "UX Design", active: true, location: "Tokyo" },127 { id: 3, name: "Amara Okafor", age: 35, email: "amara.okafor@techcorp.com", salary: 125000, department: "DevOps", active: false, location: "Lagos" },128 { id: 4, name: "Santiago Rodriguez", age: 27, email: "s.rodriguez@techcorp.com", salary: 82000, department: "Marketing", active: true, location: "Mexico City" },129 { id: 5, name: "Priya Chakraborty", age: 33, email: "priya.c@techcorp.com", salary: 118000, department: "Engineering", active: true, location: "Kolkata" },130 { id: 6, name: "Magnus Eriksson", age: 38, email: "magnus.erik@techcorp.com", salary: 110000, department: "Product", active: false, location: "Stockholm" },131 { id: 7, name: "Zara Al-Rashid", age: 31, email: "zara.alrashid@techcorp.com", salary: 98000, department: "Sales", active: true, location: "Dubai" },132 { id: 8, name: "Luca Rossi", age: 26, email: "luca.rossi@techcorp.com", salary: 75000, department: "Marketing", active: true, location: "Milan" },133 { id: 9, name: "Dr. Sarah Kim", age: 45, email: "sarah.kim@techcorp.com", salary: 165000, department: "AI Research", active: true, location: "Seoul" },134 { id: 10, name: "Olumide Adebayo", age: 30, email: "olumide.a@techcorp.com", salary: 105000, department: "Engineering", active: false, location: "Austin" },135 { id: 11, name: "Isabella Chen", age: 24, email: "isabella.chen@techcorp.com", salary: 68000, department: "UX Design", active: true, location: "London" },136 { id: 12, name: "Dmitri Volkov", age: 39, email: "dmitri.volkov@techcorp.com", salary: 135000, department: "DevOps", active: true, location: "Moscow" },137];138139export const externalFilterHeaders: AngularHeaderObject[] = [140 { accessor: "name", label: "Name", width: "1fr", minWidth: 120, filterable: true, type: "string" },141 { accessor: "age", label: "Age", width: 120, filterable: true, type: "number" },142 {143 accessor: "department",144 label: "Department",145 width: 150,146 filterable: true,147 type: "enum",148 enumOptions: DEPARTMENT_OPTIONS,149 },150 {151 accessor: "location",152 label: "Location",153 width: 150,154 filterable: true,155 type: "enum",156 enumOptions: LOCATION_OPTIONS,157 },158 { accessor: "active", label: "Active", width: 120, filterable: true, type: "boolean" },159 {160 accessor: "salary",161 label: "Salary",162 width: 120,163 filterable: true,164 type: "number",165 align: "right",166 valueFormatter: ({ value }) => `$${(value as number).toLocaleString()}`,167 },168];169170export const externalFilterConfig = {171 headers: externalFilterHeaders,172 rows: externalFilterData,173 tableProps: { externalFilterHandling: true, columnResizing: true },174} as const;175
1<script lang="ts">2 import {SimpleTable} from "@simple-table/svelte"; import type { Theme, TableFilterState } from "@simple-table/svelte";3 import { externalFilterConfig, matchesFilter } from "./external-filter.demo-data";4 import "@simple-table/svelte/styles.css";56 let { height = "400px", theme }: { height?: string | number; theme?: Theme } = $props();78 let filters = $state<TableFilterState>({});910 let filteredRows = $derived.by(() => {11 const entries = Object.entries(filters);12 if (entries.length === 0) return externalFilterConfig.rows;1314 return externalFilterConfig.rows.filter((row) =>15 entries.every(([accessor, filter]) =>16 matchesFilter(row[accessor as keyof typeof row] as any, filter)17 )18 );19 });2021 function handleFilterChange(newFilters: TableFilterState) {22 filters = newFilters;23 }24</script>2526<SimpleTable27 defaultHeaders={externalFilterConfig.headers}28 rows={filteredRows}29 externalFilterHandling={true}30 columnResizing={true}31 onFilterChange={handleFilterChange}32 {height}33 {theme}34/>
1import { createSignal, createMemo } from "solid-js";2import {SimpleTable} from "@simple-table/solid";import type { Theme, TableFilterState } from "@simple-table/solid";3import { externalFilterConfig, matchesFilter } from "./external-filter.demo-data";4import "@simple-table/solid/styles.css";56export default function ExternalFilterDemo(props: { height?: string | number; theme?: Theme }) {7 const [filters, setFilters] = createSignal<TableFilterState>({});89 const filteredData = createMemo(() => {10 const entries = Object.entries(filters());11 if (entries.length === 0) return externalFilterConfig.rows;1213 return externalFilterConfig.rows.filter((row) =>14 entries.every(([accessor, filter]) =>15 matchesFilter(row[accessor as keyof typeof row] as any, filter)16 )17 );18 });1920 return (21 <SimpleTable22 defaultHeaders={externalFilterConfig.headers}23 rows={filteredData()}24 onFilterChange={setFilters}25 externalFilterHandling26 columnResizing27 height={props.height ?? "400px"}28 theme={props.theme}29 />30 );31}
1import { SimpleTableVanilla } from "simple-table-core";2import type { Theme, TableFilterState } from "simple-table-core";3import { externalFilterConfig, matchesFilter } from "./external-filter.demo-data";4import "simple-table-core/styles.css";56export function renderExternalFilterDemo(7 container: HTMLElement,8 options?: { height?: string | number; theme?: Theme }9): SimpleTableVanilla {10 let currentFilters: TableFilterState = {};1112 const applyFilters = () => {13 const entries = Object.entries(currentFilters);14 if (entries.length === 0) {15 table.update({ rows: externalFilterConfig.rows });16 return;17 }18 const filtered = externalFilterConfig.rows.filter((row) =>19 entries.every(([accessor, filter]) =>20 matchesFilter(row[accessor as keyof typeof row] as any, filter)21 )22 );23 table.update({ rows: filtered });24 };2526 const table = new SimpleTableVanilla(container, {27 defaultHeaders: externalFilterConfig.headers,28 rows: externalFilterConfig.rows,29 externalFilterHandling: true,30 columnResizing: true,31 height: options?.height ?? "400px",32 theme: options?.theme,33 onFilterChange: (newFilters: TableFilterState) => {34 currentFilters = newFilters;35 applyFilters();36 },37 });3839 return table;40}414243// external-filter.demo-data.ts44// Self-contained demo table setup for this example.45import type { HeaderObject, TableFilterState } from "simple-table-core";464748type CellValue = string | number | boolean | null | undefined;4950export function matchesFilter(51 value: CellValue,52 filter: TableFilterState[string]53): boolean {54 const { operator } = filter;5556 switch (operator) {57 case "equals":58 return value === filter.value;59 case "notEquals":60 return value !== filter.value;61 case "contains":62 return String(value).toLowerCase().includes(String(filter.value).toLowerCase());63 case "notContains":64 return !String(value).toLowerCase().includes(String(filter.value).toLowerCase());65 case "startsWith":66 return String(value).toLowerCase().startsWith(String(filter.value).toLowerCase());67 case "endsWith":68 return String(value).toLowerCase().endsWith(String(filter.value).toLowerCase());69 case "greaterThan":70 return Number(value) > Number(filter.value);71 case "lessThan":72 return Number(value) < Number(filter.value);73 case "greaterThanOrEqual":74 return Number(value) >= Number(filter.value);75 case "lessThanOrEqual":76 return Number(value) <= Number(filter.value);77 case "between":78 return (79 filter.values != null &&80 Number(value) >= Number(filter.values[0]) &&81 Number(value) <= Number(filter.values[1])82 );83 case "in":84 return filter.values != null && filter.values.includes(value);85 case "notIn":86 return filter.values != null && !filter.values.includes(value);87 case "isEmpty":88 return value == null || value === "";89 case "isNotEmpty":90 return value != null && value !== "";91 default:92 return true;93 }94}9596const DEPARTMENT_OPTIONS = [97 { label: "AI Research", value: "AI Research" },98 { label: "UX Design", value: "UX Design" },99 { label: "DevOps", value: "DevOps" },100 { label: "Marketing", value: "Marketing" },101 { label: "Engineering", value: "Engineering" },102 { label: "Product", value: "Product" },103 { label: "Sales", value: "Sales" },104];105106const LOCATION_OPTIONS = [107 { label: "San Francisco", value: "San Francisco" },108 { label: "Tokyo", value: "Tokyo" },109 { label: "Lagos", value: "Lagos" },110 { label: "Mexico City", value: "Mexico City" },111 { label: "Kolkata", value: "Kolkata" },112 { label: "Stockholm", value: "Stockholm" },113 { label: "Dubai", value: "Dubai" },114 { label: "Milan", value: "Milan" },115 { label: "Seoul", value: "Seoul" },116 { label: "Austin", value: "Austin" },117 { label: "London", value: "London" },118 { label: "Moscow", value: "Moscow" },119];120121export const externalFilterData = [122 { id: 1, name: "Dr. Elena Vasquez", age: 42, email: "elena.vasquez@techcorp.com", salary: 145000, department: "AI Research", active: true, location: "San Francisco" },123 { id: 2, name: "Kai Tanaka", age: 29, email: "k.tanaka@techcorp.com", salary: 95000, department: "UX Design", active: true, location: "Tokyo" },124 { id: 3, name: "Amara Okafor", age: 35, email: "amara.okafor@techcorp.com", salary: 125000, department: "DevOps", active: false, location: "Lagos" },125 { id: 4, name: "Santiago Rodriguez", age: 27, email: "s.rodriguez@techcorp.com", salary: 82000, department: "Marketing", active: true, location: "Mexico City" },126 { id: 5, name: "Priya Chakraborty", age: 33, email: "priya.c@techcorp.com", salary: 118000, department: "Engineering", active: true, location: "Kolkata" },127 { id: 6, name: "Magnus Eriksson", age: 38, email: "magnus.erik@techcorp.com", salary: 110000, department: "Product", active: false, location: "Stockholm" },128 { id: 7, name: "Zara Al-Rashid", age: 31, email: "zara.alrashid@techcorp.com", salary: 98000, department: "Sales", active: true, location: "Dubai" },129 { id: 8, name: "Luca Rossi", age: 26, email: "luca.rossi@techcorp.com", salary: 75000, department: "Marketing", active: true, location: "Milan" },130 { id: 9, name: "Dr. Sarah Kim", age: 45, email: "sarah.kim@techcorp.com", salary: 165000, department: "AI Research", active: true, location: "Seoul" },131 { id: 10, name: "Olumide Adebayo", age: 30, email: "olumide.a@techcorp.com", salary: 105000, department: "Engineering", active: false, location: "Austin" },132 { id: 11, name: "Isabella Chen", age: 24, email: "isabella.chen@techcorp.com", salary: 68000, department: "UX Design", active: true, location: "London" },133 { id: 12, name: "Dmitri Volkov", age: 39, email: "dmitri.volkov@techcorp.com", salary: 135000, department: "DevOps", active: true, location: "Moscow" },134];135136export const externalFilterHeaders: HeaderObject[] = [137 { accessor: "name", label: "Name", width: "1fr", minWidth: 120, filterable: true, type: "string" },138 { accessor: "age", label: "Age", width: 120, filterable: true, type: "number" },139 {140 accessor: "department",141 label: "Department",142 width: 150,143 filterable: true,144 type: "enum",145 enumOptions: DEPARTMENT_OPTIONS,146 },147 {148 accessor: "location",149 label: "Location",150 width: 150,151 filterable: true,152 type: "enum",153 enumOptions: LOCATION_OPTIONS,154 },155 { accessor: "active", label: "Active", width: 120, filterable: true, type: "boolean" },156 {157 accessor: "salary",158 label: "Salary",159 width: 120,160 filterable: true,161 type: "number",162 align: "right",163 valueFormatter: ({ value }) => `$${(value as number).toLocaleString()}`,164 },165];166167export const externalFilterConfig = {168 headers: externalFilterHeaders,169 rows: externalFilterData,170 tableProps: { externalFilterHandling: true, columnResizing: true },171} as const;172
External filtering provides two key benefits:
- API Integration: Use
onFilterChangeto trigger server-side filtering while keeping the table's UI filter controls. - Complete Control: Use
externalFilterHandling=to disable all internal filtering and provide your own pre-filtered data.
External Filtering Configuration
| Property | Required | Description | Example |
|---|---|---|---|
Property | Required | Description | Example |
onFilterChange | Optional | Callback function triggered when filter configuration changes. Receives the current filter state with all active filters. | |
externalFilterHandlingboolean | Optional | When true, completely disables internal filtering logic. The table will not filter data internally - you must provide pre-filtered data via the rows prop. |
Enum Filter Search
For enum columns with more than 10 options, Simple Table automatically provides a search input to help users quickly find and select the desired enum values. This improves usability when dealing with large sets of enum options.