Search state
Register filters, sorting, and pagination on views and zones.
State definitions do not belong to a scope until a view or zone registers them.
Register an object of state atoms. Its keys are local names for the bindings.
Read all values with ctx.parse(view) or ctx.parse(zone). Place controls with
view.state.name or zone.state.name.
Filter, sort, and paginate records
The SDK parses state and describes controls. Your resolver must apply filters, sorting, and pagination to the query. It must also calculate the total count.
import {
defineResource,
defineFilters,
definePaginator,
defineSorter,
fields,
filters,
panel,
table,
} from '@backlit/sdk'
const orders = defineResource('orders', [
fields.text('title'),
fields.number('total'),
])
const rows = [
{ title: 'Order A', total: 12 },
{ title: 'Order B', total: 25 },
]
const search = defineFilters([filters.text('query')])
const paginator = definePaginator({ perPage: 10 })
const sorter = defineSorter({
columns: orders.pick('title', 'total'),
})
orders.view(
'list',
{ filters: search, paginator, sorter },
(ctx, view) => {
const state = ctx.parse(view)
const selected = rows.filter((row) =>
row.title.includes(state.filters.query ?? '')
)
selected.sort((left, right) => {
for (const sort of state.sorter) {
const a = left[sort.column]
const b = right[sort.column]
const comparison =
typeof a === 'number' && typeof b === 'number'
? a - b
: String(a).localeCompare(String(b))
if (comparison !== 0)
return sort.direction === 'asc'
? comparison
: -comparison
}
return 0
})
const start =
(state.paginator.page - 1) * state.paginator.perPage
return panel([
table(
selected.slice(
start,
start + state.paginator.perPage
)
)
.setFields(orders.pick('title', 'total'))
.setPaginator(view.state.paginator, {
total: selected.length,
})
.setSorter(view.state.sorter),
]).setFilters(view.state.filters)
}
)
export default orders{
"kind": "success",
"status": 200,
"node": {
"kind": "view",
"resource": "orders",
"name": "list",
"slots": {
"content": [
{
"kind": "panel",
"slots": {
"content": [
{
"kind": "table",
"fields": [
{
"kind": "text",
"name": "title",
"label": ""
},
{
"kind": "number",
"name": "total",
"label": ""
}
],
"data": [
{
"title": "Order A",
"total": 12
},
{
"title": "Order B",
"total": 25
}
],
"pagination": {
"resource": "orders",
"view": "list",
"keyword": "p",
"definesResult": false,
"dependsOnResult": true,
"param": "page",
"perPage": 10,
"total": 2
},
"sorting": {
"resource": "orders",
"view": "list",
"keyword": "s",
"definesResult": true,
"dependsOnResult": false,
"param": "by",
"columns": [
"title",
"total"
]
}
}
],
"filters": {
"kind": "filters",
"resource": "orders",
"view": "list",
"keyword": "f",
"definesResult": true,
"dependsOnResult": false,
"filters": [
{
"kind": "text",
"name": "query",
"label": ""
}
]
}
}
}
]
}
}
}defineFilters creates a FilterBar. definePaginator({ perPage, param? })
uses page as its default parameter. defineSorter({ columns, param? }) uses
by as its default parameter. Supply field builders in columns.
A missing, repeated, fractional, zero, or negative page parses to page 1.
The page size comes from the definition. A sorter parses to an ordered array
of { column, direction }. It drops unknown columns and repeated columns.
The first occurrence of a column takes precedence.
Place bindings with setPaginator(binding, { total }) and setSorter(binding)
on tables, record lists, or record timelines. Item lists and item timelines
support pagination but do not expose a sorter. Pagination and sorting on one
block must use the same scope.
URL format
The root keywords are f for filters, p for pagination, and s for sorting.
A zone adds its name below the keyword.
f[status]=open&p[page]=2&s[by]=-createdAt,title
f[activity][status]=open&p[activity][page]=2
f[tags]=new&f[tags]=urgent
A minus sign selects descending sort. A comma separates sort columns in priority order. Array values use repeated keys.
parseSearchParams(query) accepts a leading ? and returns a search object.
stringifySearchParams(search) returns text without a leading ?. Unknown
keys remain in the object. These functions encode data; state atoms validate it.
Pass the parsed object as the final argument of app.renderView or
app.renderZone.
Registration rules
State field names and zone names use letters, digits, underscores, and hyphens.
Two atoms with the same keyword in one scope cannot share a field name. Use
param to rename a paginator or sorter when a scope needs more than one.
A zone name cannot equal a view field name under the same keyword.
Filters and sorters have definesResult: true. A paginator has
dependsOnResult: true. The client uses these flags to clear an old page when
the result changes. State atoms sharing a keyword must use the same flags.
An extension can implement StateAtomContract with KEYWORD, PARSE,
fieldNames, definesResult, and dependsOnResult. Import symbols from
@backlit/sdk/symbols and the contract type from @backlit/sdk/types.