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"""
<%= label %>
""" 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)