Home > Software engineering >  removeEventListener in forEach
removeEventListener in forEach

Time:08-25

Friends!

How in JS to hang such a handler on all the buttons of the collection, so that when each of them is clicked, something happens, and the handler is removed from all the buttons in the collection? I managed to make it so that it is removed only from the one I click on (while the class is removed from everyone):

document.querySelectorAll('.string').forEach((field, i, fields) => {
  const listen = () => {
    const res = document.createElement('div');
    res.textContent = field.textContent;
    document.getElementById('container').append(res);

    fields
      .forEach((field, i, fields) => {
        field.classList.remove('bright');
        field.removeEventListener('click', listen);
      });
  };

  field.addEventListener('click', listen);
});

https://codepen.io/andreymi/pen/RwMOvOq

CodePudding user response:

Rather than adding a listener to all and then removing a listener from all, how about adding a single listener to a container element with event delegation, which can then be removed easily by referencing the function name again?

const buttons = document.querySelectorAll('.string');
const flex = document.querySelector('.flex');
flex.addEventListener('click', function handler(e) {
  if (!e.target.matches('.string')) {
    return;
  }
  flex.removeEventListener('click', handler);
  const res = document.createElement('div');
  res.textContent = e.target.textContent;
  document.getElementById('container').append(res);
  for (const button of buttons) {
    button.classList.remove('bright');
  }
});
.wrap {
  background: #808080;
  font-size: 2em;
}

.flex {
  display: flex;
  flex-wrap: nowrap;
  justify-content: space-between;
  gap: 1em;
}

.string {
  display: flex;
  flex-grow: 1;
  height: 80px;
  background: #FFFFFF;
  justify-content: center;
  align-items: center;
  font-size: 1em;
}

.bright {
  background: #58afd1;
}
<body >
  <div >
    <button type='button' >1</button>
    <button type='button' >2</button>
    <button type='button' >3</button>
    <button type='button' >4</button>
    <button type='button' >5</button>
  </div>
  <div id='container'></div>
</body>

Or perhaps have the buttons inherit their style from their container.

const buttons = document.querySelectorAll('.string');
const flex = document.querySelector('.flex');
flex.addEventListener('click', function handler(e) {
  if (!e.target.matches('.string')) {
    return;
  }
  flex.removeEventListener('click', handler);
  const res = document.createElement('div');
  res.textContent = e.target.textContent;
  document.getElementById('container').append(res);
  flex.classList.remove('bright');
});
.wrap {
  background: #808080;
  font-size: 2em;
}

.flex {
  display: flex;
  flex-wrap: nowrap;
  justify-content: space-between;
  gap: 1em;
}

.string {
  display: flex;
  flex-grow: 1;
  height: 80px;
  background: #FFFFFF;
  justify-content: center;
  align-items: center;
  font-size: 1em;
}

.bright .string {
  background: #58afd1;
}
<body >
  <div >
    <button type='button' >1</button>
    <button type='button' >2</button>
    <button type='button' >3</button>
    <button type='button' >4</button>
    <button type='button' >5</button>
  </div>
  <div id='container'></div>
</body>

That works for the code in your question, but if the code you're actually using doesn't match what's in your question, then:

  • If your buttons have child elements, the target may be one of those child elements, and not one of the buttons, and so to check that the clicked element is a descendant of a button, change to e.target.closest('.string')
  • If, in addition to the above, you may have a .string element that's a container of the .flex, clicks on the .flex that aren't on the buttons will be registered. If the HTML you're actually using has that, then check that the .string is a descendant by searching through only descendants of the .flex container with .contains. starting from the .flex container

const buttons = document.querySelectorAll('.string');
const flex = document.querySelector('.flex');
flex.addEventListener('click', function handler(e) {
  if (!flex.contains(e.target.closest('.string'))) {
    return;
  }
  flex.removeEventListener('click', handler);
  const res = document.createElement('div');
  res.textContent = e.target.textContent;
  document.getElementById('container').append(res);
  flex.classList.remove('bright');
});
.wrap {
  background: #808080;
  font-size: 2em;
}

.flex {
  display: flex;
  flex-wrap: nowrap;
  justify-content: space-between;
  gap: 1em;
}

.string {
  display: flex;
  flex-grow: 1;
  height: 80px;
  background: #FFFFFF;
  justify-content: center;
  align-items: center;
  font-size: 1em;
}

.bright .string {
  background: #58afd1;
}
<body >
  <div >
    <button type='button' >1</button>
    <button type='button' >2</button>
    <button type='button' >3</button>
    <button type='button' >4</button>
    <button type='button' >5</button>
  </div>
  <div id='container'></div>
</body>

  • If some of your child elements of .flex aren't buttons, and some descendants of those elements also have the flex class, and those .flex descendants also have some .string descendants of their own, then to guard against the possibility of selecting one of those buttons instead of one of the immediate children buttons, after selecting the closest .string, check that its parent element is the flex the listener was attached to.

const buttons = document.querySelectorAll('.string');
const flex = document.querySelector('.flex');
flex.addEventListener('click', function handler(e) {
  const button = e.target.closest('.string');
  if (!button || !button.matches('.string') || !button.parentElement === flex) {
    return;
  }
  flex.removeEventListener('click', handler);
  const res = document.createElement('div');
  res.textContent = e.target.textContent;
  document.getElementById('container').append(res);
  flex.classList.remove('bright');
});
.wrap {
  background: #808080;
  font-size: 2em;
}

.flex {
  display: flex;
  flex-wrap: nowrap;
  justify-content: space-between;
  gap: 1em;
}

.string {
  display: flex;
  flex-grow: 1;
  height: 80px;
  background: #FFFFFF;
  justify-content: center;
  align-items: center;
  font-size: 1em;
}

.bright .string {
  background: #58afd1;
}
<body >
  <div >
    <button type='button' >1</button>
    <button type='button' >2</button>
    <button type='button' >3</button>
    <button type='button' >4</button>
    <button type='button' >5</button>
  </div>
  <div id='container'></div>
</body>

But if the HTML structure you have is what's actually in your question, you may consider there to be no need to complicate things.

CodePudding user response:

your code isnt working because the listen function get recreated for each element

var fields=document.querySelectorAll('.string');

const listen = () => {
  const res = document.createElement('div');
  res.textContent = this.textContent;     // <- note 'this' keyword
  document.getElementById('container').append(res);

  fields.forEach((field, i, fields) => {
    field.classList.remove('bright');
    field.removeEventListener('click', listen);
    console.log('remove',i);
  });
};

fields.forEach((field, i, fields) => {
  console.log('add',i);
  field.addEventListener('click', listen);
});
  • Related