0

So I have a class two class named "Form" and "Question". "Form" contained an array of Question. When I add a question in the array, I call a method called render() in each question

    render() {
        let htmlElement = document.getElementById("formBody");

        let id = Question.GenerateID();
        htmlElement.innerHTML += `
                <div class="p-2">
                    <input type="text" class="input-transparent w-100" id="${id}.t" value="${this.Title}" placeholder="Unnamed question">
                </div>`;

        /**
         * @param {int} id
         */
        let questionNameChange = function(id) {
            this.Title = document.getElementById(id + ".t").value;
        };

        document.getElementById(id + ".t").onchange = questionNameChange.bind(this, id);
     }

Image of the form

Image of the form

The problem is that the element.onchange, which allows me to change the title of a question.

When I change the title of any questions, via the input text, it only works on the last question that has been rendered.

Am I doing anything wrong?

Jee Mok
  • 6,157
  • 8
  • 47
  • 80
Jtplouffe
  • 11
  • 2

1 Answers1

0

Maybe you have to delay binding the onchange until the element has been inserted in the dom. In other words, wrap it in a setTimeout:

  setTimeout(function() {
        document.getElementById(id + ".t").onchange = questionNameChange.bind(this, id);
  })

Check out this working example:

let Question = {GenerateID: () => Math.floor(Math.random() * 100)}
class MyClass {
  constructor(){
    this.Title = 'my title'
  }
  
  questionNameChange(id) {
    this.Title = document.getElementById(id + ".t").value;
    document.getElementById(`${id}-label`).innerHTML = this.Title
  }

  render() {
        let htmlElement = document.getElementById("formBody");

        let id = Question.GenerateID();
        htmlElement.innerHTML += `
                <div class="p-2">
                    <input type="text" class="input-transparent w-100" id="${id}.t" value="${this.Title}" placeholder="Unnamed question">
                    <label id="${id}-label"></label>
                </div>`;

        setTimeout(() => {
          document.getElementById(id + ".t").onchange = () => {
            this.questionNameChange(id)
          }
        })
        
  }   
}

$(() => {     
  let myClass = new MyClass()
     myClass.render()
     myClass.render()
     myClass.render()
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<form id="formBody">

</form>
JorgeObregon
  • 3,020
  • 1
  • 12
  • 12
  • Works! Thank you! – Jtplouffe Oct 10 '19 at 00:34
  • It shouldn't matter if the element is in the DOM or not. – Barmar Oct 10 '19 at 00:34
  • I don't understand this answer. Assigning to `.innerHTML` inserts it into the DOM. You seem to be suggesting that this is asynchronous, but it isn't. – Barmar Oct 10 '19 at 00:38
  • If it didn't add it to the DOM, it would get an error because `document.getElementById(id + ".t")` would return `null`. – Barmar Oct 10 '19 at 00:39
  • 1
    Oh, I see the problem. it's because it's using `innerHTML +=`. This re-renders all the HTML of the element, and that discards all the old event handlers. – Barmar Oct 10 '19 at 00:40
  • This isn't really a complete solution. It will only work for all the elements added in this loop, the elements that were added earlier will lose their listeners. See the duplicate question I just linked to for the proper solution. – Barmar Oct 10 '19 at 00:45