[Rails] 修正 simple_form 2.1 中 input value 使用英文之外的多語系字元,導致無法正常產生 label for, input id 對應的錯誤

Standard

近日在解票時,遇到一張關於 simple_form 表單問題的 ticket。
(註:該專案使用的 SimpleForm 是 v2.1.1 版本)

# 先聲明小弟是前端工程師,完全的 Rails 生手,有說明錯誤的地方請多多包涵哦 :$

情況是這樣子的,在這個專案裡,使用者個人資料的表單中有個這樣的欄位:

它是以群組核取框(checkboxes)呈現,使用者同時可以核取多個項目。

但奇怪的是,用 SimpleForm 產生的這個欄位,在點選「科技」、「網路」、「手機」的文字(label)時,預期行為應是「讓點選的 label 文字左方的核取框作用(勾選或取消勾選)」,但無論按哪個文字,都是第一項「科技」的核取框有反應而已。

這是因為產生的 HTML 語法有誤導致:

<div class="input check_boxes optional">
  <label class="check_boxes optional" for="user_profile_attributes_preferred_info">喜歡哪類資訊</label>
  <span>
    <input name="user[profile_attributes][preferred_info][]" type="hidden" value=">
    <input class="check_boxes optional" id="user_profile_attributes_preferred_info_" name="user[profile_attributes][preferred_info][]" type="checkbox" value="科技">
    <label class="collection_check_boxes" for="user_profile_attributes_preferred_info_">科技
    </label>
  </span>
  <span>
    <input name="user[profile_attributes][preferred_info][]" type="hidden" value=">
    <input class="check_boxes optional" id="user_profile_attributes_preferred_info_" name="user[profile_attributes][preferred_info][]" type="checkbox" value="網路">
    <label class="collection_check_boxes" for="user_profile_attributes_preferred_info_">網路
    </label>
  </span>
  <span>
    <input name="user[profile_attributes][preferred_info][]" type="hidden" value=">
    <input class="check_boxes optional" id="user_profile_attributes_preferred_info_" name="user[profile_attributes][preferred_info][]" type="checkbox" value="手機">
    <label class="collection_check_boxes" for="user_profile_attributes_preferred_info_">手機
    </label>
  </span>
</div>

不大正常,每個 input 的 id 以及 label 的 for 屬性值都一模一樣,當然選不到預期對應的項目囉。

嗯,這樣看來就是 SimpleForm 產生 checkboxes 的時候出了點問題。

看看產生 checkboxes 的那段程式碼:

    <%= profile.input :preferred_info, :as => :check_boxes, :collection => ['科技', '網路', '手機', '攝影', '遊戲', '比價'] %>

試著把 :collection 陣列的值改為英文,卻可以產生預期的結果。如果是中文 + 英文呢?居然只有中文的部份會消失。

在不更動這些值的前提下,要根治看來只能從這個 gem 本身下手了。

lib/simple_form/action_view_extensions/builder.rb 找到一點線索,
產生過程用到 add_default_name_and_id_for_valuesanitize_attribute_name 來產生 id、for 的 value,這兩個 method 內容為:

sanitize_attribute_name:

    def sanitize_attribute_name(attribute, value) #:nodoc:
      "#{attribute}_#{value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase}"
    end

add_default_name_and_id_for_value: (Rails 內建 method

    def add_default_name_and_id_for_value(tag_value, options)
      unless tag_value.nil?
        pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase
        specified_id = options["id"]
        add_default_name_and_id(options)
        options["id"] += "_#{pretty_tag_value}" if specified_id.blank? && options["id"].present?
      else
        add_default_name_and_id(options)
      end
    end

可以注意到其中 gsub regular expression 的規則是 /[^-\w]/,用意應該是只想保留「-」及「[a-zA-Z0-9_] 」,避免在 id 及 for 中產生規定之外的字元。

在 Rails Console 下驗證一下(特別輸入「中日韓」字元看看):

1.9.3p327 :025 > "!$%^&test-攝影り요".gsub(/[^-\w]/, "")
 => "test-"

果然都被濾掉了。

不過根據 W3C 說明( http://www.w3.org/TR/html-markup/syntax.html#syntax-text ),attribute 的 value 字元規定:

4.5. Text and character data
Text in element contents (including in comments) and attribute values must consist of Unicode characters, with the following restrictions:

  • must not contain U+0000 characters
  • must not contain permanently undefined Unicode characters
  • must not contain control characters other than space characters

其實是可以包含「-」及「[a-zA-Z0-9_] 」之外的更多字元的,「中日韓」字元可以不必被排除在外。

另外在 http://www.regular-expressions.info/unicode.html#prop 查到:

\p{L} or \p{Letter}: any kind of letter from any language.

所以我將 gsub 的 regular expression 規則改了下,並在 Rails Console 中驗證看看:

1.9.3p327 :038 > "!$%^&test攝adsf__dsf-影り요".to_s.gsub(/\s/, "").gsub(/[^-\p{L}]/, "")
 => "test攝adsfdsf-影り요"

OK,這樣的結果可達到預期的需求,那要怎麼在專案中補上修正呢?

我們可以在 config/initializers/ 中新增一支 rb 檔,並 override 掉 simple_form 的這兩個 method。

# -*- encoding : utf-8 -*-
module SimpleForm
  module ActionViewExtensions
    module Builder
      private

      def sanitize_attribute_name(attribute, value) #:nodoc:
        "#{attribute}_#{value.to_s.gsub(/\s/, "_").gsub(/[^-\p{L}]/, "").downcase}"
      end
    end
  end
end

module ActionView::Helpers
  class InstanceTag
    private

    def add_default_name_and_id_for_value(tag_value, options)
      unless tag_value.nil?
        pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/[^-\p{L}]/, "").downcase
        specified_id = options["id"]
        add_default_name_and_id(options)
        options["id"] += "_#{pretty_tag_value}" if specified_id.blank? && options["id"].present?
      else
        add_default_name_and_id(options)
      end
    end
  end
end

作了以上修改之後,SimpleForm 便能成功產生正確的 HTML,讓 label for 與 input id 能夠成功配對,有情人終成眷屬,可喜可樂!

3 thoughts on “[Rails] 修正 simple_form 2.1 中 input value 使用英文之外的多語系字元,導致無法正常產生 label for, input id 對應的錯誤

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *