DevKLiD
DevKLiD

Reputation: 159

How to apply an eventListener to multiple elements with the same class and have each element respond independently

I have a list of elements in my HTML that looks like this:

<div class='container'>
    <div class="zone green">🦊</div>
    <div class="zone red">🐰</div>
    <div class="zone blue">🐸</div>
    <div class="zone yellow">🦁</div>
    <div class="zone purple">🐯</div>
    <div class="zone brown">🐭</div>
    <div class="zone green">🦄</div>
    <div class="zone red">🐲</div>
    <div class="zone blue">🐷</div>
    <div class="zone yellow">🐺</div>
    <div class="zone purple">🐼</div>
    <div class="zone brown">🐻</div>
    <script type="text/javascript" src="script.js"></script>
  </div>

And I was trying to make it so that every element I clicked on would log to the console the animal in the element. This was my attempt:

const selection = document.querySelectorAll(".zone");


selection.forEach(element => {
    element.addEventListener('click', pickSelection(element))
})


function pickSelection(animal) {
    console.log(animal.textContent)
}

But it was not returning anything when I clicked on any of the elements. However, once I changed the eventListener to this, it started working:

selection.forEach(element => {
    element.addEventListener('click', () => pickSelection(element))
})

Why does it work in the second version of the code and not the first? In the first version, I thought I am passing the element argument to the pickSelection function by writing "pickSelection(element)", but apparently it only works if there is "() =>" in front of it, but what is the difference in this notation? Thanks.

Upvotes: 0

Views: 863

Answers (3)

marzelin
marzelin

Reputation: 11600

Sidenote: Registering a listener on each element is a bad practice. Use event delegation instead:

document.querySelector(`.container`).addEventListener(`click`, ({target}) => {
  const el = target.closest(`.zone`);
  if (el) {
    console.log(el.textContent);
  }
})
<div class='container'>
  <div class="zone green">🦊</div>
  <div class="zone red">🐰</div>
  <div class="zone blue">🐸</div>
  <div class="zone yellow">🦁</div>
  <div class="zone purple">🐯</div>
  <div class="zone brown">🐭</div>
  <div class="zone green">🦄</div>
  <div class="zone red">🐲</div>
  <div class="zone blue">🐷</div>
  <div class="zone yellow">🐺</div>
  <div class="zone purple">🐼</div>
  <div class="zone brown">🐻</div>
</div>

Upvotes: 0

user128511
user128511

Reputation:

This line

element.addEventListener('click', pickSelection(element));

Is effectively this

const temp = pickSelection(element);
element.addEventListener('click', temp);

What you want is

element.addEventListener('click', pickSelection);

Do you see the difference?

In this version

element.addEventListener('click', pickSelection(element))

You called the function pickSelection and passed it element. Then passed whatever pickSelection returns to element.addEventListener

What you wanted / needed to do is pass the function itself to element.addEventListener

The reason this version works

selection.forEach(element => {
    element.addEventListener('click', () => pickSelection(element))
})

is because you're passing an anonymous function to element.addEventListener. Those lines can be translated to this

function anonymousFn1(element) {
    function anonymousFn2() {
         pickSelection(element);
    }
    element.addEventListener('click', anonymousFn2);
}

selection.forEach(anonymousFn1);

Note that functionally there is another difference.

In this one

 element.addEventListener('click', pickSelection);

The click event passes an Event object to pickSelection whereas in the other one the variable element that was created when forEach called anonmousFn1 was "closed over" (a closure was made) so when anonymousFn2 was call that element variable was use.

So to get the first one to work you really need this

function pickSelection(event) {
    const element = event.target;
    console.log(element.textContent);
}

Example:

const selection = document.querySelectorAll(".zone");

selection.forEach(element => {
    element.addEventListener('click', pickSelection);
});

function pickSelection(event) {
    element = event.target;
    console.log(element.textContent);
}
<div class='container'>
    <div class="zone green">🦊</div>
    <div class="zone red">🐰</div>
    <div class="zone blue">🐸</div>
    <div class="zone yellow">🦁</div>
    <div class="zone purple">🐯</div>
    <div class="zone brown">🐭</div>
    <div class="zone green">🦄</div>
    <div class="zone red">🐲</div>
    <div class="zone blue">🐷</div>
    <div class="zone yellow">🐺</div>
    <div class="zone purple">🐼</div>
    <div class="zone brown">🐻</div>
</div>

Upvotes: 0

Amardeep Bhowmick
Amardeep Bhowmick

Reputation: 16908

In the first version you are executing the pickSelection function instead of passing its reference, the addEventListener expects a function reference as a callback when the particular event is triggered.

But since you passed return value of the pickSelection function which is undefined (as you are not returning anything from the pickSelection so by default it is returning undefined) it is not working.

In the second version you are actually passing the function reference to the addEventListner, which being a arrow function syntax.

This following would also work, by simply passing the reference pickSelection, but this time it will receive the event object.

const selection = document.querySelectorAll(".zone");
selection.forEach(element => {
   element.addEventListener('click', pickSelection)
})
function pickSelection(event) {
      //getting the event object from the callback, target refers to the current element clicked.
    console.log(event.target.textContent)
}
<div class='container'>
    <div class="zone green">🦊</div>
    <div class="zone red">🐰</div>
    <div class="zone blue">🐸</div>
    <div class="zone yellow">🦁</div>
    <div class="zone purple">🐯</div>
    <div class="zone brown">🐭</div>
    <div class="zone green">🦄</div>
    <div class="zone red">🐲</div>
    <div class="zone blue">🐷</div>
    <div class="zone yellow">🐺</div>
    <div class="zone purple">🐼</div>
    <div class="zone brown">🐻</div>
</div>

Upvotes: 2

Related Questions