はじめに
ancestryで作成したカテゴリーデータを用いて、選択肢を動的に変化させる機能を実装しました。
学習メモとして投稿します。
まだ、理解が浅いところもありますが参考になればと思います!
完成形
以下が完成コードです!
resources :products, except: [:index] do
get 'new/children_category', to: 'products#children_category'
get 'new/grandchildren_category', to: 'products#grandchildren_category'
end
before_action :set_categories, only: [:edit, :update]
〜省略〜
def children_category
@children_category = Category.where(ancestry: params[:parent_category_id])
render json: @children_category
end
def grandchildren_category
@grandchildren_category = Category.where(ancestry: "#{params[:parent_category_id]}/#{params[:children_category_id]}")
render json: @grandchildren_category
end
.input-field__contents
.input-field__contents-data
%p.subline
商品の詳細
.input-field__contents-image__headline
.headlabel
= f.label :category_id, "カテゴリー"
%span.necessary
必須
.sell__about__right__wrap-box.parent
%select.select-box1#parent
%option{value: 0} ---
- @parents.each do |parent|
%option{value: "#{parent.id}"} #{parent.name}
.child
%select.select-box2#child
.grand_child
.select-box3
= f.collection_select(:category_id, [], :id, :name, {prompt: "---"}, {id: "grand_child"})
$(function(){
let buildPrompt = `<option value>---</option>`
let buildHtmlOption = function(parent) {
let option = `<option value ="${parent.id}">${parent.name}</option>`
return option
}
$('#parent').change(function() {
let parent_id = $(this).val();
$.ajax({
type: 'GET',
url: 'products/new/children_category',
data: {parent_category_id: parent_id},
dataType: 'json'
})
.done(function(parent) {
$('.child').css('display', 'block');
$('#child').empty();
$('.grand_child').css('display', 'none');
$('#child').append(buildPrompt);
parent.forEach(function(child) {
var html_option = buildHtmlOption(child);
$('#child').append(html_option);
});
})
.fail(function() {
alert('エラー')
});
});
$(this).on("change", "#child", function() {
let parent_id = $("#parent").val();
let child_id = $("#child").val();
$.ajax({
type: 'GET',
url: 'products/new/grandchildren_category',
data: {
parent_category_id: parent_id,
children_category_id: child_id
},
dataType: 'json'
})
.done(function(parent) {
$('.grand_child').css('display', 'block');
$('#grand_child').empty();
$('#grand_child').append(buildPrompt);
parent.forEach(function(child) {
var html_option = buildHtmlOption(child);
console.log(buildHtmlOption(html_option));
$('#grand_child').append(html_option);
});
})
});
})
考え方
・親カテゴリーを選択しイベントを発火させたら、子カテゴリーをappend(追加)する
・子カテゴリーを選択しイベントを発火させたら、孫カテゴリーをappend(追加)する
・ajaxを使用を子カテゴリー及び孫カテゴリーが表示されるための通り道を作成する
・最終的には孫カテゴリーの値が保存される様にする
ざっくりとこんな感じです。
では、一つ一つ見ていきましょう!
ルーティング
プログラムの処理の流れとして、最終的にviewに子カテゴリーと親カテゴリーを表示させます。
それは実際に、コントローラーとjsで処理を行いますのでリクエストが会った際のコントローラーへの通り道を作成します。
resources :products, except: [:index] do
#children_categoryアクションに行くためのパス
get 'new/children_category', to: 'products#children_category'
#grandchildren_categoryアクションに行くためのパス
get 'new/grandchildren_category', to: 'products#grandchildren_category'
end
コントローラー
前提として、ajax処理行うのでjsでajax処理が行われたあとはコントローラーに行きます。
その際、コントローラーではカテゴリーの値を探してjsに返してあげる必要があります。
したがって、以下の様に書きます。
before_action :set_categories, only: [:edit, :update]
〜省略〜
def children_category
#.whereを使ってancestryから値を探して、インスタンス変数に代入する
@children_category = Category.where(ancestry: params[:parent_category_id])
#ancestryから探した値をjsに返してあげる
render json: @children_category
end
def grandchildren_category
#.whereを使ってancestryから値を探して、インスタンス変数に代入する
@grandchildren_category = Category.where(ancestry: "#{params[:parent_category_id]}/#{params[:children_category_id]}")
#ancestryから探した値をjsに返してあげる
render json: @grandchildren_category
end
JSの処理
jsでは、カテゴリーが選択されるたびにイベントが発火する様にします。
具体的に、
・親カテゴリーが選択されたら、イベントが発火し子要素のカテゴリー表示させる
・子カテゴリーが選択されたら、イベントが発火し孫要素のカテゴリー表示させる
処理としては、イベントが発火したらajaxでコントローラーから値を取得しforEachで全てを表させる流れになります。
//①=====HTMLで表示させるviewを定義===========================
$(function(){
let buildPrompt = `<option value>---</option>`
let buildHtmlOption = function(parent) {
let option = `<option value ="${parent.id}">${parent.name}</option>`
return option
}
//=================================================
//②=====親カテゴリーが選択され子カテゴリーを呼び出す処理============
$('#parent').change(function() {
let parent_id = $(this).val();
//ajaxでコントローラーに送る
$.ajax({
type: 'GET',
url: 'products/new/children_category',
data: {parent_category_id: parent_id},
dataType: 'json'
})
//以下はコントローラーからのレスポンス後の処理
.done(function(parent) {
$('.child').css('display', 'block');
$('#child').empty();
$('.grand_child').css('display', 'none');
$('#child').append(buildPrompt);
//コントローラーから取得した値をforEachで全て取得し、.appendでHTML要素に追加する
parent.forEach(function(child) {
var html_option = buildHtmlOption(child);
$('#child').append(html_option);
});
})
.fail(function() {
alert('エラー')
});
});
//=============================================
//②=====子カテゴリーが選択され孫カテゴリーを呼び出す処理============
$(this).on("change", "#child", function() {
let parent_id = $("#parent").val();
let child_id = $("#child").val();
//ajaxでコントローラーに送る
$.ajax({
type: 'GET',
url: 'products/new/grandchildren_category',
data: {
parent_category_id: parent_id,
children_category_id: child_id
},
dataType: 'json'
})
//以下はコントローラーからのレスポンス後の処理
.done(function(parent) {
$('.grand_child').css('display', 'block');
$('#grand_child').empty();
$('#grand_child').append(buildPrompt);
//コントローラーから取得した値をforEachで全て取得し、.appendでHTML要素に追加する
parent.forEach(function(child) {
var html_option = buildHtmlOption(child);
console.log(buildHtmlOption(html_option));
$('#grand_child').append(html_option);
});
})
});
//=============================================
})
最後には、HTML
HTMLで注意する点は、jsのid属性とHTMLでのid属性に齟齬かないかぐらいです。
ただし、最後の孫カテゴリーの値を保存するためには少し工夫が必要です。
.input-field__contents
.input-field__contents-data
%p.subline
商品の詳細
.input-field__contents-image__headline
.headlabel
= f.label :category_id, "カテゴリー"
%span.necessary
必須
.sell__about__right__wrap-box.parent
%select.select-box1#parent
%option{value: 0} ---
# 親カテゴリーの値を全て表示させる
- @parents.each do |parent|
%option{value: "#{parent.id}"} #{parent.name}
.child
# #childのところにjsで定義したviewが挿入される
%select.select-box2#child
.grand_child
.select-box3
# id:grand_childのところにjsで定義したviewが挿入される
# また、選択孫カテゴリーの値が保存正しく保存されるために以下の様に書きます。
= f.collection_select(:category_id, [], :id, :name, {prompt: "---"}, {id: "grand_child"})
補足で、以下の記述については以下のサイトを参考にしましたのでご確認ください
f.collection_select(:category_id, [], :id, :name, {prompt: "---"}, {id: "grand_child"})
#参考記述
#collection_select(オブジェクト名, メソッド名, 要素の配列, value属性の項目, テキストの項目 [, オプション or HTML属性 or イベント属性])
参考記事:
https://railsdoc.com/page/collection_select
終わりに
処理としては、そこまで複雑ではないため1つ1つ確認しながら行ったら上手く行きました!
もし、エラーや上手く値が取得できていない場合は、binding.pryや、console.log();、debuggerで確認してみてください!
ありがとうございました!