PJCHENder 未整理筆記

[Rails] Action View Form Helpers

2017-10-12

[Rails] Action View Form Helpers

@(Ruby on Rails)[helper]

keywords: form_for, fields_for

FormHelper @ Ruby on Rails API
Action View Form Helpers @ RailsGuides
FormTagHelper @ Ruby on Rails API
FormBuilder @ Ruby on Rails API

速查

form_for

keywords: form_for(record, options = {}, &block)

form_for @ Rails API

1
2
3
4
5
6
7
8
9
10
11
12
<%= form_for :post do |f| %>          <!-- 等同於: form_for 'post' -->

<!-- 直接在 form_for 帶入 model object -->
<%= form_for @post do |f| %> <!-- 等同於下面的寫法 -->
<%= form_for @post, as: :post, url: post_path(@post), method: :patch, html: { class: "edit_post", id: "edit_post_45" } do |f| %>

<!-- -->
<%= form_for(Post.new) do |f| %> <!-- 等同於下面的寫法 -->
<%= form_for @post, as: :post, url: posts_path, html: { class: "new_post", id: "new_post" } do |f| %>

<!-- 透過 format 可以指定回傳的資料型態 -->
<%= form_for(@post, format: :json) do |f| %>
  • :url: 如果沒代的話預設會是傳到當前的 URL
  • :html: 使用其他和 html 相關的 attributes
  • :method: get, post, patch, put, delete
  • :remote: 設為 true 時,為允許 Unobstrusive JavaScript drivers 控制表單發送的行爲,預設這個行為會是 AJAX Submit。
  • :format:設定要求回傳的檔案型態。
  • :as: 預設所有 <input name=""> 中的 name 都會是該 model 的名稱,例如 @post 的當中的 input (form.text_field :title)欄位,會編譯成 <input name=post[title]>,因此在 controller 就可以透過 params[:post] 去取得各欄位的內容。若想要改變 name 的值就可以使用 :as
  • :authenticity_token: 只有在想要客製化 authenticity_token 或者不使用它時(false)才會加上這個選項
  • :enforce_utf8
  • :namespace: 會在 form 當中的 id 欄位都加上指定的 namespace 作為前綴

使用範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<%= form_for @post do |form| %>
<div class="field">
<%= form.label :title %>
<%= form.text_field :title, id: :post_title %>
</div>

<div class="field">
<%= form.label :description %>
<%= form.text_area :description, id: :post_description %>
</div>

<div class="actions">
<%= form.submit %>
</div>
<% end %>

<!--
<form class="edit_post" id="edit_post_1" action="/posts/1" accept-charset="UTF-8" method="post">
-->

處理 Model 中不存在的屬性

使用 form_for 時,其中的欄位必須是 Model 有的屬性,那如果資料庫沒有這個欄位時,你需要在 Model 程式中加上存取方法,例如:

1
2
3
4
5
6
7
8
9
10
11
class Event < ActiveRecord::Base

#...
def custom_field
# 根據其他屬性的值或條件,來決定這個欄位的值
end

def custom_field=(value)
# 根據value,來調整其他屬性的值
end
end

這樣就可以在 form_for 裡使用 custom_field 了:

1
2
3
4
<%= form_for @event do |f| %>
<%= f.text_field :custom_field %>
<%= f.submit %>
<% end %>

記得把 :custom_field 也加到 Strong Parameters 清單裡,這樣按下送出後,就可以跟著 @event 本來的欄位一起處理了。

form_with

keywords: form_with(model: nil, scope: nil, url: nil, format: nil, **options)

form_with @ Rails API

  • :model: 使用 model 後會自動會表單加上 urlscope
  • :scope: 用來為表單中的 <input name=""> 欄位增加 namespace 的 prefix
  • :method
  • :format
  • :authenticity_token
  • :local: 如果不希望使用 remote: true ,可以加上 local: true

預設的情況下,如果 Unobstrusive JavaScript driver 有使用的話(rails-ujs),那麼使用 form_with 會直接添加 <form data-remote="true"> ,並透過 XMLHTTPRequest 來送出請求。

1
2
3
4
5
<%= form_with model: @post do |form| %>        <!--  等同於下面那行  -->
<%= form_with scope: :post, url: post_path(@post), method: :patch do |form| %>

<%= form_with model: Post.new do |form| %> <!-- 等同於下面那行 -->
<%= form_with scope: :post, url: posts_path do |form| %>

select

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--  select -->
<%= form.select :game, options_for_select(game_options_for_select), { prompt: '--請選擇--' }, { class: 'form-control', required: true } %>

<%= select_tag :question_type, options_for_select([["訂單問題"], ["退貨問題"], ["購買問題"], ["技術問題"]]), { prompt: '--請選擇--', class: 'form-control', required: true } %>

<!--
<select name="question_type" id="question_type" required="required">
<option value="">請選擇您的問題分類</option>
<option value="訂單問題">訂單問題</option>
<option value="退貨問題">退貨問題</option>
<option value="購買問題">購買問題</option>
<option value="技術問題">技術問題</option>
</select>
-->

select_tag @ RailsGuides

1
2
3
4
5
6
# options 中的陣列:[顯示的文字, 實際的 value]

def enum_options(enum_field, selected = nil)
options = enum_field.collect { |key, _| [key.humanize, key] }
options_for_select(options, selected)
end

讓第一個選項(prompt)不能被選取

直接在 select tag 上加上 required,Bootstrap 即會判斷,是否已有被選取

1
2
3
4
5
6
7
8
9
10
def gender_options(selected = nil, prompt: nil)
options = [['Male', 'male'], ['Female', 'female'], ['Other', 'other']]

# 如果有寫 prompt 且沒有選擇 selected 的項目則添加進去
options.unshift([prompt, nil, hidden: true, selected: true]) if prompt.present? && selected.blank?

options_for_select(options, selected)
end

<%= form.select :gender, gender_options(prompt: ' Gender '), {}, class: 'custom-select' %>

options for select @ Rails API

textarea

1
2
3
4
5
6
<!--  textarea  -->
<%= text_area_tag :content, nil, size: "30x10", placeholder: "請詳細描述您的問題,以利克服人員回覆", required: true %>

<!--
<textarea name="content" id="content" placeholder="請詳細描述您的問題,以利克服人員回覆" required="required" cols="30" rows="10"></textarea>
-->

radio and label

keywords: f.radio_button, label_tag
1
2
3
4
5
6
7
8
9
10
11
12
<div class="form-group">
<label>Gender</label>
<div class="w-100"></div>
<div class="custom-control custom-radio custom-control-inline">
<%= f.radio_button :gender, 'male', checked: @personal_info.gender == 'male', class: 'custom-control-input' %>
<%= label_tag 'personal_info_gender_male', 'Male', class: 'custom-control-label' %>
</div>
<div class="custom-control custom-radio custom-control-inline">
<%= f.radio_button :gender, 'female', checked: @personal_info.gender == 'female', class: 'custom-control-input' %>
<%= label_tag 'personal_info_gender_female', 'Female', class: 'custom-control-label' %>
</div>
</div>

checkbox

keywords: f.check_box, f.collection_check_boxes
1
2
3
4
5
6
7
<%# 直接關聯到 article.users %>
<%= form_with model: article, local: true, html: { class: 'needs-validation', novalidate: true } do |form| %>
<div class="form-group">
<%= form.label :status, class: 'd-block' %>
<%= form.collection_check_boxes(:user_ids, User.all, :id, :name) %>
</div>
<% end %>

⚠️ 注意:xxx_ids 這是 Rails 的慣例。

other field

1
2
3
4
5
6
<!--  text -->
<%= text_field :person, :name, required: true %>

<!--
<input required="required" type="text" name="name[person]" id="name_person">
-->
1
2
3
4
5
6
7
8
<!--  email  -->
<%= email_field(:user, :address) %>

<!-- tel -->
<%= telephone_field :user, :phone_code, required: true, required: true, placeholder: "886" %>
<!--
<input required="required" placeholder="886" type="tel" name="user[phone_code]" id="user_phone_code">
-->

基本使用

解決 double-click issues

keywords: data-disabled, data-disabled-with

使用 data-disabled=true 來避免使用者連續點擊:

1
2
3
4
5
<!-- 使用 data-disabled -->
<input type="submit" value="Create User" data-disable="true">

<!-- 使用 Rails View Helper -->
<%= submit_tag "Create User", data: { disable: true } %>

使用 data-disable-with 可以在使用者點擊後改變顯示的文字:

1
<%= submit_tag "Create User", data: { disable_with: "Creating user..." } %>

產生確認提示(confirmation checkbox)

keywords: data-confirm

使用 data-confirm 可以在使用者送出表單前產生 alert 視窗 讓使用者確認:

1
2
3
4
5
<!-- 使用 data-confirm -->
<input type="submit" value="Delete User" data-confirm="Are you sure?">

<!-- 使用 Rails View Helper -->
<%= submit_tag "Delete User", data: { confirm: "Are you sure" } %>

使用非 get 請求(Make a non-GET request)

keywords: data-method

使用 data-method 可以做出非 GET 的請求:

1
2
3
4
5
6
<!-- 使用 data-method -->
<a href="/users/1" data-method="delete">Delete user</a>

<!-- 使用 Rails View Helper -->
<%= link_to "Delete user", user_path(user), data: { method: "delete" } %>
<%= link_to "Delete user", user_path(user), data: { method: "delete", confirm: "Are you sure?" } %>

參考資料

Tags: helper

掃描二維條碼,分享此文章