skip to Main Content

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>');
    }
}

2

Answers


  1. I adjusted your code so that it would work as you expect.

    The reason why it wasn’t working for you is because a reference to the callback method handler is never saved. The same exact handler that is being used to instantiate a new event must be used to remove the event. In your case you were referencing an anonymous function that calls the event handler method, and you cannot re-refrence an anonymous function. Try using this:

    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() {
    
            // when onClickFunction is called, the HTML element that triggered the click and the Event object are automatically passed to the function, no need to specifically define them as arguments
            let onClickFunction = this.onClick;
    
            // Loop over elements.
            document.querySelectorAll('.click_me').forEach(element => {
    
                // 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() {
            event.preventDefault();
            console.log(this); // references the element that triggered the event
            console.log(event); // references the Event object that was triggered
            console.log('<br>');
        }
    }
    

    In your code, the following line is creating a new anonymous function each time the _setUpEventHandlers method is called:

    var onClickFunction = function(event) {
        this.onClick(element, event);
    }.bind(this);
    
    Login or Signup to reply.
  2. 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>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search