Form fields DSL
v0.0.1
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 as a text input. |
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 | Stored with the field. The built-in form uses it for one thing: a :select field 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, options or options_source. |
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. |
options_source | {module, atom, atom} | none | Options from a resource, as {resource, action, display_field}. The value is compiled into the config, but no built-in template or handler reads it. Use options or load. |
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. |
apply | (any, any, any -> any) | none | A custom apply function: fn value, changeset, state -> changeset. The value is compiled into the config, but no built-in template or handler reads it. |
format | (any -> any) | (any, any, any -> any) | none | A value formatter for display. The value is compiled into the config, but no built-in template or handler reads it. |
render | (any -> any) | (any, any -> any) | none | Custom HEEx for the field. The value is compiled into the config, but no built-in template or handler reads it. Use a custom field type or the UI adapter instead. |
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. |
array_fields | list(any) | [] | Fields for :array_of_maps entries. The value is compiled into the config, but no built-in template or handler reads it. |
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. true or fn state -> boolean() end. |
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:]. |
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. |
format | (any -> any) | (any, any, any -> any) | none | Value formatter. |
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 |