[Rails]使用FormBuilder自定義你的表單

in #cn7 years ago (edited)

什麼是FormBuilder

來自FormBuilder的物件,讓你可以建立跟特定物件相關連的欄位。

這個翻譯有點拗口,讓我們看看實際的例子。

當我們要編輯一個Model的物件時,我們的表單基本上可以寫成這樣。

<%= form_tag("/pages") do %>
  <%= label_tag(:user, "User") %>
  <%= text_field(:pages, :user) %>
  <%= submit_tag("submit") %>
<% end %>

text_field這個helper的第一個參數代表的是我們想要編輯的object,第二個參數則是對應的屬性。這樣的寫法有個潛在的壞處是,如果我們有好幾個欄位要處理,那就要不斷的傳入要編輯的物件名稱。

所以當我們的表單是要處理一個Model的物件時,我們通常會使用form_for這個helper來處理。以下是程式碼的範例

<%= form_for @page do |f| %>
  <%= f.label :user, "User" %>
  <%= f.text_field :user %>
  <%= f.submit %>
<% end %>

這邊的f是一個表單構造器(Form Builder)物件(f 變數)。當你傳入一個物件給form_for後(這裡是@page),當我們在block內部使用如<%= f.text_field :user %>的程式碼時,就會得到如<%= text_field(:pages, :user) %>的效果。

註:如果在表單中想要得到關聯的object,可以透過f.object得到。

客製化FormBuilder

上面簡單介紹了form_builder的常見用法,但事實上FormBuilder的用處不止於此

假設我們希望客製化我的label,當欄位資料無法通過model驗證時,讓label顯示錯誤訊息並改變顏色。這樣的效果我們可以透過form_builder幫我們客製化,而不用在view裡面使用大量的邏輯。

我們這邊需要驗證的是user這個欄位不能是空白。

class Page < ApplicationRecord
  validates :user, presence: true
end

首先在app/helpers下建立一個my_form_builder.rb檔,並繼承自ActionView::Helpers::FormBuilder class。

接著override原先的label方法,加入客製化的text訊息。(label的原始碼可以參考

class MyFormBuilder < ActionView::Helpers::FormBuilder
  def label(method, text = nil, optinos = {}, &block)
    errors = object.errors[method.to_sym]
    if errors 
      text += " <span class=\"error\" style=\"color:red\">#{errors.first}</span>"
    end
    super(method, text.html_safe, options, &block)
  end
end

然後我們要在form_for中引入這個客製化的form builder。這邊我們需要使用builder:來引入MyFormBuilder

<%= form_for @page, builder: MyFormBuilder do |f| %>
  <%= f.label :user, "User" %>
  <%= f.text_field :user %>
  <%= f.submit "送出" %>
<% end %>

這樣當我們user欄位是空白時,我們就可以得到以下的畫面。

這樣的話我們就能夠既使用我們客製化的又不影響本來form_for的效果。

客製化form_for

更進一步,你還可以客製化你的form_for方法,

到到app/helers/application_helper.rb中,新增一個my_form_for的方法。在內部引用form_for,並在options中merge builder。(form_for原始碼

module ApplicationHelper
  def my_form_for(record, options = {}, &proc)
    form_for(record, options.merge!({builder: MyFormBuilder}), &proc)
  end
end

接著將view中的form_for改寫成my_form_for

<%= my_form_for @page do |f| %>
  <%= f.label :user, "User" %>
  <%= f.text_field :user %>
  <%= f.submit "送出" %>
<% end %>

可以達成一樣的效果。

參考資料

Action View Form Helpers
ActionView::Helpers::FormBuilder