[Java] Display and post multi-layered data by ancestry!! ~Ajax~

4 minute read

Introduction

Using the category data created by ancestry, we implemented a function that dynamically changes the choices.

I will post it as a learning memo. There are still some things that I do not understand, but I hope it will be helpful!

Finished 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
           Required
           .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);
      });
    })
  });
})

Thought

・If you select the parent category and fire the event, append the child category ・If you select the child category and fire the event, append the ** grandchild ** category Use ajax to create a wayway for displaying child and grandchildren categories ・Finally, the value of the grandchild category is saved

It is roughly like this.

Let’s take a look at each one!

routing

As a processing flow of the program, the view finally displays the child category and the parent category. It actually does the work with the controller and js so it creates a path to the controller when a request is met.

routes



resources :products, except: [:index] do
Path to go to #children_category 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

controller

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 category value and return it to js. Therefore, write as follows.

puroducts_controller


before_action :set_categories, only: [:edit, :update]


~abridgement~

  def children_category
    Find value from ancestry using #.where and assign to instance variable
    @children_category = Category.where(ancestry: params[:parent_category_id])
Return the value found from #ancestry to js
    render json: @children_category
  end

  def grandchildren_category
Search for a value from ancestry using #.where and assign it to an instance variable
    @grandchildren_category = Category.where(ancestry: "#{params[:parent_category_id]}/#{params[:children_category_id]}")
Return the value found from #ancestry to js
    render json: @grandchildren_category
  end

JS processing

In js, I want the event to fire each time a category is selected. Specifically, ・If a parent category is selected, an event fires and a child category is displayed. ・If a child category is selected, an event fires and a grandchild element category is displayed.

As the process, when the event fires, the value will be acquired from the controller with ajax and all will be displayed with forEach.

category_js


// ①===== Define the 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 call the child category when the parent category is selected ============
  $('#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 values obtained from controller with forEach and add to HTML element with .append
      parent.forEach(function(child) {
        var html_option = buildHtmlOption(child);
        $('#child').append(html_option);
      });
    })
    .fail(function() {
      alert('error')
    });
  });
//====================================================


//②===== Processing for calling a grandchild category when a child category is selected ============
  $(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 values obtained from controller with forEach and add to HTML element with .append
       parent.forEach(function(child) {
        var html_option = buildHtmlOption(child);
         console.log(buildHtmlOption(html_option));
        $('#grand_child').append(html_option);
      });
    })
  });
//====================================================
})

Finally, HTML

The only thing to be careful about in HTML is whether it conflicts with the id attribute of js and the id attribute of HTML.

However, a little work is needed to save the values of the last grandchildren 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
           Required
           .sell__about__right__wrap-box.parent
             %select.select-box1#parent
               %option{value: 0} ---
            # Display all values in the 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
The view defined in js is inserted in place of # id:grand_child
            # Also, in order to save the value of the selected grandchild category correctly, write it 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, value attribute item, text item [, option or HTML attribute or event attribute])

Reference article: https://railsdoc.com/page/collection_select

At the end

The processing is not so complicated, so I checked each one and went well!

If you get an error or the value is not getting well, please check with binding.pry, console.log();, debugger!

Thank you very much!