vipulbhj

vipulbhj

Flop Phoenix - what is the recommended way of doing complex filters

I recently started using Flop and Flop Phoenix, before this we were doing a ton of manual work. For basic filters, this makes things extremely easy, but how do you do complex filters ?

For example, assume you have

@derive {Flop.Schema,
  filterable: [
    :search,
    :status,
    :invoice_date,
    :invoice_number
  ],
  adapter_opts: [
    join_fields: [
      client_name: [
        binding: :client,
        field: :name,
        ecto_type: :string
      ]
    ],
    compound_fields: [
      search: [:invoice_number, :client_name]
    ]
  ],
  default_order: %{
    order_by: [:invoice_number],
    order_directions: [:desc]
  }
}
schema “invoices” do
  field(:invoice_number, :string)
  field(:invoice_date, :date)
  field(:status, :string)
  
  belongs_to(:client, NellieBackend.Clients.Client)

I want to setup filters with a search field that can search invoice_number and client_name, a select input with fixed values to filter on status and a date range filter for invoice_date.

Currently we are handling these manually, as I had no idea how to get this done with <.filter_fields :let={i} form={@form} fields={@fields}>

defp invoice_filters(assigns) do
  filters = assigns.meta.flop.filters || []

  search_value =
    Enum.find_value(filters, "", fn f ->
      if f.field == :search, do: f.value || ""
    end)

status_value =
  Enum.find_value(filters, "", fn f ->
    if f.field == :status && f.op == :==, do: f.value || ""
  end)

date_from_value =
  Enum.find_value(filters, "", fn f ->
    if f.field == :invoice_date && f.op == :>=, do: f.value || ""
  end)

date_to_value =
  Enum.find_value(filters, "", fn f ->
    if f.field == :invoice_date && f.op == :<=, do: f.value || ""
  end)

assigns =
  assigns
  |> assign(:search_value, search_value)
  |> assign(:status_value, status_value)
  |> assign(:date_from_value, date_from_value)
  |> assign(:date_to_value, date_to_value)

~H"""
<form phx-change="update-filter" class="border border-gray-300 rounded-xl px-6 py-4 flex flex-col gap-4 bg-white">
  <%!-- Row 1: Search and Status --%>
  <div class="flex gap-3 items-center">
    <div class="relative flex-grow">
      <.icon
        name="hero-magnifying-glass"
        class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400"
      />
      <input type="hidden" name="filters[0][field]" value="search" />
      <input type="hidden" name="filters[0][op]" value="ilike" />
      <input
        type="text"
        name="filters[0][value]"
        value={@search_value}
        placeholder="Search by invoice # or client..."
        class="w-full pl-9 pr-4 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
        autocomplete="off"
        phx-debounce="300"
      />
    </div>

    <div class="w-60">
      <input type="hidden" name="filters[1][field]" value="status" />
      <input type="hidden" name="filters[1][op]" value="==" />
      <select
        name="filters[1][value]"
        class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
      >
        <%= for {label, value} <- Invoice.status_options() do %>
          <option value={value} selected={@status_value == value}>{label}</option>
        <% end %>
      </select>
    </div>
  </div>

  <%!-- Row 2: Date Range --%>
  <div class="flex items-center gap-4">
    <div class="flex items-center gap-2">
      <label class="text-sm font-medium text-gray-700">From:</label>
      <input type="hidden" name="filters[2][field]" value="invoice_date" />
      <input type="hidden" name="filters[2][op]" value=">=" />
      <input
        type="date"
        name="filters[2][value]"
        value={@date_from_value}
        class="block rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
      />
    </div>
    <div class="text-gray-400">-</div>
    <div class="flex items-center gap-2">
      <label class="text-sm font-medium text-gray-700">To:</label>
      <input type="hidden" name="filters[3][field]" value="invoice_date" />
      <input type="hidden" name="filters[3][op]" value="<=" />
      <input
        type="date"
        name="filters[3][value]"
        value={@date_to_value}
        class="block rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
      />
    </div>
  </div>
</form>
"""

end

Anyone know how can we do this is a more idiomatic way ?

Most Liked

sodapopcan

sodapopcan

You wanna take a look at field configuration. When the fields attr is a keyword list with some specific options but you can also add any other data you need.

fields = [
  search: [
    type: text,
    label: "Invoice number / Client name",
    op: :ilike_and
  ],
  status: [
    type: :select,
    label: "Status",
    op: :==,
    options: Invoice.status_options()
  ],
  invoice_date: [
    type: :date,
    label: "Date",
    op: :==
  ],
  ...
]

~H"""
<Flop.Phoenix.filter_fields :let={i} form={@filter_form} fields={@fields}>
  <.input
    field={i.field}
    label={i.label}
    type={i.type}
    {i.rest}
    phx-debounce={i.type == "text" && "300"}
  />
</Flop.Phoenix.filter_fields>
"""

Note that i.rest is injected for you.

So this works “cleanest” when all of your inputs are the same component like in core components, but of course you can switch based of data for each field:

~H"""
<Flop.Phoenix.filter_fields :let={i} form={@filter_form} fields={@fields}>
  <.input :if={i.type == :text}  ... />
  <.select :if={i.type == :select}  ... />
</Flop.Phoenix.filter_fields>

"""
sodapopcan

sodapopcan

No prob! I just realized I totally butchered my second sentence after an edit but glad you got the gist! It’s what I get for answering late at night.

vipulbhj

vipulbhj

Thank you for pointing that out, this in combination of hidden_inputs_for_filter did the trick.

Thanks a ton

Where Next?

Popular in Questions 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
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New

Other popular topics Top

minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
985 44532 311
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
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

We're in Beta

About us Mission Statement