0

I'm creating multiple arrows that rotate when clicked. However, I'm getting an error "div.addEventListener is not a function" when using an addEventListener. Is there a way to make this work? Thanks in advance for any help.

(function (document) {
  var div = document.getElementsByClassName("container");
  var icon = document.getElementsByClassName("arrow");
  var open = false;

  div.addEventListener("click", function () {
    if (open) {
      icon.className = "fa fa-arrow-down";
    } else {
      icon.className = "fa fa-arrow-down rotate";
    }

    open = !open;
  });
})(document);
.fa-arrow-down {
  transform: rotate(0deg);
  transition: transform 1s linear;
}

.fa-arrow-down.open {
  transform: rotate(180deg);
  transition: transform 1s linear;
}
<div class="container">
  <i class="fa fa-arrow-down arrow"></i>
</div>

<div class="container">
  <i class="fa fa-arrow-down arrow"></i>
</div>

<div class="container">
  <i class="fa fa-arrow-down arrow"></i>
</div>

2 Answers2

0

The getElementsByClassName() method of Document interface returns an array-like object of all child elements which have all of the given class name. It returns array like object, so way of accessing that div should be like accessing certain element of array.

You can use for loop

(function (document) {
  const div = document.getElementsByClassName("container");
  const divLen = div.length;
  let open = false;

  for(let i = 0; i < divLen; ++i) {
    div[i].addEventListener("click", function () {
      const icon = this.getElementsByClassName("arrow");
      if (open) {
        icon.className = "fa fa-arrow-down";
      } else {
        icon.className = "fa fa-arrow-down rotate";
      }

      open = !open;
    }); 
  }
})(document);

Here's ES6 example of forEach() loop

(function(document) {
  const div = document.getElementsByClassName("container");
  let open = false;

  div.forEach(function(elem) { 
    elem.addEventListener("click", function() {
      const icon = elem.getElementsByClassName("arrow")[0];
      if (open) {
        icon.className = "fa fa-arrow-down";
      } else {
        icon.className = "fa fa-arrow-down rotate";
      }
     open = !open;
    });
  });
})(document);

Extraterrestrial
  • 600
  • 1
  • 6
  • 19
0

Your issue was caused by you trying to bind event to an array instead of the element due to getElementsByClassName will return HTMLCollection which is type of array.

You can refer this answer which explained why bind event to array was not working.

I assuming you are using Vanilla JS only, first get all the elements which you want trigger the click event by class name, which is container class:

/* getElementsByClassName return array */
var divs = document.getElementsByClassName("container");

Then you have to loop though these divs:

/* loop through divs array */
Array.from(divs).forEach(function (div) {
    /* only you bind "click" event to "div" at here */
});

And here only you bind "click" event to your div, following code will find the arrow class elements in the container class and toggle the open class.

div.addEventListener("click", function (event) {
    /* get the clicked element, which is the "container" */
    const clickedElement = event.currentTarget;

    /* find "arrow" class elements in "container" */
    const arrows = clickedElement.getElementsByClassName("arrow");

    /* loop through each "arrow" class element and toggle the "open" class name */
    Array.from(arrows).forEach(function (arrow) {
        arrow.classList.toggle("open");
    });
});

Demo:

(function(document) {
  /* getElementsByClassName return array */
  var divs = document.getElementsByClassName("container");

  /* loop through divs array, bind the click event to each div */
  Array.from(divs).forEach(function(div) {
    div.addEventListener("click", function(event) {
      /* get the clicked element, which is the "container" */
      const clickedElement = event.currentTarget;

      /* find "arrow" class elements in "container" */
      const arrows = clickedElement.getElementsByClassName("arrow");

      /* loop through each "arrow" class element and toggle the "open" class name */
      Array.from(arrows).forEach(function(arrow) {
        arrow.classList.toggle('open');
      });
    });
  });
})(document);
.fa-arrow-down {
  transform: rotate(0deg);
  transition: transform 1s linear;
}

.fa-arrow-down.open {
  transform: rotate(180deg);
  transition: transform 1s linear;
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.2/css/all.min.css" integrity="sha512-1sCRPdkRXhBV2PBLUdRb4tMg1w2YPf37qatUFeS7zlBy7jJI8Lf4VHwWfZZfpXtYSLy85pkm9GaYVYMfw5BC1A==" crossorigin="anonymous" referrerpolicy="no-referrer"
/>
<div class="container">
  <i class="fa fa-arrow-down arrow"></i>
</div>

<div class="container">
  <i class="fa fa-arrow-down arrow"></i>
</div>

<div class="container">
  <i class="fa fa-arrow-down arrow"></i>
</div>
Stern Chen
  • 303
  • 1
  • 8
  • I was a bit confused there but now I understand. Thank you so much! Before I usually used getElementById and the addEventListener did work. But now that I need multiple containers/arrows and I got confused about why div.addEventListener didn't work. – デンズドネザ Aug 06 '22 at 05:43
  • **getElementById** did work was because ID is unique and it always return one element. But with class name is possible to have multiple elements. So the way you select multiple container by **getElementsByClassName** was correct, just you need to loop though each container and **addEventListener** to each of them, this is by using **Vanilla JS**. If you using **jQuery**, there will be more shorten code way: `$( ".container" ).click(function() { /* ... */ });` which allows you to bind click event to all elements with class name `container`. – Stern Chen Aug 06 '22 at 06:17