text_field_with_collection_auto_completeが欲しい!
銀行支店を検索する始めの一歩は、以下のようなコードだった。
<%# ビュー %> <%= text_field_with_auto_comolete :payee_account, :bank_id %>
# コントローラー auto_complete_for :payee_account, :bank_id
text_field_with_auto_completeの最もシンプルな形だ。とてもスッキリしている。ところが、使い易さを考えてコードを修正して行くと、前回までのような長い長いコードになってしまった...。やりたいことは実現できたのだが...。
会計システムを作っていると、同じような検索プルダウンリストを他にもたくさん作りたくなる。例えば、科目と補助科目の選択、取引先の選択、従業員の選択...などのケース。ところが、同じような検索プルダウンリストを他にも作ろうとした時、ちょっと気が重くなる...。メソッドをコピーした後の名前の修正は単純なのだが、似たような名前で修正箇所が意外と多く、漏れがあったり、勘違いしていたり...。(あれれ、名前を修正したいんだけど...どんな風に関連していたんだっけ?修正して、実行して、エラー確認しての繰り返しが多くなる。)
そして、気付くのでした。そうだ!text_field_with_collection_auto_completeが欲しい!と。
text_field_with_collection_auto_complete を定義する
text_field_with_auto_completeのソースを参考に、以下のように定義した。
# ---------- app/helpers/application_helper.rb ---------- # object: 設定したいモデル名。 # method: 設定したいフィールド名。 # collection_object: 検索したいモデル名。 # tag_options = {}: text_field、hidden_fieldのオプション # completion_options = {}: auto_complete_fieldのオプション # 例: # <%= text_field_with_collection_auto_complete :payee_account, :bank_id, :bank, {:size=>30}, {:min_chars=>-1} %> def text_field_with_collection_auto_complete(object, method, collection_object, tag_options = {}, completion_options = {}) auto_complete_id = tag_options[:index] ? "#{collection_object}_#{tag_options[:index]}_text" : "#{collection_object}_text" (completion_options[:skip_style] ? "" : auto_complete_stylesheet) + text_field(collection_object, :text, tag_options) + content_tag("div", "", :id => "#{auto_complete_id}_auto_complete", :class => "auto_complete") + auto_complete_field(auto_complete_id, {:url=>{:action=>"collection_auto_complete_for_#{collection_object}_text", :id=>tag_options[:index]}}.update(completion_options)) + hidden_field(object, method, tag_options) end
- id保存用のhidden_fieldを追加した。
- ついでなので、:indexオプションにも対応できるようにしてみた。
collection_auto_complete_for を定義する
auto_complete_forのソースを参考に、上記ヘルパメソッドとセットのcollection_auto_complete_forを、以下のように定義した。
# ---------- app/controllers/application.rb ---------- # object: 設定したいモデル名。 # method: 設定したいフィールド名。 # collection_object: 検索したいモデル名。 # list_methods: 選択リストに表示したいフィールド名の配列。 # text_methods: リストを選択した時、text_fieldに表示したいフィールド名の配列。 # options{:digit=> }: 数字を指定した桁数に揃える。不足した桁は0で埋まる。 # 例: # collection_auto_complete_for :payee_account, :bank_id, :bank, %w(id kana name), %w(id name), :digit=>4 def self.collection_auto_complete_for(object, method, collection_object, list_methods, text_methods, options = {}) define_method("collection_auto_complete_for_#{collection_object}_text") do sarch_text = params[:id] ? params[collection_object.to_s][params[:id]][:text] : params[collection_object.to_s][:text] @phrase_zen = sarch_text.split.last.gsub(/[ ]/,' ').strip.downcase rescue '' @phrase_han = Kana.hira2eb(@phrase_zen) sql_text = list_methods.map {|list_method| "LOWER(#{list_method}) LIKE ? OR LOWER(#{list_method}) LIKE ?"}.join(' OR ') sql_values = list_methods.map {|list_method| ["%#{@phrase_zen}%", "%#{@phrase_han}%"]}.flatten digit = options.delete(:digit) find_options = { :conditions => [sql_text] + sql_values, :limit => 10 }.merge!(options) @items = collection_object.to_s.camelize.constantize.find(:all, find_options) render :inline => <<-END <%= collection_auto_complete_result(@items, %w(#{list_methods.join(' ')}), [@phrase_zen, @phrase_han], :connect=>{'#{object}_#{method}'=>%w(id), '#{collection_object}_text'=>%w(#{text_methods.join(' ')})}, :digit=>#{digit}) %> END end end
- 全角と半角、かな、カナの区別無く検索できるようにした。
- 複数のフィールドを結合した文字列を描画できるようにした。
- multicontrolls.jsのconnect属性を使って、複数箇所の更新が出来るようにした。
collection_auto_complete_result を定義する
collection_auto_complete_forの中で、選択リストを描画する時に呼び出される。
# ---------- app/helpers/application_helper.rb ---------- # entries: 選択リストの配列。 # fields: 選択リストに表示したいフィールド名の配列。 # phrase: 強調表示したい文字列の配列。 # options{:empty_message=> }: 何も見つからない時のメッセージ。(デフォルト値='何も見つかりません。') # options{:digit=> }: 数字を指定した桁数に揃える。不足した桁は0で埋まる。 # options{:connect=>{}}: {接続したいtext_fieldのid属性 => text_fieldに設定したいフィールドの配列}のハッシュで指定する。 # 例: # def auto_complete_for_bank_kana # @phrase_name = params[:bank][:kana].gsub(/[ ]/,' ').strip.downcase # @phrase_kana = Kana.hira2eb(@phrase_name) # find_options = { # :conditions => ["(LOWER(id) LIKE ? OR LOWER(kana) LIKE ? OR LOWER(name) LIKE ?)", # "#{@phrase_kana.gsub(/^0+/, '')}", "%#{@phrase_kana}%", "%#{@phrase_name}%"], # :order => "kana ASC", # :limit => 20 } # @items = Bank.find(:all, find_options) # render :inline => <<-END # <%= collection_auto_complete_result @items, %w(id kana name), [@phrase_name, @phrase_kana], # :digit=>4, :connect=>{:payee_account_bank_id=>%w(id), :bank_kana=>%w(id name)} %> # END # end def collection_auto_complete_result(entries, fields, phrase = nil, options={}) options.merge!({:empty_message=>'何も見つかりません。'}) return content_tag("ul", content_tag("li", options[:empty_message])) if entries.blank? items = entries.map do |entry| item = [] fields.each do |field| entry_field = fit_digit(entry[field], options[:digit]) # 全角、半角両方のケースでハイライト処理する。 highlight_entry_field = (phrase.inject(entry_field){|r, p| highlight(r, p)} rescue highlight(entry_field, phrase)) item << content_tag("span", phrase ? highlight_entry_field : h(entry_field)) end options[:connect].each do |connect_id, connect_fields| connect_value = connect_fields.map {|field| fit_digit(entry[field], options[:digit])}.join(' ') item << content_tag("span", h(connect_value), :connect=>connect_id, :style=>"display:none") end content_tag("li", "#{item.join(' ')}") end content_tag("ul", items.uniq) end
その他の関連ファイル
# ---------- app/helpers/application_helper.rb ---------- # 数値の桁数を統一した文字列を返す。 # fit_digit(1, 4) => "0001" # fit_digit(12, 4, '_') => "__12" def fit_digit(value, digit, fill_string='0') if /^\d+$/ =~ value.to_s fill_string * (digit - value.to_s.size) + value.to_s rescue value.to_s else value.to_s end end
- 上記fit_digitも追加必要。
- Kanaモジュール必要。
- multicontrolls.js必要。
- <%= javascript_include_tag :defaults %>忘れずに。
- <%= javascript_include_tag 'multicontrols' %>忘れずに。
使い方
以上の定義をすると、銀行検索の部分は以下のように書ける。(支店検索との連動は出来ていない状態)
<%# ビュー %> <%= text_field_with_collection_auto_complete :payee_account, :bank_id, :bank, {:size=>30}, {:min_chars=>-1} %>
# コントローラー collection_auto_complete_for :payee_account, :bank_id, :bank, %w(id kana name), %w(id name), {:digit=>4}
-
-
- 支店検索と連動させるためには、コントローラーについてはcollection_auto_complete_forを使わずに、自分で定義する必要がある。
-