function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Bertrand DBBertrand DB 

RenderAs PDF: preview and save as File

Hello,

I have a Visualforce page, designed to generate PDF Document.
It looks like:
<apex:page standardController="Opportunity" showHeader="false" renderAs="pdf">
<h2 style="font-family: sans-serif; text-align: center;">My PDF</h2>
<p><strong>Soci&eacute;t&eacute; : </strong>{!Opportunity.Account.Name}<br />
    <strong>Nom : </strong>{!Opportunity.Amount}<br />
    <strong>Pr&eacute;nom : </strong>{!Opportunity.Amount}<br />
    <strong>Adresse : </strong>adresse</p>
</apex:page>

Using Lightning, I would like to:
  • Have a button on my Opportunity page, "Generate PDF"
  • On click, open an iframe over the same page, that displays the PDF (from the Visualforce page)
  • Have two buttons on this iframe: Save // Cancel
  • If I click save, it saves the PDF as a File, related to the current record
Any suggestions?
Thanks!
Raj VakatiRaj Vakati
Please read my comments...

Have a button on my Opportunity page, "Generate PDF" 

Raj -- Can be done with VF Page .. But Problem is You can't set Save and Cancel Button with VF Page 

On click, open an iframe over the same page, that displays the PDF (from the Visualforce page)
Raj - Not Possible with VF Page   
Have two buttons on this iframe: Save // Cancel
Raj - Not Possible with VF Page   
If I click save, it saves the PDF as a File, related to the current record
Raj - You can't have to save   on VF page render as PDF 

Overall .. 
If You wanted to implement the same, use PDF.Js or other JS libraries 
 
Bertrand DBBertrand DB
Ok, I understand this is not the right way :)

Do you have any suggestion how I can use PDF.js for this usage?
I'm not familiar enough to design something.
Antonio VarvaroAntonio Varvaro

Hi Bertrand.
I did it, letting the User to modify the PDF too!
I'm going to tell you what you need in the following description:

- A Custom Field on the Opportunity (Long Text Area) of the highest length possible  (Optional)                   [ConfigField]
- A Visualforce Page that contains the iFrame of the PDF (better if CSS is imported as Static Resource)      [ConfigPage]
- A Visualforce Page with renderas PDF Attribute (better if CSS is imported as Static Resource)                  [PdfPage]
- An Apex Controller (as Extension) shared between the ConfigPage and the PdfPage                                 [PdfController]
-
An Apex Utility Class                                                                                                                           [UtilityClass]
- A Visualforce Page to bypass a bug of the getContent method of the PageReference object                       [FinishPage]
- An Apex Controller for the FinishPage                                                                                                    [FinishController]

Solution:
Clicking on the Action of the Opportunity Layout, you will be redirected to the ConfigPage
The ConfigPage must contains the Save/Cancel Button Group and the PDF iFrame (referring to the PdfPage PageReference created in the PdfController Constructor).
The PdfPage must have a boolean parameter in the url (like 'pdfPage=true').
The UtilityClass must have a Constructor that take in input an Opportunity Id, and starts the setup querying all information you need to put in the PDF
The PdfController Constructor must verify if the pdfPage attribute of the page is true, and do different things based on that check (if needed). It must have an attribute referred to an UtilityClass object and it must contains all the getters needed by both pages (configPage and pdfPage) to show info inside them. Getters must take informations from the UtilityClass object.
PdfController also must create the pdfPage PageReference in the Constructor, and pass the url to the iFrame, to let it works.
Clicking on the Cancel Button of the ConfigPage you could redirect the user back to the Opportunity record Page, Clicking on the Save button instead, you must redirect the User to the FinishPage.
The FinishPage is made by just 1 code row: the <apex:page> tag with the "action" attribute. It must take the opportunityId as page parameter too.
The FinishController must contains the FinishPage action method, that will create the PdfPage PageReference and get its content with the getContent() method. After taking the PDF content, it must create a ContentVersion with the content, and one ContentLink for each sObject to relate to this PDF file. The method, in the end, could return the Opportunity page reference to redirect the user to the Opportunity record.
If you do so, the user won't see this middle step and he/she will think that the Save button redirects on the Opportunity record page.

You need to make this additional step because for a little bug (i think), if you try to get the Content of the PdfPage in the PdfController, you will get the ConfigPage content instead. This is a workaround I found to bypass this wrong behavior

If you want to let the user modity the PDF information, you need to store a serialized version of the UtilityClass in the Opportunity ConfigField (updating the record) after each user Action, and deserialize it in the PdfController if the pdfPage page attribute is true. Rerendering the iFrame section of the page too, the user will see the Update live on the PDF preview of the iFrame

Hope that this helped you

Regards,
Antonio

MasahiroYMasahiroY

You can make PDF in iframe and set teh buttons to Save or Cancel. (I have made it within my org)
1. Set VF page for redneras PDF page
2. Set another VF page that shows (1) PDF above in the iframe <apex:iframe>
3. extensions ApexClass in (2) generate PDF data
4. exntension ApexClass in (2) call the action savePDF by <apex:commanButton> action="savePDF"

Here're snippets from my codes:

iframe and buttons

<apex:page standardController="Opportunity" extensions="PdfGeneratorController" >
...
<apex:iframe width="900px" src="data:application/pdf;base64,{!paramvalue}"></apex:iframe>
...
<apex:commandButton action="{!cancel}" value="Cancel"/>
<apex:commandButton action="{!savePdf}" value="Save PDF"/>
</apex:page>
Apex Class
public with sharing class PdfGeneratorController {
// generate PDF part, see below
// savePDF method part, see below
}
Apex Class (generate PDF part)
public PdfGeneratorController(ApexPages.StandardController standardPageController) {
    PageReference pdf = Page.xxx; // xxx is your Visualforce name renderasPDF
    Blob body; //create a blob for the PDF content
    if (!Test.isRunningTest()) { //if we are not in testing context
        body = pdf.getContent(); //generate the pdf blob
        paramvalue = EncodingUtil.base64Encode(body); //encode as base64, pass to vf page
    } else {
        body = Blob.valueOf('Some Text for a boring PDF file...');
    }
}
Apex Class (savePDF method part)
public PageReference savePdf() {    
    public PdfGeneratorController(ApexPages.StandardController standardPageController) {
        PageReference pdf = Page.xxx; // xxx is your Visualforce name renderasPDF
            // add parent id to the parameters for standardcontroller
            pdf.getParameters().put('id',parentId);
       
         oppt = new Opportunity();
         oppt = [SELECT id,Name,AccountName__c,Opportunity_Number__c FROM Opportunity WHERE Id=:parentId];
        pdfname = oppt.Name + system.today();
            
    
        Blob body; //create a blob for the PDF content
            if (!Test.isRunningTest()) { //if we are not in testing context
                body = pdf.getContent(); //generate the pdf blob
                paramvalue = EncodingUtil.base64Encode(body); //encode as base64, pass to vf page
            } else { 
                body = Blob.valueOf('Some Text for a boring PDF file...');
            }
    }
    
// File
ContentVersion conVer = new ContentVersion();

conVer.ContentLocation = 'S'; // to use S specify this document is in Salesforce
conVer.PathOnClient = pdfname +'.pdf'; // The files name
conVer.Title = pdfname; // Display name of the files
conVer.VersionData = body;
insert conVer;    //Insert ContentVersion

// First get the Content Document Id from ContentVersion Object
Id conDoc = [SELECT ContentDocumentId FROM ContentVersion WHERE Id =:conVer.Id].ContentDocumentId;

//create ContentDocumentLink  record 
ContentDocumentLink conDocLink = New ContentDocumentLink();
conDocLink.LinkedEntityId = parentId; // Specify RECORD ID (Standard Object/Custom Object)
conDocLink.ContentDocumentId = conDoc;  //ContentDocumentId Id from ContentVersion
conDocLink.shareType = 'V';
insert conDocLink;
    
    
// send the user to the account to view results
return new PageReference('/'+parentId);
}

PDF data is stored in blob body, then referred to File as VersionData, linkback to parentId (Opportunity.Id in this case). Styling and details are omitted for your customization. This is the working version in my organization. PDF.js is 0% needed. Just alternative solution for everyone.