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.

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 as a text input.

Options:

OptionTypeDefaultDescription
sourceatomnoneData source attribute. Defaults to field name.
requiredbooleanfalseWhether the field is required.
show_on:create | :updatenoneShows the field only in one mode.
defaultanynoneStored with the field. The built-in form uses it for one thing: a :select field 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, options or options_source.
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.
options_source{module, atom, atom}noneOptions 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_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.
apply(any, any, any -> any)noneA 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)noneA 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)noneCustom 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.
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.
array_fieldslist(any)[]Fields for :array_of_maps entries. The value is compiled into the config, but no built-in template or handler reads it.
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. true or fn state -> boolean() end.

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:].
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.
format(any -> any) | (any, any, any -> any)noneValue 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.

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