Mix.install([
{:phoenix_playground, git: "https://github.com/phoenix-playground/phoenix_playground"},
{:ecto, "~> 3.11"},
{:phoenix_ecto, "~> 4.6"}
])
defmodule CoreComponents do
use Phoenix.Component
attr(:id, :any)
attr(:name, :any)
attr(:label, :string, default: nil)
attr(:field, Phoenix.HTML.FormField,
doc: "a form field struct retrieved from the form, for example: @form[:email]"
)
attr(:errors, :list)
attr(:required, :boolean, default: false)
attr(:options, :list, doc: "...")
attr(:rest, :global, include: ~w(disabled form readonly))
attr(:class, :string, default: nil)
def checkgroup(assigns) do
new_assigns =
assigns
|> assign(:multiple, true)
|> assign(:type, "checkgroup")
input(new_assigns)
end
attr(:id, :any, default: nil)
attr(:name, :any)
attr(:class, :string, default: nil)
attr(:label, :string, default: nil)
attr(:type, :string,
default: "text",
values: ~w(checkbox color date datetime-local email file hidden month number password
range radio search select tel text textarea time url week checkgroup)
)
attr(:value, :any)
attr(:field, Phoenix.HTML.FormField,
doc: "a form field struct retrieved from the form, for example: @form[:email]"
)
attr(:checked, :boolean, doc: "the checked flag for checkbox inputs")
attr(:prompt, :string, default: nil, doc: "the prompt for select inputs")
attr(:options, :list, doc: "the options to pass to Phoenix.HTML.Form.options_for_select/2")
attr(:multiple, :boolean, default: false, doc: "the multiple flag for select inputs")
attr(:rest, :global, include: ~w(autocomplete cols disabled form max maxlength min minlength
pattern placeholder readonly required rows size step))
def input(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do
assigns
|> assign(field: nil, id: assigns.id || field.id)
|> assign_new(:name, fn -> if assigns.multiple, do: field.name <> "[]", else: field.name end)
|> assign_new(:value, fn -> field.value end)
|> input()
end
def input(%{type: "checkgroup"} = assigns) do
~H"""
"""
end
def input(%{type: "checkbox"} = assigns) do
assigns =
assign_new(assigns, :checked, fn ->
Phoenix.HTML.Form.normalize_value("checkbox", assigns[:value])
end)
~H"""
"""
end
end
defmodule DemoLive do
use Phoenix.LiveView
use Phoenix.VerifiedRoutes,
router: PhoenixPlayground.Router.LiveRouter,
endpoint: PhoenixPlayground.Endpoint
import CoreComponents
defmodule Category do
defstruct [:id, :name]
end
def mount(_params, _session, socket) do
categories = [%Category{id: 0, name: "Category 1"}, %Category{id: 1, name: "Category 2"}]
categories_form = parse_filter(%{all: false, selected: [0]})
categories_filter = apply_filter(categories_form)
categories_options = Enum.map(categories, &{&1.name, &1.id})
{:ok,
assign(socket,
categories: categories,
filtered_categories: filter_categories(categories, categories_filter),
filter: categories_filter,
options: categories_options
)
|> assign_form(categories_form)}
end
def render(assigns) do
~H"""
<%= c.name %>
<.form id="categories" for={@categories_form} phx-change="change-categories">
<.input type="checkbox" field={@categories_form[:all]} label="Show all" />
<.checkgroup field={@categories_form[:selected]} options={@options} label="Categories" />
"""
end
def handle_params(params, _, socket) do
form = parse_filter(socket.assigns.filter, params)
filter = apply_filter(form)
{:noreply,
socket
|> assign(
filter: filter,
filtered_categories: filter_categories(socket.assigns.categories, filter)
)
|> assign_form(form)}
end
def handle_event("change-categories", %{"categories" => params}, socket) do
form = parse_filter(socket.assigns.filter, params)
filter = apply_filter(form)
{:noreply, socket |> push_patch(to: ~p"/?#{filter}")}
end
defp parse_filter(default_filter, attrs \\ %{}) do
fields = %{
all: :boolean,
selected: {:array, :integer}
}
{default_filter, fields}
|> Ecto.Changeset.cast(attrs, Map.keys(fields))
|> Map.put(:action, :validate)
end
defp apply_filter(%Ecto.Changeset{} = changeset) do
changeset
|> Ecto.Changeset.apply_changes()
end
defp assign_form(socket, %Ecto.Changeset{} = changeset) do
assign(socket, :categories_form, to_form(changeset, as: :categories))
end
defp filter_categories(categories, %{all: all, selected: selected_ids}) do
if all do
categories
else
Enum.filter(categories, fn category -> category.id in selected_ids end)
end
end
end
PhoenixPlayground.start(live: DemoLive, port: 4003)