Skip to content
Backlit
Esc
navigateopen⌘Jpreview
On this page

Extend field roles

Convert and display a field value through a custom role.

A field kind defines the value, such as text or a number. A role defines how the server prepares that value for a use. A variant selects an option within the role. Backlit includes the display and input roles.

Add a custom role when several blocks need the same data conversion. Use a variant or client styling for a display change that does not change the data. This example adds a summary role for text and shortens long notes on the server.

Protocol and server

Augment FieldRoleRegistry in @backlit/contract. Map each supported field kind to its variants. Use never for a supported kind with no variants. A missing kind does not support the role.

Register a complete role map with remappers.registerRole. The example calls the extension directly to keep the resource example self-contained. In an app, pass the extension to defineApp({ extensions: [summary], areas }).

import { defineResource, fields, table } from '@backlit/sdk'
import { summary } from './summary.ts'
summary()
const orders = defineResource('orders', [
  fields.text('title'),
  fields.text('note', { summaryVariant: 'excerpt' }),
])
orders.view('list', () =>
  table([
    {
      title: 'Order A',
      note: 'This note contains more than forty characters and is shortened on the server.',
    },
  ])
    .setFields(orders.pick('title', 'note'))
    .setRoles({ note: 'summary' })
)
export default orders
{
  "kind": "success",
  "status": 200,
  "node": {
    "kind": "view",
    "resource": "orders",
    "name": "list",
    "slots": {
      "content": [
        {
          "kind": "table",
          "fields": [
            {
              "kind": "text",
              "name": "title",
              "label": ""
            },
            {
              "kind": "text",
              "name": "note",
              "label": "",
              "summaryVariant": "excerpt"
            }
          ],
          "roles": {
            "note": "summary"
          },
          "data": [
            {
              "title": "Order A",
              "note": "This note contains more than forty ch..."
            }
          ]
        }
      ]
    }
  }
}

Import the protocol module from both server and client extension modules. FieldRemapper and BacklitExtension come from @backlit/sdk/types. A remapper returns the value sent to the client. It can call report(reason) to omit an invalid value. The client does not receive the original record.

Client

Supply a matching field primitive through the client extension. The key joins the field kind and the capitalized role, so this role uses textSummary. Use the same extension name and version on both sides.

import './protocol.ts'
import type { TextField } from '@backlit/contract'
import type {
  BacklitClientExtension,
  FieldPrimitiveProps,
  FieldRolePrimitiveMap,
} from '@backlit/ui/types'

function TextSummary({ value }: FieldPrimitiveProps<TextField>) {
  return <span>{String(value ?? '')}</span>
}

const primitives = {
  textSummary: TextSummary,
} satisfies FieldRolePrimitiveMap<'summary'>

export const summaryClient = {
  name: 'summary',
  version: '1.0.0',
  primitives,
} satisfies BacklitClientExtension

Pass summaryClient in the extensions prop of BacklitApp. The app host must also supply its link component, navigation function, search adapter, and children. The client compares the extension identities reported by the server.

Select a role

Use setRoles({ fieldName: 'summary' }) on a table, datalist, lookup, form field block, or repeater. A field without a selection uses the block default: display for data blocks, and input for form fields and repeaters. Role selections travel beside the fields and data.

A role map must contain every kind that its declaration supports. Use registerRemappers only to replace entries in a registered role. A missing runtime handler reports a defect and uses the fallback role. The client also has a fallback when a matching primitive is missing.

Relations delegate conversion to their declared display field. Do not register a has remapper to calculate a relation count. Fetch the count as a number and show it through a number field. Groups also use their members to convert values. See Extensions for registry scope and builder contracts.

Was this page helpful?