[elixir! #0002] [譯] 在Phoenix中實現動態表單 by José Valim

原文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屬性和合法檢查,並顯示全部的輸入錯誤。數據庫

咱們旨在不添加第三方依賴,就能在咱們本身的應用中使用不多的代碼實現這些。這樣當咱們的應用變化時,就能隨意地修改和擴展它們。瀏覽器

設置

在構建咱們的inputhelper以前,讓咱們生成一個新的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。函數

添加changeset合法檢查

仍是在「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函數。

添加Bootstrap類

最後,讓咱們添加一些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_typePhoenix.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中,咱們可使用框架自帶的抽象來實現,使得咱們能夠在徹底控制生成的樣式的同時實現大部分功能。

相關文章
相關標籤/搜索