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

Introduction

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);
      });
    })
  });
})

Way of thinking

-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!

routing

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

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

JS processing

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);
      });
    })
  });
//=============================================
})

Finally, HTML

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

At the end

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!

Recommended Posts

Display and post multi-layered data by ancestry !! ~ Ajax ~
[Rails] Display of multi-layered data using select boxes