#[derive(RecordForm)]
{
// Attributes available to this derive:
#[form]
}
Expand description
Derive RecordForm for the typed value a resource’s form writes.
One field per model column the form writes, named and typed like the
model’s field. A scalar (String, a TypedValue type, an Options enum,
or an Option of one) binds the key its control posts; a #[form(embed)] field binds
every key of an EmbeddedForm value and is written whole.
#[derive(tablo_core::RecordForm)]
#[form(model = User)]
pub struct UserForm {
pub name: String,
#[form(blank = "member")]
pub role: String,
#[form(blank = 0)]
pub age: i64,
}The derive also emits UserFormField, one variant per field, which
Posted and Resource::validate_record’s FieldErrors key on and
RecordForm::fields answers with each variant’s keys.
It emits UserFormControls, one control per field chosen from the field —
a bool is a toggle, #[form(options = T)] a choice over T’s options,
#[form(options)] a choice over the field type’s options,
#[form(relationship = R)] a choice over R’s records (a multiple choice over a Vec of
their keys), #[form(file)] a file field, #[form(embed)] the embedded value’s schema,
#[form(repeat)] a repeater, and any
other field a text field — with controls() handing them over, typed by the form so only a
Schema<UserForm> places them, and RecordForm::control answering one
field’s. A resource’s ResourceDef defaults its form to them; ResourceDef::form arranges
or extends them instead, and a form renders the controls it does not place after the ones
it does. The table and detail page read the model, not the form: see
ModelColumns.
§Attributes
#[form(model = User)]on the struct: the model the form writes.#[form(blank = <expr>)]on a scalar: the value an empty submission reads as.#[form(optional)]on aString: an empty submission reads as"".#[form(options)]on anOptionsenum or anOptionof one: a choice over its options. Without it, the field is a text field.#[form(options = Status)]: a choice overStatus::options().#[form(relationship = AuthorResource)]on a foreign key: a choice over the source’s records, each labelled by itsrecord_title. The source is anyOptionSource, which everyResourceis.#[form(relationship = TagResource)]on aVecof the source’s keys, named like the model’s#[has_many(via = joins.target)]field: a many-to-many field, a multiple choice whose records the write links through the join model, one row each. No record is its blank answer; an edit that does not post it keeps the record’s links.#[form(file)]on aString: a file field.#[form(embed)]on anEmbeddedFormvalue.#[form(repeat)]on a#[document]list ofRepeaterItemvalues: a repeater, a row per item, which the browser adds, removes and moves. No rows is its blank answer.
A scalar’s blank answer is its blank, "" for an optional String, None for an
Option, or false for a bool. A field with none is required: the panel renders its
control required and the parse refuses an empty submission. No control declares presence.
A generic struct, a tuple struct, an empty struct, a Deferred<_> field,
blank or optional on an Option, an embedded value or a many-to-many field, optional
on a type other than String, and an unknown key are compile errors. So are a field the
model lacks, a type the model’s field does not have, a scalar that is not a FormScalar,
and a many-to-many field whose keys are not its source’s primary key.
Derive RecordForm for the typed value a resource’s form writes; documented on the tablo-core
re-export.