I'm cloning a flea market app for the final assignment of a programming school. I will describe the contents of the work in implementing the "category selection function" required for the product listing function.
--Creating a pull-down selection form using JavaScript
--Display a 3-step category selection form via Ajax communication
When the parent element (ladies) is selected as shown below, the child element selection form is displayed, and when the child element is selected, the grandchild element selection form is displayed ** by asynchronous communication **. Also, if you change the choices of children and grandchildren, the form contents will be initialized or the form itself will disappear.
As a premise --It is assumed that data creation using ancestry has been completed.
First, install the required gem.
gem jquery-rails
bundle install
app/javascript/packs/application.js
require('jquery')
require('item/category')
config/webpack/environment.js
const { environment } = require('@rails/webpacker')
const webpack = require('webpack')
environment.plugins.prepend('Provide',
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
jquery: 'jquery',
})
)
module.exports = environment
What I immediately stumbled upon here was that my development environment was rails6. Most of the articles I searched on the net are information before rails 5 and In rails6, it seems that the generation location of the application.js file and the description for calling are different, I was confused because it didn't load well at first.
When developing, check the version and describe it according to your environment.
I referred to this article for the introduction of jquery in rails6. => How to install jquery in rails6
To add a search action to the items controller later Add the routing to the search action.
routes.rb
Rails.application.routes.draw do
resources :items do
collection do
get :search
end
end
end
This is just for reference of my description. In this way, I first created a form for the parent category.
ruby:qpp/view/items/new.html.haml
=form_with model @item do |f|
= f.collection_select :category_id, @parents, :id, :name, {prompt: "Please select"}, {class: "categoryForm", id: "category_form"}
Get the record with the new action.
items_controller
def new
@item = Item.new
@item_image = ItemImage.new
end
Also, I set before_action because I want to get the information of the parent element before the new action and create action.
ite,s_controller.rb
before_action :set_parents, only: [:new, :create]
def set_parents
@parents = Category.where(ancestry: nil)
end
ancestry = nil, that is, the value of the parent element is acquired and assigned to the variable @parents. The reason why set_parents is needed not only for new but also for create is I referred to the article in here. (Honestly, when I was writing the code, I knew it, I didn't know ...)
items_controller.rb
def search
respond_to do |format|
format.html
#ajax communication started
format.json do
#Child category information@Substitute for childrens
@childrens = Category.find(params[:parent_id]).children
end
end
end
As an aside, I felt uncomfortable adding "s" to the plural "children". I thought it would be difficult to understand how to use child and children properly, so I decided to use children.
ruby:app/view/items/search.json.jbuilder
json.array! @childrens do |children|
json.id children.id
json.name children.name
end
For the time being, only the child elements are listed for now.
category.js
//Form to select child elements
function add_childSelect_tag() {
let child_select_form = `
<select name="item[category_id]" id="item_category_id" class="child_category_id">
<option value="">Select a category</option>
</select>
`
return child_select_form;
}
I'm not good at writing this HTML part, but I feel that it's easiest to write by looking at the form of the parent element with the view validation tool.
name = "item [category_id] is the destination of the data.
Later, I want to delete or initialize the form of the child element (when the parent element is changed, etc.), so I also added the class name specific to the child element.
2. Describe the option to get the data in the displayed selection form.
category.js
function add_Option(children) {
let option_html = `
<option value=${children.id}>${children.name}</option>
`
return option_html;
}
3. Set the event that occurs after selecting the parent category.
category.js
//Events after selecting a parent category
$("#category_form").on("change", function() {
let parentValue = $("#category_form").val();
if (parentValue.length !== 0) {
$.ajax({
url: '/items/search',
type: 'GET',
data: { parent_id: parentValue},
dataType: 'json'
})
.done(function (data){
let child_select_form = add_childSelect_tag
$(".ItemInfo__category--form").append(child_select_form);
data.forEach(function(d){
let option_html = add_Option(d)
$(".child_category_id").append(option_html);
});
})
.fail(function (){
alert("Failed to get the category");
});
As a rough flow,
Basically, just repeat the steps for the child elements.
ruby:app/views/items/search.json.jbuilder
json.array! @grandchildrens do |grandchildren|
json.id grandchildren.id
json.name grandchildren.name
end
category.js
//Grandchild element selection form
function add_grandchildSelect_tag(){
let grandchild_select_form = `
<select name="item[category_id]" id="item_category_id" class="grandchild_category_id">
<option value="">Select a category</option>
</select>
`
return grandchild_select_form
}
categroy.js
//Events after selecting a child category
$(document).on("change", ".child_category_id", function(){
let childValue = $(".child_category_id").val();
if (childValue.length !=0){
$.ajax({
url: '/items/search',
type: 'GET',
data: { children_id: childValue},
dataType: 'json'
})
.done(function (gc_data){
let grandchild_select_form = add_grandchildSelect_tag
$(".ItemInfo__category--form").append(grandchild_select_form);
gc_data.forEach(function (gc_d){
let option_html = add_Option(gc_d);
$(".grandchild_category_id").append(option_html);
})
})
.fail(function (){
alert("Failed to get the category");
});
})
Now, when you select a child element, a grandchild element selection form will appear.
At first glance, it looks like it was completed in steps 1-4, If this is left as it is, the following problems will occur. --Even if you change the parent or child category, the contents of the child category remain the same. --If you change the child or grandchild category, a new category will appear under the grandchild.
In short, if you play with the child or grandchild category, the form will be added endlessly.
So, as a desirable behavior
--When you change the parent category, the grandchild category form disappears and the contents of the child category form change to the contents associated with the parent category. --If you change the child category, the contents of the grandchild category will change to the data associated with the child category. --If you set the parent or child category to the initial value (not selected), the form in the next hierarchy disappears.
This behavior must be achieved. Therefore, the following description was added.
category.js
//Events after selecting a parent category
$("#category_form").on("change", function() {
let parentValue = $("#category_form").val();
if (parentValue.length !== 0) {
$.ajax({
url: '/items/search',
type: 'GET',
data: { parent_id: parentValue},
dataType: 'json'
})
.done(function (data){
$(".child_category_id").remove();
$(".grandchild_category_id").remove();
let child_select_form = add_childSelect_tag
$(".ItemInfo__category--form").append(child_select_form);
data.forEach(function(d){
let option_html = add_Option(d)
$(".child_category_id").append(option_html);
});
})
.fail(function (){
alert("Failed to get the category");
})
}else{
$(".child_category_id").remove();
$(".grandchild_category_id").remove();
}
});
I added these two lines after ①.done
$(".child_category_id").remove();
$(".grandchild_category_id").remove();
(2) This part is divided into conditions by else after .fail.
else{
$(".child_category_id").remove();
$(".grandchild_category_id").remove();
}
Every time the content of the parent category changes in ①, the child / grandchild category is deleted once, and the child category appears on it. After the else statement in ②, when the parent selects the initial value.
I added the same code to the event part after selecting the child category.
And to activate these JS after loading all the pages,
category.js
window.addEventListener('load', function () {
}
This concludes the entire code.
[Rails] How to implement the category function of a certain flea market app Category function of Furima app ~ Use gem: ancestry ~ [Rails] Creating a category box using Ajax communication
Recommended Posts