-
-
Save LostKobrakai/c13883df724543ac8258b9cce1399e6f to your computer and use it in GitHub Desktop.
Phoenix LiveView filter toogle all problem
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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""" | |
<div phx-feedback-for={@name}> | |
<input type="hidden" name={@name} value="" /> | |
<div :for={{label, value} <- @options}> | |
<input | |
type="checkbox" | |
id={"#{@name}-#{value}"} | |
name={@name} | |
value={value} | |
checked={value in @value} | |
{@rest} | |
/> | |
<%= label %> | |
</div> | |
</div> | |
""" | |
end | |
def input(%{type: "checkbox"} = assigns) do | |
assigns = | |
assign_new(assigns, :checked, fn -> | |
Phoenix.HTML.Form.normalize_value("checkbox", assigns[:value]) | |
end) | |
~H""" | |
<div> | |
<label> | |
<input type="hidden" name={@name} value="false" disabled={@rest[:disabled]} /> | |
<input type="checkbox" id={@id} name={@name} value="true" checked={@checked} {@rest} /> | |
<%= @label %> | |
</label> | |
</div> | |
""" | |
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: []}, %{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""" | |
<div :for={c <- @filtered_categories}> | |
<%= c.name %> | |
</div> | |
<.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" /> | |
</.form> | |
""" | |
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) | |
|> adjust_by_latest_changes(Enum.map(socket.assigns.categories, & &1.id)) | |
filter = apply_filter(form) | |
{:noreply, socket |> push_patch(to: ~p"/?#{[all: filter.all, selected: filter.selected]}")} | |
end | |
defp parse_filter(default_filter, attrs) do | |
fields = %{ | |
all: :boolean, | |
selected: {:array, :integer} | |
} | |
{default_filter, fields} | |
|> Ecto.Changeset.change(selected: []) | |
|> Ecto.Changeset.cast(attrs, Map.keys(fields)) | |
|> Map.put(:action, :validate) | |
end | |
defp adjust_by_latest_changes(changeset, all) do | |
sorted = Enum.sort(all) | |
case Ecto.Changeset.fetch_change(changeset, :all) do | |
{:ok, true} -> | |
Ecto.Changeset.change(changeset, selected: sorted) | |
{:ok, false} -> | |
Ecto.Changeset.change(changeset, selected: []) | |
:error -> | |
with {:ok, selected} <- Ecto.Changeset.fetch_change(changeset, :selected) do | |
case Enum.sort(selected) do | |
^sorted -> Ecto.Changeset.change(changeset, all: true) | |
_ -> Ecto.Changeset.change(changeset, all: false) | |
end | |
else | |
_ -> changeset | |
end | |
end | |
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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment