Home > Enterprise >  Trying to add buttons to dynamic sub-menu with insertBefore results in "Failed to execute '
Trying to add buttons to dynamic sub-menu with insertBefore results in "Failed to execute '

Time:12-24

I'm trying to dynamically add buttons to toggle the visibility of sub-menus in a navigation system. The idea being if a user created a sub-menu in the CMS, JavaScript would dynamically add a button above this at the same level as the sub-menu's parent

  • element to allow content to be toggled.

    So static HTML like this

    <nav>
      <ul >
        <li >nav item
        <!-- sub menu-->
          <ul >
            <li >child nav item</li>
            <li >child nav item</li>
            <li >child nav item</li>
          </ul>
        </li>
        <li >nav item
        <!-- sub menu-->
          <ul >
            <li >child nav item</li>
            <li >child nav item</li>
            <li >child nav item</li>
          </ul>
        </li>
      </ul>
    </nav>
    

    Would be dynamically changed to something like the mark-up below, with the button to toggle visibility of the sub-menu appearing above the sub-menu's element

    <nav>
      <ul >
         <li >nav item
         <button>Show show menu</button>
         <!-- sub menu -->
           <ul >
             <li >child nav item</li>
             <li >child nav item</li>
             <li >child nav item</li>
           </ul>
          </li>
          <li >nav item
          <button>Show sub menu</button>
            <!-- sub menu -->
           <ul >
             <li >child nav item</li>
             <li >child nav item</li>
             <li >child nav item</li>
           </ul>
         </li>
     </ul>
    </nav>`
    

    However when I use querySelectorAll and insertBefore I get the following error message

    Uncaught TypeError: Failed to execute 'insertBefore' on 'Node': parameter 2 is not of type 'Node'.
    

    The code below results in the error:

    const navItem = document.querySelectorAll(".nav-item");
    const subNav = document.querySelectorAll(".child-nav");
    
    const button = document.createElement("button");
    button.textContent = "sub-menu";
    
    // add button if sub-menu is present
    for (i = 0; i < navItem.length; i   ) {
      if (navItem[i].hasChildNodes() === true)  {
        navItem[i].insertBefore(button, subNav);
      }
    }
    

    However if I use querySelector(".child-nav") as below and amend navItem[i] to navItem[0] what I hoped to happen does, that is the button is added, obviously though this is only added to the first element and I need it to be added above all the the sub-menu elements if present.

    const navItem = document.querySelectorAll(".nav-item");
    const subNav = document.querySelector(".child-nav");
    
    // add button if sub-menu is present
    for (i = 0; i < navItem.length; i   ) {
      if (navItem[i].hasChildNodes() === true)  {
        navItem[0].insertBefore(button, subNav);
      }
    }
    

    Can anyone help? I'm completely stumped.

    I've tried swapping querySelectorAll(".child-nav") for getElementsByClassName("child-nav") thus switching from a nodeList to a HTMLCollection but I still get the error message about parameter 2 not being a node, though everything works fine if I just use querySelector.

  • CodePudding user response:

    You could rewrite your code like this (the Javascript part).

    const navItem = document.querySelectorAll(".nav-item");
    //const subNav = document.querySelector(".child-nav"); <== not here
    
    // add button if sub-menu is present
    for (i = 0; i < navItem.length; i   ) {
      if (navItem[i].hasChildNodes() === true)  {
        //navItem[i].insertBefore(button, subNav); <== not here
        var button = document.createElement("button");
        button.textContent = "sub-menu";
       
        var subNav = navItem[i].querySelector(".child-nav");
        navItem[i].insertBefore(button,subNav);
      }
    }
    <nav>
      <ul >
        <li >nav item
        <!-- sub menu-->
          <ul >
            <li >child nav item</li>
            <li >child nav item</li>
            <li >child nav item</li>
          </ul>
        </li>
        <li >nav item
        <!-- sub menu-->
          <ul >
            <li >child nav item</li>
            <li >child nav item</li>
            <li >child nav item</li>
          </ul>
        </li>
      </ul>
    </nav>

    You'll notice that Ive removed the button and subnav definitions to be inside the for loop. The reason for this is:

    • each of the buttons you want to add to your navigation are separate DOM elements, and should be unique, even if their appearance and background code is the same. Think of it like this - let's say that your button is a person. With the original code, it would be as if you were trying to tell that person to be at multiple places at the same time
    • things are similar for the subnav - we are checking the children of the current navitem, and attaching the button before any of them

    CodePudding user response:

    Try using AppendChild or Prepend.

    • Related