0

I am trying to create Parent and Child rows of data in an HTML table. Currently the code works, but due to my unfamiliarity with JavaScript I am not always selecting the correct element. In the provided code, clicking the first parent only displays one child not both, and clicking the second parent only displays the first child of the first parent.

I'm 90% sure the error is in the JavaScript.

var toggler = document.getElementsByClassName("parent");
var i;
for (i = 0; i < toggler.length; i++) {
  toggler[i].addEventListener("click", function() {
    this.parentElement.querySelector(".child").classList.toggle("active");
    this.classList.toggle("parent-down");
    this.parentElement.querySelector(".arrow").classList.toggle("arrow-down ");
  });
}
.parent {
  cursor: pointer;
  user-select: none;
  /* Prevent text selection */
  font-size: 16px;
}

.arrow-down::before {
  -ms-transform: rotate(90deg);
  /* IE 9 */
  -webkit-transform: rotate(90deg);
  /* Safari */
  '
 transform: rotate(90deg);
}

.parent-down {
  border: 2px solid rgb(21, 67, 96);
  font-weight: bold;
}


/* Hide the child list */

.child {
  display: none;
  background-color: rgb(240, 250, 255);
  font-size: 14px;
}

.active {
  display: table-row;
}

.arrow::before {
  content: "\25B6";
  color: black;
  display: inline-block;
  margin-right: 6px;
}
<table>
  <tr>
    <th>Word</th>
    <th>Number of Letters</th>
    <th>Do I like the word?</th>
  </tr>
  <tr class="parent">
    <td class="arrow">Long Words</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr class="child">
    <td>Bamboozle</td>
    <td>9</td>
    <td>Yes.</td>
  </tr>
  <tr class="child">
    <td>Peritoneum</td>
    <td>10</td>
    <td>No.</td>
  </tr>
  <tr class="parent">
    <td class="arrow">Short Words</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr class="child">
    <td>Squeak</td>
    <td>6</td>
    <td>Yes.</td>
  </tr>
</table>

2
  • you cant use querySelecor like that Commented Jun 7, 2019 at 12:08
  • Can you provide an alternate solution? Commented Jun 7, 2019 at 12:14

5 Answers 5

2

It's a bit harder because your html structure is not ideal for this. However you could use the nextElementSibling property and put it in a while loop to find all elements you need. You might want to write an emergency exit for your while loop in case something goes wrong. Did a check if the next sibling is not null in case we got the last element already.

Also removed the space in the class toggle because that is not allowed.

However I recommend to change your html structure and move the collapse buttons outside the tables. Tables are best for tabular data and less so for buttons and other layout functionality. It will be annoying for people who use text oriented browsers like blind people do. The word data would fit into tables just fine though.

With a better element structure it would also become easier to select the elements belonging to the right parent so you don't need to do dodgy things in while loops.

var toggler = document.getElementsByClassName("parent");
var i;
for (i = 0; i < toggler.length; i++) {
  toggler[i].addEventListener("click", function() {
    var nextSibling = this.nextElementSibling;
    while(nextSibling !== null && nextSibling.classList.contains("child")) {
      nextSibling.classList.toggle("active");
      nextSibling = nextSibling.nextElementSibling;
    }
    this.classList.toggle("parent-down");
    this.querySelector(".arrow").classList.toggle("arrow-down");
  });
}
.parent {
  cursor: pointer;
  user-select: none;
  /* Prevent text selection */
  font-size: 16px;
}

.arrow-down::before {
  -ms-transform: rotate(90deg);
  /* IE 9 */
  -webkit-transform: rotate(90deg);
  /* Safari */
  '
 transform: rotate(90deg);
}

.parent-down {
  border: 2px solid rgb(21, 67, 96);
  font-weight: bold;
}


/* Hide the child list */

.child {
  display: none;
  background-color: rgb(240, 250, 255);
  font-size: 14px;
}

.active {
  display: table-row;
}

.arrow::before {
  content: "\25B6";
  color: black;
  display: inline-block;
  margin-right: 6px;
}
<table>
  <tr>
    <th>Word</th>
    <th>Number of Letters</th>
    <th>Do I like the word?</th>
  </tr>
  <tr class="parent">
    <td class="arrow">Long Words</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr class="child">
    <td>Bamboozle</td>
    <td>9</td>
    <td>Yes.</td>
  </tr>
  <tr class="child">
    <td>Peritoneum</td>
    <td>10</td>
    <td>No.</td>
  </tr>
  <tr class="parent">
    <td class="arrow">Short Words</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr class="child">
    <td>Squeak</td>
    <td>6</td>
    <td>Yes.</td>
  </tr>
</table>

Sign up to request clarification or add additional context in comments.

5 Comments

nice but if you are use forEach, its more appreciated
@NishargShah Thanks for your comment. I'm not sure how to select all corresponding siblings with a forEach call. Perhaps you could post an answer with that. I agree that while loops should be avoided if possible.
I added my answer, I only added forEach loop in your answer, no any changes
Ah sorry I thought you meant to replace the while loop with the forEach function. Ofcourse the for loop could be replaced with this function, but I've chosen to leave the for loop in because that is what the OP used too and that part of the code wasn't broken.
yeh, OP is using for loop but I like forEach loop little much :D
0

use a loop till the nextElementSibling do not have .child class.

var toggler = document.getElementsByClassName("parent");
var i;
for (i = 0; i < toggler.length; i++) {
  toggler[i].addEventListener("click", function() {
    this.parentElement.querySelector(".arrow").classList.toggle("arrow-down");
    let next = this.nextElementSibling;
    while(next && next.classList.contains('child')) {
      next.classList.toggle('active');
      this.classList.toggle("parent-down");
      next = next.nextElementSibling;
    }
  })
};
.parent {
  cursor: pointer;
  user-select: none;
  /* Prevent text selection */
  font-size: 16px;
}

.arrow-down::before {
  -ms-transform: rotate(90deg);
  -webkit-transform: rotate(90deg);
  transform: rotate(90deg);
}

.parent-down {
  border: 2px solid rgb(21, 67, 96);
  font-weight: bold;
}


/* Hide the child list */

.child {
  display: none;
  background-color: rgb(240, 250, 255);
  font-size: 14px;
}

.active {
  display: table-row;
}

.arrow::before {
  content: "\25B6";
  color: black;
  display: inline-block;
  margin-right: 6px;
}
<table>
  <tr>
    <th>Word</th>
    <th>Number of Letters</th>
    <th>Do I like the word?</th>
  </tr>
  <tr class="parent">
    <td class="arrow">Long Words</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr class="child">
    <td>Bamboozle</td>
    <td>9</td>
    <td>Yes.</td>
  </tr>
  <tr class="child">
    <td>Peritoneum</td>
    <td>10</td>
    <td>No.</td>
  </tr>
  <tr class="parent">
    <td class="arrow">Short Words</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr class="child">
    <td>Squeak</td>
    <td>6</td>
    <td>Yes.</td>
  </tr>
</table>

Comments

0

The main change I made was setting an ID on parents and set the same ID as a class on its child.

var togglers = document.querySelectorAll(".parent");
togglers.forEach(function(toggler) {
  toggler.addEventListener("click", function() {
    document.querySelectorAll("." + this.id).forEach(function (child) {
    	child.classList.toggle("active");
    })
    this.classList.toggle("parent-down");
    this.querySelector(".arrow").classList.toggle("arrow-down");
  })
})
.parent {
 cursor: pointer;
 user-select: none; /* Prevent text selection */
 font-size: 16px;
 }

.arrow-down::before {
  -ms-transform: rotate(90deg); /* IE 9 */
  -webkit-transform: rotate(90deg); /* Safari */
  transform: rotate(90deg);
}

.parent-down {
  border: 2px solid rgb(21, 67, 96);
  font-weight: bold;
}

/* Hide the child list */
.child {
  display: none;
  background-color: rgb(240, 250, 255);
  font-size: 14px;
}

.active {
  display: table-row;
}

.arrow::before {
  content: "\25B6";
  color: black;
  display: inline-block;
  margin-right: 6px;
}
<table>
 <tr>
  <th>Word</th>
   <th>Number of Letters</th> 
   <th>Do I like the word?</th>    
 </tr>
  <tr id="parent01" class = "parent">
   <td class = "arrow">Long Words</td> 
   <td>-</td>
   <td>-</td>
   </tr>
   <tr class = "child parent01">
    <td>Bamboozle</td> 
    <td>9</td>
    <td>Yes.</td>
   </tr>
   <tr class = "child parent01">
    <td>Peritoneum</td> 
    <td>10</td>
    <td>No.</td>
   </tr>      
   <tr id="parent02" class = "parent">
    <td class = "arrow">Short Words</td> 
    <td>-</td>
    <td>-</td>
   </tr>
   <tr class = "child parent02">
    <td>Squeak</td> 
    <td>6</td>
    <td>Yes.</td>
   </tr>                  
</table>

Comments

0

You can also solve this with forEach loop with ES6 syntax

let toggler = document.querySelectorAll(".parent");

toggler.forEach(element => {
    element.addEventListener("click", function() {
    let nextSibling = this.nextElementSibling;
    while(nextSibling != null && nextSibling.classList.contains("child")) {
      nextSibling.classList.toggle("active");
      nextSibling = nextSibling.nextElementSibling;
    }
    this.classList.toggle("parent-down");
    this.querySelector(".arrow").classList.toggle("arrow-down");
  });
});
.parent {
  cursor: pointer;
  user-select: none;
  /* Prevent text selection */
  font-size: 16px;
}

.arrow-down::before {
  -ms-transform: rotate(90deg);
  /* IE 9 */
  -webkit-transform: rotate(90deg);
  /* Safari */
  '
 transform: rotate(90deg);
}

.parent-down {
  border: 2px solid rgb(21, 67, 96);
  font-weight: bold;
}


/* Hide the child list */

.child {
  display: none;
  background-color: rgb(240, 250, 255);
  font-size: 14px;
}

.active {
  display: table-row;
}

.arrow::before {
  content: "\25B6";
  color: black;
  display: inline-block;
  margin-right: 6px;
}
<table>
  <tr>
    <th>Word</th>
    <th>Number of Letters</th>
    <th>Do I like the word?</th>
  </tr>
  <tr class="parent">
    <td class="arrow">Long Words</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr class="child">
    <td>Bamboozle</td>
    <td>9</td>
    <td>Yes.</td>
  </tr>
  <tr class="child">
    <td>Peritoneum</td>
    <td>10</td>
    <td>No.</td>
  </tr>
  <tr class="parent">
    <td class="arrow">Short Words</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr class="child">
    <td>Squeak</td>
    <td>6</td>
    <td>Yes.</td>
  </tr>
</table>
 Run code snippet

Comments

0

var toggler = document.getElementsByClassName("parent");
var i;
for (i = 0; i < toggler.length; i++) {
  toggler[i].addEventListener("click", function() {
    this.classList.toggle("active");
    nextSibling(this);
  });
}

var nextSibling = function(element) {
  if (!element.nextElementSibling || element.nextElementSibling.classList.contains("parent")) return false;
  element.nextElementSibling.classList.toggle("active-row");
  return arguments.callee(element.nextElementSibling);
}
.parent {
  cursor: pointer;
  user-select: none;
  /* Prevent text selection */
  font-size: 16px;
}

.active .arrow::before {
  -ms-transform: rotate(90deg);
  /* IE 9 */
  -webkit-transform: rotate(90deg);
  /* Safari */
  '
 transform: rotate(90deg);
}


/* Hide the child list */

.child {
  background-color: rgb(240, 250, 255);
  font-size: 14px;
  display: none;
}

.active {
  color: red;
}

.active-row {
  display: table-row;
}

.arrow::before {
  content: "\25B6";
  color: black;
  display: inline-block;
  margin-right: 6px;
}
<table>
  <tr>
    <th>Word</th>
    <th>Number of Letters</th>
    <th>Do I like the word?</th>
  </tr>
  <tr class="parent">
    <td class="arrow">Long Words</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr class="child">
    <td>Bamboozle</td>
    <td>9</td>
    <td>Yes.</td>
  </tr>
  <tr class="child">
    <td>Peritoneum</td>
    <td>10</td>
    <td>No.</td>
  </tr>
  <tr class="parent">
    <td class="arrow">Short Words</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr class="child">
    <td>Squeak</td>
    <td>6</td>
    <td>Yes.</td>
  </tr>
</table>

Use nextElementSibling to select the contiguous sibling class ".child". Please see the snippet.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.