nelek's answer is the best one posted so far, but it relies on setting the css value: white-space: pre, which might be undesirable.
I'd like to offer a different solution, which tries to tackle the real question that should've been asked here:
"How to insert untrusted text into a DOM element?"
If you trust the text, why not just use innerHTML?
domElement.innerHTML = trustedText.replace(/\r/g, '').replace(/\n/g, '<br>');
should be sufficient for all the reasonable cases.
If you decided you should use .textContent instead of .innerHTML, it means you don't trust the text that you're about to insert, right? This is a reasonable concern.
For example, you have a form where the user can create a post, and after posting it, the post text is stored in your database, and later on appended to pages whenever other users visit the relevant page.
If you use innerHTML here, you get a security breach. i.e., a user can post something like
<script>alert(1);</script>
which won't trigger an alert if you use innerHTML, but it should give you an idea why using innerHTML can have issues. a smarter user would post
<img src="invalid_src" onerror="alert(1)" />
which would trigger an alert for every other user that visits the page. Now we have a problem. An even smarter user would put display: none on that img style, and make it post the current user's cookies to a cross domain site. Congratulations, all your user login details are now exposed on the internet.
So, the important thing to understand is, using innerHTML isn't wrong, it's perfect if you're just using it to build templates using only your own trusted developer code. The real question should've been "how do I append untrusted user text that has newlines to my HTML document".
This raises a question: which strategy do we use for newlines? do we use
elements?
s or s?
Here is a short function that solves the problem:
function insertUntrustedText(domElement, untrustedText, newlineStrategy) {
domElement.innerHTML = '';
var lines = untrustedText.replace(/\r/g, '').split('\n');
var linesLength = lines.length;
if(newlineStrategy === 'br') {
for(var i = 0; i < linesLength; i++) {
domElement.appendChild(document.createTextNode(lines[i]));
domElement.appendChild(document.createElement('br'));
}
}
else {
for(var i = 0; i < linesLength; i++) {
var lineElement = document.createElement(newlineStrategy);
lineElement.textContent = lines[i];
domElement.appendChild(lineElement);
}
}
}
You can basically throw this somewhere in your common_functions.js file and then just fire and forget whenever you need to append any user/api/etc -> untrusted text (i.e. not-written-by-your-own-developer-team) to your html pages.
usage example:
insertUntrustedText(document.querySelector('.myTextParent'), 'line1\nline2\r\nline3', 'br');
the parameter newlineStrategy accepts only valid dom element tags, so if you want [br] newlines, pass 'br', if you want each line in a [p] element, pass 'p', etc.