Phoenix Form
<.form for={@form} phx-submit="save_phoenix"
class="flex flex-col gap-space-lg w-full max-w-xl"
>
<.tags_input field={@form[:tags]} class="tags-input">
<:label>Keywords</:label>
<:close><.heroicon name="hero-x-mark" /></:close>
<:error :let={msg}>
<.heroicon name="hero-exclamation-circle" class="icon" />
{msg}
</:error>
</.tags_input>
<.action type="submit" class="button ui-accent">
Submit
</.action>
</.form>
defmodule MyAppWeb.TagsInputFormLive do
use MyAppWeb, :live_view
def mount(_params, _session, socket) do
phoenix_form =
Phoenix.Component.to_form(%{"tags" => ["alpha", "beta"]}, as: :tags_input_phoenix, id: "tags-input-live-form-phoenix")
{:ok, assign(socket, :phoenix_form, phoenix_form)}
end
def handle_event("save_phoenix", %{"tags_input_phoenix" => params}, socket) do
tags = List.wrap(params["tags"])
{:noreply, put_flash(socket, :info, "Submitted: tags=#{inspect(tags)}")}
end
def handle_event("save_phoenix", _params, socket) do
tags = socket.assigns.phoenix_form.params["tags"] |> List.wrap()
{:noreply, put_flash(socket, :info, "Submitted: tags=#{inspect(tags)}")}
end
end
Phoenix Form + Ecto
<.form for={@form} phx-change="validate" phx-submit="save"
class="flex flex-col gap-space-lg w-full max-w-xl"
>
<.tags_input field={@form[:tags]} class="tags-input">
<:label>Keywords</:label>
<:close><.heroicon name="hero-x-mark" /></:close>
<:error :let={msg}>
<.heroicon name="hero-exclamation-circle" />
{msg}
</:error>
</.tags_input>
<.action type="submit" class="button ui-accent">
Submit
</.action>
</.form>
defmodule MyAppWeb.TagsInputFormLive do
use MyAppWeb, :live_view
def mount(_params, _session, socket) do
ecto_form =
%MyApp.Forms.TagsInputForm{}
|> MyApp.Forms.TagsInputForm.changeset_validate(%{})
|> Phoenix.Component.to_form(as: :tags_input_ecto, id: "tags-input-live-form-ecto")
{:ok, assign(socket, :ecto_form, ecto_form)}
end
def handle_event("validate", %{"tags_input_ecto" => params}, socket) do
changeset =
%MyApp.Forms.TagsInputForm{}
|> MyApp.Forms.TagsInputForm.changeset_validate(params)
|> Map.put(:action, :validate)
{:noreply,
assign(
socket,
:ecto_form,
Phoenix.Component.to_form(changeset,
action: :validate,
as: :tags_input_ecto,
id: "tags-input-live-form-ecto"
)
)}
end
def handle_event("save", %{"tags_input_ecto" => params}, socket) do
case MyApp.Forms.TagsInputForm.changeset_validate(%MyApp.Forms.TagsInputForm{}, params) do
%Ecto.Changeset{valid?: true} = changeset ->
_data = Ecto.Changeset.apply_changes(changeset)
{:noreply, put_flash(socket, :info, "Submitted")}
changeset ->
{:noreply,
assign(
socket,
:ecto_form,
Phoenix.Component.to_form(changeset,
action: :validate,
as: :tags_input_ecto,
id: "tags-input-live-form-ecto"
)
)}
end
end
def handle_event("save", _params, socket) do
{:noreply, socket}
end
end
defmodule MyApp.Forms.TagsInputForm do
use Ecto.Schema
import Ecto.Changeset
embedded_schema do
field :tags, {:array, :string}
end
def changeset(form, attrs \\ %{}) do
form
|> cast(attrs, [:tags])
|> validate_required([:tags])
|> validate_tag_count(:tags, 3)
end
def changeset_validate(form, attrs \\ %{}) do
form
|> cast(attrs, [:tags])
|> validate_required([:tags], message: "can't be blank")
|> validate_tag_list(:tags, 3)
end
defp validate_tag_count(changeset, field, max) do
validate_change(changeset, field, fn _, value ->
n = if is_list(value), do: length(value), else: 0
if n <= max, do: [], else: [{field, "must have at most #{max} tags"}]
end)
end
defp validate_tag_list(changeset, field, max) do
validate_change(changeset, field, fn _, value ->
cond do
not is_list(value) -> [{field, "is invalid"}]
length(value) > max -> [{field, "must have at most #{max} tags"}]
Enum.any?(value, &String.contains?(&1, ";")) -> [{field, "must not contain semicolons"}]
true -> []
end
end)
end
end
Phoenix Form + Ecto + Invalid
<.form for={@ecto_invalid_form} phx-change="validate_invalid" phx-submit="save_invalid"
class="flex flex-col gap-space-lg w-full max-w-xl"
>
<.tags_input
id="tags-input-live-form-ecto-invalid-tags"
field={@ecto_invalid_form[:tags]}
class="tags-input"
invalid={Corex.FormField.invalid?(@ecto_invalid_form[:tags])}
>
<:label>Keywords</:label>
<:close><.heroicon name="hero-x-mark" /></:close>
<:error :let={msg}>
<.heroicon name="hero-exclamation-circle" />
{msg}
</:error>
</.tags_input>
<.action type="submit" id="tags-input-live-form-ecto-invalid-submit" class="button ui-accent">
Submit
</.action>
</.form>
defmodule MyAppWeb.TagsInputFormLive do
use MyAppWeb, :live_view
def handle_event("validate_invalid", %{"tags_input_ecto_invalid" => params}, socket) do
validate_ecto_invalid(socket, params)
end
def handle_event("save_invalid", %{"tags_input_ecto_invalid" => params}, socket) do
save_ecto_invalid(socket, params)
end
end
defmodule MyApp.Forms.TagsInputForm do
use Ecto.Schema
import Ecto.Changeset
embedded_schema do
field :tags, {:array, :string}
end
def changeset(form, attrs \\ %{}) do
form
|> cast(attrs, [:tags])
|> validate_required([:tags])
|> validate_tag_count(:tags, 3)
end
def changeset_validate(form, attrs \\ %{}) do
form
|> cast(attrs, [:tags])
|> validate_required([:tags], message: "can't be blank")
|> validate_tag_list(:tags, 3)
end
defp validate_tag_count(changeset, field, max) do
validate_change(changeset, field, fn _, value ->
n = if is_list(value), do: length(value), else: 0
if n <= max, do: [], else: [{field, "must have at most #{max} tags"}]
end)
end
defp validate_tag_list(changeset, field, max) do
validate_change(changeset, field, fn _, value ->
cond do
not is_list(value) -> [{field, "is invalid"}]
length(value) > max -> [{field, "must have at most #{max} tags"}]
Enum.any?(value, &String.contains?(&1, ";")) -> [{field, "must not contain semicolons"}]
true -> []
end
end)
end
end