Edited at

【JS】動的なカテゴリーボックスの実装方法


はじめに

動的なセレクトボックスを実装するにあたり、非同期通信を用いたので備忘録としてここ記す。


テーブル


categoriesテーブル

Column
Type
Options

category-name
string
null false

ancestry
string
null false

ancestryのGemを使い多階層としている。

親、子、孫が一つのカラムの中に存在している。


実装


category.js

  $(function(){

function appendOption(category){ // optionの作成
var html = `<option value="${category.id}">${category.name}</option>`;
return html;
}
function appendChidrenBox(insertHTML){ // 子セレクトボックスのhtml作成
var childSelectHtml = '';
childSelectHtml = `<div class='product-select-wrapper' id= 'children_wrapper'>
<div class='product_category-select'>
<select class="category_select-box" id="child_category" name="item[category_id]">
<option value="---">---</option>
${insertHTML}
</select>
<i class='fa fa-chevron-down'></i>
</div>
<div class= 'product_select-grandchildren'>
</div>
</div>`
;
$('.product_select-children').append(childSelectHtml);
}
function appendgrandChidrenBox(insertHTML){ // 孫セレクトボックスのhtml作成
var grandchildrenSelectHtml = '';
grandchildrenSelectHtml = `<div class='product-select-wrapper' id= 'grandchildren_wrapper'>
<div class='product_category-select'>
<select class="category_select-box" id="grandchild_category" name="item[category_id]">
<option value="---">---</option>
${insertHTML}
</select>
<i class='fa fa-chevron-down'></i>
</div>
<div class= 'product_select-grandchildren'>
</div>
</div>`
;
$('.product_select-grandchildren').append(grandchildrenSelectHtml);
}

$(document).on('change', '#category_select', function(){ // 親セレクトボックスの選択肢を変えたらイベント発火
var productcategory = document.getElementById('category_select').value;
// ↑ productcategoryに選択した親のvalueを代入
if (productcategory != ''){
// ↑ productcategoryが空ではない場合のみAjax通信を行う。選択肢を初期選択肢'---'に変えると、通信失敗となってしまうため。
$.ajax({
url: 'category_children',
type: 'GET',
data: { productcategory: productcategory },
dataType: 'json'
})
.done(function(children){ // 送られてきたデータをchildrenに代入
var insertHTML = '';
children.forEach(function(child){
// forEachでchildに一つずつデータを代入。子のoptionが一つずつ作成される。
insertHTML += appendOption(child);
});
appendChidrenBox(insertHTML);
$(document).on('change', '#category_select', function(){
// 通信成功時に親の選択肢を変えたらイベント発火。子と孫を削除。selectのidにかけるのではなく、親要素にかけないと残ってしまう
$('#children_wrapper').remove();
$('#grandchildren_wrapper').remove();
})
})
.fail(function(){
alert('カテゴリー取得に失敗しました');

})
}
});

// document、もしくは親を指定しないと発火しない
$(document).on('change', '#child_category', function(){
var productcategory = document.getElementById('child_category').value;
if (productcategory != ''){
$.ajax ({
url: 'category_grandchildren',
type: 'GET',
data: { productcategory: productcategory },
dataType: 'json'
})
.done(function(grandchildren){
var insertHTML = '';
grandchildren.forEach(function(grandchild){
insertHTML += appendOption(grandchild);
});
appendgrandChidrenBox(insertHTML);
$(document).on('change', '#child_category',function(){
$('#grandchildren_wrapper').remove();
})
})
.fail(function(){
alert('カテゴリー取得に失敗しました');
})
}
});
});



routes.rb

Rails.application.routes.draw do

devise_for :users
root to: "items#index"
# ここから itemsコントローラのAjax通信でのアクション先指定
resources :items do
collection do
get 'category_children'
get 'category_grandchildren'
end
end
# ここまで
end


items_controller.rb

  def new

@category = Category.all.order("id ASC").limit(13) # categoryの親を取得
end

def category_children
@category_children = Category.find(params[:productcategory]).children
end
# Ajax通信で送られてきたデータをparamsで受け取り、childrenで子を取得

def category_grandchildren
@category_grandchildren = Category.find(params[:productcategory]).children
end
# Ajax通信で送られてきたデータをparamsで受け取り、childrenで孫を取得。(実際には子カテゴリーの子になる。childrenは子を取得するメソッド)



category_children.json.jbuilder

json.array! @category_children do |child|

json.id child.id
json.name child.category_name
end


ategory_grandchildren.json.jbuilder

json.array! @category_grandchildren do |grandchild|

json.id grandchild.id
json.name grandchild.category_name
end


new.html.haml

.product_select-details

.product_select-group
.product_header
カテゴリー
.product_require
必須
.product_category-select
= f.collection_select :category, @category, :id, :category_name, { prompt: "---" }, { class: "category_select-box", id: "category_select" }
%i.fa.fa-chevron-down
.product_select-children


さいごに

間違っているところがあればご指摘下さい