RuntimeError
RuntimeError

Reputation: 1390

Uncaught ReferenceError: variable is not defined at HTMLButtonElement.onclick

I have a HTML file with some JS logic where I want to create buttons based on an array.

for (var i = 0; i < arrayButtons.length; i++) {
            console.log("console! ", arrayButtons[i].title); // works!
            let buttonTitle = arrayButtons[i].title;

            document.getElementById("buttons").innerHTML +=
                "<div><button class='changeButton' " +
                       "onclick= document.getElementById(\"changeByButtonClick\").innerHTML = buttonTitle'>" 
                      + "<i class=\"icon\"></i>"+ buttonTitle + "</button></div>";}

In this line: "onclick= document.getElementById(\"changeByButtonClick\").innerHTML = buttonTitle'>", it throws an error that buttonTitle is not defined:

Uncaught ReferenceError: buttonTitle is not defined at HTMLButtonElement.onclick

I absolutely don't understand why. As you can see, I defined buttonTitle inside the method and just want to access it. And on console.log(), it works. Why is it undefined in onClick()?

Unfortunately, this and this solutions don't work for me.

Help will be much appreciated!

Upvotes: 0

Views: 2092

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1074495

Your first use of buttonTitle is inside the string that you're assigning to the innerHTML of the id="buttons" element, inside the onclick attribute you're using there. The code in the onclick string is evaluated at global scope,¹ where you don't have a buttonTitle variable.

I strongly recommend not building code strings like that, not least for this reason. Instead, use a function:

for (var i = 0; i < arrayButtons.length; i++) {
    console.log("console! ", arrayButtons[i].title); // works!
    let buttonTitle = arrayButtons[i].title;

    const buttons = document.getElementById("buttons")
    buttons.insertAdjacentHTML(
        "beforeend",
        "<div><button class='changeButton'>" +
        "<i class=\"icon\"></i>"+ buttonTitle + "</button></div>"
    );
    // Get the button we just added (the last one)
    const buttonList = buttons.querySelectorAll("button");
    const newButton = buttonList[buttonList.length - 1];
    // Add the handler to it
    newButton.addEventListener("click", function() {
        document.getElementById("changeByButtonClick").innerHTML = buttonTitle;
    });
}

Live Example:

const arrayButtons = [
    {title: "first"},
    {title: "second"},
    {title: "third"},
    {title: "fourth"},
];

for (var i = 0; i < arrayButtons.length; i++) {
    console.log("console! ", arrayButtons[i].title); // works!
    let buttonTitle = arrayButtons[i].title;

    const buttons = document.getElementById("buttons")
    buttons.insertAdjacentHTML(
        "beforeend",
        "<div><button class='changeButton'>" +
        "<i class=\"icon\"></i>"+ buttonTitle + "</button></div>"
    );
    // Get the button we just added (the last one)
    const buttonList = buttons.querySelectorAll("button");
    const newButton = buttonList[buttonList.length - 1];
    // Add the handler to it
    newButton.addEventListener("click", function() {
        document.getElementById("changeByButtonClick").innerHTML = buttonTitle;
    });
}
#changeByButtonClick {
    min-height: 1em;
}
<div id="changeByButtonClick"></div>
<div id="buttons">
    This is the initial content that we don't remove.
</div>

Also note that I used insertAdjacentHTML instead of using += on innerHTML. (Thanks to Niet the Dark Absol for pointing it out, I missed it was a +=.) Never use += on innerHTML, when you do that, the browser has to do this:

  • Recurse through the nodes in the target element building an HTML string.
  • Pass that string to the JavaScript layer.
  • Receive the new string from the JavaScript layer.
  • Tear down the entire contents of the target element.
  • Parse the string.
  • Build new replacement nodes for the previous contents plus the new nodes for the new content.

In the process it loses event handlers and other non-HTML information. In contrast, with insertAdjacentHTML, all it has to do is parse the string and insert the new nodes.


¹ "...at global scope..." Actually, it's a bit more complicated than that, it's a nested scope using (effectively) with blocks. But for the purposes of the question, the details there aren't important; it's nearly at global scope.

Upvotes: 2

Related Questions