Last active
January 24, 2023 09:37
-
-
Save andreasknoepfle/794f9c3c8917f119f44a42b849c05bd6 to your computer and use it in GitHub Desktop.
Custom Flop filters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Flop.CustomFilters do | |
@default_op :== | |
@type filter :: atom() | {field :: atom(), op :: atom()} | |
@type filter_value :: binary() | nil | |
@type filter_values :: %{required(filter()) => filter_value()} | |
@doc """ | |
Remove a filter from the params for flop, so flop does not handle them | |
anymore. This can be used to implement missing features in flop filters. | |
pop_filter(%{"filters" => %{"0" => %{"field" => "search", "value" => "foo", "op" => "ilike"} | |
"1" => %{"field" => "other", "value" => "bar"}}, | |
"order_by" => ["reference"], ...}, | |
{:search, :ilike}) | |
=> {"foo", %{"filters" => %{"1" => %{"field" => "other", "value" => "bar"}}, | |
"order_by" => ["reference"], ...}} | |
""" | |
@spec pop_filter(params :: map(), filter :: filter()) :: | |
{filter_value(), params_without_filter :: map()} | |
def pop_filter(params, field) when is_atom(field) or is_binary(field) do | |
pop_filter(params, {field, @default_op}) | |
end | |
def pop_filter(%{"filters" => map_of_indexed_filters} = params, field_and_op) do | |
{value, updated_list_of_indexed_filters} = | |
map_of_indexed_filters | |
|> Enum.into([]) | |
|> do_pop_filter(field_and_op, fn {_index, filter}, key -> | |
filter[to_string(key)] | |
end) | |
{value, %{params | "filters" => Map.new(updated_list_of_indexed_filters)}} | |
end | |
def pop_filter(%{filters: list_of_filters} = params, field_and_op) do | |
{value, updated_list_of_filters} = | |
do_pop_filter(list_of_filters, field_and_op, fn filter, key -> | |
filter[key] | |
end) | |
{value, %{params | filters: updated_list_of_filters}} | |
end | |
def pop_filter(params, _field_and_op) do | |
{nil, params} | |
end | |
defp do_pop_filter([], _field_and_op, _filter_get_fun), do: {nil, []} | |
defp do_pop_filter([filter | rest], field_and_op, filter_get_fun) do | |
if filter_matches?(filter, field_and_op, filter_get_fun) do | |
{filter_get_fun.(filter, :value), rest} | |
else | |
{result, rest} = do_pop_filter(rest, field_and_op, filter_get_fun) | |
{result, [filter | rest]} | |
end | |
end | |
defp filter_matches?(filter, {field, op}, filter_get_fun) do | |
field_in_filter = to_string(filter_get_fun.(filter, :field)) | |
op_in_filter = to_string(filter_get_fun.(filter, :op) || "==") | |
to_string(field) == field_in_filter && to_string(op) == op_in_filter | |
end | |
@doc """ | |
Uses pop_filter/2 on multiple filters and returns a map of {field, op} => values for | |
the poped filters. | |
""" | |
@spec pop_filters(params :: map(), [filter()]) :: | |
{filter_values(), params_without_filter :: map()} | |
def pop_filters(all_params, filters) do | |
Enum.reduce(filters, {%{}, all_params}, fn | |
filter, {filter_values, params} -> | |
{value, params} = pop_filter(params, filter) | |
if value in [nil, ""] do | |
{filter_values, params} | |
else | |
{Map.put(filter_values, filter, value), params} | |
end | |
end) | |
end | |
@doc """ | |
This can be used to push a filter that was poped before onto the metadata of flop, | |
so the form field value is still filled out even though the filter is handled customly. | |
""" | |
@spec push_filter(flop :: Flop.Meta.t(), filter(), filter_value()) :: Flop.Meta.t() | |
def push_filter(meta, {field, op}, value) do | |
Map.update!(meta, :flop, fn flop -> | |
Map.update!(flop, :filters, fn filters -> | |
[%Flop.Filter{field: field, op: op, value: value} | filters] | |
end) | |
end) | |
end | |
def push_filter(meta, field, value), do: push_filter(meta, {field, @default_op}, value) | |
@doc """ | |
Same as push_filter but for multiple values. Can be called with the filter_values | |
result of `pop_filters/2`, to push back the poped filters. | |
""" | |
@spec push_filters(flop :: Flop.Meta.t(), filter_values()) :: Flop.Meta.t() | |
def push_filters(meta, filter_values) do | |
Enum.reduce(filter_values, meta, fn {filter, value}, meta -> | |
push_filter(meta, filter, value) | |
end) | |
end | |
end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment