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 ?






















