Formex
Formex is an abstract layer that helps to build forms in Phoenix and Ecto. With this library you
don't write changesets, but create form module with list of fields
(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.
Installation
mix.exs
def deps do
[{:formex, "~> 0.1.0"}]
end
def application do
[applications: [:formex]]
end
config/config.exs
config :formex,
repo: App.Repo,
translate_error: &App.ErrorHelpers.translate_error/1
web/web.ex
def controller do
quote do
import Formex.Builder
end
end
def view do
quote do
import Formex.View
end
end
Usage
We have models Article and Category:
schema "articles" do
field :title, :string
field :content, :string
field :hidden, :boolean
belongs_to :category, App.Category
end
schema "categories" do
field :name, :string
end
Let's create a form for Article using Formex:
# /web/form/article_form.ex
defmodule App.ArticleForm do
use Formex.Form
def build_form(form) do
form
|> add(:text_input, :title, label: "Title")
|> add(:textarea, :content, label: "Content", phoenix_opts: [
rows: 4
])
|> add(:checkbox, :hidden, label: "Is hidden", required: false)
|> add(:select_assoc, :category_id, label: "Category", phoenix_opts: [
prompt: "Choose category"
])
end
end
controller:
def new(conn, _params) do
form = create_form(App.ArticleForm, %Article{})
render(conn, "new.html", form: form)
end
def create(conn, %{"article" => article_params}) do
App.ArticleForm
|> 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
def edit(conn, %{"id" => id}) do
article = Repo.get!(Article, id)
form = create_form(App.ArticleForm, article)
render(conn, "edit.html", article: article, form: form)
end
def update(conn, %{"id" => id, "article" => article_params}) do
article = Repo.get!(Article, id)
App.ArticleForm
|> create_form(article, article_params)
|> update_form_data
|> case do
{:ok, article} ->
conn
|> put_flash(:info, "Article updated successfully.")
|> redirect(to: article_path(conn, :show, article))
{:error, form} ->
render(conn, "edit.html", article: article, form: form)
end
end
template:
<%= formex_for @form, @action, fn f -> %>
<%= if @form.changeset.action do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below.</p>
</div>
<% end %>
<%= form_row f, :name %>
<%= form_row f, :content %>
<%= form_row f, :category_id %>
<div class="form-group">
<%= submit "Submit", class: "btn btn-primary" %>
</div>
<% end %>
CSS (optional):
.required .control-label:after {
content: '*';
margin-right: 3px;
}
final effect:
It's very simple, isn't it? You don't need to create any changeset, nor write query to get options for a Category select.
Advanced
If you need to change something in changeset, there is a callback for that:
defmodule App.ArticleForm do
def changeset_after_create_callback(changeset) do
# modify changeset and return it
changeset
end
end
Documentation
TODO
- use phoenix_simple_form for templating
- nested forms
- tests