[Java] [Rails] Implementation of multi-level category function using ancestry I made a window with Bootstrap 3

3 minute read

Target

ezgif.com-video-to-gif.gif

Development environment

・Ruby: 2.5.7 Rails: 5.2.4 ・Vagrant: 2.2.7 ・VirtualBox: 6.1 ・OS: macOS Catalina

Premise

Implemented below.

Slim introductionBootstrap3 installedFont Awesome introductionImplementation of login functionPosting function implementationMany-to-many category function implementationMulti-level category function implementation (preparation)Multi-level category function implementation (seed) ・[Multi-level category function implementation (creation form)] (https://qiita.com/matsubishi5/items/4afb4a4f307023126c66) ・[Multi-level category function implementation (edit form)] (https://qiita.com/matsubishi5/items/10e61f314f6c56b8690d)

1. Edit the controller

homes_controller.rb


# Addition
def category_window
  @children = Category.find(params[:parent_id]).children
end

[Explanation]

① Extract the child category of the category corresponding to the parameter sent by Ajax communication and assign it to the instance variable.

@children = Category.find(params[:parent_id]).children

2. Create/edit the json.jbuilder file

terminal


$ touch app/views/homes/category_window.json.jbuilder

ruby:category_window.json.jbuilder


json.array! @children do |children|
  json.id children.id
  json.name children.name
end

[Explanation]

① Iterate the records extracted by get_category_children action and create an array.

json.array! @children do |children|

② Store each ID and name in the array created in .

json.id children.id
json.name children.name

** ◎ Return value when the mouse is over the parent category (business) **

[
  {
    "id": 2,
    "name": "finance"
  },
  {
    "id": 6,
    "name": "economy"
  },
  {
    "id": 9,
    "name": "Management"
  },
  {
    "id": 13,
    "name": "marketing"
  },
]

** ◎ Return value when the mouse is over the child category (finance) **

[
  {
    "id": 3,
    "name": "share"
  },
  {
    "id": 4,
    "name": "Exchange"
  },
  {
    "id": 5,
    "name": "tax"
  },
]

3. Add routing

routes.rb


# Addition
get'get_category/new', to:'homes#category_window', defaults: {format:'json'}

4. Edit view

slim:application.html.slim


body
  header
    nav.navbar.navbar-default.navbar-fixed-top
      .container-fluid
        ul.nav.navbar-nav.navbar-right
          li.dropdown role='presentation'
            a.dropdown-toggle data-toggle='dropdown' href='#' role='button' aria-expanded='false'
              i.fas.fa-list-ul
              span
                | Search by category
              span.caret
            ul.dropdown-menu role='menu'
              li role='presentation'
                -Category.where(ancestry: nil).each do |parent|
                  = link_to parent.name, root_path, id: "#{parent.id}", class:'parent-category'
              br
              li role='presentation' class='children-list'
              br
              li role='presentation' class='grandchildren-list'

[Explanation]

** * I will omit about how to write Bootstrap. **

① The value of ancestry is nil, that is, all parent categories are extracted and displayed in the pull-down menu.

- Category.where(ancestry: nil).each do |parent|
  = link_to parent.name, root_path, id: "#{parent.id}", class:'parent-category'

② Prepare a place to display child categories.

li role='presentation' class='children-list'

③ Prepare a place to display the grandchild category.

li role='presentation' class='grandchildren-list'

5. Create/edit JavaScript file

terminal


$ touch app/assets/javascripts/category_window.js

category_window.js


$(function() {
  function buildChildHTML(children) {
    let html = `
      <a class="children-category" id="${children.id}" href="/">
        ${children.name}
      </a>
    `;
    return html;
  }

  $('.parent-category').on('mouseover', function() (
    let id = this.id;
    $('.children-category').remove();
    $('.grandchildren-category').remove();
    $.ajax({
      type:'GET',
      url:'/get_category/new',
      data: {
        parent_id: id,
      },
      dataType:'json',
    }).done(function(children) {
      children.forEach(function(child) {
        let html = buildChildHTML(child);
        $('.children-list').append(html);
      });
    });
  });

  function buildGrandChildHTML(children) {
    let html = `
      <a class="grandchildren-category" id="${children.id}" href="/">
        ${children.name}
      </a>
    `;
    return html;
  }

  $(document).on('mouseover','.children-category', function() {
    let id = this.id;
    $.ajax({
      type:'GET',
      url:'/get_category/new',
      data: {
        parent_id: id,
      },
      dataType:'json',
    }).done(function(children) {
      children.forEach(function(child) {
        let html = buildGrandChildHTML(child);
        $('.grandchildren-list').append(html);
      });
      $(document).on('mouseover','.children-category', function() {
        $('.grandchildren-category').remove();
      });});
  });
});

[Explanation]

① Create HTML for the child category.

function buildChildHTML(children) {
  let html = `
    <a class="children-category" id="${children.id}" href="/">
      ${children.name}
    </a>
  `;
  return html;
}

② Change the display contents of the child category depending on which parent category the mouse is over.

  $('.parent-category').on('mouseover', function() (
    let id = this.id;
    $('.children-category').remove();
    $('.grandchildren-category').remove();
    $.ajax({
      type:'GET',
      url:'/get_category/new',
      data: {
        parent_id: id,
      },
      dataType:'json',
    }).done(function(children) {
      children.forEach(function(child) {
        let html = buildChildHTML(child);
        $('.children-list').append(html);
      });
    });
  });

** ◎ Create an event that fires when the mouse enters the parent category. **

$('.parent-category').on('mouseover', function() {});

** ◎ Assign the ID sent from category_window.json.jbuilder to a variable. **

let id = this.id;

**◎ Delete the child category and below for the time being. **

$('.children-category').remove();
$('.grandchildren-category').remove();

** ◎ Set the variable created earlier in parameter (parent_id) and execute the category_window action asynchronously. **

  $.ajax({
    type:'GET',
    url:'/get_category/new',
    data: {
      parent_id: id,
    },
    dataType:'json',
  })

** ◎ If Ajax communication is successful, create and display HTML for the corresponding child category. **

.done(function(children) {
  children.forEach(function(child) {
    var html = buildChildHTML(child);
    $('.children-list').append(html);
  });
});

③ Create the grandchild category HTML.

function buildGrandChildHTML(children) {
  var html = `
    <a class="grandchildren-category" id="${children.id}" href="/">
      ${children.name}
    </a>
  `;
  return html;
}

④ Change the display content of the grandchild category depending on which child category the mouse is over. (Since it is almost the same as , the explanation is omitted)

$(document).on('mouseover','.children-category', function() {
  var id = this.id;
  $.ajax({
    type:'GET',
    url:'/get_category/new',
    data: {
      parent_id: id,
    },
    dataType:'json',
  }).done(function(children) {
    children.forEach(function(child) {
      var html = buildGrandChildHTML(child);
      $('.grandchildren-list').append(html);
    });
    $(document).on('mouseover','.children-category', function() {
      $('.grandchildren-category').remove();
    });
  });
});

Note

If you do not disable turbolinks, the pull-down menu will not work asynchronously, so be sure to disable it.

How to disable turbolinks