Default form template for MishkaGervaz.
Implements MishkaGervaz.Form.Behaviours.Template and serves as the
out-of-the-box renderer for every mishka_gervaz form do … end block.
Custom templates inherit from this module via use MishkaGervaz.Form.Behaviours.Template, which delegates the four
optional callbacks (render_loading/1, render_field/1,
render_group/1, render_step_indicator/1) here.
Layout modes
:standard— single-page form, fields rendered in their declared groups (or flat when no groups are declared).:wizard— multi-step form with a sequential step indicator and step-aware submit / next / previous buttons.:tabs— same step structure as:wizardbut with free navigation between tabs.
Mode selection is read from @state.static.layout_mode. The render
pipeline (render/1) dispatches notices (render_notices_at/2),
the form header / footer chrome, the body (render_groups/1 or
render_steps/1), upload sections, and submit buttons
in a fixed order so notice positioning stays predictable.
Steps
A :wizard or :tabs form draws the groups of every step, each step in a data-step container
that is hidden unless it is the current one. Every input stays in the form, so a step left
keeps what was typed into it, a password and a picked file included, and the save sends them all.
The form is novalidate, as the browser cannot point at a hidden input.
The submit row holds Cancel, the UI adapter's step_navigation (Back on every step but the first,
Next on every step but the last) and, on the last step only, the submit button. Next is a submit
button, so Enter in a field presses it: see MishkaGervaz.Form.Web.Events.StepHandler for what
a submit does on each step.
Two-axis composition
- Template (this module) — where things go: groups, steps, notices, submit area.
- UI adapter (e.g.
MishkaGervaz.UIAdapters.Tailwind) — how things look: every concrete element (field_group,alert,button,step_indicator, …) is dispatched throughMishkaGervaz.Helpers.dynamic_component/1, which forwards to the adapter declared on the resource.
Field-type dispatch
render_field/1 delegates per-type rendering to modules under
MishkaGervaz.Form.Types.Field.* (e.g.
MishkaGervaz.Form.Types.Field.Hidden,
MishkaGervaz.Form.Types.Field.Relation). Built-in types are wired
through render_input/4; custom types implement
MishkaGervaz.Form.Behaviours.FieldType and are referenced directly
in the field DSL (field :foo, MyApp.FieldTypes.Color).
Input scoping
render_input/4 puts :table_id — the UI adapter's name for the scope a control belongs to —
on the base assigns of every field, alongside the :id taken from the Phoenix form field. The
adapter's searchable controls (search_select, load_more_select, multi_select, combobox,
string_list_input) build their wrapper id from that scope plus the field name, so two forms on
one page may share a field name without their ids colliding.
render_string_list_input/4 sets the same scope itself, since it builds its assigns from the
template assigns rather than from that base.
Upload progress
render_upload_entries/1 sets its track width with an inline style="width: N%" rather than a
Tailwind width class, as does MishkaGervaz.UIAdapters.Tailwind.upload_progress/1.
See MishkaGervaz.Form.Behaviours.Template,
MishkaGervaz.Form.Behaviours.FieldType,
MishkaGervaz.Behaviours.UIAdapter,
MishkaGervaz.Helpers, and
MishkaGervaz.Form.Web.UploadHelpers.
Summary
Functions
Draws state.form_errors, the messages of a failed save that no field shows, as one :error
alert above the submit row. Draws nothing when there are none.
One group through the UI adapter's field_group, or nothing when none of its fields is shown.
The form while its record loads: the UI adapter's spinner over a "Loading…" label, in a box at
least 280px tall, marked data-role="gervaz-form-loading".
Functions
Draws state.form_errors, the messages of a failed save that no field shows, as one :error
alert above the submit row. Draws nothing when there are none.
One group through the UI adapter's field_group, or nothing when none of its fields is shown.
A group declared collapsed true is drawn collapsible and closed, whether or not it also says
collapsible true: a group that starts closed can always be opened. A collapsed group opens
when one of its fields shows an error, so a failed save never hides the error inside it.
The form while its record loads: the UI adapter's spinner over a "Loading…" label, in a box at
least 280px tall, marked data-role="gervaz-form-loading".