Search code examples
javascripthtmlinnerhtml

JS: innerHTML inconsistency


I am using Google Chrome version 43.0.2357.130. I am trying to append output to an HTML element using innerHTML, which is being done inside of a loop. I get the expected result most of the time, but if I click on the "Generate" button over and over, eventually it will give me an unexpected result. For instance, one of the passwords will be chopped off at a random spot. I used the JS debugger in Chrome, but that didn't help much. Then I tried to debug it myself by using the alert() function alongside the innerHTML property so that I could compare the output. The output in the alert() popup was never truncated, unlike innerHTML.

I highlighted what I think is the problem code with '/* PROBLEM CODE */' in the JS file. The files should be placed in the same directory. Here is the HTML and JS:

<!DOCTYPE html>
<html>
<head>
    <title>PassGen - Random Password Generator</title>
    <!--link rel="stylesheet" src="//normalize-css.googlecode.com/svn/trunk/normalize.css"-->
    <!--link href="css/main.css" rel="stylesheet" type="text/css"-->
    <!--script src="../app/js/jquery-2.1.4.js"></script-->
</head>
<body>
    <form>
        <h2>Password amount</h2>
        <input type="text" id="amount" name="amount" />
        <h2>Letter amount</h2>
        <input type="text" id="letters" name="letters" />
        <h2>Number amount </h2>
        <input type="text" id="numbers" />
        <h2>Symbol amount</h2>
        <input type="text" id="symbols" />
        <input onclick="generatePassword(); return false;" type="submit" value="Generate" />
    </form>
    <p id="output"></p>
    <script src="plain-app.js"></script>
</body>
</html>

// get the DOM element we will be using for the final output
var output = document.getElementById("output");

function generatePassword(amount) {

    clearPasswords();

    // get DOM form elements (user input)
    var amount = document.getElementById("amount").value;
    var letters = document.getElementById("letters").value;
    var numbers = document.getElementById("numbers").value;
    var symbols = document.getElementById("symbols").value;

    // populate character sets
    var letterSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    var numberSet = "0123456789";
    var symbolSet = "~!@#$%^&*()-_+=><";
    var array = [];

    // if there is no password amount specified, create one password
    if(amount === undefined) {
        amount = 1;
    }

    for(var j = 0; j < amount; j++) {

        // random character sets to be concatenated later
        var rl = "";
        var rn = "";
        var rs = "";
        var tp = ""; // concatenated password before shuffling

        // 3 random letters
        for(var i = 0; i < letters; i++) {
            var rnd = Math.floor((Math.random() * 52));
            rl += letterSet[rnd];
        }

        // 3 random numbers
        for(var i = 0; i < numbers; i++) {
            var rnd = Math.floor((Math.random() * 10));
            rn += numberSet[rnd];
        }

        // 3 random symbols
        for(var i = 0; i < symbols; i++) {
            var rnd = Math.floor((Math.random() * 17));
            rs += symbolSet[rnd];
        }

        tp = rl + rn + rs; // string concatentation
        tp = tp.split(''); // transform string into an array

        // shuffling
        for(var i = 0; i < tp.length; i++) {
            var rnd = Math.floor(Math.random() * tp.length);
            var temp = tp[i];
            tp[i] = tp[rnd];
            tp[rnd] = temp;
        }

        // transform the array into a string
        tp = tp.join("");

        array[j] = tp; // for logging and debugging purposes

        // tp can be replaced with array[j], but still get the inconsistency

        /* PROBLEM CODE */
        output.innerHTML += (tp + "<br />");
        /* PROBLEM CODE */

        //alert(array[j]);
    }
    console.log(array);

    return array; // not useful?
}

// clear all password output
function clearPasswords() {

    while(output.hasChildNodes()) {
        output.removeChild(output.firstChild);
    } 
}

Does innerHTML have side effects I don't know about or am I using it incorrectly? Should it not be used for appends? Should appendChild() be used instead?


Solution

  • The problem is that some characters have special meaning in HTML: <, >, &.

    Therefore, you can

    • Remove those characters from the list
    • Escape them: &lt;, &gt;, &amp;

      output.innerHTML += tp.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/&/g, '&amp;') + "<br />";
      
    • Parse the text as text instead of as HTML, e.g.