Current section
Files
Jump to
Current section
Files
lib/cldr_html_territories.ex
if Cldr.Code.ensure_compiled?(Cldr.Territory) do
defmodule Cldr.HTML.Territory do
@moduledoc """
Implements `Phoenix.HTML.Form.select/4` specifically for
localised territory display.
"""
@type select_options :: [
{:territories, [atom() | binary(), ...]}
| {:locale, Cldr.Locale.locale_name() | Cldr.LanguageTag.t()}
| {:mapper, function()}
| {:backend, module()}
| {:selected, atom() | binary()}
]
@doc """
Generate an HTML select tag for a territory list
that can be used with a `Phoenix.HTML.Form.t`.
## Arguments
* A `Phoenix.HTML.Form.t()` form
* A `Phoenix.HTML.Form.field()` field
* A `Keyword.t()` list of options
## Options
For select options see `Phoenix.HTML.Form.select/4`
* `:territories` defines the list of territories to be
displayed in the the `select` tag. The list defaults to
the territories returned by `Cldr.known_territories/0`.
* `:style` is the format of the territory name to be used.
The options are `:standard` (the default), `:short` and `:variant`.
Not all territories have `:short` or `:variant` names in which
case `:standard` is used for those territories.
* `:locale` defines the locale to be used to localise the
description of the territories. The default is the locale
returned by `Cldr.get_locale/1`
* `:backend` is any backend module. The default is
`Cldr.default_backend!/0`
* `:mapper` is a function that creates the text to be
displayed in the select tag for each territory. It is
passed the territory definition as a map containing the keys
`:territory_code`, `:name` and `:flag`. The default function
is `&({&1.flag <> " " <> &1.name, &1.territory_code})`
* `:selected` identifies the territory that is to be selected
by default in the `select` tag. The default is `nil`. This
is passed unmodified to `Phoenix.HTML.Form.select/4`
* `:prompt` is a prompt displayed at the top of the select
box. This is passed unmodified to `Phoenix.HTML.Form.select/4`
# Examples
=> Cldr.HTML.Territory.select(:my_form, :territory, selected: :AU)
=> Cldr.HTML.Territory.select(:my_form, :territory, selected: :AU, locale: "ar")
=> Cldr.HTML.Territory.select(:my_form, :territory, territories: [:US, :AU, :JP],
mapper: &({&1.name, &1.territory_code}))
"""
@spec select(
form :: Phoenix.HTML.Form.t(),
field :: Phoenix.HTML.Form.field(),
select_options
) ::
Phoenix.HTML.safe()
| {:error, {Cldr.UnknownTerritoryError, binary()}}
| {:error, {Cldr.UnknownLocaleError, 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
# Invalid options
defp select(_form, _field, {:error, reason}, _selected) do
{:error, reason}
end
# Selected territory
defp select(form, field, options, _selected) do
select_options =
options
|> Map.take([:selected, :prompt])
|> Map.to_list
options =
options
|> maybe_include_selected_territory
|> territory_options
Phoenix.HTML.Form.select(form, field, options, select_options)
end
defp validate_options(options) do
options = Map.new(options)
with options <- Map.merge(default_options(), options),
{:ok, options} <- validate_locale(options),
{:ok, options} <- validate_selected(options),
{:ok, options} <- validate_territories(options) do
options
end
end
defp default_options do
Map.new(
territories: default_territory_list(),
locale: Cldr.get_locale(),
backend: nil,
mapper: &{&1.flag <> " " <> &1.name, &1.territory_code},
selected: nil
)
end
defp validate_selected(%{selected: nil} = options) do
{:ok, options}
end
defp validate_selected(%{selected: selected} = options) do
with {:ok, territory} <- Cldr.validate_territory(selected) do
{:ok, Map.put(options, :selected, territory)}
end
end
# Return a list of validated territories or an error
defp validate_territories(%{territories: territories} = options) do
validate_territories(territories, options)
end
defp validate_territories(territories) when is_list(territories) do
Enum.reduce_while(territories, [], fn territory, acc ->
case Cldr.validate_territory(territory) do
{:ok, territory} -> {:cont, [territory | acc]}
{:error, reason} -> {:halt, {:error, reason}}
end
end)
end
defp validate_territories(territories, options) do
case validate_territories(territories) do
{:error, reason} -> {:error, reason}
territories -> {:ok, Map.put(options, :territories, Enum.reverse(territories))}
end
end
defp validate_locale(options) do
{locale, backend} = Cldr.locale_and_backend_from(options[:locale], options[:backend])
with {:ok, locale} <- Cldr.validate_locale(locale, backend) do
options
|> Map.put(:locale, locale)
|> Map.put(:backend, locale.backend)
|> wrap(:ok)
end
end
defp wrap(term, atom) do
{atom, term}
end
defp maybe_include_selected_territory(%{selected: nil} = options) do
options
end
defp maybe_include_selected_territory(%{territories: territories, selected: selected} = options) do
if Enum.any?(territories, &(&1 == selected)) do
options
else
Map.put(options, :territories, [selected | territories])
end
end
defp territory_options(options) do
territories = Map.fetch!(options, :territories)
territories
|> Enum.map(&territory_info(&1, options))
|> Enum.sort()
|> Enum.map(fn territory_info -> options[:mapper].(territory_info) end)
end
defp territory_info(territory, %{backend: backend} = options) do
options = info_options(options)
name =
with {:ok, name} <- Cldr.Territory.from_territory_code(territory, backend, options) do
name
else
{:error, {Cldr.UnknownStyleError, _}} ->
default_style_options = Keyword.delete(options, :style)
{:ok, name} = Cldr.Territory.from_territory_code(territory, backend, default_style_options)
name
end
flag =
with {:ok, flag} <- Cldr.Territory.to_unicode_flag(territory) do
flag
else
_ -> " "
end
%{territory_code: territory, name: name, flag: flag}
end
defp info_options(%{locale: locale, style: style}) do
[locale: locale, style: style]
end
defp info_options(%{locale: locale}) do
[locale: locale]
end
defp info_options(%{style: style}) do
[style: style]
end
# Default territory list to legal tender territories
# if Money is available, otherwise the full list of
# Cldr territories (which is almost identitical to ISO4217)
defp default_territory_list() do
Cldr.known_territories()
end
end
end