2

I'm trying to create a multi-level dropdown using Bootstrap and some JSON data.

Ideally, I want the dropdown to have this kind of nested functionality:

enter image description here

I'm working with an array of objects with nested data, and it looks like:

[
  { 
    ID: 1,
    Title: "Middle Earth",
    Locations: [
        {
           Category: "Lothlorien",
           Child: [
             {
               PersonTitle: "Galadriel",
               Link: "https://www.galadriel.lot"
             },
             {
               PersonTitle: "Celeborn",
               Link: "https://www.celeb.lot"
             }
           ]
        },
        {
           Category: "Mordor",
           Child: [
             {
               PersonTitle: "Saruman",
               Link: "https://www.srmn.org"
             }
           ]
        }
    ]
  },
  {
    ID: 2,
    Title: "Arrakis",
    Locations: [...]
  }
]

Questions:

  • Can I create the submenu functionality with a template literal, and perform the mapping from there? (see code below)
  • Or do I have to create separate $.each or forEach blocks for every layer of the dropdown? For instance, "For every region (Middle Earth, Arrakis, etc) create 1 layer of the dropdown. Then for every Location within that region, create a submenu. Then for every PersonTitle within that Location, create..." etc.

I've worked with arrays of objects before, but not with this level of complexity, nor have I created submenu dropdowns with data.


Here's a code sample:

console.log("regions", regionsData);

if (regionsData.length > 0) {
          $.each(regionsData, (index, item) => {
            const csContainer = $(".mo_cs-dropdown");

            const dropdownGroup = `
            <div class="submenu-item">
                <a class="dropdown-item dropdown-toggle dropright" data-region="${item.Title}" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">${item.Title}</a>
                <div class="dropdown-menu">
                    <li class="dropdown dropend">
                        <ul class="dropdown-menu aria-labelledby="dropdownMenuLink">
                            <li class="dropdown dropend">
                            ${Object.keys(
                                item.Locations.map(key => {
                                    return `<a class='dropdown-item dropdown-toggle dropright' href='#' role='button' data-toggle='dropdown' aria-haspopup='true' aria-expanded='false'>${item.Locations.Child}</a>`;
                                })
                            )}
                            <ul class="dropdown menu" aria-labelledby="">
                                <li><a class="dropdown-item" href="#">test item</a></li>
                            </ul>
                            </li>
                        </ul>
                    </li>
                </div>
            </div>`;

            csContainer.append(dropdownGroup);
          });
} else {
...
}

Here's a screencap of console.log(regionsData) w/ redacted info: https://i.stack.imgur.com/HQEXo.png

Bodrov
  • 840
  • 1
  • 15
  • 29

1 Answers1

2

You need to loop through arrays and on each iteration you can append htmls inside some variable using += .Then , append this html generated inside your ul tag .

I have taken some codes from this post as we need to control each submenu click you can use jquery code so on each click add/remove show class from other submenu .

Demo Code :

var regionsData = [{
    ID: 1,
    Title: "Middle Earth",
    Locations: [{
        Category: "Lothlorien",
        Child: [{
            PersonTitle: "Galadriel",
            Link: "https://www.galadriel.lot"
          },
          {
            PersonTitle: "Celeborn",
            Link: "https://www.celeb.lot"
          }
        ]
      },
      {
        Category: "Mordor",
        Child: [{
          PersonTitle: "Saruman",
          Link: "https://www.srmn.org"
        }]
      }
    ]
  },
  {
    ID: 2,
    Title: "Arrakis",
    Locations: [{
      Category: "Lothlorien1",
      Child: [{
          PersonTitle: "Galadriel1",
          Link: "https://www.galadriel.lot"
        },
        {
          PersonTitle: "Celeborn",
          Link: "https://www.celeb.lot"
        }
      ]
    }]
  }
]
var csContainer = $(".mo_cs-dropdown");
$.each(regionsData, (index, item) => {
  //append li
  var dropdownGroup = `<li class="dropdown-submenu"><a class="dropdown-item dropdown-toggle"  id="${index}_main" data-region="${item.Title}" href="#"  >${item.Title}</a>
                            <ul class="dropdown-menu" aria-labelledby="${index}_main">`
  //loop through loaction array
  $.each(item.Locations, (index, items) => {
    dropdownGroup += ` <li class="dropdown-submenu"><a class="dropdown-toggle dropdown-item"  id="${index}_inner"  href="#" >${items.Category}</a><ul class="dropdown-menu" aria-labelledby="${index}_inner">`
    //loop through child array
    $.each(items.Child, (index, items) => {
      dropdownGroup += ` <li><a class="dropdown-item "  href="${items.Link}" aria-labelledby="${index}_inner"  href="#">${items.PersonTitle}</a></li>`
    })
    dropdownGroup += `</ul></li>` //closes tags
  })

  dropdownGroup += `</ul></li>`; //close tags..

  csContainer.append(dropdownGroup);
});
//on click of `a` tag
$(document).on('click', '.dropdown-menu a.dropdown-toggle', function(e) {
  if (!$(this).next().hasClass('show')) {
    $(this).parents('.dropdown-menu').first().find('.show').removeClass('show'); //remove show classs from others
  }
  var $subMenu = $(this).next('.dropdown-menu');
  $subMenu.toggleClass('show');
  //on hidden...remove class as well
  $(this).parents('li.nav-item.dropdown.show').on('hidden.bs.dropdown', function(e) {
    $('.dropdown-submenu .show').removeClass('show');
  });
  return false;//prevnt deafult..
});
.dropdown-submenu {
  position: relative;
}

.dropdown-submenu a::after {
  transform: rotate(-90deg);
  position: absolute;
  right: 6px;
  top: .8em;
}

.dropdown-submenu .dropdown-menu {
  top: 0;
  left: 100%;
  margin-left: .1rem;
  margin-right: .1rem;
}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">

<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.11.0/umd/popper.min.js" integrity="sha384-b/U6ypiBEHpOf/4+1nzFpr53nxSS+GLCkfwBdFNTxtclqqenISfwAzpKaMNFNmj4" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/js/bootstrap.min.js" integrity="sha384-h0AbiXch4ZDo7tp9hKZ4TsHbi047NrKGLO3SEJAg45jXxnGIfYzk4Si90RDIqNm1" crossorigin="anonymous"></script>

<nav class="navbar navbar-expand-lg navbar-light bg-light">
  <a class="navbar-brand" href="#">Navbar</a>
  <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
    <span class="navbar-toggler-icon"></span>
  </button>
  <div class="collapse navbar-collapse" id="navbarNavDropdown">

    <ul class="navbar-nav">
      <li class="nav-item active">
        <a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
      </li>
      <li class="nav-item dropdown">
        <a class="nav-link dropdown-toggle" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
          Dropdown link
        </a>
        <ul class="dropdown-menu mo_cs-dropdown" aria-labelledby="dropdownMenuLink">
        </ul>
        <li>
    </ul>
  </div>
</nav>
Swati
  • 28,069
  • 4
  • 21
  • 41
  • 1
    Thank you very much @swati, it worked perfectly. I had considered going with the nested `$.each` for each level, but I wasn't implementing it right and tried some other ways to display the data. – Bodrov Jun 16 '21 at 01:19