Formex

Formex is an extensible form library for Phoenix. With this library you don’t write changeset (as in Ecto), but a separate module that declares fields of form (like in Symfony).

You can also use it with Ecto - see formex_ecto. That library will build changeset and additional Ecto queries for itself.

Formex doesn’t validate data for itself - it uses validation libraries instead.

Formex comes with helper functions for templating. For now there is only a Bootstrap 3 form template, but you can easily create your own templates.

TL;DR

Installation

In addition to the main library, you have to install some validator adapter. In this example we will use Vex. List of available adapters

mix.exs

def deps do
  [{:formex, "~> 0.6.0"},
   {:formex_vex, "~> 0.1.0"}]
end

def application do
  [applications: [:formex]]
end

config/config.exs

config :formex,
  validator: Formex.Validator.Vex,
  translate_error: &AppWeb.ErrorHelpers.translate_error/1,  # optional, from /lib/app_web/views/error_helpers.ex
  template: Formex.Template.BootstrapHorizontal,            # optional, can be overridden in a template
  template_options: [                                       # optional, can be overridden in a template
    left_column: "col-sm-2",
    right_column: "col-sm-10"
  ]

web/web.ex

def controller do
  quote do
    use Formex.Controller
  end
end

def view do
  quote do
    use Formex.View
  end
end

Usage

Let’s create a form for article.

Model

# /web/model/article.ex
defmodule App.Article do
  defstruct [:title, :content, :hidden]
end

Form Type

# /web/form/article_type.ex
defmodule App.ArticleType do
  use Formex.Type

  def build_form(form) do
    form
    |> add(:title, :text_input, label: "Title", validation: [presence: true])
    |> add(:content, :textarea, label: "Content", phoenix_opts: [
      rows: 4
    ], validation: [presence: true])
    |> add(:hidden, :checkbox, label: "Is hidden?", required: false)
    |> add(:save, :submit, label: "Submit", phoenix_opts: [
      class: "btn-primary"
    ])
  end
end

Please note that required option is used only to generate an asterisk. Any validation must be done via validation option.

The :text_input and so on are function names from Phoenix.HTML.Form

Controller

def new(conn, _params) do
  form = create_form(App.ArticleType, %Article{})
  render(conn, "form.html", form: form)
end

def create(conn, %{"article" => article_params}) do
  App.ArticleType
  |> create_form(%Article{}, article_params)
  |> handle_form
  |> case do
    {:ok, article} ->
      # do something with a new article struct
    {:error, form} ->
      # display errors
      render(conn, "form.html", form: form)
  end
end

Template

form.html.eex

<%= formex_form_for @form, article_path(@conn, :create), [class: "form-horizontal"], fn f -> %>
  <%= if @form.submitted? do %>Oops, something went wrong!<% end %>

  <%= formex_row f, :title %>
  <%= formex_row f, :content %>
  <%= formex_row f, :hidden %>
  <%= formex_row f, :save %>

  <%# or generate all fields at once: formex_rows f %>
<% end %>

Put an asterisk to required fields:

.required .control-label:after {
  content: '*';
  margin-left: 3px;
}

The final effect after submit:

Documentation

https://hexdocs.pm/formex

Basic usage

Custom fields

Templating

Guides

Extensions

Validation adapters