ひよっこエンジニアの雑多な日記

なんとかWeb系のエンジニアをやっています。

【Rails】Ajaxでセレクトボックスの内容を動的に変更する

こんばんは!1週に一回記事更新しますわ〜とか言っておきながら1ヶ月ぶりの更新です…笑

今回の内容は2つのセレクトボックスがあった時に片方のセレクトボックスが変更されたら、もう片方のセレクトボックスで選択できる内容が動的に変更されると言った内容です!
概要だけだと何言ってるかわけわからないと思うので例を挙げると、県と市区町村が選択できるセレクトボックスがそれぞれ存在する時に、県を選択したら市区町村のセレクトボックスの方が選択された県に存在する市区町村が選択肢になるようなイメージです。

どう作ればいいかわからない状態から作ったのでかなり時間がかかったので備忘録がてら書いときます

想定

とりあえずテーブル構造はこんな感じ

f:id:kimuraysp:20160616234834p:plain

prefecturesとcitesとマスタテーブルとしていて存在していて、そんでprofilesに住んでいる県と市区町村が登録されるみたいな想定

ビュー

new.html.erb

<%= form_for(@profile) do |f| %>
  <% if @profile.errors.any? %>
    <div id="error_explanation">
      <ul>
      <% @profile.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="prefecture-area">
    <%= label :prefecture_id %><br>
    <%= collection_select :profile, :prefecture_id, Prefecture.all, :id, :name %>
  </div>
  
  <div class="city-area">
    <%= render partial: 'select_city', locals: {prefecture_id: Prefecture.first.id} %>
  </div>
<% end %>

_select_city.html.erb

<%= label :profile, :city_id %><br>
<%= collection_select :profile, :city_id, Prefecture.where(prefecture_id: prefecture_id), :id, :city_name %>

動的にセレクトボックスを入れ替えるcity_idの方はselect_cityという名前でパーシャル化。
パーシャル化することでAjaxで返ってくる結果をレンダリングしやすくしとく。

コントローラ

class ProfilesController < ApplicationController
  def new
    @profile = Profile.new
  end

  # Ajax処理を行う処理
  def get_cities
    render partial: 'select_city', locals: {prefecture_id: params[:prefecture_id]}
  end
end

セレクトボックスを動的に変える処理をget_citiesというアクションで受け取る。
Ajaxでこのアクションを呼ぶ。
ルーティングは以下の感じで

Rails.application.routes.draw do
  resources :profiles, only: :new do
    collection do
      get 'get_cities' # /profiles/get_cities
    end
  end
end

モデル

今回の記事の本質からは外れるので割愛します。
だいたいテーブル構造で想像してください。

Coffee Script

セレクトボックスを動的に変えるのはAjaxを使って実現するためcoffeeやjavascriptが肝になる。
ここがいまいちわかっていなかったので色々詰まっていた…

$(document).on 'change', '#profile_prefecture_id', ->
  $.ajax(
    type: 'GET'
    url: '/profiles/get_cities'
    data: {
      prefecture_id: $(this).val()
    }
  ).done (data) ->
    $('.city-area').html(data)

$(document).on 'change', '#profile_prefecture_id'
この処理でprefecture_idのセレクトボックスが変更されたことを検知する。
profile_prefecture_idはcorrection_selectで自動生成されるid。correction_selectというよりForm系のヘルパーで生成される。

$.ajax(
    type: 'GET'
    url: '/profiles/get_cities'
    data: {
      prefecture_id: $(this).val()
    }

ここでAjaxの通信を開始している。typeにメソッド、urlに呼び出すアクションのパス、dataにコントローラに渡す値を設定する。
今回のdataはセレクトボックスで選択された値をparamsprefecture_idとして乗せる。

done(data)でアクションの結果を受け取る。今回のアクションはprofilesコントローラのget_citiesアクションでget_citiesの中身はrender partial: 'select_city', locals: {prefecture_id: prefecture_id}なので_select_city.html.erbレンダリングした結果を受け取る。
アクションの処理結果はdataに格納されている。

ここのdata$.ajaxでコントローラに渡したdataとは別もんな気がするので注意。

$('.city-area').html(data)レンダリングされたHTMLソースを<div class="city-area">内に差し込むことでセレクトボックスを入れ替える。

ここまでやることでセレクトボックスの入れ替えが実現できました!

いざ記事にしてみると大して難しいことはやっていないのだけど、初めて実装する機能などはやはり難易度が高いように感じてしまう…。
こうやって色々試して、ブログなんかで知識を蓄積して慣れていくしかないですかね…。

うわぁ、がんばろう