I'm sure as everyone is diving into lightning components development, they are getting acquainted with the nuances of the Lightning components framework. As well as, its current limitations. Being a new framework, this is bound to happen. Although we have our users still using salesforce classic, we have started using lightning components framework our primary development platform and Visualforce is considered primarily for rendering lightning components within Classic Service console.
Recently, while re-architecting a critical module, we encountered a problem wherein we needed to generate PDF from lightning components. Now, being Javascript intensive framework, it has limited room for such features (may be included in future roadmap). As of now, there is no native feature within the lightning framework to do so (at least I didn't find anything).
Common Scenario - Create Visualforce page to retrieve data and generate PDF
For scenarios where the data exist within Salesforce, it may be a comparatively easier task, by developing a visualforce page, which can accept arguments in querystring and generate PDF by retrieving data via Apex controller. Further, Lightning component can generate desired URL to retrieve the desired PDF and open that URL in a new window.
Complex Scenario - Generate PDF from Lightning components with in-memory data
However, in our scenario, all the data is being retrieved from an external application at run-time. This data is never saved within Salesforce and hence we have to make use of in-memory data. However, we do have this feature available within Visualforce. As we are still using Salesforce classic and embedding our lightning components on visualforce page, we have an advantage in this case.So, here is a quick list of challenges and how I solved them in this solution (not saying that there could not be any better solutions)
Challenge | Solution |
---|---|
Generate PDF | Generate PDF via Visualforce |
Send Data to PDF page | Use hidden fields or method arguments to pass data from Lightning components to Visualforce page controller |
Steps
So, I utilized Visualforce PDF generation feature triggered from Lightning component. The main steps in this task are:-
- Create Lightning component to accept an external method and invoke it on button click
- Embed Lightning component within Visualforce Page
- Passing in-memory data within lightning component to VF Page Controller
- Generating PDF
Create Lightning component to accept an external method and invoke it on button click
In this step, we will create a lightning component which accepts a javascript method as an attribute and on button click (of button within lightning component), invokes visualforce page's javascript method
We can create an attribute within Lightning component to accept a function. For demo purposes, I've added a button, which will invoke PDF generation
1 2 3 4 5 6 7 8 | <aura:component access="global"> <!-- attribute to accept Visualforce page's javascript method --> <aura:attribute name="sendData" type="object"/> <!-- Button component to invoke PDF download --> <lightning:button label="Download Document" onclick="{!c.downloadDocument}" /> </aura:component> |
We can create lightning component's controller to invoke VF Page javscript method on button click
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /* Component controller */ ({ downloadDocument : function(component, event, helper){ var sendDataProc = component.get("v.sendData"); var dataToSend = { "label" : "This is test" }; //this is data you want to send for PDF generation //invoke vf page js method sendDataProc(dataToSend, function(){ //handle callback }); } }) |
Embed Lightning component within Visualforce Page
Here, we will embed the newly created lightning component to our VF page MainPage. We can pass a javascript function (defined within v the sualforce page) to lightning component upon initialization.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | function saveData(data, callback){ //TO DO } $Lightning.use("DemoApp", function() { $Lightning.createComponent("PDFGenertorExample", { sendData : saveData }, "lightning", function(cmp) { // do some stuff }); }); |
Passing in-memory data within lightning component to VF Page Controller
The way I have achieved it is by using a hidden control to store the data and bind it with controller property.
Second, embed an apex:inputhidden component to bind this property to page
Thirdly, bind the data received from component to page's control
Now, the last part is to actually invoke the PDF generation logic. Now, we need to create a visualforce page to generate the actual PDF and invoke this page.
So, for the sake of simplicity, we create a visualforce page PDFPage as
Now, we need to create an apex controller method in VFPageController class to open the PDF page (to trigger generation and download of PDF document).
Lastly, we need to add an actionfunction to MainPage, to invoke controller method; and invoke this actionfunction method on receiving invocation from Lightning component
So, our actionfunction code will be:
and, our updated saveData method will be:
Update(Mar 15, 2018): I’ve uploaded sample code to Github Gist(finally!!). Following is the link
https://gist.github.com/toanshulverma/16244d19ac68364cb75443695d81403b
1 2 3 4 | /* Controller code */ public class VFPageController { public String PDFData {get; set;} } |
Second, embed an apex:inputhidden component to bind this property to page
1 2 | <!-- Page code --> <apex:inputhidden id="hidData" value="{!PDFData}"/> |
Thirdly, bind the data received from component to page's control
1 2 3 4 5 6 | <!-- Page JS code --> function saveData(data, callback){ var hidData = document.getElementById('{!$Component.hidData}') hidData.value = JSON.stringify(data); } |
Generate PDF
So far, we have successfully brought lightning component's in-memory data to Visualforce page control, where the page control is further bound to Apex controller's property.Now, the last part is to actually invoke the PDF generation logic. Now, we need to create a visualforce page to generate the actual PDF and invoke this page.
So, for the sake of simplicity, we create a visualforce page PDFPage as
1 2 3 4 | <!-- PDFPage --> <apex:page controller="VFPageController" renderAs="pdf"> {!PDFData} </apex:page> |
Now, we need to create an apex controller method in VFPageController class to open the PDF page (to trigger generation and download of PDF document).
1 2 3 4 5 6 7 8 9 | <!-- generate and download pdf document --> public PageReference downloadPDF(){ System.PageReference pageRef = new System.PageReference('/apex/PDFPage'); //ensure pdf downloads and is assigned with defined name pageRef.getHeaders().put('content-disposition', 'attachment; filename=TestPDF.pdf'); return pageRef; } |
Lastly, we need to add an actionfunction to MainPage, to invoke controller method; and invoke this actionfunction method on receiving invocation from Lightning component
So, our actionfunction code will be:
<apex:actionfunction name="jsGeneratePDF" action="{!downloadPDF}" />
1 2 3 4 5 6 7 8 9 10 11 | function saveData(data, callback){ //bind lightning component data to page component var hidData = document.getElementById('{!$Component.hidData}') hidData.value = JSON.stringify(data); //invoke PDF Generation jsGeneratePDF(); //invoke callback; if(typeof callback == 'function') callback(); } |
Update(Mar 15, 2018): I’ve uploaded sample code to Github Gist(finally!!). Following is the link
https://gist.github.com/toanshulverma/16244d19ac68364cb75443695d81403b
Thank you!
ReplyDeletei want to have button on my web page and when we click the button it should create PDF same as my web page's output
ReplyDelete