Form fields DSL
v0.0.7
The fields section of a resource's form block declares every input of the form: its type, its validation flags, its options and its nested shape. Fields can also be discovered from the resource's attributes with auto_fields.
fields
Holds the field entities and the field order. It sits at mishka_gervaz.form.fields.
| Option | Type | Default | Description |
|---|---|---|---|
field_order | list(atom) | none | Field display order. Fields not in list appear at end. |
field
Declares one field. The inline form works too: field :title, :text, required: true. Without a type, Gervaz infers it from the Ash attribute. It sits at mishka_gervaz.form.fields.field.
Call: field name, type
Arguments:
| Argument | Type | Default | Description |
|---|---|---|---|
name | atom | none | Required. Field identifier. |
type | nil | :text | :password | :textarea | :number | :checkbox | :date | :datetime | :select | :multi_select | :relation | :json | :key_map | :key_list | :nested | :array_of_maps | :string_list | :file | :hidden | :toggle | :range | :upload | :combobox | module | none | The field type. Leave it out to infer it from the Ash attribute. Built-in types: :text, :password, :textarea, :number, :checkbox, :date, :datetime, :select, :multi_select, :relation, :json, :key_map, :key_list, :nested, :array_of_maps, :string_list, :file, :hidden, :toggle, :range, :upload and :combobox. The Standard form template draws :array_of_maps as a plain text input. A module that implements the FieldType behaviour also works. Gervaz calls its sanitize/2, parse_params/2 and validate/2, and the Standard template draws the field with the module's render/2. A save that validate/2 refuses writes nothing. |
Options:
| Option | Type | Default | Description |
|---|---|---|---|
source | atom | none | Data source attribute. Defaults to field name. |
required | boolean | false | Whether the field is required. |
show_on | :create | :update | none | Shows the field only in one mode. |
default | any | none | The value a create form starts with: it is drawn in the field's input and saved when it is left. A value the mount's defaults gives the field is saved instead. A :select with a default has no empty choice. |
depends_on | atom | none | Field this depends on (for cascading selects). |
virtual | boolean | false | Virtual field (not in resource attributes). |
resource | module | none | The resource for a virtual or relation field. A virtual :relation or :select field needs a resource or options. |
derive_value | (any -> any) | none | Reads the current value off the record in edit mode: fn record -> value. A virtual field has no attribute to read, so it needs this. Gervaz calls it for :relation fields and for fields that another field depends on. |
options | list(any) | (-> any) | (any -> any) | none | The choices: a list, or a function. A zero-arity function runs when the options load. A :relation field with no resource may take fn state -> options instead. Gervaz reads it again each time the options load: at init, on search and when a depends_on parent changes. It can offer what the parent's value in state.field_values allows, and it loads at init even while the parent is empty, so the function decides what an empty parent means. A :key_map or :key_list field may take fn state -> keys end. Gervaz reads it with the form state each time it draws the field and each time it reads the field's params, so the keys can follow state.defaults. See MishkaGervaz.Form.Types.Field.KeyMap. A :select or :multi_select field may take fn state -> options end. Gervaz reads it with the form state each time it draws the field, so the options can follow what the form holds (state.form, state.defaults, state.current_user). It runs on every draw, so read what it needs from the state or a cache rather than querying. Its options are returned as it gives them, with labels untranslated. |
display_field | atom | (any -> any) | (any, any -> any) | none | How a relation or select option is labeled: a field name, or a one-argument function of the record. The form ignores a function of two arguments. |
search_field | atom | none | Field to search on for autocomplete. |
value_field | atom | none | The attribute of the chosen record to store instead of :id. |
readonly | boolean | (any -> any) | false | Render as read-only. Boolean or fn state -> boolean end. |
mode | :static | :load_more | :search | :search_multi | :static | How a select or relation field loads its options: :static, :load_more, :search or :search_multi. |
page_size | pos_integer | 20 | Page size for load_more/search modes. |
load_action | atom | {atom, atom} | none | The action that loads options: an atom or {master_action, tenant_action}. If the action paginates, it must set required?: false. |
load | (any, any -> any) | none | Scopes the list of options: fn query, state -> query. |
position | any | none | Field position (integer, :first, :last, {:before, :field}, {:after, :field}). |
include_nil | boolean | String.t | (-> any) | false | On a :relation field, adds an empty choice. A string, or a zero-arity function, is its label. A :select field ignores it. |
min | integer | none | Minimum value for number/range fields. The value is compiled into the config, but no built-in template or handler reads it. The Standard form template does not pass it to the input. |
max | integer | none | Maximum value for number/range fields. The value is compiled into the config, but no built-in template or handler reads it. The Standard form template does not pass it to the input. |
min_chars | integer | none | Minimum characters before search triggers. |
auto_fields | boolean | false | When true with explicit nested_field entries, auto-detect remaining fields from the resource and merge with overrides. |
nested_fields | list(any) | [] | Filled by the nested_field entities. |
add_label | String.t | (-> any) | none | Label for add button in array/nested/string_list fields. String or fn -> gettext(...) end. |
remove_label | String.t | (-> any) | none | Label for remove button in array/nested/string_list fields. String or fn -> gettext(...) end. |
visible | boolean | (any -> any) | true | Static or dynamic visibility. fn state -> boolean() end. |
restricted | boolean | (any -> any) | false | Restrict to master users with true, or decide with fn state -> boolean() end, whose true restricts the field for every user, masters included, and whose false allows it for every user. A restricted field is hidden and left out of what the form saves. |
field ui
How the input is drawn. It sits at mishka_gervaz.form.fields.field.ui.
| Option | Type | Default | Description |
|---|---|---|---|
label | String.t | (-> any) | none | Field label. String or fn -> gettext(...) end for i18n. |
placeholder | String.t | (-> any) | none | Placeholder text. String or fn -> gettext(...) end for i18n. |
description | String.t | (-> any) | none | Help text below the field. String or fn -> gettext(...) end for i18n. |
icon | String.t | none | Icon name (e.g. hero icon). |
class | String.t | none | Field CSS classes. The value is compiled into the config, but no built-in template or handler reads it. |
wrapper_class | String.t | none | Wrapper element CSS classes. The value is compiled into the config, but no built-in template or handler reads it. |
debounce | integer | none | Debounce milliseconds for validation. |
span | pos_integer | none | How many grid columns the input spans, from 1 to 4. |
rows | integer | none | Number of rows for textarea. |
step | integer | float | none | Step value for number/range fields. The value is compiled into the config, but no built-in template or handler reads it. |
autocomplete | String.t | none | HTML autocomplete attribute value. |
add_label | String.t | (-> any) | none | Label for add button in array/nested/string_list fields. String or fn -> gettext(...) end. |
remove_label | String.t | (-> any) | none | Label for remove button in array/nested/string_list fields. String or fn -> gettext(...) end. |
disabled_prompt | String.t | (-> any) | none | Prompt text shown when field is disabled. |
extra | map | %{} | Options for the field type. The schema does not check the keys. |
field preload
Relationships a relation field loads, in three tiers. It sits at mishka_gervaz.form.fields.field.preload.
| Option | Type | Default | Description |
|---|---|---|---|
always | list(atom | {atom, atom}) | [] | Always preload these relationships. The value is compiled into the config, but no built-in template or handler reads it. |
master | list(atom | {atom, atom}) | [] | Additional preloads for master users. The value is compiled into the config, but no built-in template or handler reads it. |
tenant | list(atom | {atom, atom}) | [] | Additional preloads for tenant users. The value is compiled into the config, but no built-in template or handler reads it. |
nested_field
A sub-field of a :nested field. It is valid only inside one. It sits at mishka_gervaz.form.fields.field.nested_field.
Call: nested_field name, type
Arguments:
| Argument | Type | Default | Description |
|---|---|---|---|
name | atom | none | Required. Sub-field name. Must match an attribute on the embedded resource. |
type | nil | :text | :textarea | :number | :checkbox | :date | :datetime | :select | :hidden | :toggle | :range | :json | :key_map | :key_list | none | Sub-field type. Nil for auto-detection from embedded attribute. |
Options:
| Option | Type | Default | Description |
|---|---|---|---|
required | boolean | none | Overrides required. Leave it out to infer it from allow_nil?. |
visible | boolean | true | Whether this sub-field is visible. |
readonly | boolean | false | Render as read-only. |
default | any | none | Default value for this sub-field. |
options | list(any) | none | What the sub-field may hold: the choices of a :select, or the declared keys of a :key_map or :key_list, each as [name:, type:, label:, placeholder:, required:]. A key with type: :password is drawn as a masked input whose value is never written back into the page, and a key with required: true shows the required mark. A sub-field of a nested row takes a list only. |
position | any | none | Position in the nested field list (integer, :first, :last, {:before, :field}, {:after, :field}). |
nested_field ui
How the sub-field is drawn. It sits at mishka_gervaz.form.fields.field.nested_field.ui.
| Option | Type | Default | Description |
|---|---|---|---|
label | String.t | (-> any) | none | Sub-field label. String or fn -> gettext(...) end. |
placeholder | String.t | (-> any) | none | Placeholder text. String or fn -> gettext(...) end for i18n. |
description | String.t | none | Help text. |
class | String.t | none | CSS classes for this sub-field input. |
rows | integer | none | Number of rows for textarea sub-fields. |
span | pos_integer | none | Grid column span (1-2). |
add_label | String.t | (-> any) | none | Label of the button that adds a row to a :key_list sub-field. |
remove_label | String.t | (-> any) | none | Label of the button that removes one row of a :key_list sub-field. |
extra | map | %{} | Additional options. |
auto_fields
Discovers fields from the attributes of the resource. It sits at mishka_gervaz.form.fields.auto_fields.
| Option | Type | Default | Description |
|---|---|---|---|
except | list(atom) | [] | Exclude these attributes. |
only | list(atom) | none | Only these attributes (overrides except). |
position | :start | :end | :end | Where to place auto fields. |
defaults
Default options for the discovered fields. It sits at mishka_gervaz.form.fields.auto_fields.defaults.
| Option | Type | Default | Description |
|---|---|---|---|
required | boolean | false | Default required value. |
visible | boolean | true | Default visible value. |
readonly | boolean | false | Default readonly value. |
ui_defaults
Default UI options for the discovered fields. It sits at mishka_gervaz.form.fields.auto_fields.ui_defaults.
| Option | Type | Default | Description |
|---|---|---|---|
boolean_widget | :checkbox | :toggle | :select | :checkbox | Widget for boolean fields. |
textarea_threshold | integer | 255 | Max length above which string becomes textarea. |
number_step | integer | 1 | Default step for number inputs. The value is compiled into the config, but no built-in template or handler reads it. |
select_prompt | String.t | "Select..." | Default select prompt text. The value is compiled into the config, but no built-in template or handler reads it. |
datetime_format | atom | :medium | Default datetime display format. The value is compiled into the config, but no built-in template or handler reads it. |
override
Overrides one discovered field. It sits at mishka_gervaz.form.fields.auto_fields.override.
Call: override name
Arguments:
| Argument | Type | Default | Description |
|---|---|---|---|
name | atom | none | Required. Field name to override. |
Options:
| Option | Type | Default | Description |
|---|---|---|---|
type | :text | :textarea | :number | :checkbox | :date | :datetime | :select | :multi_select | :relation | :json | :nested | :array_of_maps | :string_list | :file | :hidden | :toggle | :range | module | none | Overrides the detected type with a built-in type except :upload, :combobox, :password, :key_map and :key_list, or with a module. Declare those five as explicit fields. |
required | boolean | none | Override required. |
visible | boolean | none | Override visible. |
readonly | boolean | none | Override readonly. |
override ui
The ui block of an override. It takes the same options as a field ui block. It sits at mishka_gervaz.form.fields.auto_fields.override.ui.
| Option | Type | Default | Description |
|---|---|---|---|
label | String.t | (-> any) | none | Field label. String or fn -> gettext(...) end for i18n. |
placeholder | String.t | (-> any) | none | Placeholder text. String or fn -> gettext(...) end for i18n. |
description | String.t | (-> any) | none | Help text below the field. String or fn -> gettext(...) end for i18n. |
icon | String.t | none | Icon name (e.g. hero icon). |
class | String.t | none | Field CSS classes. The value is compiled into the config, but no built-in template or handler reads it. |
wrapper_class | String.t | none | Wrapper element CSS classes. The value is compiled into the config, but no built-in template or handler reads it. |
debounce | integer | none | Debounce milliseconds for validation. |
span | pos_integer | none | How many grid columns the input spans, from 1 to 4. |
rows | integer | none | Number of rows for textarea. |
step | integer | float | none | Step value for number/range fields. The value is compiled into the config, but no built-in template or handler reads it. |
autocomplete | String.t | none | HTML autocomplete attribute value. |
add_label | String.t | (-> any) | none | Label for add button in array/nested/string_list fields. String or fn -> gettext(...) end. |
remove_label | String.t | (-> any) | none | Label for remove button in array/nested/string_list fields. String or fn -> gettext(...) end. |
disabled_prompt | String.t | (-> any) | none | Prompt text shown when field is disabled. |
extra | map | %{} | Options for the field type. The schema does not check the keys. |
Types inferred from Ash attributes
A field without a type takes the type of its Ash attribute. An auto_fields field follows the same rules:
| Ash type | Field type |
|---|---|
Ash.Type.String | :select with a one_of constraint. :textarea when max_length is above the textarea_threshold (255 by default). Otherwise :text. |
Ash.Type.Atom | :select with a one_of constraint, otherwise :text. |
Ash.Type.Integer, Ash.Type.Float, Ash.Type.Decimal | :number |
Ash.Type.Boolean | The boolean_widget of auto_fields ui_defaults: :checkbox (the default), :toggle or :select. |
Ash.Type.Date | :date |
Ash.Type.DateTime, Ash.Type.UtcDatetime, Ash.Type.UtcDatetimeUsec | :datetime |
Ash.Type.UUID, Ash.Type.UUIDv7 | :hidden |
Ash.Type.Map | :nested when the constraints declare fields, otherwise :json. |
An embedded resource, or an array of one, or an array of maps with declared fields | :nested |
| An array of strings | :string_list |
| Any other array | :json |
| anything else | :text |