Formex
Formex is an abstract layer that helps to build forms in Phoenix and Ecto. With this library you
don’t write changeset, but a separate module that declares fields of form
(like in Symfony).
Formex will build changeset and additional Ecto queries (to get options for <select>
) for itself.
Formex also comes with helper functions for templating. For now there is only a Bootstrap 3 form template, but you can easily create your own templates.
It doesn’t require a database, although there is an Ecto dependency.
You can use Ecto’s embedded_schema
(instead of schema
) to use it without database.
TL;DR

Installation
mix.exs
def deps do
[{:formex, "~> 0.4.0"}]
end
def application do
[applications: [:formex]]
end
config/config.exs
config :formex,
repo: App.Repo,
translate_error: &App.ErrorHelpers.translate_error/1,
template: Formex.Template.BootstrapHorizontal, # optional, can be overridden in a .eex template
template_options: [ # optional, also can be overridden in the template
left_column: "col-sm-2",
right_column: "col-sm-10"
]
web/web.ex
def model do
quote do
use Formex.Schema
end
end
def controller do
quote do
use Formex.Controller
end
end
def view do
quote do
use Formex.View
end
end
Usage
We have models Article, Category and Tag:
schema "articles" do
field :title, :string
field :content, :string
field :hidden, :boolean
belongs_to :category, App.Category
many_to_many :tags, App.Tag, join_through: "articles_tags" #...
end
schema "categories" do
field :name, :string
end
schema "tags" do
field :name, :string
end
Let’s create a form for Article using Formex:
# /web/form/article_type.ex
defmodule App.ArticleType do
use Formex.Type
alias Formex.CustomField.SelectAssoc
def build_form(form) do
form
|> add(:title, :text_input, label: "Title")
|> add(:content, :textarea, label: "Content", phoenix_opts: [
rows: 4
])
|> add(:category_id, SelectAssoc, label: "Category", phoenix_opts: [
prompt: "Choose a category"
])
|> add(:tags, SelectAssoc, label: "Tags")
|> add(:hidden, :checkbox, label: "Is hidden?", required: false)
|> add(:save, :submit, label: "Submit", phoenix_opts: [
class: "btn-primary"
])
end
end
We need to slightly modify a controller:
def new(conn, _params) do
form = create_form(App.ArticleType, %Article{})
render(conn, "new.html", form: form)
end
def create(conn, %{"article" => article_params}) do
App.ArticleType
|> create_form(%Article{}, article_params)
|> insert_form_data
|> case do
{:ok, _article} ->
conn
|> put_flash(:info, "Article created successfully.")
|> redirect(to: article_path(conn, :index))
{:error, form} ->
render(conn, "new.html", form: form)
end
end
A template:
form.html.eex
<%= formex_form_for @form, @action, fn f -> %>
<%= if @form.changeset.action do %>Oops, something went wrong!<% end %>
<%= formex_row f, :name %>
<%= formex_row f, :content %>
<%= formex_row f, :category_id %>
<%= formex_row f, :tags %>
<%= formex_row f, :hidden %>
<%= formex_row f, :save %>
<%# or generate all fields at once: formex_rows f %>
<% end %>
Also replace changeset: @changeset
with form: @form
in new.html.eex
Put an asterisk to required fields:
.required .control-label:after {
content: '*';
margin-right: 3px;
}
The final effect:

It’s very simple, isn’t it? You don’t need to create any changeset nor write a query to get options for a Category select. Furthermore, the form code is separated from the template.
Documentation
Basic usage
Custom fields
Templating
Tests
Run this command to migrate:
MIX_ENV=test mix ecto.migrate -r Formex.TestRepo
Now you can use tests via mix test
.
TODO
- [x] more options for
Formex.CustomField.SelectAssoc
- [x]
choice_label
- [x]
query
- [x]
GROUP BY
- [x] multiple_select
- [x] validate if sent
<option>
exists in generated:select
- [x] nested forms
- [x] _to_one
- [x] _to_many
- [x] templating
- [x] tests
- [x] submit button
- [x] usage without schemas
- [ ] make it work without Ecto so everyone will be happy