原文html
今天咱們將要學習如何在Phoenix中使用咱們的schema信息來動態地構建帶有合法性檢查,報錯等功能的輸入框。咱們的目標是在模板中支持下列API:web
<%= input f, :name %> <%= input f, :address %> <%= input f, :date_of_birth %> <%= input f, :number_of_children %> <%= input f, :notifications_enabled %>
生成的每一個表單會有合適的樣式和類別(在本例中咱們會使用Bootstrap),包括合適的HTML屬性,例如對於必填的輸入框有required
屬性和合法檢查,並顯示全部的輸入錯誤。數據庫
咱們旨在不添加第三方依賴,就能在咱們本身的應用中使用不多的代碼實現這些。這樣當咱們的應用變化時,就能隨意地修改和擴展它們。瀏覽器
在構建咱們的input
helper以前,讓咱們生成一個新的resource,做爲咱們試驗的對象(若是你手頭上沒有Phoenix應用,先運行mix phoenix.new your_app
):app
mix phoenix.gen.html User users name address date_of_birth:datetime number_of_children:integer notifications_enabled:boolean
在完成了例行操做以後,打開「web/templates/user/form.html.eex」文件,咱們會看到以下的輸入列表:框架
<div class="form-group"> <%= label f, :address, class: "control-label" %> <%= text_input f, :address, class: "form-control" %> <%= error_tag f, :address %> </div>
咱們的目標是用一行<%= input f, field %>
,取代上面的每一個group。函數
仍是在「form.html.eex"模板裏,能夠看到一個隊Ecto changesets的操做:學習
<%= form_for @changeset, @action, fn f -> %>
所以,若是要在表單中自動展現合法檢查,第一步就是在changeset裏聲明這些合法檢查。打開「web/models/user.ex」,在changeset
函數的末尾添加一些新的合法檢查:ui
|> validate_length(:address, min: 3) |> validate_number(:number_of_children, greater_than_or_equal_to: 0)
在作進一步修改以前,先讓咱們運行mix phoenix.server
,並訪問ttp://localhost:4000/users/new
查看一下默認的表單。this
input
函數咱們已經設置好了基礎代碼,如今讓咱們來實現input
函數。
YourApp.InputHelpers
模塊咱們的input
函數會被定義在一個名爲YourApp.InputHelpers
的模塊中(這裏的YourApp
是你的應用名),咱們把這個模塊放在新文件「web/views/input_helpers.ex」裏。咱們這樣定義:
defmodule YourApp.InputHelpers do use Phoenix.HTML def input(form, field) do "Not yet implemented" end end
注意,咱們在模塊的頂部use了Phoenix.HTML
來從Phoenix.HTML項目中導入函數。咱們將在以後依賴這些函數來構建樣式。
若是想讓input
函數在全部views均可用,咱們須要在「web/web.ex」文件中的「def view」裏的imports列表中添加它:
import YourApp.Router.Helpers import YourApp.ErrorHelpers import YourApp.InputHelpers # Let's add this one import YourApp.Gettext
定義並導入了模塊以後,讓咱們修改「form.html.eex」函數來使用新的input
函數。先刪除5個「form-group」 div:
<div class="form-group"> <%= label f, :address, class: "control-label" %> <%= text_input f, :address, class: "form-control" %> <%= error_tag f, :address %> </div>
添加5個輸入調用:
<%= input f, :name %> <%= input f, :address %> <%= input f, :date_of_birth %> <%= input f, :number_of_children %> <%= input f, :notifications_enabled %>
Phoenix會自動刷新頁面,而後咱們會看到「Not yet implemented」重複5次。
咱們首先要實現的是渲染像以前同樣的表單。咱們會用到Phoenix.HTML.From.input_type 函數,它會接受一個表名和內容名並返回咱們應該使用的輸入類型。例如,對於:name
,它會返回:text_input
。對於:date_of_birth
,它會返回:datetime_select
。咱們可讓返回的原子在Phoenix.HTML.Form
模塊中被調用,以此構建咱們的輸入:
def input(form, field) do type = Phoenix.HTML.Form.input_type(form, field) apply(Phoenix.HTML.Form, type, [form, field]) end
下一步,讓咱們來顯示標籤和錯誤提示,它們都包裝在一個div裏:
def input(form, field) do type = Phoenix.HTML.Form.input_type(form, field) content_tag :div do label = label(form, field, humanize(field)) input = apply(Phoenix.HTML.Form, type, [form, field]) error = YourApp.ErrorHelpers.error_tag(form, field) || "" [label, input, error] end end
咱們使用content_tag
來構建div
包裝,還使用了Phoenix爲每一個新應用生成的用於構建錯誤提示樣式的YourApp.ErrorHelpers.error_tag
函數。
最後,讓咱們添加一些HTML類,來映射Bootstrap樣式:
def input(form, field) do type = Phoenix.HTML.Form.input_type(form, field) wrapper_opts = [class: "form-group"] label_opts = [class: "control-label"] input_opts = [class: "form-control"] content_tag :div, wrapper_opts do label = label(form, field, humanize(field), label_opts) input = apply(Phoenix.HTML.Form, type, [form, field, input_opts]) error = YourApp.ErrorHelpers.error_tag(form, field) [label, input, error || ""] end end
很好!咱們已經生成了與原來相同的樣式。只用了14行代碼。但咱們尚未完成,讓咱們更進一步地自定義咱們的input函數。
如今咱們能夠根據應用的需求來對input作進一步的擴展。
爲加強用戶體驗,若是格式出現錯誤,自動爲每一個輸入框套用不一樣的樣式。讓咱們將wrapper_opts
重寫爲:
wrapper_opts = [class: "form-group #{state_class(form, field)}"]
並定義私有函數state_class
:
defp state_class(form, field) do cond do # The form was not yet submitted !form.source.action -> "" form.errors[field] -> "has-error" true -> "has-success" end end
如今提交錯誤的表單,你會看到每一個標籤和輸入框都呈綠色(成功)或是紅色(出錯)。
咱們可使用Phoenix.HTML.Form.input_validations
函數來從changesets裏獲取合法性,並將它們做爲輸入屬性合成到咱們的input_opts
中。將下面兩行添加到input_opts
變量的定義以後(content_tag
調用以前):
validations = Phoenix.HTML.Form.input_validations(form, field) input_opts = Keyword.merge(validations, input_opts)
完成以上修改以後,如咱們試圖在沒有填寫「Address」的狀況下提交表單,瀏覽器不會容許表單被提交,由於咱們設定了長度至少3個字符。不是每一個人都喜歡瀏覽器的合法性檢查,所以你能夠直接地控制是否使用它們。
順便提一下,Phoenix.HTML.Form.input_type
和Phoenix.HTML.Form.input_validations
是做爲Phoenix.HTML.FormData
協議的一部分被定義的。這意味着若是你決定在Ecto changesets中使用一些別的東西來對輸入的數據進行調用和檢查,全部咱們以前構建的功能依然能夠運行。若是想要進一步學習它們,我建議去看看Phoenix.Ecto項目並經過簡單地實現一些Phoenix中的協議來學習Ecto和Phoenix是如何集成在一塊兒的。
最後,咱們要爲input
函數添加對單個輸入進行設置的功能。例如,對於給定的輸入,咱們可能不想它的類型被input_type
影響。咱們能夠添加一個選項來解決:
def input(form, field, opts \\ []) do type = opts[:using] || Phoenix.HTML.Form.input_type(form, field)
這意味着咱們如今能夠控制使用Phoenix.HTML.Form
中的哪一個函數來構建咱們的輸入:
<%= input f, :new_password, using: :password_input %>
咱們也沒必要受限於Phoenix.HTML.Form
所支持的輸入樣式。例如,若是你想要用自定義的日期選擇器來替換:datetime_select
輸入,只須要將其包裝到一個函數中,而後模式匹配你想要自定義的輸入。
讓咱們來看看如今的input
函數是什麼樣子,包括對自定義輸入的支持(省略了輸入合法檢查):
defmodule YourApp.InputHelpers do use Phoenix.HTML def input(form, field, opts \\ []) do type = opts[:using] || Phoenix.HTML.Form.input_type(form, field) wrapper_opts = [class: "form-group #{state_class(form, field)}"] label_opts = [class: "control-label"] input_opts = [class: "form-control"] content_tag :div, wrapper_opts do label = label(form, field, humanize(field), label_opts) input = input(type, form, field, input_opts) error = YourApp.ErrorHelpers.error_tag(form, field) [label, input, error || ""] end end defp state_class(form, field) do cond do # The form was not yet submitted !form.source.action -> "" form.errors[field] -> "has-error" true -> "has-success" end end # Implement clauses below for custom inputs. # defp input(:datepicker, form, field, input_opts) do # raise "not yet implemented" # end defp input(type, form, field, input_opts) do apply(Phoenix.HTML.Form, type, [form, field, input_opts]) end end
當你實現了你本身的:datepicker
以後,只須要在你的模板中加入:
<%= input f, :date_of_birth, using: :datepicker %>
當你的應用有了這些代碼以後,你就能控制輸入的類型和自定義樣式。幸運的是Phoenix搭載了作夠多的功能,幫助咱們快速開始,而不須要受限於咱們修改表現層的能力。
這篇文章展現瞭如何使用咱們已經在schemas中肯定了的信息,並藉助Phoenix.HTML
,來動態地構建表格。儘管這個例子應用於直接映射到數據庫的User schema,Ecto 2.0 容許咱們使用schemas來映射到任何數據源,因此input
函數能夠不加修改地應用於檢查搜索表格,登陸頁面,等等。
咱們已經開發了例如Simple Form等項目來解決Rails項目中的這類的問題,在Phoenix中,咱們可使用框架自帶的抽象來實現,使得咱們能夠在徹底控制生成的樣式的同時實現大部分功能。