0

I wish to dynamically create a row of 4 buttons with an .onclick function within dynamically created div. This works perfectly well within a single div, however when attempting to create the buttons inside the nested div, only the buttons created in last div are functioning properly.

I have found questions relating to using var in for loops creating issues, but this doesn't seem to be the case here. I've tried assigning a separate function to .onclick to no avail, and just need to get it sorted!

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id="entries">
    </div>
</body>

<script>
let synth = window.speechSynthesis;

let createButton = function (pCat, index, phraseDivIndex) {
    let button = document.createElement("button");
    button.innerText = pCat[index];
    button.className = "entry";

    button.onclick = function () {
        synth.speak(new SpeechSynthesisUtterance("boo")); //speak phrase from json
    }
        document.getElementsByClassName("phraseDiv")[phraseDivIndex].appendChild(button); //adds buttons to newly created div for focus management.
}


let populateEntries = function () {
    let phraseDivIndex = -1;
    let pCat = ["one", "two", "three", "four", "five","six","seven","eight","nine"];
    for (let i = 0; i < pCat.length; i++) {
        if (i % 4 == 0) {
            phraseDivIndex++;
            document.getElementById("entries").innerHTML += "<div class=phraseDiv tabindex = 0 class = 'catRow'></div>";
        }
        createButton(pCat, i, phraseDivIndex);
    }
}

//create buttons
populateEntries()

</script>

Outcome of code: Only the last row of buttons created have an .onclick event attached to them. That is a problem!

*edit: array index

mumbleMam
  • 3
  • 2
  • Poor technique. `.innerHTML +=` requires a DOM read and write. Store that in a `var results = '';` that you `results += 'innerHTMLCodeHere';` then `Element.innerHTML = results;`. Of course, I don't even recommend `.innerHTML` to create DOM Elements most of the time, since they have to be added to the DOM before you can attach Events to them. `document.createElement(tag)` is how to create... then it's like `Element.className = 'class1 class2 class3'; Element.tabIndex = -1;` and the like. `Element.onclick = funciton(){ /* do stuff */ }; OtherElement.appendChild(Element)`. Just advice. – StackSlave Aug 08 '19 at 23:36

2 Answers2

2

Welcome to StackOverflow.

Your case is actually a really tricky one. Let me explain though why your event listeners disappear.

The problem lies in this line:

document.getElementById("entries").innerHTML += "<div class=phraseDiv tabindex = 0 class = 'catRow'></div>";

What happens when you use innerHTML in combination with += is the string of innerHTML is accessed and then concatenated with whatever you define.

You defined the onclick handler as a DOM element property (read more about attributes vs properties here) and these are not included when you access the elements via innerHTML and concatenate them with another HTML string. What happens is that you're removing the onclick properties every 4th round because they won't be stringified.

What you could do to fix this is not use =+ with innerHTML but rather appendChild which keeps the DOM intact.

if (i % 4 == 0) {
  phraseDivIndex++;
  const newContainer = document.createElement('div');
  newContainer.classList.add('phraseDiv', 'catRow');
  newContainer.tabIndex = 0;

  document.getElementById("entries").appendChild(newContainer)
}

You can find a running version here. Hope this helps. :)

stefan judis
  • 3,416
  • 14
  • 22
0

I think you are talking about a live event listener. You can create it like this:

document.addEventListener('click', e => {
  const button = e.target.closest('button')

  if (button) {
       synth.speak(new SpeechSynthesisUtterance("boo"));
  }
})

Also, calling document.getElementById("entries").innerHTML += for every iteration is very imperformant. Append to a string instead (the button too, best using template literals) and set innerHTML in the end.

Fabian von Ellerts
  • 4,763
  • 40
  • 35