defmodule ReflectOS.ConsoleWeb.FormComponents do @moduledoc """ Provides form UI components. """ use Phoenix.Component alias Ecto.Changeset alias ReflectOS.Kernel.OptionGroup alias ReflectOS.Kernel.Option @doc """ Renders an input with label and error messages. A `Phoenix.HTML.FormField` may be passed as argument, which is used to retrieve the input name, id, and values. Otherwise all attributes may be passed explicitly. ## Types This function accepts all HTML input types, considering that: * You may also set `type="select"` to render a ` <%= @label %>

<%= Phoenix.LiveView.TagEngine.component( @help_text, [], {__ENV__.module, __ENV__.function, __ENV__.file, __ENV__.line} ) %>

<%= render_slot(@help_text) %>

<.error :for={msg <- @errors}><%= msg %> """ end def input(%{type: "select"} = assigns) do ~H"""
<.label for={@id}> <%= @label %>

<%= Phoenix.LiveView.TagEngine.component( @help_text, [], {__ENV__.module, __ENV__.function, __ENV__.file, __ENV__.line} ) %>

<%= render_slot(@help_text) %>

<.error :for={msg <- @errors}><%= msg %>
""" end def input(%{type: "textarea"} = assigns) do ~H"""
<.label for={@id}><%= @label %>

<%= Phoenix.LiveView.TagEngine.component( @help_text, [], {__ENV__.module, __ENV__.function, __ENV__.file, __ENV__.line} ) %>

<%= render_slot(@help_text) %>

<.error :for={msg <- @errors}><%= msg %>
""" end # All other inputs text, datetime-local, url, password, etc. are handled here... def input(assigns) do ~H"""
<.label for={@id}><%= @label %>
<%= render_slot(@icon) %>

<%= Phoenix.LiveView.TagEngine.component( @help_text, [], {__ENV__.module, __ENV__.function, __ENV__.file, __ENV__.line} ) %>

<%= render_slot(@help_text) %>

<.error :for={msg <- @errors}><%= msg %>
""" end @doc """ Renders a label. """ attr :for, :string, default: nil slot :inner_block, required: true def label(assigns) do ~H""" """ end @doc """ Generates a generic error message. """ slot :inner_block, required: true def error(assigns) do ~H"""

<%= render_slot(@inner_block) %>

""" end @doc """ Renders a simple form. ## Examples <.simple_form for={@form} phx-change="validate" phx-submit="save"> <.input field={@form[:email]} label="Email"/> <.input field={@form[:username]} label="Username" /> <:actions> <.button>Save """ attr :for, :any, required: true, doc: "the data structure for the form" attr :as, :any, default: nil, doc: "the server side parameter to collect all input under" attr :rest, :global, include: ~w(autocomplete name rel action enctype method novalidate target multipart), doc: "the arbitrary HTML attributes to apply to the form tag" slot :inner_block, required: true slot :actions, doc: "the slot for form actions, such as a submit button" def simple_form(assigns) do ~H""" <.form :let={f} for={@for} as={@as} {@rest}>
<%= render_slot(@inner_block, f) %>
<%= render_slot(action, f) %>
""" end # Renders an input for a given `ReflectOS.Kernel.Option`. attr :option, Option, doc: "Provided configuration for the input" attr :form, Phoenix.HTML.Form defp config_form_part(%{option: %OptionGroup{}} = assigns) do ~H"""
<%= @option.label %>

<%= Phoenix.LiveView.TagEngine.component( @option.description, [], {__ENV__.module, __ENV__.function, __ENV__.file, __ENV__.line} ) %>

<.config_form_part :for={option <- @option.options} form={@form} option={option} />
""" end defp config_form_part( %{form: form, option: %Option{key: key, label: label, hidden: hidden, config: config}} = assigns ) do data = Changeset.apply_changes(form.source) assigns |> assign(form: nil, option: nil) |> assign(field: form[key], label: label) |> assign(config) |> assign(hidden?: hidden != nil && hidden.(data)) |> input() end @doc """ Config Form """ attr :for, :any, required: true, doc: "the data structure for the form" attr :as, :any, default: nil, doc: "the server side parameter to collect all input under" attr :rest, :global, include: ~w(autocomplete name rel action enctype method novalidate target multipart), doc: "the arbitrary HTML attributes to apply to the form tag" slot :actions, doc: "the slot for form actions, such as a submit button" attr :config_options, :list, doc: "The list of optoins for this configuration" def config_form(assigns) do ~H""" <.simple_form :let={f} for={@for} as={@as} {@rest}> <.config_form_part :for={option <- @config_options} form={@for} option={option} />
<%= render_slot(action, f) %>
""" end @doc """ Translates an error message using gettext. """ def translate_error({msg, opts}) do # When using gettext, we typically pass the strings we want # to translate as a static argument: # # # Translate the number of files with plural rules # dngettext("errors", "1 file", "%{count} files", count) # # However the error messages in our forms and APIs are generated # dynamically, so we need to translate them by calling Gettext # with our gettext backend as first argument. Translations are # available in the errors.po file (as we use the "errors" domain). if count = opts[:count] do Gettext.dngettext(ReflectOS.ConsoleWeb.Gettext, "errors", msg, msg, count, opts) else Gettext.dgettext(ReflectOS.ConsoleWeb.Gettext, "errors", msg, opts) end end @doc """ Translates the errors for a field from a keyword list of errors. """ def translate_errors(errors, field) when is_list(errors) do for {^field, {msg, opts}} <- errors, do: translate_error({msg, opts}) end end