Necessity is the mother of invention.
フォームのスタイリングを抽象化する方法はいくつかありますが、個人的にフォームフィールドはヘルパー関数を自作するのが一番シンプルな気がしており、実際にやってみた結果も気に入ってます。
やりたいこと
- 各フォームフィールドを一行で書けるようにしたい。
- 共通のCSSクラスは自動的に適用したい。
例えば、mix phx.gen.auth
コマンドで生成される以下のようなフォームフィールドがあります。
<%=labelf,:email%><%=email_inputf,:email,required:true%><%=error_tagf,:email%>
それをこのように共通のCSSクラスも含め一行で簡潔に記述したいのです。
<%=bulma_inputf,:email%>
動作環境
elixir 1.13.4-otp-24erlang 24.3.4
❯ mix phx.new --versionPhoenix installer v1.6.8
アイデア
カスタムビューヘルパーを書くために必要な知識とアイデアはElixir言語の作者José Valimさんがこの記事(Dynamic forms with Phoenix)の中で丁寧に解説してくれています。
ですのでそれを読めば大体わかります。
また、Phoenix自体がPhoenix.HTML.Form
等ビルトインのヘルパー関数を多数持っているのでそれらを大いに活用することも大事だと思います。
やってみる
lib/my_app_web/views/input_helpers.ex
ファイルを作成。
defmoduleMyAppWeb.InputHelpersdousePhoenix.HTML# TODO: define my custom view helper functionsend
lib/my_app_web.ex
のview_helpers
関数でそれを忘れずにインポートしておく。
defp view_helpers do quote do # Use all HTML functionality (forms, tags, etc) use Phoenix.HTML # Import LiveView and .heex helpers (live_render, live_patch, <.form>, etc) import Phoenix.LiveView.Helpers import MyAppWeb.LiveHelpers # Import basic rendering functionality (render, render_layout, etc) import Phoenix.View+ import MyAppWeb.InputHelpers import MyAppWeb.ErrorHelpers import MyAppWeb.Gettext alias MyAppWeb.Router.Helpers, as: Routes end end
あとはMyAppWeb.InputHelpers
に好きなようにヘルパー関数を定義するだけ。
先日たまたまBulma CSSフレームワークを使って遊んでいた時に、それ用に一つ作ってみました。
一つのサンプルコードになるかもしれません。
defmoduleMyAppWeb.InputHelpersdousePhoenix.HTMLdefbulma_input(form,field,opts\\[])dolabel_opts=Keyword.take(opts,~w[required label]a)input_opts=Keyword.drop(opts,~w[required label]a)content_tag:div,class:"field"do[build_label(form,field,label_opts),build_input(form,field,input_opts),MyAppWeb.ErrorHelpers.error_tag(form,field)]endenddefbulma_checkbox(form,field,opts\\[])docontent_tag:label,class:"checkbox"do[checkbox(form,field,opts),' ',opts[:label]||field|>to_string()|>Phoenix.Naming.humanize()]endenddefpbuild_label(form,field,opts)dorequired=opts[:required]||Keyword.get(input_validations(form,field),:required)label_text=(opts[:label]||humanize(field))<>ifrequired,do:" *",else:""Phoenix.HTML.Form.label(form,field,label_text,class:"label")enddefpbuild_input(form,field,opts)doinput_fun_name=opts[:using]||Phoenix.HTML.Form.input_type(form,field)permitted_attributes=Keyword.drop(opts,[:using])input_class=caseinput_fun_namedo:textarea->"textarea "_->"input "end<>form_state_class(form,field)input_opts=[{:class,input_class}|permitted_attributes]|>Enum.reject(&is_nil(elem(&1,1)))content_tag:div,class:"control"doapply(Phoenix.HTML.Form,input_fun_name,[form,field,input_opts])endenddefpform_state_class(form,field)doconddo# Some forms may not use a Map as a source. E.g., :user!is_map(form.source)->""# Ignore Conn-based form.Map.get(form.source,:__struct__)==Plug.Conn->""# The form is not yet submitted.!Map.get(form.source,:action)->""# This field has an error.form.errors[field]->"is-danger"true->"is-success"endendend
lib/my_app_web/views/error_helpers.ex
にPhoenixが生成したerror_tag
があるので、そこのCSSクラスも必要に応じて変更します。
def error_tag(form, field) do Enum.map(Keyword.get_values(form.errors, field), fn error -> content_tag(:span, translate_error(error),- class: "invalid-feedback",+ class: "invalid-feedback help is-danger", phx_feedback_for: input_name(form, field) ) end)
Bulmaでスタイリングされたフォームフィールドを生成するbulma_input
関数ができました。
bulma_inputf,:email
使用するPhoenix.HTML.Form
の関数を切り替えるオプションも受け付けます。
# Phoenix.HTML.Form.text_input/3の代わりにPhoenix.HTML.Form.textarea/3を使用したい場合bulma_inputf,:email,using::textarea
必要に応じてHTML 属性を追加できるようにしました。
bulma_inputf,:email,placeholder:"E-mail",autocomplete:"off"
IExで検証
テンプレート上のフォームで実際に実装した方が早いですが、興味があったのでIEx上でランできる方法を探しました。
Phoenixのソースコードのテストの中にヒントがあったので、その知識で適当にフォームを生成します。
iexaliasMyAppWeb.AccountsaliasMyAppWeb.Accounts.Userchangeset=Accounts.change_user_registration(%User{})form=Phoenix.HTML.Form.form_for(changeset,"/registration",[])bulma_input(form,:email,placeholder:"E-mail",autocomplete:"off")|>Phoenix.HTML.Safe.to_iodata()|>to_string()|>IO.puts()
<divclass="field"><labelclass="label"for="user_email">Email *</label><divclass="control"><inputautocomplete="off"class="input "id="user_email"name="user[email]"placeholder="E-mail"type="email"></div></div>
🎉
Elixirコミュニティに初めて接する方は下記がオススメです
Elixirコミュニティ の歩き方 -国内オンライン編-
https://speakerdeck.com/elijo/elixirkomiyunitei-falsebu-kifang-guo-nei-onrainbian
日程からイベントを探すならElixirイベントカレンダー📆
** Elixirイベントカレンダー **
https://elixir-jp-calendar.fly.dev/
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse