May
May

Reputation: 23

Why EventListener remove function doesnt work with "querySelectorAll"

I asked a long question about that before but I thnk people dont wanna waste time to read all code.. so I make simpler code to understand my question.. I reasearch that question in here too but couldnt find a solution...

question is easy : addeventlistener works but why removeeventlistener doesnt work...

Thanks.

function addHandler() {
  elements = document.querySelectorAll('.bbb');
  elements.forEach(element => {
    element.addEventListener('click', whatever = function() {
      alert("");
    })
  })
}

function removeHandler() {
  elements = document.querySelectorAll('.bbb');
  elements.forEach(element => {
    element.removeEventListener('click', whatever)
  });
}
<script type="text/javascript" language="javascript" src="https://code.jquery.com/jquery-3.5.1.js"></script>

<html>

<body>
  <div id="kanban">

    <a style="margin-left:20px" class="bbb">AAAA </a>
    <a style="margin-left:20px" class="bbb">AAAA </a>
    <a style="margin-left:20px" class="bbb">AAAA </a>
    <a style="margin-left:20px" class="bbb">AAAA </a>
    <a style="margin-left:20px" class="bbb">AAAA </a>
    <a style="margin-left:20px" class="bbb">AAAA </a>

  </div>
  <br>
  <button onclick="removeHandler()">Remove</button>
  <button onclick="addHandler()">start</button>

</body>

</html>

Upvotes: 0

Views: 53

Answers (2)

T.J. Crowder
T.J. Crowder

Reputation: 1074178

You're overwriting the value of whatever every time you assign to it in your forEach, so only the last one is remembered. Later, when removing, that only succeeds in removing it from the last element.

You need to remember each function you create if you want to remove it later. For instance, you could keep them in a WeakMap keyed by the element (example below). That's just one option, though. Often, you can avoid adding handlers to a bunch of elements by using event delegation instead.

Here's that WeakMap example:

const handlers = new WeakMap();

function addHandler() {
    const elements = document.querySelectorAll(".bbb");
    elements.forEach(element => {
        const handler = function() {
          alert("");
        };
        element.addEventListener("click", handler);
        handlers.set(element, handler);
    });
}

function removeHandler() {
    const elements = document.querySelectorAll(".bbb");
    elements.forEach(element => {
        const handler = handlers.get(element);
        if (handler) {
            handlers.delete(element);
            element.removeEventListener("click", handler);
        }
    });
}
<script type="text/javascript" language="javascript" src="https://code.jquery.com/jquery-3.5.1.js"></script>

<html>

<body>
  <div id="kanban">

    <a style="margin-left:20px" class="bbb">AAAA </a>
    <a style="margin-left:20px" class="bbb">AAAA </a>
    <a style="margin-left:20px" class="bbb">AAAA </a>
    <a style="margin-left:20px" class="bbb">AAAA </a>
    <a style="margin-left:20px" class="bbb">AAAA </a>
    <a style="margin-left:20px" class="bbb">AAAA </a>

  </div>
  <br>
  <button onclick="removeHandler()">Remove</button>
  <button onclick="addHandler()">start</button>

</body>

</html>

Here's an example using event delegation, though, which is often cleaner:

function handler(e) {
    // Starting from the innermost target of the event,
    // see if that element or any of its parents (in order)
    // matches the selector
    const element = e.target.closest(".bbb");
    // If we found one and it's inside the element we hooked
    // the event on, fire the handler code
    if (element && this.contains(element)) {
        alert(`Handled on "${element.textContent}"`);
    }
}

function addHandler() {
    // id="kanban" seems to be the nearest container of all
    // of the elements we want, so use that as the base for
    // delegation
    document.getElementById("kanban").addEventListener("click", handler);
}

function removeHandler() {
    document.getElementById("kanban").removeEventListener("click", handler);
}
<script type="text/javascript" language="javascript" src="https://code.jquery.com/jquery-3.5.1.js"></script>

<div id="kanban">

<a style="margin-left:20px" class="bbb">AAAA </a>
<a style="margin-left:20px" class="bbb">BBBB </a>
<a style="margin-left:20px" class="bbb">CCCC </a>
<a style="margin-left:20px" class="bbb">DDDD </a>
<a style="margin-left:20px" class="bbb">EEEE </a>
<a style="margin-left:20px" class="bbb">FFFF </a>

</div>
<br>
<button onclick="removeHandler()">Remove</button>
<button onclick="addHandler()">start</button>


Side note: If you look at the code in the answer above, you'll see const in various places where you didn't have anything. Your code was falling prey to what I call The Horror of Implicit Globals by assigning to undeclared identifiers. That's a bad idea. Instead, always declare your variables (and use strict mode — the default in standard modules — so that assigning to an undeclared identifier is the error it always should have been).

Upvotes: 2

Barmar
Barmar

Reputation: 780861

At the end of the forEach loop, whatever will contain the event listener that was added to the last .bbb element. If you try to use that when removing from other elements it won't work, because the listener functions will be different.

Rather than using a single variable, you can use an array of listeners.

let bbb_listeners = [];

function addHandler() {
  elements = document.querySelectorAll('.bbb');
  elements.forEach((element, i) => {
    let listener = function() {
      alert(i);
    };
    bbb_listeners.push(listener);
    element.addEventListener('click', listener)
  })
}

function removeHandler() {
  elements = document.querySelectorAll('.bbb');
  elements.forEach((element, i) => {
    element.removeEventListener('click', bbb_listeners[i])
  });
}
<script type="text/javascript" language="javascript" src="https://code.jquery.com/jquery-3.5.1.js"></script>

<html>

<body>
  <div id="kanban">

    <a style="margin-left:20px" class="bbb">AAAA </a>
    <a style="margin-left:20px" class="bbb">AAAA </a>
    <a style="margin-left:20px" class="bbb">AAAA </a>
    <a style="margin-left:20px" class="bbb">AAAA </a>
    <a style="margin-left:20px" class="bbb">AAAA </a>
    <a style="margin-left:20px" class="bbb">AAAA </a>

  </div>
  <br>
  <button onclick="removeHandler()">Remove</button>
  <button onclick="addHandler()">start</button>

</body>

</html>

Upvotes: 1

Related Questions