【Rails】Ajaxでセレクトボックスの中身が動的に変わる実装(3段構成)
- 評価
- クリップ 0
- VIEW 217
やりたいこと
親のカテゴリを変更すると、子のカテゴリの中身が動的に変わり、子のカテゴリが選択されると、孫のカテゴリも動的に変わるセレクトボックスをつくりたいです。
以下を参考にしました。
Ajaxでセレクトボックスの中身が動的に変わるRailsアプリの作り方(Qiita)
具体的にはUserが
Prefecture(都道府県)→School(学校)→Course(学科)
のカテゴリを動的に選択できるようにしたいです。
今のところPrefecture(都道府県)→School(学校)の2段目までは動いています。
コード
View
users/school.html.erb
<% prefecture_options = Prefecture.order(:id).map { |s| [s.name, s.id, data: { children_path: prefecture_schools_path(s) }] }%>
<div class="field">
<%= f.label :prefecture_id,"都道府県" %><br>
<%= f.select :prefecture_id, prefecture_options, { include_blank: true }, class: 'select-parent' %>
<span class="pulldown"><i class="fa fa-angle-down" aria-hidden="true"></i></span>
</div>
<% schools = @user.prefecture.try(:schools) || [] %>
<% school_options = schools.map { |s| [s.name,s.id , data: { grandchildren_path: prefecture_school_courses_path(s) }] } %>
<div class="field">
<%= f.label :school_id,"大学名" %><br>
<%= f.select :school_id, school_options, { include_blank: true }, class: 'select-children' %>
<span class="pulldown"><i class="fa fa-angle-down" aria-hidden="true"></i></span>
</div>
<% courses = @user.school.try(:courses) || [] %>
<% course_options = courses.map { |s| [s.name, s.id] } %>
<div class="field">
<%= f.label :course_id,"学科名" %><br>
<%= f.select :course_id, course_options, { include_blank: true }, class: 'select-grandchildren' %>
<span class="pulldown"><i class="fa fa-angle-down" aria-hidden="true"></i></span>
</div>
$(document).on 'turbolinks:load', ->
replaceSelectOptions = ($select, results) ->
$select.html $('<option>')
$.each results, ->
option = $('<option>').val(this.id).text(this.name)
$select.append(option)
#都道府県から大学名
replaceChildrenOptions = ->
childrenPath = $(@).find('option:selected').data().childrenPath
$selectChildren = $(@).closest('form').find('.select-children')
if childrenPath?
$.ajax
url: childrenPath
dataType: "json"
success: (results) ->
replaceSelectOptions($selectChildren, results)
error: (XMLHttpRequest, textStatus, errorThrown) ->
console.error("Error occurred in replaceChildrenOptions")
console.log("XMLHttpRequest: #{XMLHttpRequest.status}")
console.log("textStatus: #{textStatus}")
console.log("errorThrown: #{errorThrown}")
else
replaceSelectOptions($selectChildren, [])
#大学名から学科名
replaceGrandChildrenOptions = ->
grandchildrenPath = $(@).find('option:selected').data().grandchildrenPath
$selectGrandChildren = $(@).closest('form').find('.select-grandchildren')
console.log($(@).find('option:selected').data().grandchildrenPath)
if grandchildrenPath?
$.ajax
url: grandchildrenPath
dataType: "json"
success: (results) ->
replaceSelectOptions($selectGrandChildren, results)
error: (XMLHttpRequest, textStatus, errorThrown) ->
console.error("Error occurred in replaceChildrenOptions")
console.log("XMLHttpRequest: #{XMLHttpRequest.status}")
console.log("textStatus: #{textStatus}")
console.log("errorThrown: #{errorThrown}")
else
replaceSelectOptions($selectGrandChildren, [])
$('.select-parent').on
'change': replaceChildrenOptions
$('.select-children').on
'change': replaceGrandChildrenOptions
Controller
class SchoolsController < ApplicationController
def index
prefecture = Prefecture.find(params[:prefecture_id])
render json: prefecture.schools.select(:id, :name)
end
end
class CoursesController < ApplicationController
def index
school = School.find(params[:school_id])
render json: school.courses.select(:id, :name)
end
end
Model
class User < ApplicationRecord
belongs_to :prefecture, optional: true
belongs_to :school, optional: true
belongs_to :course, optional: true
end
class Prefecture < ApplicationRecord
has_many :schools, ->{ order(:id) }
end
class School < ApplicationRecord
belongs_to :prefecture, optional: true
has_many :courses, ->{ order(:id) }
end
class Course < ApplicationRecord
belongs_to :school, optional: true
end
Routing
Rails.application.routes.draw do
resources :users do
collection do
get '/:id/school' => 'users#school', as: "school"
end
end
resources :prefectures, only: [] do
resources :schools, only: :index do
resources :courses, only: :index
end
end
end
困ってるところ
子カテゴリ(学校)の決定後、
coffeescript内のconsole.log($(this).find('option:selected').data().grandchildrenPath);
がundefinedとなり、孫のカテゴリ(学科)が変化しない。
お詳しい方、ぜひともご教授ください。
よろしくお願いします。
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
checkベストアンサー
+1
ERBの中で<% school_options = schools.map { |s| [s.name,s.id , data: { grandchildren_path: prefecture_school_courses_path(s) }] } %>
のようにしてdata属性を追加していますが、これはあくまでサーバーサイドで生成されるoptionです。
都道府県のオプションが変更されるとJS内で動的に学校のoptionが作成されます。このときにはdata属性が付かないので、grandchildrenPathの情報も当然取得できなくなってしまいます。
というわけで、以下のような対応方針を取るのはどうでしょうか。
- 学校のJSONを返すときにgrandchildrenPathに相当する情報も一緒に返す
- JS内で動的にoptionを作成する際に、data属性も一緒に付与する
class SchoolsController < ApplicationController
def index
prefecture = Prefecture.find(params[:prefecture_id])
schools = prefecture.schools.map do |school|
{
id: school.id,
name: school.name,
# pathの情報も付与する
path: prefecture_school_courses_path(school)
}
end
render json: schools
end
end
replaceSelectOptions = ($select, results) ->
$select.html $('<option>')
$.each results, ->
option = $('<option>').val(this.id).text(this.name)
# サーバーから返ってきたpath情報をdata属性として付与(pathが返ってきた場合のみ)
if this.path
option.data('path', this.path)
$select.append(option)
なお、replaceSelectOptions
は再利用可能なメソッドになっているため、grandchildrenPath
のような具体的なdata属性ではなく汎用的なpath
という名前にしてあります。
なので、それに合わせてERBのgrandchildren_path
やgrandchildrenPath
もpath
に統一しておきましょう。
-<% school_options = schools.map { |s| [s.name,s.id , data: { grandchildren_path: prefecture_school_courses_path(s) }] } %>
+<% school_options = schools.map { |s| [s.name,s.id , data: { path: prefecture_school_courses_path(s) }] } %>
replaceGrandChildrenOptions = ->
- grandchildrenPath = $(@).find('option:selected').data().grandchildrenPath
+ grandchildrenPath = $(@).find('option:selected').data().path
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
0
$(@).find('option:selected')
は、都道府県のoptionと大学のoptionをどちらも取得してしまうため、grandchildrenPath
がエラーになってしまうのでは?
フォームを増やしたのですから、idなどを追加してそれぞれ絞り込む必要があると思います。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 91.06%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
2017/12/25 06:38 編集