4

Given the minimal code sample below I would expect that each dynamically created button in each table row would produce an alert based on the associated onclick event handler.

class A {
  constructor() {
    var root = document.getElementById('root');
    root.innerHTML =
      `
            <button id="btn-add">+</button>
            <table id="tbl"></table>
          `
    document.getElementById('btn-add').onclick = () => {
      this.add()
    }
    this.counter = 0
  }
  add() {
    var tbl = document.getElementById('tbl').innerHTML +=
      `
            <li>
              <button id="${this.counter}-btn-rmv">-</button>
            </li>
            
          `
    document.getElementById(this.counter + '-btn-rmv').onclick = () => {
      alert('Click event!')
    } //<-- each should have an event handler!
    this.counter++
  }
}
new A()
<div id="root" />

However only the last button in the last row has a functional event handler and produces an alert. The event handlers on the previously created buttons, for some reason are removed. I'm not sufficiently famillier with javascript to understand what is going wrong here, please let me know.

2
  • 2
    IDs on html elements must be unique. If this code is used to create multiple instances of the same html, then you're going to have multiple elements with the same IDs, which might cause this issue. Commented Oct 17, 2019 at 22:08
  • Script elements must be in the head or body, not outside the body. Browsers will auto correct though and put the script element inside the body. <div id="root"/> is invalid HTML, browsers will auto–correct but will guess at where to put the closing div tag. Commented Oct 17, 2019 at 22:41

1 Answer 1

3

The problem is

var tbl = document.getElementById('tbl').innerHTML +=
`
<li>
<button id="${this.counter}-btn-rmv">-</button>
</li>
`

This is functionally equivalent to

const existingHTML = document.getElementById('tbl').innerHTML;
document.getElementById('tbl').innerHTML = '';
document.getElementById('tbl').innerHTML = existingHTML + ...

The container element essentially gets cleared, and then its contents are re-parsed by the browser according to the new concatenated string with +=. The old elements no longer exist in the document, so the listeners attached to the old elements aren't triggerable (and clicking on any of the - buttons other than the last button will do nothing).

Use insertAdjacentHTML instead, which does not corrupt the existing elements:

class A {
  constructor() {
    var root = document.getElementById('root');
    root.innerHTML =
      `
            <button id="btn-add">+</button>
            <table id="tbl"></table>
          `
    document.getElementById('btn-add').onclick = () => {
      this.add()
    }
    this.counter = 0
  }
  add() {
    document.getElementById('tbl').insertAdjacentHTML('beforeend', `
            <li>
              <button id="${this.counter}-btn-rmv">-</button>
            </li>

          `);
    document.getElementById(this.counter + '-btn-rmv').onclick = () => {
      alert('Click event!')
    } //<-- each should have an event handler!
    this.counter++
  }
}
new A()
<div id="root" />

That said, dynamically created IDs are quite a code smell - consider using event delegation instead:

class A {
  constructor() {
    var root = document.getElementById('root');
    root.innerHTML =
      `
            <button id="btn-add">+</button>
            <table id="tbl"></table>
          `
    root.addEventListener('click', ({
      target
    }) => {
      if (target.matches('[data-btn-rmv]')) {
        console.log('Clicked on', target.dataset.btnRmv);
      }
    });

    document.getElementById('btn-add').onclick = () => {
      this.add()
    }
    this.counter = 0
  }
  add() {
    document.getElementById('tbl').insertAdjacentHTML('beforeend', `
            <li>
              <button data-btn-rmv=${this.counter}>-</button>
            </li>

          `)
    this.counter++
  }
}
new A()
<div id="root" />

Sign up to request clarification or add additional context in comments.

1 Comment

It's not but if you inspect the dom while you are clicking you will see the elements clear each time and get replaced

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.