How to render linebreaks as <br> tags with Aurelia
Asked Answered
I

3

6

I am retrieving some textual data using JSON, this data includes text formatted with linebreaks. I would very much like to render those linebreaks to the user.

Question: What is the "correct" / "recommended" approach to achieve this?

Options I've tried:

  • Binding normally: <p>${myText}</p> : Renders no linebreaks
  • Using <pre>: <p><pre>${myText}></pre></p> : Renders linebreaks, but have all the known and loved issues with long <pre> text, like horizontal scrolling in some browsers and suboptimal word breaking.
  • Binding normally using a valueConverter that replaces linebreaks with <br> tags: <p>${myText | textFormat}</p>
export class TextFormatValueConverter {
  toView(value) {
    return value.replace(new RegExp('\r?\n','g'), '<br>');
  }
}

This does render <br> tags, but the Aurelia binder escapes the tags and shows them as literal text to the user. * Binding using the above converter and innerHTML: <p innerHTML.bind="myText | textFormat"></p>: Renders ok but I'm worried that it might be vulnerable to exploits, as the texts comes from a legacy system that does not do any input sanitazion with regards to usage for the web.

Invocate answered 15/1, 2016 at 10:17 Comment(5)
What about sanitize-html converter?Forsterite
Thanks :) Did not know about that one. so one possible solution would be to pipe through the sanitizer, or just extend my own converter to include sanitation. I do believe some sanitation is included in the innerHtml binding (looking at the code of you linked above, it seems to be just scripts that are stopped here and I belive the innerHtml protects from that by default (could be wrong though). I'm not yet knowledgeable enough to say if protecting from script tags is good enough or if more protection should be considered..Invocate
Please see "Should questions include “tags” in their titles?", where the consensus is "no, they should not"!Euell
@AndreasNiedermair sometimes they should when they make the question clearer. This is one of those cases.Homiletic
@JeremyDanyow this is not one of the cases, where tags should be used, but rather a complete sentence for the title should be the target ... Tags are never the way to go!Euell
H
4

What you're doing is correct. Binding to innerHTML is sometimes necessary. The docs at aurelia.io include instructions for using a sanitization converter and a note about using a more complete implementation using the sanitize-html project.

That said, you can create a really light-weight custom attribute that does only what you need:

http://plnkr.co/edit/qykvo9PKAD0TawTlQ5sp?p=preview

preserve-breaks.js

import {inject} from 'aurelia-framework';

function htmlEncode(html) {
  return document.createElement('a').appendChild( 
    document.createTextNode(html)).parentNode.innerHTML;
}

@inject(Element)
export class PreserveBreaksCustomAttribute {
  constructor(element) {
    this.element = element;
  }

  valueChanged() {
    let html = htmlEncode(this.value);
    html = html.replace(/\r/g, '').replace(/\n/g, '<br/>');
    this.element.innerHTML = html;
  }
}

app.js

export class App {
  message = `this is my text
it has some line breaks
and some <script>evil javascript</script>
the line breaks were replaced with <br/> tags`;
}

app.html

<template>
  <require from="./preserve-breaks"></require>

  <div preserve-breaks.bind="message"></div>
</template>
Homiletic answered 15/1, 2016 at 12:40 Comment(4)
can it also be done as a custom element? <my-p-newline-replacer>${myText}</my-p-newline-replacer> Or will that bring in a lot of overhead?Invocate
thinking about it, an attribute makes a lot of sense, as it can be reused on different elements.Invocate
right- an attribute is more flexible for something like this, but a custom element would work as well.Homiletic
A better regex would be: html.replace(/(?:\r\n|\r|\n)/g, '<br />');Refund
B
2

The problem is that Aurelia renders your converted HTML as escaped tags. To get around this just use your RegExp function to convert to <br>, then use innerHTML binding like so:

<p innerHTML.bind=“htmlText”>${myText}</p>

This will stop Aurelia from escaping the HTML. I see you're worried about using this approach, as you're afraid there could be bad HTML somewhere, but there's no other way to get around this as you can't tell Aurelia to only render specific tags.

If you're that concerned about the potential for bad HTML, why don't you write a piece of custom JS to unescape all the <br> tags after page load? (Ugly as hell, but I can't see another way.)

Bibliogony answered 15/1, 2016 at 11:38 Comment(3)
might it be possible to use a function that replaces all the tags by safe tags and converts BR's back to unsafe, then bind that to innerHTML. That would basically solve it, right? (I have no aurelia experience)Schou
Why the downvote? The accepted answer does exactly what I suggest, but keeping the carriage return instead of working with unescaped.Bibliogony
no clue, wasn't me... guess the poster got spiteful :-/Schou
M
2

I have found solution on Github : usage of style="white-space: pre-wrap;" on parent element fixes issue.

Martini answered 14/7, 2018 at 13:16 Comment(1)
This deserves more votes - it's a single css statement, and it fixes exactly what OP asked for. (Maybe this is obvious, but do note that you need to have everything on one line <p>${myText}</p> - if you try to move ${myText} to a new line then all the indentation in the html document will appear on the page as well.)Kuchen

© 2022 - 2024 — McMap. All rights reserved.