5

I have a Lightning Web Component (LWC) which takes a text input from the user. The text input is entered via a lightning-textarea, i.e. can also contain text with newlines.

The LWC contains an iframe which refers to a Visualforce (VF) Page, which is rendered as a PDF. The URL of the iframe reference pdfUrl is generated in the LWC, containing the text input textValue of the user as GET parameter. The URL is encoded using the javascript function encodeURI() before passing it to the VF controller.

The VF page takes the text input, and renders it in the PDF.

This process works great apart from the fact, that potential newlines in the user's input in the lightning-textarea are not preserved. I tried different solutions mentioned on the web but couldn't make it work, hence the dedicated question:

How can I preserve newlines when passing a LWC text input to a VF controller for PDF rendering.


Minimum working example (MWE)

LWC HTML markup

Defining 2-column LWC design with the input lightning-textarea and the iframe to preview the PDF

mWEPdfNewLine.html

<template>
  <div class="slds-grid slds-wrap">
    <div class="slds-col slds-size_1-of-2">
      <lightning-textarea
        label='My text input'
        value={textValue}
        onchange={handleTextInputChange}>
      </lightning-textarea>
    </div>
    <div class="slds-col slds-size_1-of-2">
      <iframe class='pdf-preview' src={pdfUrl}></iframe>
    </div>
  </div>
</template>     

LWC Javascript controller

Passes the pdfUrl. The non-encoded pdfUrl with a two line input is displayed in the developer console in the browser as:

/apex/MWEPdfNewLine?textValue=foo and bar and  double space
foobar

The encoded pdfUrl (which is actually passed) is shown as (%20 for a space character and %0A for the new line feed)

/apex/MWEPdfNewLine?textValue=foo%20and%20bar%20and%20%20double%20space%0Afoobar

mWEPdfNewLine.js

import { LightningElement, track } from 'lwc';

export default class mWEPdfNewLine extends LightningElement {     

  @track textValue = 'foo and bar and  double space';
  @track pdfUrl = '/apex/MWEPdfNewLine';

  renderedCallback() {
    this.updatePdfUrl();
  }

  handleTextInputChange(event) {
    this.textValue = event.target.value;
  }

  updatePdfUrl() {
    var pdfUrl = '/apex/MWEPdfNewLine?textValue='+this.textValue;
    console.log(pdfUrl);
    var encodedPdfUrl = encodeURI(pdfUrl);
    console.log(encodedPdfUrl);
    this.pdfUrl = encodedPdfUrl;
  }
}

Apex controller

Retrieves the textValue as GET parameter.

MWEPdfNewLine.cls

public with sharing class MWEPdfNewLine {
    public String textValue {get; set;}
    
    public MWEPdfNewLine() {
        Map<String, String> parameters = ApexPages.currentPage().getParameters();
        if(parameters.get('textValue') != null) this.textValue = parameters.get('textValue');
    }
}

Visualforce page

Renders the PDF output, testing different output solutions. Also using the white-space: pre-line CSS property, which should help to wrap on newlines, but unfortunately doesn't.

MWEPdfNewLine.page

<apex:page renderAs="pdf" controller="MWEPdfNewLine" applyBodyTag="false">
    <head>
        <!-- Following style supposed to preserve new lines while collapsing multiple spaces, but has no helping effect -->
        <style>
            .preserve {
                white-space: pre-line;
            }
        </style>
    </head>
    <body>
        <p class="preserve">
            <i>Direct output:</i><br/>{!textValue}
        </p>

        <p class="preserve">
            <i>outputText:</i><br/><apex:outputText styleClass="preserve" value="{!textValue}" />
        </p>

        <p class="preserve">
            <i>outputText (escape=False):</i><br/><apex:outputText styleClass="preserve" value="{!textValue}" escape="false" /> 
        </p>
        <!--
            No option, can only be used with Sobjects, not with custom variables
            <i>outputField:</i><br/><apex:outputField value="{!textValue}" />
        </p>
        -->
    </body>
</apex:page>

Output

All teste solutions neglect the newline and simply concat both lines without a space. Having a single space in the text is shown correctly and also multiple spaces are properly collapsed to a single space.

Tested with Google Chrome and Mozilla Firefox browsers.

Output

1
  • 2
    It's a shame I can only upvote this question once. Very well written, and great job showing your work.
    – sfdcfox
    Commented Jan 17 at 13:28

2 Answers 2

2

It's not the encoding, it's the rendering. I duplicated your problem with a simple test:

<apex:page renderAs="pdf">
    <head>
        <style>
            .preserve {
            white-space: pre-line;
            }
        </style>
    </head>
    <body>
        <div class="preserve">
            This is a test.
            This is another.
        </div>
    </body>
</apex:page>

The fix is to emit your own HTML. The following code renders correctly:

<apex:page renderAs="pdf" controller="preserveSpaceController" applyBodyTag="false" applyHtmlTag="false" showHeader="false" docType="html-5.0">
    <html>
        <head>
            <style>
                .preserve {
                white-space: pre-line;
                }
            </style>
        </head>
        <body>
            <div class="preserve">
                {!message}
            </div>
        </body>
    </html>
</apex:page>
public class preserveSpaceController {
    public String message { get; set; }
    public preserveSpaceController() {
        message = ApexPages.currentPage().getParameters().get('message');
    }
}

Edited to demonstrate URL parameters working well.

/apex/preserveSpace?message=line1%0aline2

Output:

Demonstration of example code from the earlier code blocks

6
  • Thanks for your support. I changed my apex page settings from <apex:page renderAs="pdf" controller="MWEPdfNewLine" applyBodyTag="false"> to <apex:page renderAs="pdf" controller="MWEPdfNewLine" showHeader="false" applyBodyTag="false" applyHtmlTag="false" docType="html-5.0"> but the behaviour persists, no newlines in my output... Even if I render it as HTML with just <apex:page controller="MWEPdfNewLine"> Any other ideas?
    – HeXor
    Commented Jan 17 at 13:43
  • 1
    @HeXor You need to also add the <html> tag, as demonstrated in this answer.
    – sfdcfox
    Commented Jan 17 at 13:46
  • I can confirm if I run your apex:page snippet, it renders correctly with 2 lines. But adding my controller <apex:page [...] controller="MWEPdfNewLine" [...]> and replacing the explicit text with my {!textValue} variable again does not properly render the newlines. Any idea what an apex controller does with a text input containing %0A for newlines in the ApexPages.currentPage().getParameters().get('textValue')? If I print this with System.Debug() it already shows the compacted string, no %0A contained anymore in the debug console output.
    – HeXor
    Commented Jan 17 at 14:30
  • 1
    @HeXor I updated this to include a URL parameter, and a screenshot of the output. It might be helpful if you edit your original question to include the latest version of the code.
    – sfdcfox
    Commented Jan 17 at 14:48
  • Thanks for updating this helps alot. I tried to reproduce your result. Which URL prefix are you adding before the /apex? If I use https://<my-domain>.sandbox.lightning.force.com/apex/preserveSpace?message=line1a%20line1b%0aline2 (which I assume is being used by the iframe) it forwards to https://<my-domain>--c.sandbox.vf.force.com/apex/preserveSpace?message=line1a+line1bline2 replacing all %20 with a + and simply drops all %0A, hence no new line. If I access https://<my-domain>--c.sandbox.vf.force/apex/preserveSpace?message=line1a%20line1b%0aline2 directly it works fine.
    – HeXor
    Commented Jan 17 at 15:30
1

The final visual force URL needs to be defined explicitly to ensure proper handling of newlines. This can be done using the DomainCreator.getVisualforceHostname() apex class function.

Big shoutout and thanks to @sfdcfox for his amazing support effort.


Issue

In the Javascript controller I define the URL for the PDF/Visualforce page to be shown in the iframe with /apex/MWEPdfNewLine?<parameters>.

The following textValue is used as example with a newline and two spaces between line1 elements.

line1a  line1b
line2

This results in Salesforce/the browser completing the URL to https://<my-domain>.sandbox.lightning.force.com/apex/MWEPdfNewLine?textValue=line1a%20%20line1b%0Aline2 with the encoded textValue with %20 for spaces and %0A for the new line.

The URL is forwarded automatically to https://<my-domain>--c.sandbox.vf.force.com/apex/MWEPdfNewLine?textValue=line1a++line1bline2 i.e. replacing all %20 with + and dropping all %0A, hence neglecting newlines.

This is also related to the usage of enhanced domains in Salesforce.

Solution

My solution is to define the Visualforce domain https://<my-domain>--c.sandbox.vf.force.com excplicitly. To have a dynamic solution depending on domain name and production/sandbox environment one can use the DomainCreator.getVisualforceHostname() apex class function, which returns exactly this domain.

Updated minimum working example (MWE)

LWC HTML markup

(not changed)

mWEPdfNewLine.html

<template>
  <div class="slds-grid slds-wrap">
    <div class="slds-col slds-size_1-of-2">
      <lightning-textarea
        label='My text input'
        value={textValue}
        onchange={handleTextInputChange}>
      </lightning-textarea>
    </div>
    <div class="slds-col slds-size_1-of-2">
      <iframe class='pdf-preview' src={pdfUrl}></iframe>
    </div>
  </div>
</template>     

LWC Javascript controller

Added a @wired function getVisualForceDomain() to retrieve the visualforce domain from the apex controller.

Note: I added some handling in the renderedCallback() function ensure the visualForceDomain parameter is set before loading the PDF. This is not fulfilled on the first call of renderedCallback() automatically called by the browser. Once the text input is changed this is called again and the parameter is available. Since I do some initialization in my actual code, waiting for other input, this behaviour is fine for me. Pleae consider checking this.

mWEPdfNewLine.js

import { LightningElement, track, wire } from 'lwc';

import getVFOrigin  from '@salesforce/apex/MWEPdfNewLine.getVFOrigin';

export default class mWEPdfNewLine extends LightningElement {    
    
  static get VISUALFORCE_PAGE_URL() {return '/apex/MWEPdfNewLine'};

  @track textValue = 'line1a  line1b';
  @track pdfUrl = '';
  @track visualForceDomain = null;

  renderedCallback() {
    if(this.visualForceDomain == null) return;
    this.updatePdfUrl();
  }

  handleTextInputChange(event) {
    this.textValue = event.target.value;
  }

  updatePdfUrl() {    
    var pdfUrl = this.visualForceDomain; // https://<my-domain>--c.sandbox.vf.force.com
    pdfUrl += mWEPdfNewLine.VISUALFORCE_PAGE_URL;
    pdfUrl += '?textValue='+this.textValue;

    var encodedPdfUrl = encodeURI(pdfUrl);
    this.pdfUrl = encodedPdfUrl;
  }

  @wire(getVFOrigin)
  getVisualForceDomain({error, data}){
    this.visualForceDomain = data;
  }
}

Apex controller

Added the @AuraEnabled function getVFOrigin() to call the DomainCreator.getVisualforceHostname() apex function to retrieve the proper Visualforce Domain.

Note: The getVisualforceHostname() expects a parameter packageName. Since I am using an unmanaged package, I am passing the value null. See DomainCreator documentation for more information.

MWEPdfNewLine.cls

public with sharing class MWEPdfNewLine {
  public String textValue {get; set;}
    
  public MWEPdfNewLine() {
    Map<String, String> parameters = ApexPages.currentPage().getParameters();
      if(parameters.get('textValue') != null) this.textValue = parameters.get('textValue');
  }

  @AuraEnabled(cacheable=true)
  public static String getVFOrigin() {
    String packageName = null; // no managed package is used, hence pass null
    String visualForceHostname = 'https://' + DomainCreator.getVisualforceHostname(packageName);
    return visualForceHostname;
  }
}

Visualforce page

(simplified)

MWEPdfNewLine.page

<apex:page renderAs="pdf" controller="MWEPdfNewLine" applyBodyTag="false">
  <head>
    <!-- Whitespace is preserved by the browser. Text will wrap when necessary, and on line breaks -->
    <style>            
      .preserve {
        white-space: pre-line;                
      }
    </style>
  </head>
  <body>
    <p class="preserve">{!textValue}</p>
  </body>
</apex:page>

Output

Solutions works, tested with Google Chrome and Mozilla Firefox browsers.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .