1

I have a simple setup: Two buttons with different values. I want to use the .each() function from jquery to add an click event listener to each of the buttons that simply logs the values of each button. But somehow when using .each(), jquery overwrites the click handler for the previous button(s). This leaves me with two questions:

  1. Why is that?
  2. How do add individual event listeners to a collection of jquery objects?

See code below

  <html lang="en">
    
    <head>
        <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
        <script>
            $(() => {
                $(`button[toggle]`).each((_, button) => {
                    let $toggle = $(button);
                    type = $toggle.attr("type");
                    value = $toggle.attr("value");
                    $toggle.on("click", () => {
                        console.log(type, value);
                    });
                });
            });
        </script>
    
    </head>
    
    <body>
        <div>
            <button toggle type="one" value="true">One</button>
            <button toggle type="two" value="false">Two</button>
        </div>
    </body>
    
    </html>
Aalexander
  • 4,987
  • 3
  • 11
  • 34
BingeCode
  • 1,260
  • 1
  • 10
  • 21

3 Answers3

1

use $(this) inside the click callback

$(() => {
  $(`button[toggle]`).each((_, button) => {
    $(button).on("click", function(){
       type   = $(this).attr("type");
       value  = $(this).attr("value");
      console.log(type, value);
    });
  });
});
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>

<button toggle type="one" value="true">One</button>
<button toggle type="two" value="false">Two</button>

And also no need to mention inside the each callback. You could call in normal way like below

$(() => {
  $(`button[toggle]`).on("click", function(){
       type   = $(this).attr("type");
       value  = $(this).attr("value");
      console.log(type, value);
    });
});
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>

<button toggle type="one" value="true">One</button>
<button toggle type="two" value="false">Two</button>
prasanth
  • 22,145
  • 4
  • 29
  • 53
  • Thank you, that is the most elegant answer so far - question: why can I not use anonymous function i.e. () => {} as click handler? – BingeCode Jan 08 '21 at 13:20
  • You can, this answer just changed the format of the handler definition – Rory McCrossan Jan 08 '21 at 13:23
  • Because jquery have it own function call inside the click callback if you are using ES6 `Arrow` method you need to pass params like `event` . if is empty `this` not working .That why prefer function call in jquery . [reference](https://stackoverflow.com/questions/27670401/using-jquery-this-with-es6-arrow-functions-lexical-this-binding) – prasanth Jan 08 '21 at 13:25
  • @RoryMcCrossan . Yes you are right.. But jquery have a default loop behaviour while element click call. So that why i prefer normal click call instead of loop click event – prasanth Jan 08 '21 at 13:30
1

The issue in your code is because you've implicitly declare the type and value variables, so they are in window scope. As such each following iteration overwrites the values so that only the final values are seen after the loop ends.

To fix this, define the variables within the loop:

$(() => {
  $(`button[toggle]`).each((_, button) => {
    let $toggle = $(button);
    let type = $toggle.attr("type");
    let value = $toggle.attr("value");
    $toggle.on("click", () => {
      console.log(type, value);
    });
  });
});
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<div>
  <button toggle type="one" value="true">One</button>
  <button toggle type="two" value="false">Two</button>
</div>

However, you do not need the each() loop at all. You can fix the issue and improve the code quality by binding the same event handler to all the button elements and using the target property of the event which is raised to access their attributes.

In addition, note that adding your own non-standard attributes, toggle in this case, can lead to issues in the UI and JS. A better approach there is to use a class to group the elements.

Finally, one and two are not valid values for the type attribute. Use a data attribute instead to attach your own custom metadata to an element.

With all that said, try this:

jQuery($ => {
  $('.toggle').on('click', e => {
    let $toggle = $(e.target);
    let type = $toggle.data("type"); 
    let value = $toggle.prop("value");
    console.log(type, value);
  });
});
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<div>
  <button class="toggle" data-type="one" value="true">One</button>
  <button class="toggle" data-type="two" value="false">Two</button>
</div>
Rory McCrossan
  • 331,213
  • 40
  • 305
  • 339
0
  1. Because the listener binds to the last value of $toggle.
  2. By creating and running a new function as shown in the snippet.

Explained better in this question.

<html lang="en">
    
    <head>
        <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
        <script>
            $(() => {
                $(`button[toggle]`).each((_, button) => {
                    let $toggle = $(button);
                    type = $toggle.attr("type");
                    value = $toggle.attr("value");
                    $toggle.on("click", ((type, value) => {
                      return () => {
                        console.log(type, value);
                      };
                    })(type, value));
                });
            });
        </script>
    
    </head>
    
    <body>
        <div>
            <button toggle type="one" value="true">One</button>
            <button toggle type="two" value="false">Two</button>
        </div>
    </body>
    
    </html>
Kostas
  • 1,903
  • 1
  • 16
  • 22