Filters
Declare the controls that narrow the data of a view or a zone.
A filter describes one query value and the control that changes it. The user changes the control, the value goes into the URL, and your view reads the typed value and applies it to its query.
A filter is not a field. A field describes a piece of data in a record. A filter describes a question about the records, and one filter can search many columns or none. Thus filters have their own builders, and they do not accept field declarations.
This introduction explains how a filter goes from a declaration to a query. The page for each filter type has the full options.
The URL is the state
The current value of each filter is in the query string and nowhere else. The protocol response describes the controls and does not contain their values. This has three results. A filtered list has a URL that a user can share or bookmark. The back button of the browser restores the previous filters. And a reload shows the same records.
The four steps of a filter
Declare. defineFilters creates a filter bar from an array of filters. The
order of the array is the order of the controls.
const listFilters = defineFilters([
filters.text('search').setLabel('Search'),
filters.select('status', statusOptions).setLabel('Status'),
])
Bind. A filter bar has no place in the URL until you register it on a view or a
zone. The owner gives the filters their scope. A view filter uses f[name] in the
query string, and a zone filter uses f[zone][name], so two zones on one page can
each have a status filter.
orders.view('list', { filters: listFilters }, async (ctx, view) => {
// ...
})
Read. ctx.parse(view).filters returns one typed property for each filter. A
value that is missing or not valid is undefined. Backlit parses the values and
does not apply them. Your callback applies them to your query, so you decide what
“search” means for your data.
const { filters: applied } = ctx.parse(view)
const rows = await db.orders.list({
search: applied.search,
status: applied.status,
})
Place. A filter bar is not a content block. A section and a panel each have a
separate slot for it, which you set with setFilters. The controls then render in
the header of that block.
return section('Orders')
.setFilters(view.state.filters)
.setContent([panel([table(rows).setFields(columns)])])
When the owner of the filter bar is a zone, a change to a control requests only that zone. See Binding and placement.
Select a filter type
| Your need | Filter |
|---|---|
| Search with free text | Text |
| Select yes, no, or no selection | Toggle |
| Select one or more values from a fixed set | Select |
| Select the records between two calendar dates | Date range |
| Select the records between two numbers | Number range |
| Select the grouping unit of a chart, such as day or month | Period |
A select filter with one value can also render as a row of buttons above a list. See the segments block.
Filters are one of three types of search state. A paginator and a sorter use the same steps: declare, bind, read, and place. See Search state.
API
defineFilters(filters: FilterBuilderContract[]): FilterBar
Filter names can contain letters, numbers, underscores, and hyphens. Each name must be unique in one filter bar.
Declaration
Declare related filters together. Their array order is their control order.
import {
defineFilters,
defineResource,
filters,
section,
} from '@backlit/sdk'
const orders = defineResource('orders', [])
const orderFilters = defineFilters([
filters.text('search').setLabel('Search orders'),
filters.toggle('paid').setLabel('Paid'),
])
orders.view(
'list',
{ filters: orderFilters },
(_ctx, view) =>
section('Orders').setFilters(view.state.filters)
)
export default orders{
"kind": "success",
"status": 200,
"node": {
"kind": "view",
"resource": "orders",
"name": "list",
"slots": {
"content": [
{
"kind": "section",
"title": "Orders",
"slots": {
"content": [],
"filters": {
"kind": "filters",
"resource": "orders",
"view": "list",
"keyword": "f",
"definesResult": true,
"dependsOnResult": false,
"filters": [
{
"kind": "text",
"name": "search",
"label": "Search orders"
},
{
"kind": "toggle",
"name": "paid",
"label": "Paid"
}
]
}
}
}
]
}
}
}Label
Every filter supports setLabel. If you do not set a label, the SDK sends an
empty label.
import {
defineFilters,
defineResource,
filters,
section,
} from '@backlit/sdk'
const orders = defineResource('orders', [])
const orderFilters = defineFilters([
filters.text('search').setLabel('Search orders'),
])
orders.view(
'list',
{ filters: orderFilters },
(_ctx, view) =>
section('Orders').setFilters(view.state.filters)
)
export default orders{
"kind": "success",
"status": 200,
"node": {
"kind": "view",
"resource": "orders",
"name": "list",
"slots": {
"content": [
{
"kind": "section",
"title": "Orders",
"slots": {
"content": [],
"filters": {
"kind": "filters",
"resource": "orders",
"view": "list",
"keyword": "f",
"definesResult": true,
"dependsOnResult": false,
"filters": [
{
"kind": "text",
"name": "search",
"label": "Search orders"
}
]
}
}
}
]
}
}
}All filter types
One filter bar can contain each built-in filter type. A select filter can also accept several selected values.
import {
defineFilters,
defineResource,
filters,
section,
} from '@backlit/sdk'
const orders = defineResource('orders', [])
const orderFilters = defineFilters([
filters.text('search').setLabel('Search orders'),
filters.toggle('paid').setLabel('Paid'),
filters
.select('status', [
{ value: 'open', label: 'Open' },
{ value: 'shipped', label: 'Shipped' },
{ value: 'cancelled', label: 'Cancelled' },
])
.setLabel('Status')
.setCardinality('many'),
filters.dateRange('placed').setLabel('Order date'),
filters
.period('period', ['week', 'month', 'quarter', 'year'])
.setLabel('Group by'),
])
orders.view(
'list',
{ filters: orderFilters },
(_ctx, view) =>
section('Orders').setFilters(view.state.filters)
)
export default orders{
"kind": "success",
"status": 200,
"node": {
"kind": "view",
"resource": "orders",
"name": "list",
"slots": {
"content": [
{
"kind": "section",
"title": "Orders",
"slots": {
"content": [],
"filters": {
"kind": "filters",
"resource": "orders",
"view": "list",
"keyword": "f",
"definesResult": true,
"dependsOnResult": false,
"filters": [
{
"kind": "text",
"name": "search",
"label": "Search orders"
},
{
"kind": "toggle",
"name": "paid",
"label": "Paid"
},
{
"kind": "select",
"name": "status",
"label": "Status",
"options": [
{
"value": "open",
"label": "Open"
},
{
"value": "shipped",
"label": "Shipped"
},
{
"value": "cancelled",
"label": "Cancelled"
}
],
"cardinality": "many"
},
{
"kind": "date-range",
"name": "placed",
"label": "Order date"
},
{
"kind": "period",
"name": "period",
"label": "Group by",
"units": [
"week",
"month",
"quarter",
"year"
]
}
]
}
}
}
]
}
}
}