Why do I get undefined when I use elements from a node-list or HTML-Collection when traversing the DOM?

587 Views Asked by At

While traversing the DOM there are sometimes many child elements under one parent.

I can get these children as either node-lists using a querySelectorAll or I can get them as HTML-Collections by using element.children.

But when I select one to use to put in my DOM tree traversal I get undefined.

I have tried turning the node-lists and HTML-Collections into arrays and using them that way with no success. I have tried using for-of loops regular for loops and foreach loops for selecting a single element, yet always with failure undefined.

I have created a made up situation that shows this situation, although the problem is solvable without having to Traverse the DOM, you have to image that it is necessary.

  • HTML
<!DOCTYPE html>
<html lang="en">
   <head>
      <title>Testing</title>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <link rel="stylesheet" href="eric_meyer_reset_2.css" />
      <link rel="stylesheet" href="styles.css" />
   </head>
   <body>
      <div id="wrapper">
         <div id="topOfPage">
            <header>
               <h1>Random Number Generator</h1>
            </header>
            <div id="simulated RNG">
               <p>Your number is: <span id="theRandomNumber">2</span></p>
               <button type="button" id="prizeOpenButton">Press for Prize</button>
            </div>
         </div>
         <div id="bottomOfPage">
            <!-- These sections are added dynamically during the script operation -->
            <section class="joy" data-id="0">Hurray</section>
            <section class="joy" data-id="1">Hurrah</section>
            <section class="joy" data-id="2">Yay</section>
            <section class="joy" data-id="3">Congrats</section>
         </div>
      </div>
      <script src="main.js"></script>
   </body>
</html>
  • CSS
.joy {
   display: none
}
  • JavaScript
const randomNumber = document.getElementById("theRandomNumber");
const prizeButton = document.getElementById("prizeOpenButton")

let matchNumberToPrizeSection = function (event) {
   let prizeSections = document.querySelectorAll(".joy");

   let theSection = null;

   for (let i = 0; i < prizeSections.length; i++) {
      if (prizeSections[i].dataset.id === randomNumber) {
         theSection = prizeSections[i];
      }
   }

   // lets say for some reason we have to traverse the DOM
   // starting with the button going to the "theSection" above
   let winningSection = event.target.parentElement.parentElement.nextElementSibling.theSection;

   console.log(winningSection.innerHTML);
}
prizeButton.addEventListener("click", matchNumberToPrizeSection);
2

There are 2 best solutions below

0
William S On BEST ANSWER

While you cannot use node-list or HTML-Collection elements directly or indirectly when you are building up a DOM traversal. What I finally found was that I can use the element before the many child elements, in my original question that was a div with id of bottomOfPage. What I had to do was find the index of the child element that I needed using the node-list elements and use that index in the parent div like this. event.target.parentElement.parentElement.nextElementSibling.children(index)

5
PsiKai On

I saw a number of issues, but the below code works.

For one, you were comparing elements instead of the text of the element, or the data-id. I went through and cleaned up the inconsistencies there.

  1. this is returning an element
const randomNumber = document.getElementById("theRandomNumber");
  • but your are comparing it with a number dataset.id. They will never match
if (prizeSections[i].dataset.id === randomNumber)
let winningSection = event.target.parentElement.parentElement.nextElementSibling.theSection
  • the variable theSection is pointing to an element, and that is not a property of nextElementSibling. I you want to do the element chaining and search for an element that is a child of nextElementSibling, then call querySelector on it and pass in an id or class, or whatever
let winningSection = event.target.parentElement.parentElement.nextElementSibling.querySelector([`data-id="${theSection.dataset.id}"`])

The above is redundant in this example, and I saw no reason to traverse the DOM like you did. That's an easy way to create bugs, especially if you move around or add items to your markup.

Since you have the data-id, you can query the entire document for that id. This will be hardened to changes to the DOM in the future.

const randomNumber = document.getElementById("theRandomNumber").innerText;
const prizeButton = document.getElementById("prizeOpenButton")

let matchNumberToPrizeSection = function (event) {
   let prizeSections = document.querySelectorAll(".joy");

   let theSection = null;

   for (let i = 0; i < prizeSections.length; i++) {
      if (prizeSections[i].dataset.id === randomNumber) {
         theSection = prizeSections[i].dataset.id;
      }
   }

   let winningSection = document.querySelector(`[data-id="${theSection}"]`);

   console.log(winningSection.innerHTML);
}
prizeButton.addEventListener("click", matchNumberToPrizeSection);
.joy {
   display: none
}
      <div id="wrapper">
         <div id="topOfPage">
            <header>
               <h1>Random Number Generator</h1>
            </header>
            <div id="simulated RNG">
               <p>Your number is: <span id="theRandomNumber">2</span></p>
               <button type="button" id="prizeOpenButton">Press for Prize</button>
            </div>
         </div>
         <div id="bottomOfPage">
            <!-- These sections are added dynamically during the script operation -->
            <section class="joy" data-id="0">Hurray</section>
            <section class="joy" data-id="1">Hurrah</section>
            <section class="joy" data-id="2">Yay</section>
            <section class="joy" data-id="3">Congrats</section>
         </div>
      </div>