Split a form into steps

v0.0.1

A Gervaz form can show its groups one step at a time as a wizard, or as tabs the reader moves between freely. This guide switches a form to each mode, defines the steps, controls how the reader moves, and gates a step with your own rule. The examples come from the wizard and tabs resources in the Gervaz test suite. No form in Mishka CMS uses steps.

Before you begin

  • Have a form with fields and groups. A step names groups, not fields, so every field needs a group. See Build an admin form.

Pick a layout mode

The layout section sets the mode:

ModeStepsBehavior
:standardForbiddenOne page. The default.
:wizardRequiredOne step at a time, in order.
:tabsRequiredEvery step is a tab. navigation :free is allowed. The Standard template draws it like a wizard, one step at a time.

The hierarchy is step, then groups, then fields. A group may sit in at most one step.

Define the steps of a wizard

Each step lists its groups. Its ui block sets the label in the step indicator. The Standard template passes only the name, label and state of each step to the indicator, so icon, description and class are accepted but not drawn.

elixir
groups do
  group :basic do
    fields [:title, :content]
  end

  group :meta do
    fields [:status, :priority]
  end

  group :flags do
    fields [:featured]
  end
end

layout do
  mode :wizard
  columns 2
  navigation :sequential

  step :details do
    groups [:basic]

    ui do
      label "Details"
      icon "hero-information-circle"
      description "Enter basic info"
    end
  end

  step :settings do
    groups [:meta]

    ui do
      label "Settings"
      icon "hero-cog"
    end
  end

  step :review do
    groups [:flags]
    summary true

    ui do
      label "Review"
      icon "hero-check-circle"
    end
  end
end

submit do
  create label: "Finish"
end

The form handles three events: next_step, prev_step and goto_step. The submit button appears on the last step, and a submit section labels it. At most one step may set summary true. Gervaz checks that when it compiles, and the built-in template draws the step like any other.

A step's visible option is accepted, but no built-in code reads it, so it does not hide a step.

Use tabs for free navigation

Switch the mode to :tabs and set navigation :free. The form then accepts a goto_step event for any step at any time.

elixir
layout do
  mode :tabs
  columns 1
  navigation :free

  step :content_tab do
    groups [:content_group]

    ui do
      label "Content"
      icon "hero-document"
    end
  end

  step :settings_tab do
    groups [:settings_group]

    ui do
      label "Settings"
    end
  end
end

With navigation :sequential, a goto_step event is accepted only for a step already visited. navigation :free with :wizard is reported when you compile, because a wizard is sequential by design. Use :tabs for free navigation. The built-in step indicator is not clickable and sends no goto_step event, so a link to a step is something your own adapter or template draws.

Gate a step with your own rule

A step has the options action, on_enter, before_leave and after_leave, but Gervaz does not call them. Navigation goes through four overridable functions of the step handler: can_advance?/2, advance/2, go_back/2 and goto_step/3. To stop the reader from leaving a step, override can_advance?/2. When it returns false, the step is marked as an error and the wizard stays where it is.

elixir
defmodule MyApp.Form.StepHandler do
  use MishkaGervaz.Form.Web.Events.StepHandler

  def can_advance?(state, :details), do: state.field_values[:title] not in [nil, ""]
  def can_advance?(state, step), do: super(state, step)
end

Wire the module in the DSL:

elixir
form do
  events do
    step MyApp.Form.StepHandler
  end
end

The helpers find_next_step/2, find_prev_step/2 and step_exists?/2 are public, so an override can reuse them.

Add a notice to some steps

A form notice can show on named steps only. only_steps takes step names, and Gervaz reports one that does not exist when it compiles. This notice binds to the form's errors and shows on the first two steps of the wizard above:

elixir
layout do
  notice :validation_summary do
    position :form_top
    type :error
    bind_to :validation
    title fn _state -> "Please fix the errors below" end
    only_steps [:details, :settings]
  end
end

Next steps