Jex’s Note

Rails - 表單 Form

介紹

Rails 原生表單寫起來會比較囉嗦了點, code 看起來就沒那麼乾淨, 以下除了會紀錄一些原生表單的筆記, 也試用了其他可以讓表單寫起來更乾淨的套件 :

  • simple_form 讓表單寫起來更乾淨, 也支援 bootstrap 樣式, 推薦這套, 它也支援 devise
  • bootstrap_form 也是讓表單更簡單, 但預設使用的是 bootstrap 樣式

Rails 原生表單

form

# method 預設是 post
<%= form_for @post, url: posts_path(@post) do |f| %>
    <%= f.label :title %>
    <%= f.text_field :title %>
    <%= f.label :content %>
    <%= f.text_field :content %>
    <%= f.button :submit, disable_with: 'Submiting' %>
<% end %>

form_for 與 form_tag 差別

使用 form_for 的話一定要在 controller 的先 new 好 (@post = Post.new)

所以要 create 必須先 new 好

view :

form_for @post, do |p|
    p.text_field :title
end

title 必須與 db 一致, 因為它會去抓 model 的欄位

使用 form_tag 的話欄位名稱就可以自己取, 只不過要自己手動抓欄位名稱 (aaa = params[:title])

顯示 validation 錯誤訊息

顯示在欄位後面

<%= f.label :title %> : <%= f.text_field :title, :placeholder => 'At least 5 characters' %><%= @post.errors.full_messages_for(:title).first %>

全部錯誤訊息

<% @post.errors.full_messages.each do |msg| %>
  <li><%= msg %></li>
<% end %>

全部錯誤訊息的第一個

<%= @post.errors.full_messages.first if @post.errors.any? %>

顯示某個欄位的錯誤

<%= @post.errors.full_messages_for(:title).first %>

當 form 的錯誤訊息發生, 造成跑版

因為 label 及 text_field 會被 <div class="field_with_errors"> 包起來, 所以造成跑版

在 config/application.rb 加上就會顯示原始的 html 了

config.action_view.field_error_proc = Proc.new { |html_tag, instance|
  html_tag
}

ActionController::InvalidAuthenticityToken

如果是自己寫 HTML 的 form 送出表單造成沒有一起把 token 送出去, 加入以下這行到 form 即可解決

<%= tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token) %>

simple_form

Install

gem 'simple_form'

執行

rails generate simple_form:install

基本用法

<%= simple_form_for @user, defaults: { input_html: { class: 'default_class' } } do |f| %>
  <%= f.input :username, input_html: { class: 'special' }, wrapper_html: { class: 'username' }  %>          # wrapper_html 會在 label 及 input 外層包一個 div
  <%= f.input :password, input_html: { maxlength: 20 }, label_html: { class: 'my_class' } %>
  <%= f.input :role, as: :radio_buttons, collection: { t('.role_client') => 'client', t('.role_translator') => 'translator' }, checked: 'client' %>
  <%= f.input :remember_me, input_html: { value: '1' } %>
  <%= f.button :submit %>
<% end %>

f.input 包含了 label, input

bootstrap

執行

$ rails generate simple_form:install --bootstrap

   identical  config/initializers/simple_form.rb
      create  config/initializers/simple_form_bootstrap.rb
       exist  config/locales
   identical  config/locales/simple_form.en.yml
   identical  lib/templates/erb/scaffold/_form.html.erb

locales 只會產生出 simple_form EN 的, 自己再 copy 改成 zh-TW 版本的

基本用法

<div class="row">
  <div class="col-md-6">
    <div class="panel panel-primary">
      <div class="panel-heading">Simple Form: Basic Form</div>
      <div class="panel-body">
        <%= simple_form_for @post, url: posts_path(@post), html: { class: 'form-horizontal' } do |f| %>
          <%= f.error_notification %>

          <%= f.input :title, input_html: {class: 'form-control'} %>

          <%= f.input :content, input_html: {class: 'form-control'} %>

          <%= f.button :submit, disable_with: 'Submiting', input_html: {class: 'btn btn-success'} %>
        <% end %>
      </div>
    </div>
  </div>
</div>

連錯誤訊息都會自動顯示在欄位下, 非常方便

submit button 不能用 f.submit, 應該用 :

`<%= f.button :submit, t('form.submit'), class: 'btn btn-success' %>`

default value, fail validation 會填原本送出的值

<%= f.input :contact_email, required: true, input_html: {value: (params[:user].nil?) ? f.object.email : params[:user]['contact_email']} %>

i18n

copy config/locales/simple_form.en.yml to config/locales/simple_form.zh-TW.yml, 替換第一行 en -> zh-TW

collection + i18n:

Radio

<%= f.input :sex, as: :radio_buttons, collection: [:male, :female] %>
<%= f.input :sex, as: :radio_buttons, collection: User.genders.keys.map { |x| x.to_sym } %>

collection 不能直接用 User.genders.keys, 因為它只是 array, 值一定要 symbol i18n 才會 work

simple_form.zh-TW.yml :

simple_form:
    options:
      user:
        gender:
          male: '男'
          female: '女'
    labels:
      user:
        gender: "性別"
    hints:
      user:
        gender: "請選擇性別"

Radio + ActiveRecord enum

<%= f.input :gender, as: :radio_buttons, collection: User.genders.keys %>

devise + simple_form + bootstrap - Sign_up

<div class="row">
  <div class="col-md-4 col-md-offset-4">
    <h2><%= t('.sign_up', :default => "Sign up") %></h2>

    <%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
      <%= f.error_notification %>
      <%= f.input :email, required: true, autofocus: true %>
      <%= f.input :password, required: true, hint: (t('.characters_minimum', num: 8) if @minimum_password_length) %>
      <%= f.input :password_confirmation, required: true %>
      <%= f.button :submit, t('form.submit'), class: 'btn btn-success' %>
    <% end %>

    <%= render "devise/shared/links" %>
  </div>
</div>

bootstrap_form

gem 'bootstrap_form'

application.css 加上

/*
 *= require rails_bootstrap_forms
 */

基本用法

<%= bootstrap_form_for(@user) do |f| %>
  <%= f.email_field :email %>
  <%= f.password_field :password %>
  <%= f.check_box :remember_me %>
  <%= f.submit "Log In" %>
<% end %>

它會產生

<form accept-charset="UTF-8" action="/users" class="new_user" id="new_user" method="post">
  <div class="form-group">
    <label for="user_email">Email</label>
    <input class="form-control" id="user_email" name="user[email]" type="email">
  </div>
  ...略...
</form>

Comments