Thursday, July 27, 2017

Lightning: Generate PDF from Lightning components with in-memory data

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

Process flow

Process flow - Generate PDF from Lightning components with in-memory data


Steps

So, I utilized Visualforce PDF generation feature triggered from Lightning component. The main steps in this task are:-
  1. Create Lightning component to accept an external method and invoke it on button click
  2. Embed Lightning component within Visualforce Page
  3. Passing in-memory data within lightning component to VF Page Controller
  4. 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.


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}" />
and, our updated saveData method will be:


 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

Saturday, July 1, 2017

Quick Tips: Salesforce environment strategy: Single Org vs Multiple Org



Much has been written and presented around Salesforce single organisation and multi-organisation strategies. But, still the question is asked quite frequently. There are really good articles and blogs and I'm hereby trying to compile the information and add my views to it, to provide a summary.


Single Org. Approach

In a single org. approach, an organisation maintains one single Salesforce org. all their required operations (planned to be executed via Salesforce) are executed via same org.

Ideal for: Small and Medium Businesses

Pros

  1. Less environment maintenance overhead
  2. Uniform processes - a very good opportunity for organisations which want to implement uniform processes/ functionalities across all units
  3. Easier user provisioning - once a user is setup, they have access to required functionalities

Cons

  1. Complicated processes as organisation grows larger and include various operations/ geographies
  2. Increased application conflicts - with same processes being used across different departments/ geographies, one change for a specific unit can impact other unit
  3. Performance issues - with same objects being used across different units, the data volume per object can substantially increase. This leads to special design consideration during design and development phase and also possibly performance degradation or limitations

Multi Org. Approach

Ideal for: Larger organisations with multi-country operations or multiple segregated business units

Note:- It's not that large organisations should not use single organisation, but it's viability varies based on application usage & complexity, data volume and performance expectations.

Pros

  1. Flexibility to have unit specific operations within specific environment
  2. Higher Performance - Only unit specific data to be persisted in their Salesforce environment. Leading to better performing functionality
  3. Tailored Solution - Provides ability for each instance to have it's own tailor made solutions, to fulfil desired functionalities with less overhead. In an organisation having multiple segregated business units of Line of businesses, it can help segregate required functionalities/ data based on intended audience.

Cons

  1. Reporting - reporting can be complex if data from all (or multiple) organisations is required
  2. Process disparity - without proper control on design and architecture, the applications/ functionalities within each environment can become more and more disparate, leading to operations challenges
  3. Complex user provisioning - a user may be required to be setup in multiple organisations based on needs
  4. Licensing - If a user is required to access more than one organisation, they'll need to have dedicated user account in each org (unless a specific mechanism is built to expose data to them in some way). This leads to additional licensing costs
  5. Complex application maintenance - with some common functionalities across all organisations, it can be complex to roll-out changes in these functionalities across all organisations

Popular Posts