CRUD / foldkit-crud
Showing values
How a column shows
A Display is foldkit-form's Input from the reading side: how one selected
member shows, described without a renderer.
const Post = Blog.Post.pipe(
Entity.annotateMembers({
id: Display.of(Display.hidden()), // read, so a row can be opened by it, and not shown
cents: Display.of(Display.number(cents => `$${(cents / 100).toFixed(2)}`)),
}),
)
Display.show(column.display, row[column.key], { yes: 'Live', no: 'Draft' }) // the cell's text| Kind | For |
|---|---|
Text, Number | a value as it is, or through format |
Flag | a boolean, in the words given (yes / no) |
Hidden | a member read and not shown |
Ref | a relation selected with true: its id, or ids |
Nested | a relation read through a Selection: the target's own columns, with shape one, many, or page |
A display is resolved from the most to the least explicit source: Display.of
metadata on the member, how the relation was selected, then the shape of the
schema. Display.show is the text any view can fall back on; a view that wants
a link or a badge reads display and the value.
foldkit-mixins-crud draws a list as a table and a
detail as a description list from exactly this.
There is one primitive, as with a form's controls: a kind, whether it is
shown, its data, and its text. The kinds above are made with
Display.kind, and so is yours:
const Badge = Display.kind<{ readonly tone: string }>('Badge', {
text: (_, value) => String(value), // the floor: what it says when nothing draws it specially
})
const Post = Blog.Post.pipe(Entity.annotateMembers({ status: Display.of(Badge.of({ tone: 'soft' })) }))Badge.is(display) narrows display.data. A drawn view draws it specially
once given a renderer for Badge.
A relation picker's choices
A form names a relation's target Entity and leaves listing it to you, because a
relationship existing is no licence to read a table. A list is that licence: a
query you declared and your server authorizes. A list with a choice offers
its loaded rows as choices, and Crud.options hands each picker of a form the
list over its target:
const pickers = Crud.options(EditPostForm, [AuthorList])
Placed.view(model, h, { options: pickers(model) }) // with foldkit-mixins-formpickers(model) is keyed by the form's keys:
{ authorId: [{ value, label }, …] }. A list is matched to a picker by
Entity, so one author list serves authorId and editorId alike. A picker
whose target no list is over, or whose list has no choice, throws when
Crud.options is called, not when the form is drawn. AuthorList.choices(model)
is one list's choices on its own. Both are empty until the page is loaded, and
hold only the rows loaded so far.
When a picker searches
const AuthorList = Authors.at({
data: Data,
// What the picker's search box holds is the form's; here it is the query's input.
input: model => Option.some({ search: EditPostForm.search(model.editor.form, 'authorId') }),
})
const pickers = Crud.options(EditPostForm, [AuthorList], {
chosen: model => model.editor.form, // the form's Model, wherever the page keeps it
})
const subscriptions = Data.subscriptions({ authors: AuthorList.active, chosen: pickers.active })A search narrows the list, and what the picker already holds may no longer be
in it. With chosen, those rows stay among the choices, first, and
pickers.active makes them a requirement so Remote reads them and they can be
named. Without it a select would show a post's author as blank the moment a
search stopped finding them.