Jemt / Fit.UI

Fit.UI is a JavaScript based UI framework built on Object Oriented principles
http://fitui.org
GNU Lesser General Public License v3.0
19 stars 7 forks source link

Fit.Dom.Text - preserve linebreaks on modern browsers #159

Open Jemt opened 2 years ago

Jemt commented 2 years ago

Fit.Dom.Text(elm):string does not preserve linebreaks on modern browsers due to the use of textContent. Consider using innerHTML, replace <br> with \n, strip HTML, and replace HEX and HTML entities with real characters. This will produce a value identical to the one .textContent produces, but with linebreaks preserved. This would also allow us to completely get rid of the use of innerText which first of all is not cross browser compatible, and second is aware of rendered content (e.g. excludes content with display:hidden). This in turn results in different results across different browsers.

The example below demonstrates the desired logic and proves the results is identical to the result of .textContent - except for the preserved linebreaks, of course.

function replaceHexEntities(str)
{
    return str.replace(/&#(\d+);/g, function(fullMatch, captureGroup) { return String.fromCharCode(captureGroup); });
}

function replaceHtmlEntities(str)
{
    var result = str;
    result = result.replace(/&amp;/g, "&");
    result = result.replace(/&lt;/g, "<");
    result = result.replace(/&gt;/g, ">");
    result = result.replace(/&nbsp;/g, " "); // NOTICE: Not a space but a non-breaking space (ALT+Space on Mac)
    result = result.replace(/&quot;/g, "\"");
    return result;
}

function getText(elm)
{
    var val = elm.innerHTML;
    val = val.replace(/<br ?\/?>/g, "\n");
    val = Fit.String.StripHtml(val);
    val = replaceHexEntities(val);
    val = replaceHtmlEntities(val);
    return val;
}

var elm = Fit.Dom.CreateElement("<h1>Hello there</h1>  <br><br>  Super <b>cool</b> &gt;text&lt; &nbsp;&nbsp;&nbsp; goes here &#128514;  <script>alert(123)</script>  <div style='display: none'>This text is hidden</div>");
var val = getText(elm);

console.log("Result with new getText(..) - like .textContent, but with linebreaks preserved:", "'" + val + "'");
console.log("Result with .textContent - same as above, except linebreaks are not preserved:", "'" + elm.textContent + "'");
console.log("Are values identical if linebreaks are eliminated (expected: true)?:", (val.replace(/\n/g, "") === elm.textContent));

New implementation should naturally also work as a setter function, allowing for new text values to be applied - also with line breaks preserved (\n being replaced by <br> and special HTML characters being replaced by HTML entities). Value should be assigned to element using cross browser compatible innerHTML.

function setText(elm, str)
{
    var html = str;
    html = html.replace(/&/g, "&amp;");
    html = html.replace(/</g, "&lt;");
    html = html.replace(/>/g, "&gt;");
    html = html.replace(/ /g, "&nbsp;"); // NOTICE: Not a space but a non-breaking space (ALT+Space on Mac)
    html = html.replace(/"/g, "&quot;");
    html = html.replace(/\n/g, "\n<br>");
    console.log("Value being set with setText(..):", html);
    elm.innerHTML = html;
}

var div1 = document.createElement("div");
var div2 = document.createElement("div");
var test = "Hi there - want som \"pizza\"? 🍕 \n\n Non-breaking spaces (ALT+Space on Mac):      . 1 < 2 is true & 2 > 1 is also true. <b>HTML</b> &amp; entities such as &lt; and &#128514; is not a problem.";

setText(div1, test);
div2.textContent = test;

console.log("div1.textContent:", div1.textContent);
console.log("div2.textContent:", div2.textContent);
console.log("Are .textContent values identical using different setter approaches (expected: true):", div1.textContent === div2.textContent);

console.log("How does different setter approaches affect HTML result if linebreak differences (which were preserved with setText(..)) are eliminated?:");
console.log("div1.innerHTML:", div1.innerHTML.replace(/<br>/g, ""));
console.log("div2.innerHTML:", div2.innerHTML);
console.log("Are .innerHTML values identical using different setter approaches (expected: true):", div1.innerHTML.replace(/<br>/g, "") === div2.innerHTML);