kevinschweikert

kevinschweikert

Need help implementing a "Select All" checkbox with form data

Hi together,

i want to build a filter UI where the user can select some items with a checkbox and then the results get filtered based on the selected checkboxes. For example, a use case where we have some categories in a DB and give the user a checkbox for each category to filter for. So far so good and i had no problem implementing that.
But i want to have another checkbox which lets the user select all the categories at once or deselect every category at once. Additionally, the behaviour should be, that when a user deselects one category while the select all box is checked it should become unchecked and vice versa, when the user selects every category by hand, the select all checkbox should be checked as well.

My first step was to define a schema for the form which looks something like this:

  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

Is store the filter params in the URL with:

  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
  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

I’ve tried with some Ecto.Changeset.get_change/put_change in the validation with parse_filter/2 but all and selected would overwrite each other.

I’ve prepared a gist with my idea, but i can’t figure out if i am on the right track with my plan. Does anybody have a solution for this problem or can point me in the right direction?

Marked As Solved

LostKobrakai

LostKobrakai

You weren’t far off: Revisions · Phoenix LiveView filter toogle all problem · GitHub

Needed a bit working around the fact that empty lists cannot be represented in url encoding.

Also Liked

greven

greven

Here’s a snippet of what I have done in a multi-step form some years ago. Probably there is a more elegant solution for this, but this was done “in a rush” about 4 years ago, anyway, it might help you.

The amount of possible options here was like 4 and static, maybe if you have dynamic options it might not work that well with this approach.

defmodule Web.JobTypeStepComponentLive do

  @primary_key false
  embedded_schema do
    field(:job_types, {:array, :string})
  end

  @impl true
  def mount(socket) do
    socket = assign(socket, all_checked?: false)
    {:ok, socket}
  end

  @impl true
  def update(assigns, socket) do
    socket =
      socket
      |> assign(assigns)
      |> assign_job_types()
      |> assign_changeset()
      |> assign_job_type_options()

    {
      :ok,
      socket
      |> assign(all_checked?: all_checked?(socket))
    }
  end

  @impl true
  def handle_event("validate", params, socket) do
    params = Map.get(params, "job_type_step_component", %{"job_types" => []})
    all_checked? = all_checked?(socket, params["job_types"])

    changeset =
      %__MODULE__{}
      |> changeset(params)
      |> Map.put(:action, :validate)

    {:noreply, assign(socket, changeset: changeset, all_checked?: all_checked?)}
  end

  @impl true
  def handle_event("toggle_all", _payload, socket) do
    all_ids =
      socket.assigns.job_type_options
      |> Enum.map(&(elem(&1, 1) |> to_string()))

    all_checked? = all_checked?(socket)
    new_selection = if all_checked?, do: [], else: all_ids

    changeset =
      %__MODULE__{}
      |> changeset(%{"job_types" => new_selection})
      |> Map.put(:action, :validate)

    {:noreply, assign(socket, changeset: changeset, all_checked?: !all_checked?)}
  end

  defp all_checked?(socket) do
    current_ids = get_field(socket.assigns.changeset, :job_types) || []
    all_checked?(socket, current_ids)
  end

  defp all_checked?(socket, current_ids) do
    all_ids =
      socket.assigns.job_type_options
      |> Enum.map(&(elem(&1, 0) |> to_string()))

    length(current_ids) == length(all_ids)
  end

@impl true
  def render(assigns) do
    ~H"""
       (...)

          <div class="flex justify-center">
          <button type="button" class="btn btn--link btn--compact" phx-click="toggle_all" phx-target= 
               {@myself}>
            <%= if @all_checked?, do: gettext("Deselect all"), else: gettext("Select all") %>
          </button>
        </div>
    """
  end
end
LostKobrakai

LostKobrakai

So handle_params being called the first time is “special”. I’d have an assign preselected, which you fill in mount, in handle_params you adjust the result of parse_filter with all preselected categories and at the end of handle_params you set preselected: [] to essentially disable preselection going forward.

Last Post!

greven

greven

Saw this blog post from FullStackPhoenix that is also related. :slight_smile:

Where Next?

Popular in Questions Top

nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I forese...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New

Other popular topics Top

electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 44139 214
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New

We're in Beta

About us Mission Statement