defmodule ForageWeb.ForageView do @moduledoc """ Helper functions for veiws that feature forage filters, pagination buttons or sort links. """ import Phoenix.HTML, only: [sigil_e: 2] import Phoenix.HTML.Link, only: [link: 2] import Phoenix.HTML.Form, only: [input_value: 2, form_for: 4, select: 4] alias Phoenix.HTML.FormData alias ForageWeb.Naming defmacro __using__(options) do routes_module = case Keyword.fetch(options, :routes_module) do {:ok, module} -> module :error -> raise ArgumentError, "Requires a `:routes_module`." end prefix = case Keyword.fetch(options, :prefix) do {:ok, val} -> val :error -> raise ArgumentError, "Requires a `:prefix`." end resource_path_fun_name = String.to_atom("#{prefix}_path") pagination_widget_fun_name = String.to_atom("#{prefix}_pagination_widget") sort_link_fun_name = String.to_atom("#{prefix}_sort_link") search_form_for_fun_name = String.to_atom("#{prefix}_search_form_for") quote do import ForageWeb.ForageView def unquote(search_form_for_fun_name)(conn, options \\ [], fun) do action = unquote(routes_module).unquote(resource_path_fun_name)(conn, :index) forage_search_form_for( conn, action, options, fun ) end def unquote(sort_link_fun_name)(conn, field, content, options \\ []) do forage_sort_link( conn, unquote(routes_module), unquote(resource_path_fun_name), field, content, options ) end def unquote(pagination_widget_fun_name)(conn, resource, options \\ []) do forage_pagination_widget( conn, resource, unquote(routes_module), unquote(resource_path_fun_name), options ) end end end def forage_select(form, field, opts) do # Params path = Keyword.fetch!(opts, :path) display = Keyword.fetch!(opts, :display) remote_field = Keyword.fetch!(opts, :remote_field) # Derived values field_value = Map.get(form.data, field) ~e""" """ end def forage_select_filter(form, field, opts) do # Params path = Keyword.fetch!(opts, :path) display = Keyword.fetch!(opts, :display) remote_field = Keyword.fetch!(opts, :remote_field) # Derived values IO.inspect({form.data, field}) field_value = Map.get(form.data, field) ~e""" """ end @text_operators [ {"Contains", "contains"}, {"Equal to", "equal_to"}, {"Starts with", "starts_with"}, {"Ends with", "ends_with"} ] @number_operators [ {"Equal to", "equal_to"}, {"Greater than", "greater_than"}, {"Less than", "less_than"}, {"Greater than or equal to", "greater_than_or_equal_to"}, {"Less than or equal to", "less_than_or_equal_to"} ] @operator_class "col-sm-3" @value_class "col-sm-9" defp name_to_search_id(name) do ["_search", to_string(name)] end defp sort_direction(conn, field) do direction_string = get_in(conn.params, ["_sort", to_string(field), "direction"]) case direction_string do "asc" -> :asc "desc" -> :desc nil -> nil end end defp sort_by(conn, field, direction) do conn.params # Remove the old pagination data, which is useless now |> Map.delete("_pagination") |> Map.put("_sort", %{field => %{"direction" => direction}}) end def forage_sort_link(conn, mod, fun, field, content, options \\ []) do icon_down = Keyword.get(options, :icon_down, " ↓") icon_up = Keyword.get(options, :icon_up, " ↑") {link_content, new_conn_params} = case sort_direction(conn, field) do :asc -> {[content, " ", icon_down], sort_by(conn, field, "desc")} :desc -> {[content, " ", icon_up], sort_by(conn, field, "asc")} nil -> {content, sort_by(conn, field, "desc")} end destination = apply(mod, fun, [conn, :index, new_conn_params]) link(link_content, to: destination) end def forage_pagination_link_previous(conn, resource, mod, fun, contents) do if resource.metadata.before do before_params = Map.put(conn.params, :_pagination, %{before: resource.metadata.before}) destination = apply(mod, fun, [conn, :index, before_params]) ~e'
  • <%= contents %>
  • ' else ~e'' end end def forage_pagination_link_next(conn, resource, mod, fun, contents) do if resource.metadata.after do after_params = Map.put(conn.params, :_pagination, %{after: resource.metadata.after}) destination = apply(mod, fun, [conn, :index, after_params]) ~e'
  • <%= contents %>
  • ' else ~e'' end end def forage_pagination_widget(conn, resource, mod, fun, options) do previous_text = Keyword.get(options, :previous, "« Previous") next_text = Keyword.get(options, :next, "Next »") classes = Keyword.get(options, :classes, "pagination-sm no-margin pull-right") ~e""" """ end @doc """ Form group for horizontal forms. """ def forage_horizontal_form_group(name, opts \\ [], do: content) do label = Keyword.get(opts, :label, [Naming.humanize(name), ":"]) input_id = Keyword.get(opts, :id, name_to_search_id(name)) {label_class, inputs_class} = Keyword.get(opts, :classes, {"col-sm-2", "col-sm-10"}) ~e"""
    <%= content %>
    """ end def forage_active_filters?(%{params: %{"_search" => _}} = _conn), do: true def forage_active_filters?(_conn), do: false @spec forage_search_form_for( FormData.t(), String.t(), Keyword.t(), (FormData.t() -> Phoenix.HTML.unsafe()) ) :: Phoenix.HTML.safe() def forage_search_form_for(conn, action, options \\ [], fun) do new_options = options |> Keyword.put_new(:as, :_search) |> Keyword.put_new(:method, "get") |> Keyword.put_new(:class, "form-horizontal") form_for(conn, action, new_options, fun) end @doc false def input_value_and_name(form, {name, index} = _field_spec) do # A custom implementation of `Phoenix.HTML.Form.input_value/2` # to handle cases where there are fields with the same name but # different indices indexed_map = input_value(form, name) stringified_index = to_string(index) value = Map.get(indexed_map, stringified_index) name_for_form = ~e'<%= name %>[<%= index %>]' {value, name_for_form} end def input_value_and_name(form, name) do # Use the default implementation (same as in `Phoenix.HTML.Form`) {input_value(form, name), name} end defp generic_forage_filter(type, form, field_spec, default_operators, opts) do operators = Keyword.get(opts, :operators, default_operators) # Extract the field name from the id if necessary {field_values, name} = input_value_and_name(form, field_spec) {operator, value} = case field_values do nil -> [{_operator_name, operator_value} | _rest] = operators {operator_value, nil} %{"op" => operator, "val" => value} -> {operator, value} end {operator_class, value_class} = Keyword.get(opts, :classes, {@operator_class, @value_class}) ~e"""
    """ end def forage_text_filter(form, name, opts \\ []) do generic_forage_filter("text", form, name, @text_operators, opts) end def forage_numeric_filter(form, name, opts \\ []) do generic_forage_filter("number", form, name, @number_operators, opts) end def forage_date_filter(form, name, opts \\ []) do generic_forage_filter("date", form, name, @number_operators, opts) end def forage_time_filter(form, name, opts \\ []) do generic_forage_filter("time", form, name, @number_operators, opts) end end