かな、カナ、カナ、どれでも検索可能なプルダウンリスト

だいぶ良い感じになってきたが、もう一つストレスを感じる所がある。それは、「かな、カナ、カナ」の違い。「ひらがな or カタカナ(全角) or カタカナ(半角)」普通に検索すると、文字の種類が違っていると異なるキーワードと見なされて一致しない。
それから、「シンジュク(小さなサイズのュ) or シンジユク(普通サイズのユ)」全銀フォーマットで利用できるのは普通サイズの「ユ」だけで、「シンジュク」の場合は「シンジユク」と表現されている。
つまり人情的に考えれば、「しんじゅく」「しんじゆく」「シンジュク」「シンジユク」「シンジュク」「シンジユク」どれで検索しても、「シンジユク」がヒットするようにしたいのだ。

      • 日本語の取り扱いって、本当、大変だ...。

Kanaモジュールを作ってみる

ひらがなを含めた全角を全銀フォーマット対応の半角文字に変換する、以下のようなモジュールを作ってみた。

# ---------- lib/kana.rb ----------
module Kana
  # 全角を半角に変換
  def self.zen2han(str)
    return if str.nil?
    han = str.tr("A-Za-z0-9", "A-Za-z0-9")
    han.tr!('&%()! .,+−ー—', '&%()! .,+-')

    dakuon     = 'ガギグゲゴザジズゼゾダヂヅデドバビブベボヴ'
    daku_clear = 'カキクケコサシスセソタチツテトハヒフヘホウ'
    handakuon     = 'パピプペポ'
    handaku_clear = 'ハヒフヘホ'
    han.gsub!(/[#{dakuon}]/) {|c| c + ''}
    han.gsub!(/[#{handakuon}]/) {|c| c + ''}
    han.tr!(dakuon + handakuon, daku_clear + handaku_clear)

    zenkaku = '。「」、・ァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン゛゜ヵヶヰヱヮ'
    hankaku = '。「」、.ァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン゙゚カケイエワ'
    han.tr(zenkaku, hankaku) || han
  end

  # ひらがなを含めて、全角を半角に変換
  def self.hira2han(str)
    return if str.nil?
    zen2han(str.tr('あ-ん','ア-ン'))
  end

  # 全角を全銀フォーマットで利用可能な半角に変換
  def self.zen2eb(str)
    return if str.nil?
    zen2han(str).upcase.tr('ァィゥェォャュョッ_','アイウエオヤユヨツ-')
  end

  # ひらがなを含めて、全角を全銀フォーマットで利用可能な半角に変換
  def self.hira2eb(str)
    return if str.nil?
    zen2eb(str.tr('あ-ん','ア-ン'))
  end
end

Kanaモジュールを利用してみる

上記Kanaモジュールを利用して、コントローラーを以下のように修正してみた。

# コントローラー:銀行の検索
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 :partial => 'auto_complete_for_bank_kana'
end

# コントローラー:支店の検索
def auto_complete_for_branch_kana
  @phrase_name = params[:branch][:kana].gsub(/[ ]/,' ').strip.downcase
  @phrase_kana = Kana.hira2eb(@phrase_name)
  Branch.with_scope(:find=>{:conditions=>[ "bank_id=?", params[:payee_account][:bank_id] ]}) do
    find_options = { 
      :conditions => ["(LOWER(code) LIKE ? OR LOWER(kana) LIKE ? OR LOWER(name) LIKE ?)",
                      "#{@phrase_kana.gsub(/^0+/, '')}", "%#{@phrase_kana}%", "%#{@phrase_name}%"],
      :order => "kana ASC",
      :limit => 10 }
    @items = Branch.find(:all, find_options)
    render :partial => 'auto_complete_for_branch_kana'
  end
end

選択リストを描画するビューも、若干修正したので掲載。

<%# ビュー_auto_complete_for_bank_kana.rhtml %>
<ul>
  <% @items.each do |item| %>
    <li>
      <%# 支店情報をクリアするための項目 %>
      <span connect="payee_account_branch_id"></span>
      <span connect="branch_kana"></span>

      <%# 値を設定するための非表示項目 %>
      <span connect="payee_account_bank_id" style="display:none"><%= item.id %></span>
      <span connect="bank_kana" style="display:none"><%= fit_digit(item.id, 4) %> <%= item.name %></span>

      <%# 選択リストに表示する項目 %>
      <span class="informal" style="margin:2.8em"><%= highlight(item.kana, @phrase_kana) %><br /></span>
      <span class="informal"><%= fit_digit(item.id, 4) %> <%= highlight(item.name, @phrase_name) %></span>
    </li>
  <% end %>

  <% if @items.blank? %>
    <li>
      <span connect="payee_account_branch_id"></span>
      <span connect="branch_kana"></span>

      <span connect="payee_account_bank_id"></span>
      <span connect="bank_kana"></span>
      <span class="informal">銀行が見つかりません。</span>
    </li>
  <% end %>
</ul>
<%# ビュー_auto_complete_for_branch_kana.rhtml %>
<ul>
  <% @items.each do |item| %>
    <li>
      <%# 値を設定するための非表示項目 %>
      <span connect="payee_account_branch_id" style="display:none"><%= item.id %></span>
      <span connect="branch_kana" style="display:none"><%= fit_digit(item.code, 3) %> <%= item.name %></span>

      <%# 選択リストに表示する項目 %>
      <span class="informal" style="margin:2.3em"><%= highlight(item.kana, @phrase_kana) %><br /></span>
      <span class="informal"><%= fit_digit(item.code, 3) %> <%= highlight(item.name, @phrase_name) %></span>
    </li>
  <% end %>

  <% if params[:payee_account][:bank_id].blank? %>
    <li>
      <span connect="payee_account_branch_id"></span>
      <span connect="branch_kana"></span>
      <span class="informal">銀行を先に入力してください。</span>
    </li>
  <% elsif @items.blank? %>
    <li>
      <span connect="payee_account_branch_id"></span>
      <span connect="branch_kana"></span>
      <span class="informal">支店が見つかりません。</span>
    </li>
  <% end %>
</ul>

これで、ひらがな、全角、半角を気にしないで検索できるようになった!ストレス無く、銀行と支店を選択することが出来る。

      • bsjournalの中では、上記のような仕組みのauto_completeを多用してます。興味のある方は、こちらから試してみることも出来ます。

見た目の調整、キー操作の調整

追加で、スタイルシートを指定して、autocompleteなtext_fieldには▽マークを表示してみる。それから、未入力の状態であっても、escキーや矢印↑↓キーで選択リストが表示されるようにしてみた。

/* ---------- スタイルシート ---------- */
input.autocomplete[type="text"]{
  background: #fcfcfc url(../images/arrow_down.gif) right 50% no-repeat;
  padding: 0 18px 0 0;
  }
  • arrow_down.gifは▽マークの画像。背景画像として指定した。
<%# ビュー %>
<%= text_field_with_auto_complete :bank, :kana,
      {:class=>'autocomplete'}, 
      {:min_chars=>-1} %>
<%= hidden_field :payee_account, :bank_id %>

<%= text_field_with_auto_complete :branch, :kana, 
      {:class=>'autocomplete'}, 
      {:with=>"value + '&payee_account[bank_id]=' + $('payee_account_bank_id').value", :min_chars=>-1} %>
<%= hidden_field :payee_account, : branch_id %>
  • class属性に'autocomplete'を追加した。
  • :min_chars=>-1で、未入力の状態でもescキーや矢印↑↓キーの操作により、選択リストが表示される。

解決できない問題

  • 日本語を入力する時は、かな入力中でも、変換確定後であっても、auto_complete_fieldが反応してくれない...。(MacBook環境)
    • その場合は、変換確定後、ESCキーや矢印キーで操作することにより、検索が開始される。
    • ちなみに、escキーは選択リストの表示、非表示を切り替える。矢印↑↓キーは選択カーソルを移動する。tabキーで確定。
  • どうすれば日本語入力中であっても、リアルタイムに検索動作を始動できるのだろう?OS側の問題かと疑ったが、Google サジェストを見てみると、日本語の入力でもちゃんと検索してくれている。日本語でもストレス無く検索できる方法が知りたい!