A contrived example
# file: lib/form_demo_web/controllers/page_controller.ex
#
defmodule FormDemoWeb.PageController do
use FormDemoWeb, :controller
# Store user form data under conn.params["user"]
def index(conn, params) do
# Data to preload form with
user = %{"name" => "Bruce", "username" => "redrapids"}
conn
|> update_user(user) # add "user" to conn
|> render("index.html")
end
def update(conn, %{"user" => user}) do
case validate_user(user) do
{:ok, user} ->
conn
|> update_user(user)
|> put_flash(:info, "Updated to #{user["name"]}, #{user["username"]}!")
|> render("index.html")
{:error, errors} ->
render(conn, "index.html", errors: errors)
end
end
# Make data available under conn.params["user"]
# for Phoenix.HTML.Form.form_for/4
#
# Plug.Conn implements Phoenix.HTML.FormData used by form_for/4
#
defp update_user(conn, user),
do: Map.update!(conn, :params, &Map.put(&1, "user", user))
defp validate_user(user) do
errors =
[]
|> check_value(user, :name)
|> check_value(user, :username)
case errors do
[] ->
{:ok, user}
_ ->
{:error, errors}
end
end
def check_value(errors, user, key) do
cond do
Map.get(user, Atom.to_string(key), "") == "" ->
# format expected by FormDemoWeb.ErrorHelpers.error_tag/2
[{key, {"can't be blank", []}} | errors]
true ->
errors
end
end
end
# file: lib/form_demo_web/router.ex
#
defmodule FormDemoWeb.Router do
use FormDemoWeb, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
end
pipeline :api do
plug :accepts, ["json"]
end
scope "/", FormDemoWeb do
pipe_through :browser
get "/", PageController, :index
post "/", PageController, :update # <- Added to validate form data
end
end
# file: lib/form_demo_web/views/page_view.ex
#
defmodule FormDemoWeb.PageView do
use FormDemoWeb, :view
def get_errors(%Plug.Conn{assigns: %{errors: errors}}) when is_list(errors),
do: errors
# FormDemoWeb.ErrorHelpers.error_tag/2 needs a list
def get_errors(_),
do: []
def errors?(%Phoenix.HTML.Form{errors: [_ | _]}),
# non-empty list of errors
do: true
def errors?(_),
do: false
end
<!-- file: lib/form_demo_web/templates/page/index.html.eex -->
<!--
Phoenix.HTML.Form.form_for/4
Phoenix.HTML.Form.text_input/3
Phoenix.HTML.Form.submit/1
@conn --- Plug.Conn implements Phoenix.HTML.FormData protocol
Routes.page_path(@conn, :update) --- generates the URL to PageController.update/2
[as: :user] --- form data is stored under conn.params["user"]
[errors: get_errors(@conn)] --- extracts the errors for the conn to include it in the Phoenix.HTML.Form struct
errors?/1, get_errors/1 --- defined under FormDemoWeb.PageView
error_tag/2 --- from generated module FormDemoWeb.ErrorHelpers
-->
<h1>Current User</h1>
<%= form_for @conn, Routes.page_path(@conn, :update), [as: :user, method: "post", errors: get_errors(@conn)], fn form -> %>
<%= if errors?(form) do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below.</p>
</div>
<% end %>
<div>
<%= text_input form, :name, placeholder: "Name" %>
<%= error_tag form, :name %>
</div>
<div>
<%= text_input form, :username, placeholder: "Username" %>
<%= error_tag form, :username %>
</div>
<%= submit "Update User" %>
<% end %>
<!DOCTYPE html>
<html lang="en">
<!-- file: lib/form_demo_web/templates/layout/app.html.eex -->
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>FormDemo · Phoenix Framework</title>
<link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
</head>
<body>
<main role="main" class="container">
<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
<%= render @view_module, @view_template, assigns %>
</main>
<script type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
</body>
</html>