[RUBY] Implementation of category selection form using JS in flea market application creation

Introduction

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

What you can see in this article

--Display a 3-step category selection form via Ajax communication

Goals you want to achieve with this feature

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. Image from Gyazo

Development environment

Rough flow

  1. Install gem
  2. Create a controller and JS file for using JS, and describe the call.
  3. Add routing.
  4. Set the #search function to search data on the controller.
  5. Edit the js file.
  6. Edit the json.jbuilder file used for Ajax communication.
  7. Repeat 4-6 above with child elements and grandchild elements.
  8. Add an action when the child element / grandchild element is changed.
  9. Completion

As a premise --It is assumed that data creation using ancestry has been completed.

Specific mounting procedure

0. Install gem

First, install the required gem.

gem jquery-rails
bundle install

1. Write to use JavaScript

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

2. Add routing

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

3. Create a category selection form in the view

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"}

4. Add search action to controller

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 ...)


Then, set the search action for the category search of children and grandchildren.

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.

5. Create json.jbulder file

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.

6. Edit the js file and receive the data sent by the above action

  1. First, create a "child element selection form" that appears after selecting the parent element.

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,

  1. Get the data of the parent element and assign it to parentValue.
  2. If the data is not the initial value with if ~, ajax communication.
  3. Use append in .done to display the child element form created in the previous step after the parent element form.
  4. Extract the data extracted by option one by one and display it.

This completes the display of child elements!
If you select the parent element, the child element form will be displayed.

4. Create a display of grandchild elements.

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.

5. Add a description to initialize the form.

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.

This is really complete! !!


After this, I inadvertently forgot to tell the team members "db: seed", There was a situation like "I can't select a category !! Why ?!", but I was able to solve it safely.

Referenced articles

[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

Implementation of category selection form using JS in flea market application creation
[Rails] Implementation of multi-layer category function using ancestry "Creation form"
Implementation of digit grouping in flea market apps
[Rails] Implementation of multi-layer category function using ancestry "Editing form"
[Rails] Implementation of retweet function in SNS application
CSRF countermeasure policy and implementation example in REST application using "Spring Boot" + "EXT JS"
[Rails] Implementation of multi-layer category function using ancestry "seed"
[Rails] Implementation of category function
Implementation of category pull-down function
Implementation of gzip in java
Implementation of tri-tree in Java
Implementation of HashMap in kotlin
[Rails] Implementation of new registration function in wizard format using devise
Let's create a TODO application in Java 4 Implementation of posting function
Let's create a TODO application in Java 6 Implementation of search function
Let's create a TODO application in Java 8 Implementation of editing function