Skip to content
Backlit
Esc
navigateopen⌘Jpreview
On this page

Lookup target

Let a form input select a value from fetched records.

A lookup target connects an input to a lookup registered on a resource. The lookup declares the fields its records return. The target names the lookup and supplies the args for its params. An input variant holds the target and names the two fields the control reads: value, the field a taken record writes into the input, and label, the field the closed input shows.

Two input variants use a lookup target. lookupVia makes a picker with a search. optionsVia makes a closed list. An enum field and a ref field accept both.

API

lookupVia(
  source: LookupDefinition | (() => LookupDefinition),
  args?: (FieldBuilder | string | number | boolean)[],
  options: {
    value: string
    label: string
    create?: VisitTarget
  }
): SearchInputVariant

optionsVia(
  source: LookupDefinition | (() => LookupDefinition),
  args?: (FieldBuilder | string | number | boolean)[],
  options: {
    value: string
    label: string
  }
): OptionsInputVariant

value and label must name fields the lookup’s resolver declares. TypeScript checks both names, and checks that the value field’s value fits the input field. The value is a scalar, always, because that is what a store holds.

args is required when the lookup declares params, and not permitted when it declares none. The tuple has one entry for each param, in param order. A field builder is read on the client as the value of the field with that name on the same record as the input. A literal is sent as it is.

Search picker

The picker searches the lookup and shows its declared fields. Taking a record writes its value field to the input.

import {
  datalist,
  defineResource,
  fields,
  lookupVia,
} from '@backlit/sdk'

import { customerPicker, customers } from './customers.ts'

const orders = defineResource('orders', [
  fields.ref('customer', () => customers, {
    label: 'Customer',
    display: 'name',
    inputVariant: lookupVia(customerPicker, {
      value: 'id',
      label: 'name',
    }),
  }),
])

orders.view('detail', () =>
  datalist({
    customer: { id: 42, name: 'A. Customer' },
  }).setFields(orders.pick('customer'))
)

export default orders
{
  "kind": "success",
  "status": 200,
  "node": {
    "kind": "view",
    "resource": "orders",
    "name": "detail",
    "slots": {
      "content": [
        {
          "kind": "datalist",
          "fields": [
            {
              "kind": "ref",
              "name": "customer",
              "label": "Customer",
              "display": {
                "kind": "text",
                "name": "name",
                "label": "Name"
              },
              "inputVariant": {
                "kind": "search",
                "lookup": {
                  "kind": "lookup",
                  "resource": "customers",
                  "name": "picker"
                },
                "value": "id",
                "label": "name"
              }
            }
          ],
          "data": {
            "customer": "A. Customer"
          }
        }
      ]
    }
  }
}

Closed list with params

The lookup declares a param. The state input supplies the country field as its arg. The client reads the country beside the state, asks the lookup when the country has a value, and asks again when it changes. A change clears the state.

import {
  form,
  defineResource,
  fields,
  optionsVia,
} from '@backlit/sdk'
import { z } from 'zod'

import { countries, countryPicker } from './countries.ts'
import { states, statePicker } from './states.ts'

const country = fields.ref('country', () => countries, {
  label: 'Country',
  display: 'name',
  inputVariant: optionsVia(countryPicker, {
    value: 'code',
    label: 'name',
  }),
})

const state = fields.ref('state', () => states, {
  label: 'State',
  display: 'name',
  inputVariant: optionsVia(statePicker, [country], {
    value: 'code',
    label: 'name',
  }),
})

const orders = defineResource('orders', [country, state])

const placeOrder = orders.action(
  'place',
  z.object({ country: z.string(), state: z.string() }),
  () => {}
)

orders.view('create', () => {
  const formBuilder = form(placeOrder)

  return formBuilder.setContent([
    formBuilder.fields(...orders.pick('country', 'state')),
  ])
})

export default orders
{
  "kind": "success",
  "status": 200,
  "node": {
    "kind": "view",
    "resource": "orders",
    "name": "create",
    "slots": {
      "content": [
        {
          "kind": "form",
          "target": {
            "resource": "orders",
            "action": "place"
          },
          "slots": {
            "content": [
              {
                "kind": "form-fields",
                "fields": [
                  {
                    "kind": "ref",
                    "name": "country",
                    "label": "Country",
                    "display": {
                      "kind": "text",
                      "name": "name",
                      "label": "Name"
                    },
                    "inputVariant": {
                      "kind": "options",
                      "lookup": {
                        "kind": "lookup",
                        "resource": "countries",
                        "name": "picker"
                      },
                      "value": "code",
                      "label": "name"
                    }
                  },
                  {
                    "kind": "ref",
                    "name": "state",
                    "label": "State",
                    "display": {
                      "kind": "text",
                      "name": "name",
                      "label": "Name"
                    },
                    "inputVariant": {
                      "kind": "options",
                      "lookup": {
                        "kind": "lookup",
                        "resource": "states",
                        "name": "byCountry",
                        "args": [
                          {
                            "path": "country"
                          }
                        ]
                      },
                      "value": "code",
                      "label": "name"
                    }
                  }
                ]
              }
            ]
          }
        }
      ]
    }
  }
}

Create target

Pass create to lookupVia to let the picker offer to make the record it cannot find: one complete visit target, ordinarily opening a create view in a dialog over the form.

lookupVia(categoryPicker, {
  value: 'name',
  label: 'name',
  create: visitTo(categoryCreate, [], {
    text: 'New category',
    mode: 'drawer',
  }),
})

Thunk

Pass the lookup definition as a function when resource modules refer to each other. Backlit reads it when it creates the protocol node.

import {
  datalist,
  defineResource,
  fields,
  lookupVia,
} from '@backlit/sdk'

import { customerPicker, customers } from './customers.ts'

const orders = defineResource('orders', [
  fields.ref('customer', () => customers, {
    label: 'Customer',
    display: 'name',
    inputVariant: lookupVia(() => customerPicker, {
      value: 'id',
      label: 'name',
    }),
  }),
])

orders.view('detail', () =>
  datalist({
    customer: { id: 42, name: 'A. Customer' },
  }).setFields(orders.pick('customer'))
)

export default orders
{
  "kind": "success",
  "status": 200,
  "node": {
    "kind": "view",
    "resource": "orders",
    "name": "detail",
    "slots": {
      "content": [
        {
          "kind": "datalist",
          "fields": [
            {
              "kind": "ref",
              "name": "customer",
              "label": "Customer",
              "display": {
                "kind": "text",
                "name": "name",
                "label": "Name"
              },
              "inputVariant": {
                "kind": "search",
                "lookup": {
                  "kind": "lookup",
                  "resource": "customers",
                  "name": "picker"
                },
                "value": "id",
                "label": "name"
              }
            }
          ],
          "data": {
            "customer": "A. Customer"
          }
        }
      ]
    }
  }
}

Was this page helpful?