skip to Main Content

I am trying to dynamically create this bootstrap 4 dropdown (see answer by @Gerhard Gotz).

The hard coded list works for me, however, I am trying to dynamically create this list and then populate it.

CSS:

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

HTML:

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
<div class="collapse navbar-collapse" id="navbarNavDropdown">
</div>

Javascript:

$(document).ready(function () {
var data = [
    {
        "title": "Home",
        "child": null
    },
    {
        "title": "Dropdown link",
        "child": [
            {
            "title": "Action",
            "child": null
            },
            {
            "title": "Another Action",
            "child": null
            },
            {
                "title": "Submenu",
                "child": [{
                    "title": "Submenu action",
                    "child": null
                },
                    {
                        "title": "Another submenu action",
                        "child": null
                    },
                    {
                        "title": "Subsubmenu",
                        "child": [{
                            "title": "Subsubmenu action",
                            "child": null
                        },
                            {
                                "title": "Another subsubmenu action",
                                "child": null
                            }
                        ]
                    },
                    {
                        "title": "Second subsubmenu",
                        "child": [{
                            "title": "Subsubmenu action",
                            "child": null
                        },
                            {
                                "title": "Another subsubmenu action",
                                "child": null
                            }]
                    }
                ]
            }
        ]
    }
];

var list_html = "<ul class='navbar-nav'>";
var li_class = "nav-item";
var a_class = "nav-link";
var a_props = "";

for (var i = 0; i < data.length; i++) {
    //if (i == 0)
    //    li_class += " active";
    
    if (data[i].child != null) {
        li_class += " dropdown";
        a_class += " dropdown-toggle";
        a_props = " data-toggle='dropdown' id='navbarDropdownMenuLink'";
    }

    list_html += "<li class='" + li_class + "'><a class='" + a_class + "'" + a_props + ">" + data[i].title + "</a>"; 

    if (data[i].child != null) {
        list_html += CreateDynamicList(data[i], true);

    }

    list_html += "</li>";
}
list_html += "</ul>";
console.log(list_html);

$("#navbarNavDropdown").html(list_html);

$('.dropdown-menu-new a.dropdown-toggle').on('click', function (e) {
    console.log("here");
    console.log($(this));
    if (!$(this).next().hasClass('show')) {
        $(this).parents('.dropdown-menu-new').first().find('.show').removeClass('show');
    }
    var $subMenu = $(this).next('.dropdown-menu-new');
    $subMenu.toggleClass('show');


    $(this).parents('li.nav-item.dropdown.show').on('hidden.bs.dropdown', function (e) {
        $('.dropdown-submenu-new .show').removeClass('show');
    });


    return false;
});
}); // end document ready

function CreateDynamicList(data, isMainFunction) {
var idText = "";

if (isMainFunction)
    idText += " aria-labelledby='navbarDropdownMenuLink'";

var list_html = "<ul class='dropdown-menu-new'" + idText + ">";

//console.log(data);
//console.log(data.child);
if (data.child != null)
    for (var i = 0; i < data.child.length; i++) {
        //if child has no child then else
        if (data.child[i].child == null)
            list_html += "<li><a class='dropdown-item'>" + data.child[i].title + "</a></li>";
        else
            list_html += "<li class='dropdown-submenu-new'><a class='dropdown-item dropdown-toggle'>" + data.child[i].title + "</a>" + (data.child != null ? CreateDynamicList(data.child[i], false) : "") + "</li>";

        console.log("loop: " + data.child[i].title);
    }
else
    list_html += "<li><a class='dropdown-item'>" + data.title + "</a></li>";

list_html += "</ul>";
//console.log(list_html);
return list_html;
}

The list is correctly recreated, however, the show class is not being attached on click event. So, particularly the following code is not working:

$('.dropdown-menu-new a.dropdown-toggle').on('click', function (e) {
    console.log("here");
    console.log($(this));
    if (!$(this).next().hasClass('show')) {
        $(this).parents('.dropdown-menu-new').first().find('.show').removeClass('show');
    }
    var $subMenu = $(this).next('.dropdown-menu-new');
    $subMenu.toggleClass('show');


    $(this).parents('li.nav-item.dropdown.show').on('hidden.bs.dropdown', function (e) {
        $('.dropdown-submenu-new .show').removeClass('show');
    });


    return false;
});

Also note, "here" is always console logged on click, but $(this) is not!

2

Answers


  1. Chosen as BEST ANSWER

    Ok so I found the issue with my code was that the first tier was built differently and had different classes. So, while the rest of the code was working as expected but the first one was not (which felt like nothing was working).

    Also, my .min file was not being updated for a while as I was updating and checking my code. For this issue, I cleaned and rebuilt my code.

    The answers given by both @Phil and @Death-is-the-real-truth are still correct. I have added the following pieces of code to make it work in document.ready function.

    $('#navbarNavDropdown').on('click', '.nav-item a.nav-link', function (e) {
        console.log($(this).next('.dropdown-menu-new').hasClass('show'));
    
        if (!$(this).next().hasClass('show')) {
            $(this).parents('.dropdown-menu-new').first().find('.show').removeClass('show');
        }
        var $subMenu = $(this).next('.dropdown-menu-new');
        $subMenu.toggleClass('show');
    
    
    
        $(this).parents('li.nav-item.dropdown.show').on('hidden.bs.dropdown', function (e) {
            $('.dropdown-submenu-new .show').removeClass('show');
        });
    
    
        return false;
    });
    $('.dropdown-menu-new a.dropdown-toggle').on('click', function (e) {
        console.log($(this).next('.dropdown-menu-new').hasClass('show'));
    
        if (!$(this).next().hasClass('show')) {
            $(this).parents('.dropdown-menu-new').first().find('.show').removeClass('show');
        }
        var $subMenu = $(this).next('.dropdown-menu-new');
        $subMenu.toggleClass('show');
    
        
    
        $(this).parents('li.nav-item.dropdown.show').on('hidden.bs.dropdown', function (e) {
            $('.dropdown-submenu-new .show').removeClass('show');
        });
    
    
        return false;
    });
    

  2. You need to use event delegation concept to make your code work.

    I have added additional CSS to show you that the code worked.

    Working snippet:

    $(document).ready(function() {
      var data = [{
          "title": "Home",
          "child": null
        },
        {
          "title": "Dropdown link",
          "child": [{
              "title": "Action",
              "child": null
            },
            {
              "title": "Another Action",
              "child": null
            },
            {
              "title": "Submenu",
              "child": [{
                  "title": "Submenu action",
                  "child": null
                },
                {
                  "title": "Another submenu action",
                  "child": null
                },
                {
                  "title": "Subsubmenu",
                  "child": [{
                      "title": "Subsubmenu action",
                      "child": null
                    },
                    {
                      "title": "Another subsubmenu action",
                      "child": null
                    }
                  ]
                },
                {
                  "title": "Second subsubmenu",
                  "child": [{
                      "title": "Subsubmenu action",
                      "child": null
                    },
                    {
                      "title": "Another subsubmenu action",
                      "child": null
                    }
                  ]
                }
              ]
            }
          ]
        }
      ];
    
      var list_html = "<ul class='navbar-nav'>";
      var li_class = "nav-item";
      var a_class = "nav-link";
      var a_props = "";
    
      for (var i = 0; i < data.length; i++) {
        if (data[i].child != null) {
          li_class += " dropdown";
          a_class += " dropdown-toggle";
          a_props = " data-toggle='dropdown' id='navbarDropdownMenuLink'";
        }
    
        list_html += "<li class='" + li_class + "'><a class='" + a_class + "'" + a_props + ">" + data[i].title + "</a>";
    
        if (data[i].child != null) {
          list_html += CreateDynamicList(data[i], true);
    
        }
    
        list_html += "</li>";
      }
      list_html += "</ul>";
    
      $("#navbarNavDropdown").html(list_html);
    
      function CreateDynamicList(data, isMainFunction) {
        var idText = "";
    
        if (isMainFunction)
          idText += " aria-labelledby='navbarDropdownMenuLink'";
    
        var list_html = "<ul class='dropdown-menu-new'" + idText + ">";
    
    
        if (data.child != null)
          for (var i = 0; i < data.child.length; i++) {
            //if child has no child then else
            if (data.child[i].child == null)
              list_html += "<li><a class='dropdown-item'>" + data.child[i].title + "</a></li>";
            else
              list_html += "<li class='dropdown-submenu-new'><a class='dropdown-item dropdown-toggle'>" + data.child[i].title + "</a>" + (data.child != null ? CreateDynamicList(data.child[i], false) : "") + "</li>";
          }
        else
          list_html += "<li><a class='dropdown-item'>" + data.title + "</a></li>";
    
        list_html += "</ul>";
        return list_html;
      }
    });
    
    $("#navbarNavDropdown").on('click', '.dropdown-menu-new a.dropdown-toggle', function(e) {
      if (!$(this).next().hasClass('show')) {
        $(this).parents('.dropdown-menu-new').first().find('.show').removeClass('show');
      }
      var $subMenu = $(this).next('.dropdown-menu-new');
      $subMenu.toggleClass('show');
    
      return false;
    });
    
    $('li.dropdown a').on('hidden.bs.dropdown', function(e) {
      alert('here');
      $('.dropdown-submenu-new').toggle();
    });
    .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;
    }
    
    .show {
      color: red;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
    <div class="navbar-collapse" id="navbarNavDropdown">
    </div>

    Note: instead of .toggleClass('show') use .toggleClass('collapse') to make drop-downs open/close perfectly.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search