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, html_escape: 1] import Phoenix.HTML.Link, only: [link: 2] import Phoenix.HTML.Form, only: [input_value: 2, form_for: 4] alias Phoenix.HTML.Form alias Phoenix.HTML.FormData alias Phoenix.HTML.Tag alias ForageWeb.Naming alias Ecto.Association.NotLoaded alias ForageWeb.Display @doc """ """ 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 @doc """ Widget to select ... Required options: * `:path` (required) - the URL from which to request the data This function won't be applied to values requested from the server after the initial render. * `:remote_field` (required) - The remote field on the other side of the association. * `:foreign_key` (optionsl) - The name of the foreign key (as a string or an atom). If this is not supplied it will default to `field_id` """ def forage_select(form, field, opts) do # Params path = Keyword.fetch!(opts, :path) remote_field = Keyword.fetch!(opts, :remote_field) foreign_key = Keyword.get(opts, :foreign_key, "#{field}_id") # Derived values field_value = Map.get(form.data, field) field_id = field_value && Map.get(field_value, :id, nil) field_text = display_relation(field_value) IO.inspect(form.data, label: "form.data") IO.inspect(field_value, label: "field_value") ~e""" """ end defp display_relation(nil), do: "" defp display_relation(%NotLoaded{} = _field), do: "" defp display_relation(%{__struct__: _} = field), do: Display.display(field) @doc """ Displays a struct """ def forage_display(nil), do: "" def forage_display(%NotLoaded{} = _field), do: "" def forage_display(%{__struct__: _} = field), do: Display.display(field) # @doc """ # Widget to select ... # Required options: # * `:path` (required) - the URL from which to request the data # This function won't be applied to values requested from the server after # the initial render. # * `:remote_field` (required) - The remote field on the other side of the association. # * `:foreign_key` (optionsl) - The name of the foreign key (as a string or an atom). # If this is not supplied it will default to `field_id` # """ # def forage_select_many(form, field, opts) do # # Params # path = Keyword.fetch!(opts, :path) # display = Keyword.fetch!(opts, :display) # remote_field = Keyword.fetch!(opts, :remote_field) # foreign_key = Keyword.get(opts, :foreign_key, "#{field}_id") # # Derived values # field_value = Map.get(form.data, field) # ~e""" # # """ # end @doc """ Widget to select ... Required options: * `:path` (required) - the URL from which to request the data This function won't be applied to values requested from the server after the initial render. * `:remote_field` (required) - The remote field on the other side of the association. * `:foreign_key` (optionsl) - The name of the foreign key (as a string or an atom). If this is not supplied it will default to `field_id` """ def forage_select_filter(form, field, opts) do # Params path = Keyword.fetch!(opts, :path) remote_field = Keyword.fetch!(opts, :remote_field) field_value = Map.get(form.data, field) field_id = field_value && Map.get(field_value, :id, nil) field_text = display_relation(field_value) ~e""" """ end # Find a way of internationalizing this @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 @doc """ A link to sort a list of database rows by a certain key. """ 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 @doc """ A link to the previous page of search results. Returns the empty string if the previous page doesn't exist. """ 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'