Current section

Files

Jump to
flop_phoenix lib flop_phoenix form_data.ex
Raw

lib/flop_phoenix/form_data.ex

defimpl Phoenix.HTML.FormData, for: Flop.Meta do
alias Flop.Filter
alias Flop.Meta
alias Flop.Phoenix.Misc
def to_form(meta, opts) do
{name, opts} = name_and_opts(meta, opts)
{hidden, opts} = Keyword.pop(opts, :hidden, [])
id = Keyword.get(opts, :id) || name || "flop"
%Phoenix.HTML.Form{
data: meta.flop,
errors: meta.errors,
hidden: hidden_inputs(meta, hidden),
id: id,
impl: __MODULE__,
name: name,
options: opts,
params: meta.params,
source: meta
}
end
defp name_and_opts(_meta, opts) do
case Keyword.pop(opts, :as) do
{nil, opts} -> {nil, opts}
{name, opts} -> {to_string(name), opts}
end
end
defp hidden_inputs(
%Meta{flop: %Flop{} = flop, params: %{} = params, schema: schema},
hidden
) do
default_limit = Flop.get_option(:default_limit, for: schema)
default_order = Flop.get_option(:default_order, for: schema)
page_size = params["page_size"] || flop.page_size
limit = params["limit"] || flop.limit
first = params["first"] || flop.first
last = params["last"] || flop.last
order_by = params["order_by"] || flop.order_by
order_directions = params["order_directions"] || flop.order_directions
order_params = %{order_by: order_by, order_directions: order_directions}
hidden
|> Misc.maybe_put(:page_size, page_size, default_limit)
|> Misc.maybe_put(:limit, limit, default_limit)
|> Misc.maybe_put(:first, first, default_limit)
|> Misc.maybe_put(:last, last, default_limit)
|> Misc.maybe_put_order_params(order_params, default_order)
end
def to_form(
meta,
%{data: %Flop{} = flop} = form,
:filters,
opts
) do
no_unsupported_options!(opts)
{id, opts} = Keyword.pop(opts, :id)
{name, opts} = Keyword.pop(opts, :as)
{default, opts} = Keyword.pop(opts, :default, [])
{fields, opts} = Keyword.pop(opts, :fields)
{offset, opts} = Keyword.pop(opts, :offset, 0)
{skip_hidden_op, opts} = Keyword.pop(opts, :skip_hidden_op, false)
name = if name = name || form.name, do: name <> "[filters]", else: "filters"
id = if id = id || form.id, do: id <> "_filters", else: "filters"
filters =
if form.errors == [],
do: flop.filters,
else: Map.get(form.params, "filters", [])
filter_errors = Keyword.get(form.errors, :filters, [])
filters_with_errors =
filters
|> filters_for(fields, default, filter_errors)
|> reject_unfilterable(meta.schema)
for {{filter, errors}, index} <-
Enum.with_index(filters_with_errors, offset) do
index_string = Integer.to_string(index)
hidden = get_hidden(filter, skip_hidden_op)
{data, params} =
case filter do
%Filter{} -> {filter, %{}}
%{} -> {%Filter{}, filter}
end
%Phoenix.HTML.Form{
source: meta,
impl: __MODULE__,
index: index,
id: id <> "_" <> index_string,
name: name <> "[" <> index_string <> "]",
data: data,
errors: errors,
params: params,
hidden: hidden,
options: opts
}
end
end
def to_form(_meta, _form, field, _opts) do
raise ArgumentError,
"Only :filters is supported on " <>
"inputs_for with Flop.Meta, got: #{inspect(field)}."
end
# no filters, use default
defp filters_for([], nil, default, _), do: zip_errors(default, [])
# no static field configuration
defp filters_for(filters, nil, _, errors), do: zip_errors(filters, errors)
# with static field configuration
defp filters_for(filters, fields, _, errors) when is_list(fields) do
filters_with_errors = zip_errors(filters, errors)
fields
|> Enum.reduce([], &filter_reducer(&1, &2, filters_with_errors))
|> Enum.reverse()
end
defp get_hidden(filter, false = _skip_hidden_op) do
Misc.maybe_put([field: value(filter, :field)], :op, value(filter, :op), :==)
end
defp get_hidden(filter, true = _skip_hidden_op) do
[field: value(filter, :field)]
end
defp zip_errors(filters, []), do: Enum.map(filters, &{&1, []})
defp zip_errors(filters, errors) when is_list(errors),
do: Enum.zip(filters, errors)
defp filter_reducer({field, opts}, acc, filters)
when is_atom(field) and is_list(opts) do
op = opts[:op] || :==
default = opts[:default]
if filter = find_filter_for_field(field, op, filters) do
[filter | acc]
else
[{%Filter{field: field, op: op, value: default}, []} | acc]
end
end
defp filter_reducer(field, acc, filters) when is_atom(field) do
filter_reducer({field, []}, acc, filters)
end
defp find_filter_for_field(field, op, filters) do
Enum.find(
filters,
fn {filter, _errors} ->
value(filter, :field) in [field, Atom.to_string(field)] &&
value(filter, :op, :==) in [op, Atom.to_string(op)]
end
)
end
defp reject_unfilterable(filters, nil), do: filters
defp reject_unfilterable(filters_with_errors, schema) do
filterable = schema |> struct() |> Flop.Schema.filterable()
filterable = filterable ++ Enum.map(filterable, &Atom.to_string/1)
Enum.reject(filters_with_errors, fn {filter, _} ->
value(filter, :field) not in filterable
end)
end
defp no_unsupported_options!(opts) do
for key <- [:append, :as, :hidden, :prepend] do
if Keyword.has_key?(opts, key) do
raise ArgumentError,
"#{inspect(key)} is not supported on inputs_for with Flop.Meta."
end
end
end
def input_type(_meta, _form, :after), do: :text_input
def input_type(_meta, _form, :before), do: :text_input
def input_type(_meta, _form, :first), do: :number_input
def input_type(_meta, _form, :last), do: :number_input
def input_type(_meta, _form, :limit), do: :number_input
def input_type(_meta, _form, :offset), do: :number_input
def input_type(_meta, _form, :page), do: :number_input
def input_type(_meta, _form, :page_size), do: :number_input
def input_type(
_meta,
%{data: %Filter{field: field}, source: %{schema: schema}},
:value
)
when not is_nil(schema) do
schema |> ecto_type(field) |> input_type_for_ecto_type()
end
def input_type(_meta, _form, _field), do: :text_input
defp input_type_for_ecto_type(:boolean), do: :checkbox
defp input_type_for_ecto_type(:date), do: :date_select
defp input_type_for_ecto_type(:integer), do: :number_input
defp input_type_for_ecto_type(:naive_datetime), do: :datetime_select
defp input_type_for_ecto_type(:naive_datetime_usec), do: :datetime_select
defp input_type_for_ecto_type(:time), do: :time_select
defp input_type_for_ecto_type(:time_usec), do: :time_select
defp input_type_for_ecto_type(:utc_datetime), do: :datetime_select
defp input_type_for_ecto_type(:utc_datetime_usec), do: :datetime_select
defp input_type_for_ecto_type(_), do: :text_input
def input_validations(_meta, _form, :after), do: [maxlength: 100]
def input_validations(_meta, _form, :before), do: [maxlength: 100]
def input_validations(_meta, _form, :offset), do: [min: 0]
def input_validations(_meta, _form, :page), do: [min: 1]
def input_validations(_meta, %{source: %Flop.Meta{schema: nil}}, field)
when field in [:first, :last, :limit, :page_size],
do: [min: 1]
def input_validations(_meta, %{source: %Flop.Meta{schema: schema}}, field)
when field in [:first, :last, :limit, :page_size] do
if max_limit = Flop.get_option(:max_limit, for: schema) do
[min: 1, max: max_limit]
else
[min: 1]
end
end
def input_validations(
_meta,
%{data: %Filter{field: field}, source: %{schema: schema}},
:value
)
when not is_nil(schema) do
case :type |> schema.__schema__(field) |> input_type_for_ecto_type() do
:text_input -> [maxlength: 100]
_ -> []
end
end
def input_validations(_meta, _form, _field), do: []
def input_value(_meta, %{data: data, params: params}, field)
when is_atom(field) do
key = Atom.to_string(field)
case params do
%{^key => value} -> value
%{} -> Map.get(data, field)
end
end
def input_value(_meta, _form, field) do
raise ArgumentError, "expected field to be an atom, got: #{inspect(field)}"
end
defp value(map, key, default \\ nil) when is_atom(key) do
string_key = Atom.to_string(key)
case map do
%{^string_key => value} -> value
%{} -> Map.get(map, key, default)
end
end
defp ecto_type(module, field) do
%Flop.FieldInfo{ecto_type: type} =
module |> struct() |> Flop.Schema.field_info(field)
type
end
end