主に使用する言語はRuby(on Rails), JSです。
動的なセレクトボックスとは?
伊藤さんの書いているこちらの記事を見てみてください。
ここで記述されているセレクトボックスのように親要素が選択された時に子要素のセレクトボックスの中身が選択された親要素に紐づいている子要素になるというものをここでは動的なセレクトボックスと呼んでいます。
上記の記事ではルーティングを設定したり、ajaxの記述をしたりと色々していますが、ある要素を使えばこのセレクトボックスは簡単に実装できます。
結論:template要素を使う
ここでもCategory, SubCategoryのモデルでアソシエーションを組んで実装していきましょう。
class Category < ApplicationRecord
has_many :sub_categories
end
class SubCategory < ApplicationRecord
belongs_to :category
end
このアソシエーションによってCategoryモデルとSubCategoryモデルで1対多の関係性を築けました。
次はビューファイルの編集です。
= f.collection_select :category, Category.all, :id, :name, include_blank: "カテゴリーを選択してください"
- Category.all.each do |category|
%template{id: "sub-category-of-category#{category.id}"}
= f.collection_select :sub_category, categroy.sub_categories, :id, :name, include_blank: "サブカテゴリーを選択してください"
今の状況はこんな感じです。
<select name="category" id="category">
<option value="1">カテゴリー1</option>
.
.
.
</select>
<template id="sub-category-of-category1">#document-fragment</template>
<template id="sub-category-of-category2">#document-fragment</template>
<template id="sub-category-of-category3">#document-fragment</template>
<template id="sub-category-of-category4">#document-fragment</template>
<template id="sub-category-of-category5">#document-fragment</template>
.
.
.
template要素とは何か??
template要素は、ページの読み込み時に描画されず、後で JavaScript を使用してインスタンスを生成できるクライアント側のコンテンツを保持するメカニズムです。
(引用元:https://developer.mozilla.org/ja/docs/Web/HTML/Element/template )
簡単にいうと、JavaScriptで操作して本文へと挿入しない限りはデベロッパーツールのHTML上には表示されるがビューの見た目には何も影響しないということです。
なのでjsファイル内での記述も書いていきましょう。
今回はjQueryで書いていきます。
jsファイル内の記述
$(document).on('turbolinks:load', function() {
$(document).on('change', '#category', function() {
let categoryVal = $('#category').val();
if (categoryVal !== "") {
let selectedTemplate = $(`#sub-category-of-category${categoryVal}`);
$('#category').after(selectedTemplate.html());
};
});
});
ただ、このままだと初めは親要素のセレクトボックスしかなく、親要素を選択した際に子要素のセレクトボックスが現れるといった記述になることに加えて、一度選択して二度目の選択をする際に新しいセレクトボックスが生成されるという状況になってしまう。
少しビューファイルとjsファイルをいじってそれを直していきます。
= f.collection_select :category, Category.all, :id, :name, include_blank: "カテゴリーを選択してください"
= f.select :sub_category, [], include_blank: "サブカテゴリーを選択してください", class: "default-sub-category-select"
- Category.all.each do |category|
%template{id: "sub-category-of-category#{category.id}"}
= f.collection_select :sub_category, categroy.sub_categories, :id, :name, include_blank: "サブカテゴリーを選択してください"
これでページの読み込み時に親要素と子要素(option要素は何もない)が出来上がっている状況である。
ちなみにこの時のデフォルトで表示されている子要素のセレクトボックスのidが'sub_category'になっているということだけ覚えておいていただきたい。(jsファイルでの記述に使うから。)
そこからjsファイルの編集にうつる。
$(document).on('turbolinks:load', function() {
//HTMLが読み込まれた時の処理
let categoryVal = $('#category').val();
//一度目に検索した内容がセレクトボックスに残っている時用のif文
if (categoryVal !== "") {
let selectedTemplate = $(`#sub-category-of-category${categoryVal}`);
$('#sub_category').remove();
$('#category').after(selectedTemplate.html());
};
//先ほどビューファイルに追加したもともとある子要素用のセレクトボックスのHTML
let defaultSubCategorySelect = `<select name="sub_category" id="sub_category">
<option value>サブカテゴリーを選択してください</option>
</select>`;
$(document).on('change', '#category', function() {
let categoryVal = $('#category').val();
//親要素のセレクトボックスが変更されてvalueに値が入った場合の処理
if (categoryVal !== "") {
let selectedTemplate = $(`#sub-category-of-category${categoryVal}`);
//デフォルトで入っていた子要素のセレクトボックスを削除
$('#sub_category').remove();
$('#category').after(selectedTemplate.html());
}else {
//親要素のセレクトボックスが変更されてvalueに値が入っていない場合(include_blankの部分を選択している場合)
$('#sub_category').remove();
$('#category').after(defaultSubCategorySelect);
};
});
});
これで最初のビューに親要素・子要素のセレクトボックスが存在し、親要素が選択されていない場合はoption要素が何もないデフォルトのセレクトボックスが表示され、親要素が選択された際にその要素と紐づいた子要素が入ったセレクトボックスが現れるようになったと思う。ただIEがtemplate要素に対応してないみたいなの見たきがしないでもないからその辺りは注意してください。
リファクタリングはまた今度します。