Skip to content

Commit

Permalink
Don't depend on Phoenix.HTML.Form.input
Browse files Browse the repository at this point in the history
  • Loading branch information
angelikatyborska committed Jul 17, 2024
1 parent 604f631 commit 6b8c535
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 51 deletions.
2 changes: 1 addition & 1 deletion lib/bitstyles_phoenix.ex
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ defmodule BitstylesPhoenix do
### Translating error messages
Generated phoenix apps usally come with a helper for [translating error messages](https://github.com/phoenixframework/phoenix/blob/496123b66c03c9764be623d2c32b4af611837eb0/installer/templates/phx_web/views/error_helpers.ex#L23-L46)
Generated phoenix apps usually come with a helper for [translating error messages](https://github.com/phoenixframework/phoenix/blob/496123b66c03c9764be623d2c32b4af611837eb0/installer/templates/phx_web/views/error_helpers.ex#L23-L46)
using `gettext`. This helper can be used to translate error messages rendered with `bitstyles_phoenix`.
In order to do so you can configure the generated helper or write your own with the MFA configuration.
Expand Down
197 changes: 148 additions & 49 deletions lib/bitstyles_phoenix/component/form.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,60 +7,48 @@ defmodule BitstylesPhoenix.Component.Form do
@moduledoc """
Components for rendering input elements.
Use `ui_input`, `ui_select`, `ui_textarea` to render the respective inputs together with a label and errors.
Alternatively, this module also provides `input` and `label` functions that can be used to render only an input or only a label.
## Common attributes
All helpers in this module accept the following attributes.
All `ui_*` components in this module accept the following attributes.
- `form` *(required)* - The form to render the input form.
- `field` *(required)* - The name of the field for the input.
- `label` - The text to be used as label. Defaults to `Phoenix.HTML.Form.humanize/1`.
- `label_opts` - The options passed to the label element generated with `Phoenix.HTML.Form.label/4`.
See `Phoenix.HTML.Form.form_for/4` or LiveView `form` component for details on how to render a form.
For details on how to render a form, see:
- `simple_form` core component in a freshly-generated Phoenix app, or
- `Phoenix.Component.form/1`, or
- `Phoenix.HTML.Form.form_for/4` if using phoenix_html v3 or phoenix_html_helpers
"""

@input_mapping %{
color: :color_input,
checkbox: :checkbox,
date: :date_input,
datetime_local: :datetime_local_input,
email: :email_input,
file: :file_input,
number: :number_input,
password: :password_input,
range: :range_input,
search: :search_input,
telephone: :telephone_input,
text: :text_input,
time: :time_input,
url: :url_input
}

@wrapper_assigns_keys [:field, :form, :label, :label_opts, :hidden_label]

@type_doc_table @input_mapping
|> Enum.map(fn {type, helper} ->
"| `:#{type}` | `Phoenix.HTML.Form.#{helper}/3` |\n"
end)

@doc """
Renders various types of `<input>` element, with the associated `<label>`s, and any errors for that field.
## Attributes
- `type` - The type of the input (see table below for available types). Defaults to `type="text"`.
- `type` - The type of the input. Defaults to `type="text"`.
- `hidden_label` - Only show the label for screen readers if set to `true`.
- All options from above (see top level module doc).
- All other attributes will be passed in as input options to the underlying input
helpers from `Phoenix.HTML.Form` (see table below for used helpers).
- All other attributes will be passed onto the input element as attributes.
Defaults to `maxlength="255"` for `email`, `text` and `password` type.
Set maxlength to `false` to prevent setting maxlength.
For reference which input helper is used check out the following mapping:
## Types
This function accepts all HTML input types, considering that:
| type | Helper |
| :--: | ------ |
#{@type_doc_table}
* `type="checkbox"` is used exclusively to render boolean values
* For live file uploads, see `Phoenix.Component.live_file_input/1`
See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input
for more information. Unsupported types, such as hidden and radio,
are best written directly in your templates.
See the [bitstyles form docs](https://bitcrowd.github.io/bitstyles/?path=/docs/base-forms--fieldset) for examples of inputs, selects, textareas, labels etc. in use.
See the [bitstyles form docs](https://bitcrowd.github.io/bitstyles/?path=/docs/ui-data-forms--login-form) for examples of form layouts.
Expand All @@ -79,7 +67,7 @@ defmodule BitstylesPhoenix.Component.Form do
<label for="user_name">
Name
</label>
<input id="user_name" maxlength="255" name="user[name]" type="text"/>
<input id="user_name" name="user[name]" type="text" maxlength="255"/>
"""
'''
)
Expand All @@ -97,7 +85,7 @@ defmodule BitstylesPhoenix.Component.Form do
<label for="the_name">
Name
</label>
<input id="the_name" maxlength="255" name="user[name]" type="text"/>
<input id="the_name" name="user[name]" type="text" maxlength="255"/>
"""
'''
)
Expand All @@ -118,7 +106,7 @@ defmodule BitstylesPhoenix.Component.Form do
*
</span>
</label>
<input id="user_name" maxlength="255" name="user[name]" required="required" type="text"/>
<input id="user_name" name="user[name]" type="text" maxlength="255" required="required"/>
"""
'''
)
Expand All @@ -136,7 +124,7 @@ defmodule BitstylesPhoenix.Component.Form do
<label for="user_name">
Name
</label>
<input id="user_name" maxlength="255" name="user[name]" type="text"/>
<input id="user_name" name="user[name]" type="text" maxlength="255"/>
<span class="u-fg-warning" phx-feedback-for="user[name]">
is too short
</span>
Expand All @@ -157,7 +145,7 @@ defmodule BitstylesPhoenix.Component.Form do
<label for="user_email">
Email
</label>
<input id="user_email" maxlength="255" name="user[email]" type="text"/>
<input id="user_email" name="user[email]" type="text" maxlength="255"/>
<ul class=\"u-padding-xl-left\">
<li>
<span class=\"u-fg-warning\" phx-feedback-for=\"user[email]\">
Expand Down Expand Up @@ -187,7 +175,7 @@ defmodule BitstylesPhoenix.Component.Form do
<label class="u-sr-only" for="user_name">
Name
</label>
<input id="user_name" maxlength="255" name="user[name]" type="text"/>
<input id="user_name" name="user[name]" type="text" maxlength="255"/>
"""
'''
)
Expand Down Expand Up @@ -237,7 +225,7 @@ defmodule BitstylesPhoenix.Component.Form do
*
</span>
</label>
<input autocomplete="one-time-code" id="user_totp" inputmode="numeric" maxlength="6" name="user[totp]" pattern="[0-9]*" placeholder="6-digit code" required="required" type="text" value=""/>
<input id="user_totp" name="user[totp]" type="text" autocomplete="one-time-code" inputmode="numeric" maxlength="6" pattern="[0-9]*" placeholder="6-digit code" required="required" value=""/>
"""
'''
)
Expand All @@ -255,7 +243,7 @@ defmodule BitstylesPhoenix.Component.Form do
<label for="user_email">
Email
</label>
<input id="user_email" maxlength="255" name="user[email]" type="email"/>
<input id="user_email" name="user[email]" type="email" maxlength="255"/>
"""
'''
)
Expand All @@ -278,7 +266,7 @@ defmodule BitstylesPhoenix.Component.Form do
<label for="user_email_or_name">
Email or name
</label>
<input autofocus="autofocus" id="user_email_or_name" name="user[email_or_name]" placeholder="Search by email or name" type="search"/>
<input id="user_email_or_name" name="user[email_or_name]" type="search" autofocus="autofocus" placeholder="Search by email or name"/>
"""
'''
)
Expand All @@ -299,7 +287,7 @@ defmodule BitstylesPhoenix.Component.Form do
<label for="user_file">
File
</label>
<input accept="application/pdf" id="user_file" name="user[file]" type="file"/>
<input id="user_file" name="user[file]" type="file" accept="application/pdf"/>
</form>
"""
'''
Expand Down Expand Up @@ -336,7 +324,7 @@ defmodule BitstylesPhoenix.Component.Form do
"""
<label for="user_accept">
<input name="user[accept]" type="hidden" value="false"/>
<input id="user_accept" name="user[accept]" required="required" type="checkbox" value="true"/>
<input id="user_accept" name="user[accept]" type="checkbox" value="true" required="required"/>
Accept
<span aria-hidden="true" class="u-fg-warning u-margin-xxs-left">
*
Expand Down Expand Up @@ -365,9 +353,6 @@ defmodule BitstylesPhoenix.Component.Form do
'''
)

# TODO: if exported Phoenix.HTML.FormField ... (v4)
# then id can be read from field.id

def ui_input(assigns) do
extra = assigns_to_attributes(assigns, @wrapper_assigns_keys ++ [:type])

Expand All @@ -391,11 +376,15 @@ defmodule BitstylesPhoenix.Component.Form do
end

defp render_input(type, form, field, opts) do
apply(PhxForm, input_type(type), [form, field, default_validations(opts, type)])
end
assigns = %{
type: type,
field: form[field],
extra: default_validations(opts, type)
}

defp input_type(type) do
Map.get(@input_mapping, type, :text_input)
~H"""
<.input field={@field} type={@type} {@extra} />
"""
end

defp default_validations(extra, type) when type in [:email, :text, :password] do
Expand Down Expand Up @@ -822,6 +811,116 @@ defmodule BitstylesPhoenix.Component.Form do
"""
end

@doc """
Renders an input, select, or textarea.
Direct usage is discouraged in favor of `ui_input` that comes with a label and errors.
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 `<select>` tag
* `type="checkbox"` is used exclusively to render boolean values
* For live file uploads, see `Phoenix.Component.live_file_input/1`
See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input
for more information. Unsupported types, such as hidden and radio,
are best written directly in your templates.
## Attributes
- `field` - a `Phoenix.HTML.FormField` struct retrieved from the form, for example: @form[:email]
- `type` - string or atom, required
- `id` - string, required if `field` not passed
- `name` - string, required if `field` not passed
- `value` - any, required if `field` not passed
- any other attributes, they will be passed to the input element
"""

def input(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do
assigns
|> assign(field: nil)
|> assign_new(:id, fn -> field.id end)
|> assign_new(:name, fn -> if assigns[:multiple], do: field.name <> "[]", else: field.name end)
|> assign_new(:value, fn -> field.value end)
|> input()
end

def input(%{type: type} = assigns) when is_atom(type) do
input(%{assigns | type: Atom.to_string(type)})
end

def input(%{type: "checkbox"} = assigns) do
assigns =
assign_new(assigns, :checked, fn ->
Phoenix.HTML.Form.normalize_value("checkbox", assigns[:value])

Check warning on line 859 in lib/bitstyles_phoenix/component/form.ex

View workflow job for this annotation

GitHub Actions / unit

Nested modules could be aliased at the top of the invoking module.
end)

extra = assigns_to_attributes(assigns, [:id, :name, :checked, :value, :type])
assigns = assign(assigns, extra: extra)

~H"""
<input name={@name} type="hidden" value="false" disabled={@extra[:disabled]} />
<input
id={@id}
name={@name}
type="checkbox"
value="true"
checked={@checked}
{@extra}
/>
"""
end

def input(%{type: "select"} = assigns) do
extra = assigns_to_attributes(assigns, [:id, :name, :multiple, :prompt, :options, :value])
assigns = assign(assigns, extra: extra)

~H"""
<select
id={@id}
name={@name}
multiple={@multiple}
{@extra}
>
<option :if={@prompt} value=""><%= @prompt %></option>
<%= Phoenix.HTML.Form.options_for_select(@options, @value) %>
</select>
"""
end

def input(%{type: "textarea"} = assigns) do
extra = assigns_to_attributes(assigns, [:id, :name])
assigns = assign(assigns, extra: extra)

~H"""
<textarea
id={@id}
name={@name}
{@extra}
><%= Phoenix.HTML.Form.normalize_value("textarea", @value) %></textarea>
"""
end

# All other inputs text, datetime-local, url, password, etc. are handled here...
def input(assigns) do
extra = assigns_to_attributes(assigns, [:id, :name, :type, :value])
assigns = assign(assigns, extra: extra)

~H"""
<input
id={@id}
name={@name}
type={@type}
{@extra}
value={Phoenix.HTML.Form.normalize_value(@type, @value)}
/>
"""
end

@doc """
Renders a label. Direct usage is discouraged in favor of `ui_input` that comes with a label.
Expand Down
2 changes: 1 addition & 1 deletion lib/bitstyles_phoenix/component/sidebar.ex
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ defmodule BitstylesPhoenix.Component.Sidebar do
since typically they host the logo/header/brand name, while the main navigation is hosted in the
`sidebar_content` slot and shown on all screens. The reason for this separation is that the sidebar
in the small screen is meant to start out hidden and only be shown when needed and therefore needs
control buttons to close it again (ususally at the top of the screen).
control buttons to close it again (usually at the top of the screen).
If you have different requirements you can simply omit the `sidebar_content` block and render the
shared content twice yourself.
Expand Down

0 comments on commit 6b8c535

Please sign in to comment.