0

I am having trouble getting this toggle classes script working across all project divs.

It is set up so when the parent element is clicked, both child elements toggle the class .hide, which functions more as a swap.

Noting this similar post which uses Jquery Toggle classes inside a foreach loop, I have approached this differently because in this instance there are multiple classes to change.

I used getElementsByClassName and defined the element from the NodeList as [0]. This works for the first Project div and for the second when changed to [1].

However, I am stuck on how I can get this working across all subsequent Project divs at the same time. Is this done with a loop? Or using variables? Or by using a this keyword?

I am using Kirby CMS to generate content sections so the Project divs are templated inside a foreach loop.

document.getElementsByClassName('read-more-div')[0].addEventListener('click', function() {

  document.getElementsByClassName('read-more-btn1')[0].classList.toggle('hide');
  document.getElementsByClassName('read-more-btn2')[0].classList.toggle('hide');

  return false

})
body {
  background-color: #000000;
  color: #ffffff;
}

p,
button {
  background-color: #000000;
  color: #ffffff;
  border: none;
  padding: 0;
  margin: 0;
}

.read-more-btn1 {
  color: #888888;
  cursor: s-resize;
}

.read-more-btn2 {
  cursor: n-resize;
}

.read-more-btn1:hover {
  color: #ffffff;
}

.hide {
  display: none;
}
<div>Project A

  <div class="read-more-div">

    <button class="read-more-btn1">Read more...</button>
    <p class="read-more-btn2 hide">Toggle this project text</p>

  </div>

</div>

<div>Project B

  <div class="read-more-div">

    <button class="read-more-btn1">Read more...</button>
    <p class="read-more-btn2 hide">Toggle this project text</p>

  </div>

</div>

<div>Project C

  <div class="read-more-div">

    <button class="read-more-btn1">Read more...</button>
    <p class="read-more-btn2 hide">Toggle this project text</p>

  </div>

</div>
Johhhn
  • 19
  • 8
  • Use [event delegation](//developer.mozilla.org/docs/Learn/JavaScript/Building_blocks/Events#Event_delegation) instead of assigning multiple event listeners — it’s more maintainable, and applies to dynamically added elements. E.g., use an [event argument](//developer.mozilla.org/docs/Web/API/EventTarget/addEventListener#The_event_listener_callback)’s [`target`](//developer.mozilla.org/docs/Web/API/Event/target). See [the tag info](/tags/event-delegation/info) and [What is DOM Event delegation?](/q/1687296/4642212). – Sebastian Simon Sep 25 '21 at 11:54
  • Something like `addEventListener("click", ({ target }) => { if(target.closest(".read-more-div")){ target.querySelectorAll(".read-more-btn1, .read-more-btn2").forEach(({ classList }) => classList.toggle("hide")); } });`. – Sebastian Simon Sep 25 '21 at 11:58
  • Note that `return false;` can be removed. It doesn’t do anything. – Sebastian Simon Sep 25 '21 at 12:01
  • Good to know as another approach! I'll have to read into event delegation which looks a little more complicated. I'm still in the early stages of learning js – Johhhn Sep 25 '21 at 12:40
  • The snippet doesn't seem to work and I'm not sure why yet – Johhhn Sep 25 '21 at 12:41
  • Oops, you’re right. There’s another `.closest(".read-more-div")` missing between `target` and `.querySelectorAll`. – Sebastian Simon Sep 25 '21 at 13:06
  • If [optional chaining](//developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/Optional_chaining) is supported, there’s a shorter way: `addEventListener("click", ({ target }) => target.closest(".read-more-div")?.querySelectorAll(".read-more-btn1, .read-more-btn2")?.forEach(({ classList }) => classList.toggle("hide")));`. – Sebastian Simon Sep 25 '21 at 13:20
  • Even better: instead of `hide`, use a class called `expanded` which gets placed on the `
    `; do `.read-more-div.expanded .read-more-btn1 { display: none; } .read-more-div.expanded .read-more-btn1 { display: block; }` and `addEventListener("click", ({ target }) => target.closest(".read-more-div")?.classList?.toggle("hide"));`. _Even_ better: use the standard [`
    `](//developer.mozilla.org/docs/Web/HTML/Element/details) instead.
    – Sebastian Simon Sep 25 '21 at 13:20

1 Answers1

2

Lots of ways to do this. Ensure that you are adding event listeners to all the divs and not just the first one. Looking at your current code you can follow this approach.

this inside event listener belongs to the element that triggered the event. So when we do this.getElementsByClassName we are querying for classes only inside that particular element.

getElementsByClassName does not return an array. It returns a collection which is array-like. To use forEach() on that array-like object converting it into an array is required. Using the ... (spread) operator to convert the collection into an array.

[...document.getElementsByClassName('read-more-div')].forEach(x => x.addEventListener('click', function() {

  this.getElementsByClassName('read-more-btn1')[0].classList.toggle('hide');
  this.getElementsByClassName('read-more-btn2')[0].classList.toggle('hide');

  return false

}));
body {
  background-color: #000000;
  color: #ffffff;
}

p,
button {
  background-color: #000000;
  color: #ffffff;
  border: none;
  padding: 0;
  margin: 0;
}

.read-more-btn1 {
  color: #888888;
  cursor: s-resize;
}

.read-more-btn2 {
  cursor: n-resize;
}

.read-more-btn1:hover {
  color: #ffffff;
}

.hide {
  display: none;
}
<div>Project A

  <div class="read-more-div">

    <button class="read-more-btn1">Read more...</button>
    <p class="read-more-btn2 hide">Toggle this project text</p>

  </div>

</div>

<div>Project B

  <div class="read-more-div">

    <button class="read-more-btn1">Read more...</button>
    <p class="read-more-btn2 hide">Toggle this project text</p>

  </div>

</div>

<div>Project C

  <div class="read-more-div">

    <button class="read-more-btn1">Read more...</button>
    <p class="read-more-btn2 hide">Toggle this project text</p>

  </div>

</div>
Tushar Shahi
  • 16,452
  • 1
  • 18
  • 39
  • 1
    Thank you! This works, and thanks for explaining. Probably need to get my head around the theory but glad to know it wasn't too far off. Any tips for debugging this sort of problem? – Johhhn Sep 25 '21 at 12:22