Home > Software engineering >  InnerText JavaScript Losing the beginning newline?
InnerText JavaScript Losing the beginning newline?

Time:01-15

I have a modal in HTML, and I am trying to append some text to its InnerText attribute, but it seems that one of the newlines gets ignored. For example, I want it to be this:

class Student{

}
class AG{

}

But instead, it is:

class Student{ }
class AG{

}

I tried adding more newlines, and removing some extra ones that could have been a problem, but nothing happened. I tried using the InnterHTML attribute, but that didn't solve it either. HTML:

    <div id="buttons">
        <button id="create-box">Create Class</button>
        <button id="myBtn">View Code (C  )</button>
    </div>
    <div id="box-container"></div>
    <div id="myModal" >
    <div >
        <p></p>
    </div>

    </div>
    <script src="main.js"></script>

JS:

var generatedCode = "";
var classNameCode = {};

document.getElementById("create-box").addEventListener("click", function() {
    let box = document.createElement("div");
    box.classList.add("box");
    let title = document.createElement("h2");
    title.innerHTML = "Class";
    box.appendChild(title);
    let classTitle = document.createElement('input');
    classTitle.classList.add('class-name-form');
    let classTitleBtn = document.createElement('button');
    classTitleBtn.innerText = "Set Class Name";
    classTitle.classList.add('class-name-btn');
    box.appendChild(classTitle);
    box.appendChild(classTitleBtn);
    document.getElementById("box-container").appendChild(box);
    let createSubclassButton = document.createElement("button");
    createSubclassButton.innerHTML = "Add Method";
    box.appendChild(createSubclassButton);

    // Enable resizing and dragging
    box.addEventListener("mousedown", startDrag);
    box.addEventListener("mousemove", drag);
    box.addEventListener("mouseup", endDrag);
    box.addEventListener("mouseleave", endDrag);

    let offset = [0, 0];
    let isDown = false;

    function startDrag(e) {
        isDown = true;
        offset = [
            this.offsetLeft - e.clientX,
            this.offsetTop - e.clientY
        ];
    }

    function drag(e) {
        if (!isDown) return;
        e.preventDefault();
        this.style.left = (e.clientX   offset[0])   'px';
        this.style.top = (e.clientY   offset[1])   'px';
    }

    function endDrag(e) {
        isDown = false;
    }

    createSubclassButton.addEventListener("click", function() {
        let subBox = document.createElement("div");
        subBox.classList.add("subbox");
        let subTitle = document.createElement("h2");
        subTitle.innerText = "New Method";
        subBox.append(subTitle);
        document.getElementById("box-container").appendChild(subBox);
        subBox.style.left = box.offsetLeft   box.offsetWidth   10   'px';
        subBox.style.top = box.offsetTop   'px';

        // Enable resizing and dragging for the subbox
        subBox.addEventListener("mousedown", startDrag);
        subBox.addEventListener("mousemove", drag);
        subBox.addEventListener("mouseup", endDrag);
        subBox.addEventListener("mouseleave", endDrag);
    });

    classTitleBtn.addEventListener("click", function() {
        console.log(classTitle.value);
        // generatedCode = `class ${classTitle.value} {\n\t`
        classNameCode[classTitle.value] = `\nclass ${classTitle.value} {\n`
    });
});





// MODAL FOR CODE GENERATION
var modal = document.getElementById("myModal");


var btn = document.getElementById("myBtn");

var modalContent = document.querySelector('.modal-content');


btn.onclick = function() {
    modalContent.innerText = "";
    console.log(classNameCode);
    for(var key in classNameCode){
        var codeToAppend = `${classNameCode[key]} \n}`;
        console.log(codeToAppend);
        modalContent.innerText  = codeToAppend;
    }
    modal.style.display = "block";
}


window.onclick = function(event) {
  if (event.target == modal) {
    modal.style.display = "none";
  }
}

CSS:

body {
    background-size: 40px 40px;
    background-image: radial-gradient(circle, #000000 1px, rgba(0, 0, 0, 0) 1px);
    background-color: darkgray;
}

#create-box {
    padding: 10px 20px;
    background-color: #4CAF50;
    border-radius: 5px;
    border: none;
    color: white;
}

#create-box:hover{
    background-color: #5dc861;
    cursor: pointer;
}

#create-box:active{
    cursor:pointer
}
  
.box {
    position: absolute;
    width: 200px;
    height: 200px;
    background-color: #f1f1f1;
    border: 1px solid #d3d3d3;
    border-radius: 5px;
    resize: both;
    overflow: auto;
    z-index: 1;
}
  
.box h2 {
    margin: 0;
    padding: 10px;
}
  
.subbox {
    width: 100px;
    height: 100px;
    background-color: #f1f1f1;
    border: 1px solid #d3d3d3;
    position: absolute;
    z-index: 1;
    border-radius: 5px;
}

/* The Modal (background) */
.modal {
    display: none; /* Hidden by default */
    position: fixed; /* Stay in place */
    z-index: 1; /* Sit on top */
    left: 0;
    top: 0;
    width: 100%; /* Full width */
    height: 100%; /* Full height */
    overflow: auto; /* Enable scroll if needed */
    background-color: rgb(0,0,0); /* Fallback color */
    background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
  }
  
  /* Modal Content/Box */
  .modal-content {
    background-color: #fefefe;
    margin: 15% auto; /* 15% from the top and centered */
    padding: 20px;
    border: 1px solid #888;
    width: 80%; /* Could be more or less, depending on screen size */
  }
  
  /* The Close Button */
  .close {
    color: #aaa;
    float: right;
    font-size: 28px;
    font-weight: bold;
  }
  
  .close:hover,
  .close:focus {
    color: black;
    text-decoration: none;
    cursor: pointer;
  }

#myBtn{
    padding: 10px 20px;
    background-color: #4CAF50;
    border-radius: 5px;
    border: none;
    color: white;
}

#myBtn:hover{
    background-color: #5dc861;
    cursor: pointer;
}

#myBtn:active{
    cursor:pointer
}

#buttons{
    text-align: center;
}

CodePudding user response:

By default, new lines in HTML are not interpreted as new lines to render on screen. You can either:

  • Add white-space: pre; property to .modal-content. This element will now respect carriage returns.
  • Use innerHTML and instead of appending carriage returns (\n) append a <br/> tag.

You also probably want to do \r\n instead of just \n or it may not work properly on windows browsers.

CodePudding user response:

Solutions

Your btn.onclick handler should be modified. Any of the following should work:

  1. Avoid reading innerText while the modal is hidden by collecting your code fragments in a variable and only after the loop set the innerText:
    console.log(classNameCode);
    let code = '';
    for(var key in classNameCode){
        var codeToAppend = `${classNameCode[key]} \n}`;
        console.log(codeToAppend);
        code  = codeToAppend;
    }
    modalContent.innerText = code; // avoid reading innerText when modal is hidden
    modal.style.display = "block";
    
  2. Avoid surprising innerText read behavior for hidden elements by first making it visible. You can do so by moving modal.style.display = "block"; to the start of the btn.onclick handler function:
    modal.style.display = "block";
    modalContent.innerText = "";
    console.log(classNameCode);
    for(var key in classNameCode){
        var codeToAppend = `${classNameCode[key]} \n}`;
        console.log(codeToAppend);
        modalContent.innerText  = codeToAppend;
    }
    

Reason

While writing a value containing a line break to innerText always creates <br> elements, reading it from a hidden element behaves differently: it ignores the descendant <br> elements.

In your btn.onclick handler, you wrote .innerText = ... which means reading the current value and the writing in the next step.

In the second iteration of the loop, you are reading the existing innerText which returns the text without the line break from the first iteration (although there is a <br> in the DOM). You then write the concatenation of this and the the second codeToAppend which terminally removes the line break from the first iteration from the modal's contents.

Research

I have copied your code into local files and reduced the example until I found the actual cause:

Having only the style .modal { display: none; } active, the bad behavior can be observed. Without that style, the new lines are added as desired.

So I've found that the culprit is display: none which seems to have an effect on how innerText is working.

Looking at the HTML specification of innerText seems to confirms this, as it mentions special handling for the case where the element is not being rendered.

In particular, it states:

If this is not being rendered or if the user agent is a non-CSS user agent, then return this's descendant text content.

The descendant text content is defined as:

The descendant text content of a node node is the concatenation of the data of all the Text node descendants of node, in tree order.

Clearly, this does not include <br> elements and that's why your innerText does not contain the line breaks as you desire.

The innerText specification mentions special handling for <br> only in the case, the element is being rendered. In particular, it states that for each rendered descendant node, the rendered text collection steps are being executed and those contain:

If node is a br element, then append a string containing a single U 000A LF code point to items.

  • Related