2012年10月31日水曜日

Railsでコンボボックスを連動させるサンプル


コンボボックスで都道府県を選んだ後に、選んだ都道府県下の市町村を絞りこんで表示させたい。

Rails3になって、observe_fieldというフィールドの更新を検知する関数がなくなったり、Ajax周りの関数が変更になっていたりで、そのまま利用できるサンプルは少なかった。

最も参考になったのは、下記のブログ記事。

[Rails3]jQueryでobserve_field的なことを。

まずは、都道府県と市町村、その他住所クラスのscaffoldを作成する。(実行結果は省略。)
rails g scaffold todofuken name:string
rails g scaffold shichoson todofuken:references name:string
rails g scaffold jusho todofuken:references shichoson:references sonotajusho:string
scaffoldで作成された_form.html.erbは、TodofukenとShichosonがテキストフィールドになっているので、下記のようにコンボボックスに変更する。
<%= form_for(@jusho) do |f| %>
  <% if @jusho.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@jusho.errors.count, "error") %> prohibited this jusho from being saved:</h2>

      <ul>
      <% @jusho.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :todofuken %><br />
    <%= f.collection_select(:todofuken_id, Todofuken.find(:all), :id, :name) %>
  </div>
  <div class="field">
    <%= f.label :shichoson %><br />
    <%= f.collection_select(:shichoson_id, Shichoson.find(:all), :id, :name) %>
  </div>
  <div class="field">
    <%= f.label :sonotajusho %><br />
    <%= f.text_field :sonotajusho %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

次に、市町村の部分のhtmlをAjaxで変更できるように外出しにする。

_todoufuken_select.html.erb
<%= f.select(:shichoson_id, @shichoson) %>
_form.html.erbからは、下記のようにrenderで呼び出す。
<%= render 'todoufuken_select', {:f => f} %>
{:f => f}と指定しておくと、_form.html.erbのFormの変数(:shichoson)が、_todoufuken_select.html.erb内でも使えるようになる。

次に、都道府県のコンボボックスの変更を検知するために、JavaScriptを記述する。

application.js
jQuery(function(){
  $('#jusho_todofuken_id').change(function(){
  var todofuken_id = $("#jusho_todofuken_id").val();
  $.get("todoufuken_select.js?todofuken_id=" + todofuken_id);
});
todoufuken_select.js の部分は、URLとしては/jushos/todoufuken_select.js になる。
scaffoldを実行した直後だと、/jushos/XXXX のリクエストは、すべてコントローラーのshowメソッドで処理されてしまうので、ルーティングを下記のように追加する。

routes.rb
Mytest::Application.routes.draw do

  match '/jushos/todoufuken_select'
  resources :jushos
match~の部分をresources :jushosの先に記述する必要がある。 ※showメソッドの/jushos/XXXXのルーティングの方が範囲が広いので、先にヒットしてしまう。
次に、コントローラーを修正する。

jushos_controller.rb
  # GET /jushos/new
  # GET /jushos/new.json
  def new
    @jusho = Jusho.new
    @shichoson = [["都道府県を選択してください。", "0"]]

    respond_to do |format|
      format.html # new.html.erb
      format.json { render json: @jusho }
    end
  end
  # GET /jushos/todoufuken_select
  def todoufuken_select
    @shichoson = Shichoson.find_all_by_todofuken_id(params[:todofuken_id])
    render
  end
変更点は2箇所。
既存のnewメソッドに、市町村のコンボボックスの初期値を設定したことと、todoufuken_selectメソッドの新規追加。

次に、todoufuken_selectメソッドが呼び出すことになる_todoufuken_select.html.erbを作成する。
$("#jusho_shichoson_id").html("<%= escape_javascript(options_for_select(@shichoson.map{ |shichoson| [shichoson.name, shichoson.id] })) %>");
options_for_selectは、selectタグのoprionの部分を作成する関数だが、ここにDBの検索結果の配列(@shichoson)をそのまま渡してもうまく表示されないので、上記のように、DBの列を指定して渡すようにする。

これで完成。

初期状態

都道府県で愛知県を選択

愛知県の市町村が表示された。

1 件のコメント:

  1. こんにちは。すばらしい投稿、誠にありがとうございます。

    rails g scaffold todofuken name:string
    rails g scaffold shichoson todofuken:references name:string
    rails g scaffold jusho todofuken:references shichoson:references sonotajusho:string

    を実行しましたところ、サンプルでご提示いただいている内容とかなり異なるようでした。


    異なる部分だけを、サンプルと同じように記載すればよろしいものでしょうか?
    また、「shichosons」「todofukens」の両方に、”_form.html.erb”が生成され
    ておりますが、両方、サンプルと同じ内容で修正すればよろしいものでしょうか?


    返信削除