Skip to content

Instantly share code, notes, and snippets.

@LostKobrakai
Forked from kevinschweikert/demo.exs
Created June 25, 2024 07:34
Show Gist options
  • Save LostKobrakai/c13883df724543ac8258b9cce1399e6f to your computer and use it in GitHub Desktop.
Save LostKobrakai/c13883df724543ac8258b9cce1399e6f to your computer and use it in GitHub Desktop.
Phoenix LiveView filter toogle all problem
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