Current section

Files

Jump to
localize_web lib localize html unit.ex
Raw

lib/localize/html/unit.ex

defmodule Localize.HTML.Unit do
@moduledoc """
Generates HTML `<select>` tags and option lists for localized unit-of-measure display.
Units are displayed with their localized display name. The list of units, display style (long, short, narrow), sort order, and display format are all configurable.
"""
@type select_options :: [
{:units, [atom() | binary(), ...]}
| {:locale, Localize.locale() | Localize.LanguageTag.t()}
| {:collator, function()}
| {:mapper, (tuple() -> String.t())}
| {:selected, atom() | binary()}
| {:style, :long | :short | :narrow}
]
@omit_from_select_options [:units, :locale, :mapper, :collator, :style]
@doc """
Generates an HTML select tag for a unit list that can be used with a `t:Phoenix.HTML.Form.t/0`.
### Arguments
* `form` is a `t:Phoenix.HTML.Form.t/0` form.
* `field` is a `t:Phoenix.HTML.Form.field/0` field.
* `options` is a `t:Keyword.t/0` list of options.
### Options
* `:units` is a list of units to be displayed in the select.
* `:style` is the style of unit name to be displayed. The options are `:long`, `:short` and `:narrow`. The default is `:long`.
* `:locale` defines the locale to be used to localise the description of the units. The default is the locale returned by `Localize.get_locale/0`.
* `:collator` is a function used to sort the units. The default collator sorts by display name.
* `:mapper` is a function that creates the text to be displayed in the select tag for each unit. It receives a tuple `{display_name, unit_code}`. The default is the identity function.
* `:selected` identifies the unit to be selected by default in the select tag. The default is `nil`.
* `:prompt` is a prompt displayed at the top of the select box.
### Returns
* A `t:Phoenix.HTML.safe/0` select tag, or
* `{:error, {module(), binary()}}` if validation fails.
### Examples
iex> Localize.HTML.Unit.select(:my_form, :unit, selected: :foot)
"""
@spec select(
form :: Phoenix.HTML.Form.t(),
field :: Phoenix.HTML.Form.field(),
select_options
) ::
Phoenix.HTML.safe()
| {:error, {module(), binary()}}
def select(form, field, options \\ [])
def select(form, field, options) when is_list(options) do
select(form, field, validate_options(options), options[:selected])
end
@doc """
Generates a list of options for a unit list that can be used with `Phoenix.HTML.Form.options_for_select/2` or to create a `<datalist>`.
### Arguments
* `options` is a `t:Keyword.t/0` list of options.
### Options
See `Localize.HTML.Unit.select/3` for options.
### Returns
* A list of `{display_name, unit_code}` tuples, or
* `{:error, {module(), binary()}}` if validation fails.
"""
@spec unit_options(select_options) :: list(tuple()) | {:error, {module(), binary()}}
def unit_options(options \\ [])
def unit_options(options) when is_list(options) do
options
|> validate_options()
|> build_unit_options()
end
defp select(_form, _field, {:error, reason}, _selected) do
{:error, reason}
end
defp select(form, field, options, _selected) do
select_options =
options
|> Map.drop(@omit_from_select_options)
|> Map.to_list()
options = build_unit_options(options)
PhoenixHTMLHelpers.Form.select(form, field, options, select_options)
end
defp validate_options(options) do
with options <- Map.merge(default_options(), Map.new(options)),
{:ok, options} <- validate_locale(options),
{:ok, options} <- validate_selected(options) do
options
end
end
defp default_options do
Map.new(
units: default_unit_list(),
locale: Localize.get_locale(),
collator: &default_collator/1,
mapper: & &1,
style: :long,
selected: nil
)
end
defp default_collator(units) do
Enum.sort(units, fn {name_1, _}, {name_2, _} -> name_1 < name_2 end)
end
defp validate_selected(%{selected: nil} = options) do
{:ok, options}
end
defp validate_selected(%{selected: selected} = options) do
{:ok, Map.put(options, :selected, to_string(selected))}
end
defp validate_locale(options) do
with {:ok, locale} <- Localize.validate_locale(options[:locale]) do
options
|> Map.put(:locale, locale)
|> wrap(:ok)
end
end
defp wrap(term, atom), do: {atom, term}
defp maybe_include_selected_unit(%{selected: nil} = options) do
options
end
defp maybe_include_selected_unit(%{units: units, selected: selected} = options) do
if Enum.any?(units, &(to_string(&1) == to_string(selected))) do
options
else
Map.put(options, :units, [selected | units])
end
end
defp build_unit_options(options) when is_map(options) do
options = maybe_include_selected_unit(options)
units = Map.fetch!(options, :units)
collator = Map.fetch!(options, :collator)
mapper = Map.fetch!(options, :mapper)
options_list = Map.to_list(options)
units
|> Enum.map(&to_selection_tuple(&1, options_list))
|> collator.()
|> Enum.map(&mapper.(&1))
end
defp to_selection_tuple(unit, options) do
display_name =
case Localize.Unit.display_name(to_string(unit), options) do
{:ok, name} -> name
{:error, _} -> to_string(unit)
end
unit_code = to_string(unit)
{display_name, unit_code}
end
defp default_unit_list do
Localize.Unit.known_units_by_category()
|> Enum.flat_map(fn {_category, units} -> units end)
end
end