Dynamically add items to bootstrap dropdown list using Webcore

Hello everyone, I would like to know if this is possible

I have a navbar that contains a div with the class dropdown-menu I would like to add elements to this menu but dynamically (from an endpoint I get the elements) how can I inject these elements to the menu using webcore to html

Thanks for your help!

In the TMS Blog series I've been working on about adding JS Libraries, there's a Sample Project that incorporates whatever is being covered in the blog at that stage, but it is an ongoing project. The current version of that project (which is more recent that then latest blog posting as the blog postings are written in advance) has a full multi-level bootstrap nav-based menu included it in now. I don't actually cover it in the blog. Too many other things to talk about!

This is mostly done in JavaScript in the app at the moment, which works pretty well in TMS WEB Core of course, but could be adjusted to be more Delphi and less JavaScript. Here's the GenerateMenu function. The gist of it is that the menu is created as basically a giant HTML unordered list <ul> where each menu item is a <li> having an <a> tag with all the bootstrap classes assigned in the appropriate places to create the dropdown arrows and the rest of it.

The menu is constructed a piece at a time by creating the <ul> elements and then subsequently adding the individual <li><a>Menu Item</a></li> elements after the <ul> elements are added, keeping in mind that the menu could be rendered after each line of code and still be a valid menu. So it is dynamic in that sense, but as long as you know your menu structure, you can add more entries anytime you like. Here's the full block of code to get you started. As a bit of a sneak peek, you can see the Sample Project using this menu live at https://www.500foods.com/spv5.

procedure TMainForm.GenerateMainMenu;
var
  DisplayIcon: string;
begin

  // This is an example of how to build a multi-level Bootrap NavBar menu.  The goal is to have a main menu of options,
  // each with a vertical list of options beneath them.  And for each of those options, at least two more levels of
  // menus to be able to fit in and organize everthing.  The same approach can be extended to greater depths, but if
  // you go too much deeper it can potentially become frustrating to use.
  //
  // Some effort is also spent on making this menu responsive, meaning that on a smaller screen it will shrink to a
  // single button and then be a more traditional list-style of menu.  It works reasonably well, but that's not
  // the priority here in this case.
  //
  // What might be the priority is trying to get this into a format that is easy enough to edit and extend with
  // more options and more examples of what menu links might be able to do all on their own.


  // The very first nav bar element is going to be an icon for the project.  Nothing to interact with, so let's
  // get that set first  as it is the easiest thing we'll be doing here.
  DisplayIcon := ProjectIcon+ProjectNameShort;


  // Next up, we'll need a nav bar object to work with.  This is a TWebHTMLDiv defined in the IDE.  We could set this
  // component's HTML property in the IDE just as easily, but I'm including it here just to make it easier to reference.
  // What we'll primarily be doing is populating the <ul> at the end with <li> elements and nested <ul><li> elements
  // to define the menu structure with <a> elements used to display the menu links with icons, etc.
  divNavBar.HTML.Text := '<nav class="m-1 p-0 navbar navbar-expand-sm navbar-dark bg-none">'+
                         '  <button class="btn btn-primary pe-none Control" style="background-color: #dc3545 !important; border:none;"> Project Name </button>'+
                         '  <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#main_nav"  aria-expanded="false" aria-label="Toggle navigation">'+
                         '    <span class="navbar-toggler-icon"></span>'+
                         '  </button>'+
                         '  <div class="collapse navbar-collapse" id="main_nav">'+
                         '    <ul class="navbar-nav">'+
                         '    </ul>'+
                         '  </div>'+
                         '</nav>';

  asm
    // The Project icon is just a button with pe-none to make sure it can't be interacted with.
    // We could've set this in the text above, but we're going to be doing this a lot and it makes for a good example.
    // here we're looking at children[0] which is the <nav> element, and then children[0] again which is the first button
    // element, and then we're changing the HTML from [ Project Name ] to be what we want it to be.
    divNavBar.children[0].children[0].innerHTML = DisplayIcon;

    // The root of the menu is then going to be placed in children[0] which is the main <nav> element again, but  then
    // in children[2] which is the third <div> above, and then within that, children[0] is the <ul>.  Here we're emptying
    // it as during development and testing it had a complete menu in the HTML property, so this would just reset it.
    var menu = divNavBar.children[0].children[2].children[0];
    menu.innerHTML = '';

    // From here on, we'll indent the code to make it clear what level we're working at and also add some lines to make
    // it abundantly clear.  Also, at each step we'll define a variable to reference where we're adding elements.  The
    // gist of it is we're either appending<li> elements to expand an existing <ul> with more entries, or we'll be adding
    // new <ul> elements to be the structure for a submenu.  this is typically broken up into three lines:
    //
    // - <li> element that also defines the common parts of the <a> element
    // - The actual content of the <a> element that the user sees - the actual menu item, typically with an icon to go with it
    // - The rest of the <li> and <a> definition along with potentially a new <ul> if we'll be adding a submenu at that point
    //
    // The idea is to be able to copy & paste these blocks more or lease directly, and just update the middle line with the
    // new menu item.  Due care must be taken with submenus to also define the ids that trigger opening and closing.
    //
    // Some other notes here.
    // - <a> elements have a href="#" entry which is far more important than it looks
    // - user-select-none is intended to make it so you can't drag menu items around and so it works like an actual menu
    // - draggable="false" is intended to do the same thing for different browsers.  Not sure if it always works though.
    // - the submenu or subsubmenu, etc are pointing at the last <ul> that will then be appended to with child menu items
    // - Elements are added as complete blocks so that it always represents a valid block of HTML as it could be rendered
    //   between each of these bits of code.
    // - the id assigned to an item is used later to determine what item was clicked on.
    // - disabled is the class added to menu items that are not yet implemented, of which there are many


    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Project Menu - This is where we want to be able to save and Load projects
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    menu.innerHTML += '<li class="nav-item dropdown" id="menuProject"><a draggable="false" href="#" class="clickable nav-link dropdown-toggle ps-4 user-select-none" data-bs-toggle="dropdown">'+
                      '<i class="fas fa-archive fa-lg me-2"></i>Project'+
                      '</a><ul class="p-0 m-0 dropdown-menu"></ul></li>';
    var submenu = menu.children[0].children[1];

        submenu.innerHTML += '<li><a draggable="false" href="#" id="menuProjectSave" class="clickable disabled ps-2 pe-3 dropdown-item user-select-none">'+
                             '<i class="fas fa-download fa-fw me-2"></i>Download Project'+
                             '</a></li>';

        submenu.innerHTML += '<li><a draggable="false" href="#" id="menuProjectLoad" class="clickable disabled ps-2 pe-3 dropdown-item user-select-none">'+
                             '<i class="fas fa-upload fa-fw me-2 user-select-none"></i>Upload Project'+
                             '</a></li>';

        // Divider /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        submenu.innerHTML += '<li><hr class="dropdown-divider"></li>';
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        submenu.innerHTML += '<li><a draggable="false" href="#" id="menuSampleProjects" class="clickable disabled ps-2 pe-3 dropdown-item user-select-none">'+
                             '<i class="fas fa-gifts fa-fw me-2 user-select-none"></i>Sample Projects'+
                             '</a></li>';

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Add Menu - Big Menu to list all the objects we can add
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    menu.innerHTML += '<li class="nav-item dropdown" id="menuAdd"><a draggable="false" href="#" class="clickable nav-link dropdown-toggle ps-4 user-select-none" data-bs-toggle="dropdown">'+
                       '<i class="fas fa-puzzle-piece fa-lg me-2"></i>Add'+
                       '</a><ul class="p-0 m-0 dropdown-menu"></ul></li>';
    var submenu = menu.children[1].children[1];

        submenu.innerHTML += '<li><a draggable="false" href="#" id="menuFavorites" class="clickable disabled ps-2 pe-3 dropdown-item user-select-none">'+
                             '<i class="fas fa-star fa-fw me-2"></i>Favorites'+
                             '</a></li>';

        submenu.innerHTML += '<li><a draggable="false" href="#" id="menuFrequentlyUsed" class="clickable disabled ps-2 pe-3 dropdown-item user-select-none">'+
                             '<i class="fas fa-asterisk fa-fw me-2"></i>Frequently Used'+
                             '</a></li>';

        submenu.innerHTML += '<li><a draggable="false" href="#" id="menuRecentlyUsed" class="clickable disabled ps-2 pe-3 dropdown-item user-select-none">'+
                             '<i class="fas fa-clock fa-fw me-2 user-select-none"></i>Recently Used'+
                             '</a></li>';

        // Divider /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        submenu.innerHTML += '<li><hr class="dropdown-divider"></li>';
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        submenu.innerHTML += '<li class="dropdown dropend"><a draggable="false" href="#" id="menuTMS" class="clickable ps-2 pe-3 dropdown-item dropdown-toggle user-select-none" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">'+
                             '<i class="fas fa-cubes fa-fw me-2"></i>TMS WEB Core Components&nbsp;&nbsp;'+
                             '</a><ul class="ms-0 mt-2 p-0 submenu dropdown-menu" aria-labelledby="menuTMS"></ul></li>';
        var subsubmenu = submenu.children[4].children[1];


            subsubmenu.innerHTML += '<li class=dropdown dropend"><a draggable="false" href="#" id="menuTMSStandard" class="clickable dropdown-item dropdown-toggle user-select-none" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">'+
                                    '<i class="fas fa-cube fa-fw me-2"></i>Standard &nbsp;&nbsp;'+
                                    '</a><ul class="m-0 mt-2 p-0 submenu dropdown-menu" aria-labelledby="menuTMSStandard"></ul></li>';
            var subsubsubmenu = subsubmenu.children[0].children[1];

                subsubsubmenu.innerHTML += '<li><a draggable="false" href="#" id="TWebLabel" class="clickable ps-2 pe-3 dropdown-item user-select-none">'+
                                           '<i class="fas fa-tag fa-fw me-2"></i>TWebLabel'+
                                           '</a></li>';

                subsubsubmenu.innerHTML += '<li><a draggable="false" href="#" id="TWebEdit" class="clickable ps-2 pe-3 dropdown-item user-select-none">'+
                                           '<i class="fas fa-edit fa-fw me-2"></i>TWebEdit'+
                                           '</a></li>';

                subsubsubmenu.innerHTML += '<li><a draggable="false" href="#" id="TWebButton" class="clickable ps-2 pe-3 dropdown-item user-select-none">'+
                                           '<i class="far fa-thumbs-up fa-fw me-2"></i>TWebButton'+
                                           '</a></li>';

                subsubsubmenu.innerHTML += '<li><a draggable="false" href="#" id="TWebPanel" class="clickable ps-2 pe-3 dropdown-item user-select-none">'+
                                           '<i class="fas fa-square fa-fw me-2"></i>TWebPanel'+
                                           '</a></li>';

                subsubsubmenu.innerHTML += '<li><a draggable="false" href="#" id="TWebHTMLDiv" class="clickable ps-2 pe-3 dropdown-item user-select-none">'+
                                           '<i class="fas fa-code fa-fw me-2"></i>TWebHTMLDiv'+
                                           '</a></li>';

                subsubsubmenu.innerHTML += '<li><a draggable="false" href="#" id="TWebMemo" class="clickable ps-2 pe-3 dropdown-item user-select-none">'+
                                           '<i class="fas fa-scroll fa-fw me-2"></i>TWebMemo'+
                                           '</a></li>';

        submenu.innerHTML += '<li><a draggable="false" href="#" id="menuTMSFNC" class="clickable disabled ps-2 pe-3 dropdown-item user-select-none">'+
                             '<i class="fas fa-cubes fa-fw me-2"></i>TMS FNC Components'+
                             '</a></li>';

        submenu.innerHTML += '<li><a draggable="false" href="#" id="menuTMSXDATA" class="clickable disabled ps-2 pe-3 dropdown-item user-select-none">'+
                             '<i class="fas fa-cubes fa-fw me-2"></i>TMS XDATA Components'+
                             '</a></li>';

        // Divider /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        submenu.innerHTML += '<li><hr class="dropdown-divider"></li>';
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        submenu.innerHTML += '<li><a draggable="false" href="#" id="menuSystem" class="clickable disabled ps-2 pe-3 dropdown-item user-select-none">'+
                             '<i class="fas fa-bolt fa-fw me-2"></i>Included System Components'+
                             '</a></li>';

        submenu.innerHTML += '<li><a draggable="false" href="#" id="menujQuery" class="clickable disabled ps-2 pe-3 dropdown-item user-select-none">'+
                             '<i class="fas fa-bolt fa-fw me-2"></i>Included jQuery Components'+
                             '</a></li>';

        submenu.innerHTML += '<li><a draggable="false" href="#" id="menuJS" class="clickable disabled ps-2 pe-3 dropdown-item user-select-none">'+
                             '<i class="fas fa-bolt fa-fw me-2"></i>Included JS Libraries'+
                             '</a></li>';

        // Divider /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        submenu.innerHTML += '<li><hr class="dropdown-divider"></li>';
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        submenu.innerHTML += '<li><a draggable="false" href="#" id="menuMore" class="clickable disabled ps-2 pe-3 dropdown-item user-select-none">'+
                             '<i class="fas fa-toolbox fa-fw me-2"></i>More WEB Core Components'+
                             '</a></li>';

        submenu.innerHTML += '<li><a draggable="false" href="#" id="menuMorejQuery" class="clickable disabled ps-2 pe-3 dropdown-item user-select-none">'+
                             '<i class="fas fa-toolbox fa-fw me-2"></i>More jQuery Components</a></li>';

        submenu.innerHTML += '<li class="dropdown dropend"><a draggable="false" href="#" id="menuJSLibraries" class="clickable ps-2 pe-3 dropdown-item dropdown-toggle user-select-none" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">'+
                             '<i class="fas fa-toolbox fa-fw me-2"></i>More JS Libraries&nbsp;&nbsp;'+
                             '</a><ul class="ms-0 mt-2 p-0 submenu dropdown-menu" aria-labelledby="menuJSLibraries"></ul></li>';
        var subsubmenu = submenu.children[14].children[1];

            subsubmenu.innerHTML += '<li class="dropdown dropend"><a draggable="false" href="#" id="menuFlatPickr" class="clickable dropdown-toggle ps-2 pe-3 dropdown-item user-select-none">'+
                                    '<i class="fas fa-calendar fa-fw me-2 user-select-none"></i>FlatPickr&nbsp;&nbsp;'+
                                    '</a><ul class="m-0 mt-2 p-0 submenu dropdown-menu" aria-labelledby="menuFlatPickr"></ul></li>';
            var subsubsubmenu = subsubmenu.children[0].children[1];

                subsubsubmenu.innerHTML += '<li><a draggable="false" href="#" id="FlatPickr_Default" class="clickable ps-2 pe-3 dropdown-item user-select-none">'+
                                           '<i class="fas fa-calendar fa-fw me-2"></i>FlatPickr - Default'+
                                           '</a></li>';

                subsubsubmenu.innerHTML += '<li><a draggable="false" href="#" id="FlatPickr_Range" class="clickable ps-2 pe-3 dropdown-item user-select-none">'+
                                           '<i class="fas fa-calendar fa-fw me-2"></i>FlatPickr - Range'+
                                           '</a></li>';

                subsubsubmenu.innerHTML += '<li><a draggable="false" href="#" id="FlatPickr_Inline" class="clickable ps-2 pe-3 dropdown-item user-select-none">'+
                                           '<i class="fas fa-calendar fa-fw me-2"></i>FlatPickr - Inline'+
                                           '</a></li>';

                subsubsubmenu.innerHTML += '<li><a draggable="false" href="#" id="FlatPickr_Inline_Multiple" class="clickable ps-2 pe-3 dropdown-item user-select-none">'+
                                           '<i class="fas fa-calendar fa-fw me-2"></i>FlatPickr - Inline Multiple'+
                                           '</a></li>';

        // Divider /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        submenu.innerHTML += '<li><hr class="dropdown-divider"></li>';
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


        submenu.innerHTML += '<li class="dropdown dropend"><a draggable="false" href="#" id="menuCSSAnimations" class="clickable ps-2 pe-3 dropdown-item dropdown-toggle user-select-none" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">'+
                             '<i class="fas fa-cubes fa-fw me-2"></i>CSS Animations&nbsp;&nbsp;'+
                             '</a><ul class="ms-0 mt-2 p-0 submenu dropdown-menu" aria-labelledby="menuCSSAnimations"></ul></li>';
        var subsubmenu = submenu.children[16].children[1];

            subsubmenu.innerHTML += '<li><a draggable="false" href="#" id="CSSFireworks" class="clickable ps-2 pe-3 dropdown-item user-select-none">'+
                                    '<i class="fas fa-bomb fa-fw me-2 user-select-none"></i>Fireworks'+
                                    '</a></li>';

            subsubmenu.innerHTML += '<li><a draggable="false" href="#" id="CSSFireCircle" class="clickable ps-2 pe-3 dropdown-item user-select-none">'+
                                    '<i class="fas fa-fire-alt fa-fw me-2 user-select-none"></i>Fire Circle'+
                                    '</a></li>';

            subsubmenu.innerHTML += '<li><a draggable="false" href="#" id="CSSBubbles" class="clickable ps-2 pe-3 dropdown-item user-select-none">'+
                                    '<i class="fas fa-soap fa-fw me-2 user-select-none"></i>Bubbles'+
                                    '</a></li>';

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Settings Menu - Various options available
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    menu.innerHTML += '<li class="nav-item dropdown" id="menuSettings"><a draggable="false" href="#" class="clickable nav-link dropdown-toggle ps-4 user-select-none" data-bs-toggle="dropdown">'+
                      '<i class="fas fa-cogs fa-lg me-2"></i>Settings'+
                      '</a></li>';

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Help Menu - Tips and Tricks?
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    menu.innerHTML += '<li class="nav-item dropdown" id="menuHelp"><a draggable="false" href="#" class="clickable nav-link dropdown-toggle ps-4 user-select-none" data-bs-toggle="dropdown">'+
                      '<i class="fas fa-cogs fa-life-ring me-2"></i>Help'+
                      '</a></li>';


    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // End of menu definition
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


    // I didn't write this bit, just copied and pasted from the internet for the most part

    // Create a collapsed version of the menu, corresponding to Bootstrap Breakpoint sm
    if (window.innerWidth < 576) {
      document.querySelectorAll('.navbar .dropdown').forEach(function(everydropdown){
        everydropdown.addEventListener('hidden.bs.dropdown', function () {
          this.querySelectorAll('.submenu').forEach(function(everysubmenu){
            everysubmenu.style.display = 'none'
          })
        })
      })
      document.querySelectorAll('.dropdown-menu a').forEach(function(element){
        element.addEventListener('click', function (e) {
          var nextElement = this.nextElementSibling;
          if(nextElement && nextElement.classList.contains('submenu')) {
            e.preventDefault();
            if(nextElElement.style.display == 'block'){
              nextElement.style.display = 'none'
            } else {
              nextElement.style.display = 'block'
            }
          }
        })
      })
    }

    // Call Delphi function to decipher whatever menu item was selected
    // Note that the submenu parents may also get called
    document.querySelectorAll('.dropdown-menu a').forEach(function(element){
      element.addEventListener('click', function (e) {
        pas.SP_MainForm.MainForm.MenuClicked(e.srcElement.id);
//        e.stopPropagation();
      });
      // REALLY don't want people to drag menu items around - how hard can it be?
      // Not super-important, just super annoying that you have to do it this way
      // just for Firefox.
      element.addEventListener('dragstart',function(event){
        event.preventDefault();
      },true);
    })
    document.querySelectorAll('.nav-item a').forEach(function(element){
      element.addEventListener('dragstart',function(event){
        event.preventDefault();
      },true);
    })
  end;
end;

Hope that helps at least a little?

2 Likes