Filtering data for paginated datatable

I have a paginated datatable for products and I want users to be able to filter the product price from a min price to a max price.

This is the code in my products context:

  def list_products(criteria) when is_list(criteria) do
    query = from (r in Product), order_by: [desc: r.inserted_at]

    Enum.reduce(criteria, query, fn
      {:paginate, %{page: page, per_page: per_page}}, query ->
        from q in query,
          offset: ^((page - 1) * per_page),
          limit: ^per_page

      {:filter, %{min: nil, max: nil}}, query ->
        query

      {:filter, %{min: "", max: ""}}, query ->
        query

      {:filter, %{min: min, max: max}}, query ->
        from q in query,
          where: q.price >= ^min and q.price <= ^max
    end)
    |> Repo.all()
    |> Repo.preload(:user)
  end

Liveview:

def handle_params(params, _url, socket) do
    page = String.to_integer(params["page"] || "1")
    per_page = String.to_integer(params["per_page"] || "5")
   
    paginate_options = %{page: page, per_page: per_page}

    filter_options = %{min: nil, max: nil}

    products = Products.list_products(paginate: paginate_options, filter: filter_options)

    socket =
      assign(socket,
        options: paginate_options,
        filter: filter_options,
        products: products
      )

    {:noreply, socket}
  end

  def handle_event("filter", %{"min" => min, "max" => max}, socket) do
    
    filter_options = %{min: min, max: max}

    products = Products.list_products(paginate: socket.assigns.options, filter: filter_options)

    socket =
      push_patch(socket,
        to:
          Routes.products_index_path(
            socket,
            :index,
            products: products,
            page: socket.assigns.options.page,
            per_page: socket.assigns.options.per_page,
            min: min,
            max: max
          )
      )

    {:noreply, socket}
  end

I am not sure how to combine the pagination feature with the filtering feature.

Currently with the above code, I can get the filtering parameters appended in the URL but using push_patch I don’t get the datatable updated only when using assign which doesn’t append the filtering parameters in the URL.

After you push_patch, handle_params will be called. You are setting the min/max to nil every time. So you need to get the min & max from the params in handle_params and you don’t want to be assigning the products in the filter event.

In the event you push_patch with the URL params you want and do all the assigning in handle_params.

Something like this:

def handle_event("filter", %{"min" => min, "max" => max}, socket) do

    socket =
      push_patch(socket,
        to:
          Routes.release_notes_index_path(
            socket,
            :index,
            page: socket.assigns.options.page,
            per_page: socket.assigns.options.per_page,
            min: min,
            max: max
          )
      )

    {:noreply, socket}
  end
1 Like

Thanks for helping out!

@impl true
  def handle_params(params, _url, socket) do
    page = String.to_integer(params["page"] || "1")
    per_page = String.to_integer(params["per_page"] || "5")

    paginate_options = %{page: page, per_page: per_page}

    min = params["min"] || ""
    max = params["max"] || ""

    filter_options = %{min: min, max: max}

    products = Products.list_products(paginate: paginate_options, filter: filter_options)

    socket =
      assign(socket,
        options: paginate_options,
        filter: filter_options,
        products: products
      )

    {:noreply, socket}
  end

This will make my URL looks like this when I am not actually filtering the data: http://localhost:4000/products?page=1&per_page=5&min=&max=

How do I make it only appear when I actually fire up the def handle_event("filter", %{"min" => min, "max" => max}, socket) function?