Implemented the function to dynamically change the choices using the category data created by ancestry.
Post as a learning memo. I'm still not familiar with it, but I hope it helps!
Completed form
https://gyazo.com/8a5adc080698873d544b8665855c0901
Below is the completed code!
routes
resources :products, except: [:index] do
get 'new/children_category', to: 'products#children_category'
get 'new/grandchildren_category', to: 'products#grandchildren_category'
end
puroducts_controller
before_action :set_categories, only: [:edit, :update]
~abridgement~
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
puroducts/new_html_haml
.input-field__contents
.input-field__contents-data
%p.subline
Product details
.input-field__contents-image__headline
.headlabel
= f.label :category_id, "Category"
%span.necessary
Mandatory
.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"})
category_js
$(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('error')
});
});
$(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);
});
})
});
})
-Select the parent category and fire the event, then append the ** child ** category. -Select a child category and fire an event, then append the ** grandchild ** category. · Use ajax to create a ** path ** for the child and grandchild categories to be displayed ・ Finally, the value of the grandchild category will be saved.
It's roughly like this.
Let's take a look one by one!
As a process flow of the program, finally display the child category and the parent category in view. It actually does the processing with the controller and js, so it creates a path to the controller when the request meets.
routes
resources :products, except: [:index] do
#children_category Path to go to action
get 'new/children_category', to: 'products#children_category'
#grandchildren_category Path to go to action
get 'new/grandchildren_category', to: 'products#grandchildren_category'
end
As a premise, since ajax processing is performed, go to the controller after ajax processing is performed with js. At that time, the controller needs to find the value of the category and return it to js. Therefore, write as follows.
puroducts_controller
before_action :set_categories, only: [:edit, :update]
~abridgement~
def children_category
#.Use where to find the value from ancestry and assign it to an instance variable
@children_category = Category.where(ancestry: params[:parent_category_id])
#I will return the value searched from ancestry to js
render json: @children_category
end
def grandchildren_category
#.Use where to find the value from ancestry and assign it to an instance variable
@grandchildren_category = Category.where(ancestry: "#{params[:parent_category_id]}/#{params[:children_category_id]}")
#I will return the value searched from ancestry to js
render json: @grandchildren_category
end
In js, the event will be fired every time a category is selected. Specifically ・ When the ** parent ** category is selected, the event fires and the ** child ** element category is displayed. ・ When the ** child ** category is selected, the event fires and the ** grandchild ** element category is displayed.
As a process, when the event fires, ajax gets the value from the controller and ** for Each ** expresses everything.
category_js
//①=====Define view to be displayed in HTML ===========================
$(function(){
let buildPrompt = `<option value>---</option>`
let buildHtmlOption = function(parent) {
let option = `<option value ="${parent.id}">${parent.name}</option>`
return option
}
//=================================================
//②=====Processing to select the parent category and call the child category ============
$('#parent').change(function() {
let parent_id = $(this).val();
//Send to controller with ajax
$.ajax({
type: 'GET',
url: 'products/new/children_category',
data: {parent_category_id: parent_id},
dataType: 'json'
})
//The following is the processing after the response from the controller
.done(function(parent) {
$('.child').css('display', 'block');
$('#child').empty();
$('.grand_child').css('display', 'none');
$('#child').append(buildPrompt);
//Get all the values obtained from the controller with forEach,.Append to HTML element with append
parent.forEach(function(child) {
var html_option = buildHtmlOption(child);
$('#child').append(html_option);
});
})
.fail(function() {
alert('error')
});
});
//=============================================
//②=====Processing to select a child category and call a grandchild category ============
$(this).on("change", "#child", function() {
let parent_id = $("#parent").val();
let child_id = $("#child").val();
//Send to controller with ajax
$.ajax({
type: 'GET',
url: 'products/new/grandchildren_category',
data: {
parent_category_id: parent_id,
children_category_id: child_id
},
dataType: 'json'
})
//The following is the processing after the response from the controller
.done(function(parent) {
$('.grand_child').css('display', 'block');
$('#grand_child').empty();
$('#grand_child').append(buildPrompt);
//Get all the values obtained from the controller with forEach,.Append to HTML element with append
parent.forEach(function(child) {
var html_option = buildHtmlOption(child);
console.log(buildHtmlOption(html_option));
$('#grand_child').append(html_option);
});
})
});
//=============================================
})
The only point to note in HTML is whether there is a discrepancy between the id attribute in js and the id attribute in HTML.
However, a little trick is needed to save the value of the last grandchild category.
puroducts/new_html_haml
.input-field__contents
.input-field__contents-data
%p.subline
Product details
.input-field__contents-image__headline
.headlabel
= f.label :category_id, "Category"
%span.necessary
Mandatory
.sell__about__right__wrap-box.parent
%select.select-box1#parent
%option{value: 0} ---
#Display all values of parent category
- @parents.each do |parent|
%option{value: "#{parent.id}"} #{parent.name}
.child
# #The view defined in js is inserted in place of child
%select.select-box2#child
.grand_child
.select-box3
# id:grand_The view defined in js is inserted in place of child
#Also, in order to save the value of the selected grandchild category and save it correctly, write as follows.
= f.collection_select(:category_id, [], :id, :name, {prompt: "---"}, {id: "grand_child"})
As a supplement, please refer to the following site for the following description.
f.collection_select(:category_id, [], :id, :name, {prompt: "---"}, {id: "grand_child"})
#Reference description
#collection_select(Object name,Method name,Array of elements,Item of value attribute,Text item[,Options or HTML attributes or event attributes])
Reference article: https://railsdoc.com/page/collection_select
The process is not that complicated, so I checked each one and it worked!
If you get an error or don't get the value well, check with ** binding.pry **, ** console.log (); **, ** debugger **!
Thank you very much!