Search code examples
javascripteventsevent-handlingevent-listener

Removing Javascript event listener created in a loop


I have a website with multiple similar elements that need to have event listeners applied:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Example</title>
    </head>
    <body>
        <a href = '#' class = "click_me">Click me</a>
        <br>
        <a href = '#' class = "click_me">Click me</a>
        <br>
        <a href = '#' class = "click_me">Click me</a>
        <script src = "example.js"></script>
        <script>
            new myClass(5);
        </script>
    </body>
</html>

I do this by looping over the elements. I want the callback function to have access to the element, the parent class instance and the event. So I think I have to define the callback function within the loop. However, this makes it difficult to remove the event listeners. In this example, I call the setUpEventHandlers method twice, and the second call should remove the event listeners created by the first call. However, when I load the webpage, I can see both sets of listeners in the inspector. I think this is because the function is re-defined in the loop, so it is not treated as the same function by removeEventListener. How can I remove the first set of listeners?

class myClass {

    // Class constructor.
    constructor(input_foo) {
    
        // Process input.
        this.foo = input_foo;

        // Create event listeners for this instance.
        this._setUpEventHandlers();

        // Re-create event listeners (should remove previous ones).
        this._setUpEventHandlers();

    };

    // Loops over elements and creates event listeners.
    _setUpEventHandlers() {

        // Loop over elements.
        document.querySelectorAll('.click_me').forEach(element => {

            // Define event listener function with "this" bound and 
            // appropriate signature.
            var onClickFunction = function(event) {
                this.onClick(element, event);
            }.bind(this);
            
            // Remove existing event listeners.
            element.removeEventListener('click', onClickFunction, false);
            
            // Add event listener function for this element.
            element.addEventListener(
                'click',
                onClickFunction,
                false
                );
            }
        );
    }

    // Define event listener function.
    onClick(element, event) {
        event.preventDefault();
        console.log(this);
        console.log(event);
        console.log(element);
        console.log('<br>');
    }
}

Solution

  • An arrow function can be used to avoid bind. You can also simplify your click handler invocation by accessing event.currentTarget instead of passing in a reference to the element. Therefore, you can do this:

    class MyClass {
    
      constructor(input_foo) {
        this.foo = input_foo;
        this.onClickFunction = e => this.onClick(e);
        this._setUpEventHandlers();
        this._setUpEventHandlers();
      };
    
      _setUpEventHandlers() {
        document.querySelectorAll('.click_me').forEach(element => {
          element.removeEventListener('click', this.onClickFunction);
          element.addEventListener('click', this.onClickFunction);
        });
      }
    
      onClick(event) {
        event.preventDefault();
        console.log(this.constructor.name);
        console.log(event.constructor.name);
        console.log(event.currentTarget.constructor.name);
        console.log(event.currentTarget.innerText);
        console.log('<br>');
      }
    }
    
    let obj = new MyClass();
    <div class="click_me">hello</div>
    <div class="click_me">world</div>