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.

OptionTypeDefaultDescription
field_orderlist(atom)noneField 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:

ArgumentTypeDefaultDescription
nameatomnoneRequired. Field identifier.
typenil | :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 | modulenoneThe 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:

OptionTypeDefaultDescription
sourceatomnoneData source attribute. Defaults to field name.
requiredbooleanfalseWhether the field is required.
show_on:create | :updatenoneShows the field only in one mode.
defaultanynoneThe 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_onatomnoneField this depends on (for cascading selects).
virtualbooleanfalseVirtual field (not in resource attributes).
resourcemodulenoneThe resource for a virtual or relation field. A virtual :relation or :select field needs a resource or options.
derive_value(any -> any)noneReads 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.
optionslist(any) | (-> any) | (any -> any)noneThe 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_fieldatom | (any -> any) | (any, any -> any)noneHow 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_fieldatomnoneField to search on for autocomplete.
value_fieldatomnoneThe attribute of the chosen record to store instead of :id.
readonlyboolean | (any -> any)falseRender as read-only. Boolean or fn state -> boolean end.
mode:static | :load_more | :search | :search_multi:staticHow a select or relation field loads its options: :static, :load_more, :search or :search_multi.
page_sizepos_integer20Page size for load_more/search modes.
load_actionatom | {atom, atom}noneThe action that loads options: an atom or {master_action, tenant_action}. If the action paginates, it must set required?: false.
load(any, any -> any)noneScopes the list of options: fn query, state -> query.
positionanynoneField position (integer, :first, :last, {:before, :field}, {:after, :field}).
include_nilboolean | String.t | (-> any)falseOn a :relation field, adds an empty choice. A string, or a zero-arity function, is its label. A :select field ignores it.
minintegernoneMinimum 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.
maxintegernoneMaximum 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_charsintegernoneMinimum characters before search triggers.
auto_fieldsbooleanfalseWhen true with explicit nested_field entries, auto-detect remaining fields from the resource and merge with overrides.
nested_fieldslist(any)[]Filled by the nested_field entities.
add_labelString.t | (-> any)noneLabel for add button in array/nested/string_list fields. String or fn -> gettext(...) end.
remove_labelString.t | (-> any)noneLabel for remove button in array/nested/string_list fields. String or fn -> gettext(...) end.
visibleboolean | (any -> any)trueStatic or dynamic visibility. fn state -> boolean() end.
restrictedboolean | (any -> any)falseRestrict 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.

OptionTypeDefaultDescription
labelString.t | (-> any)noneField label. String or fn -> gettext(...) end for i18n.
placeholderString.t | (-> any)nonePlaceholder text. String or fn -> gettext(...) end for i18n.
descriptionString.t | (-> any)noneHelp text below the field. String or fn -> gettext(...) end for i18n.
iconString.tnoneIcon name (e.g. hero icon).
classString.tnoneField CSS classes. The value is compiled into the config, but no built-in template or handler reads it.
wrapper_classString.tnoneWrapper element CSS classes. The value is compiled into the config, but no built-in template or handler reads it.
debounceintegernoneDebounce milliseconds for validation.
spanpos_integernoneHow many grid columns the input spans, from 1 to 4.
rowsintegernoneNumber of rows for textarea.
stepinteger | floatnoneStep value for number/range fields. The value is compiled into the config, but no built-in template or handler reads it.
autocompleteString.tnoneHTML autocomplete attribute value.
add_labelString.t | (-> any)noneLabel for add button in array/nested/string_list fields. String or fn -> gettext(...) end.
remove_labelString.t | (-> any)noneLabel for remove button in array/nested/string_list fields. String or fn -> gettext(...) end.
disabled_promptString.t | (-> any)nonePrompt text shown when field is disabled.
extramap%{}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.

OptionTypeDefaultDescription
alwayslist(atom | {atom, atom})[]Always preload these relationships. The value is compiled into the config, but no built-in template or handler reads it.
masterlist(atom | {atom, atom})[]Additional preloads for master users. The value is compiled into the config, but no built-in template or handler reads it.
tenantlist(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:

ArgumentTypeDefaultDescription
nameatomnoneRequired. Sub-field name. Must match an attribute on the embedded resource.
typenil | :text | :textarea | :number | :checkbox | :date | :datetime | :select | :hidden | :toggle | :range | :json | :key_map | :key_listnoneSub-field type. Nil for auto-detection from embedded attribute.

Options:

OptionTypeDefaultDescription
requiredbooleannoneOverrides required. Leave it out to infer it from allow_nil?.
visiblebooleantrueWhether this sub-field is visible.
readonlybooleanfalseRender as read-only.
defaultanynoneDefault value for this sub-field.
optionslist(any)noneWhat 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.
positionanynonePosition 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.

OptionTypeDefaultDescription
labelString.t | (-> any)noneSub-field label. String or fn -> gettext(...) end.
placeholderString.t | (-> any)nonePlaceholder text. String or fn -> gettext(...) end for i18n.
descriptionString.tnoneHelp text.
classString.tnoneCSS classes for this sub-field input.
rowsintegernoneNumber of rows for textarea sub-fields.
spanpos_integernoneGrid column span (1-2).
add_labelString.t | (-> any)noneLabel of the button that adds a row to a :key_list sub-field.
remove_labelString.t | (-> any)noneLabel of the button that removes one row of a :key_list sub-field.
extramap%{}Additional options.

auto_fields

Discovers fields from the attributes of the resource. It sits at mishka_gervaz.form.fields.auto_fields.

OptionTypeDefaultDescription
exceptlist(atom)[]Exclude these attributes.
onlylist(atom)noneOnly these attributes (overrides except).
position:start | :end:endWhere to place auto fields.

defaults

Default options for the discovered fields. It sits at mishka_gervaz.form.fields.auto_fields.defaults.

OptionTypeDefaultDescription
requiredbooleanfalseDefault required value.
visiblebooleantrueDefault visible value.
readonlybooleanfalseDefault readonly value.

ui_defaults

Default UI options for the discovered fields. It sits at mishka_gervaz.form.fields.auto_fields.ui_defaults.

OptionTypeDefaultDescription
boolean_widget:checkbox | :toggle | :select:checkboxWidget for boolean fields.
textarea_thresholdinteger255Max length above which string becomes textarea.
number_stepinteger1Default step for number inputs. The value is compiled into the config, but no built-in template or handler reads it.
select_promptString.t"Select..."Default select prompt text. The value is compiled into the config, but no built-in template or handler reads it.
datetime_formatatom:mediumDefault 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:

ArgumentTypeDefaultDescription
nameatomnoneRequired. Field name to override.

Options:

OptionTypeDefaultDescription
type:text | :textarea | :number | :checkbox | :date | :datetime | :select | :multi_select | :relation | :json | :nested | :array_of_maps | :string_list | :file | :hidden | :toggle | :range | modulenoneOverrides 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.
requiredbooleannoneOverride required.
visiblebooleannoneOverride visible.
readonlybooleannoneOverride 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.

OptionTypeDefaultDescription
labelString.t | (-> any)noneField label. String or fn -> gettext(...) end for i18n.
placeholderString.t | (-> any)nonePlaceholder text. String or fn -> gettext(...) end for i18n.
descriptionString.t | (-> any)noneHelp text below the field. String or fn -> gettext(...) end for i18n.
iconString.tnoneIcon name (e.g. hero icon).
classString.tnoneField CSS classes. The value is compiled into the config, but no built-in template or handler reads it.
wrapper_classString.tnoneWrapper element CSS classes. The value is compiled into the config, but no built-in template or handler reads it.
debounceintegernoneDebounce milliseconds for validation.
spanpos_integernoneHow many grid columns the input spans, from 1 to 4.
rowsintegernoneNumber of rows for textarea.
stepinteger | floatnoneStep value for number/range fields. The value is compiled into the config, but no built-in template or handler reads it.
autocompleteString.tnoneHTML autocomplete attribute value.
add_labelString.t | (-> any)noneLabel for add button in array/nested/string_list fields. String or fn -> gettext(...) end.
remove_labelString.t | (-> any)noneLabel for remove button in array/nested/string_list fields. String or fn -> gettext(...) end.
disabled_promptString.t | (-> any)nonePrompt text shown when field is disabled.
extramap%{}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 typeField 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.BooleanThe 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