Fields
Describe the data type, the format, and the rendering of each piece of data in your app.
A field describes one piece of data: its name, its data type, its format, and how the client renders it. Fields are the base of most Backlit screens. A table uses fields for its columns, a datalist uses them for its labeled values, and a form uses them for its inputs.
This introduction explains what a field is, where you declare it, and how to select a field type. The page for each field type has the full options.
What a field is
The field name is the key that Backlit reads from your records. The field type says what the value is, such as text, a number, or a date. The options say what the value means and how to show it.
fields.number('total', { label: 'Total', format: 'currency', currency: 'USD' })
This declaration says that each record has a total key, that the value is a
number, and that the number is an amount in US dollars. It does not say how to draw
a currency amount. The client decides that, with the locale of the user. A field
declares the meaning of a value, and the client owns the presentation.
A field is not a database column
A field does not know where its data comes from. Many fields have the name of a database column, because a view often returns database rows without changes. But Backlit reads the value from the record that your view supplies, and your view can build that record from any source.
Thus a field can describe a computed value, an aggregate, or a statistic. The
openRate field below has no column. The query calculates it.
import { defineResource, fields, table } from '@backlit/sdk'
import { db } from '../db.ts'
const newsletters = defineResource('newsletters', [
fields.text('subject', { label: 'Subject' }),
fields.number('recipients', { label: 'Recipients', format: 'integer' }),
fields.number('openRate', { label: 'Open rate', format: 'percent' }),
])
newsletters.view('list', async () => {
// Each row has `subject`, `recipients`, and `openRate`. The query calculates the
// last two from the deliveries table.
const rows = await db.newsletters.listWithDeliveryStats()
return table(rows).setFields(newsletters.pick('subject', 'recipients', 'openRate'))
})
export default newsletters
Declare fields on a resource
Declare the fields of an entity one time, on its resource. Then select them where
you need them. resource.pick returns several fields in the order that you give,
and resource.get returns one field. The example below declares three fields and
uses them as the columns of a table. The protocol output shows what the client
receives.
import { defineResource, fields, table } from '@backlit/sdk'
const orders = defineResource('orders', [
fields.number('id', { label: 'Order' }),
fields.text('customer', { label: 'Customer' }),
fields.date('placedAt', { label: 'Placed' }),
])
orders.view('list', () =>
table([]).setFields(
orders.pick('id', 'customer', 'placedAt')
)
)
export default orders{
"kind": "success",
"status": 200,
"node": {
"kind": "view",
"resource": "orders",
"name": "list",
"slots": {
"content": [
{
"kind": "table",
"fields": [
{
"kind": "number",
"name": "id",
"label": "Order"
},
{
"kind": "text",
"name": "customer",
"label": "Customer"
},
{
"kind": "date",
"name": "placedAt",
"label": "Placed"
}
],
"data": []
}
]
}
}
}Because each screen uses the same declarations, a label or a format is written one
time. When you change format on the resource, each table, datalist, and form that
uses the field changes with it.
A screen can also adjust a field for its own use. configure and clone return a
new builder and do not change the declaration on the resource. You can also create a
field directly in a view, without a resource, for a value that only one screen
shows.
Display and input
Each field has a read-only rendering and an input rendering. A date field is
formatted text in a table and a date picker in a form. Backlit calls these two uses
the display role and the input role, and the block selects the role. A table
uses display, and a form uses input.
Two options refine the rendering inside a role. displayVariant selects the
read-only presentation, such as badge for an enum. inputVariant selects the
input control, such as textarea for text.
For each role, the SDK converts the value of your record to a protocol value before it sends the response. When a value cannot be converted, the SDK reports a defect and omits that value. The other values of the block stay valid. To add a role of your own, see Field roles.
Select a field type
| Your data | Field type |
|---|---|
| A string, an email address, a URL, a phone number, or Markdown | Text |
| An integer, a decimal, a currency amount, a percentage, progress | Number |
| A true or false value | Boolean |
| A calendar date, a date and time, a time, or a relative date | Date |
| One or more values from a fixed set of options, such as a status | Enum |
| Several record keys that read as one value, such as an address | Group |
| A record that the current record points to, such as the customer of an order | Ref |
| Records that point back to the current record, such as the posts of an author | Has |
When no built-in type fits your data, an extension can add a field type. See Extensions.
Shared field options
Field options accept label, description, reveal, and enable where
supported by the field contract. Use revealWhen and enableWhen to create
configured copies from typed form conditions. See Form conditions.
Simple fields also accept link: linkTo(view, args) for display navigation.
Relation displays put that link on the target field.
configure and clone return new builders. They do not change the resource’s
stored declaration. Most block setters change their builder and return it.
Create data blocks and forms inside the resolver for each request.