Search code examples
javascriptjquerybootstrap-4

dynamically create treeview dropdown - $(this) not working with click event


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!


Solution

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