Sunday, October 11, 2020

Generate PDF from Salesforce Lightning Web Component

 

Motivation behind this


I have been looking for this option to generate PDF file from Lightning Web Component (LWC) quite often. Salesforce didn't provide any support to render page or component as pdf (like Visualforce) in LWC. So, tried a find an option to do so.

Without using external JavaScript library, I have tried to achieve here. This concept can be leveraged for any use case for pdf generation.

Use Case


Business has requirement to send user input data or data fetched from database to be saved as pdf format.

Developer wants to build with LWC.

Possible End Result


After building the use case, it will perform the functionality as following video:



Solution Approach


The main challenges with this use case:

  • Till today, Salesforce doesn't provide any JS library to display page as pdf
  • If we try to use Visualforce with renderAs="pdf" with embedding LWC into it then it will not work, because this doesn't support any JavaScript to be included.
  • There are many third party JS library can be used but maintaining that is a challenge.

Approach has been taken following way:
  • Create a LWC component adding lightning-input-rich-text field. This field has value attribute which returns the HTML content (this is main trick)
  • Create a Visualforce page with renderAs="pdf" attribute and use those HTML text as value of apex:outputText with escape="false". Here, visualforce has been used only for pdf generation, nothing else. So it will be very slim. 
  • When we click on "Save As PDF" button it will implicitly call the apex class' method and use the PageReference of the visualforce and save this body content as pdf.
It's simple.

displayRichTextComponent:

displayRichTextComponent.html will prepare a screen like this:



Code as follows:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<template>
    <lightning-card>    
        <lightning-input-rich-text
            placeholder="Type something interesting"
            formats={allowedFormats}
            value={myVal}>
        </lightning-input-rich-text>
        <lightning-button label="Save as PDF"
                        onclick={saveAsPdf}>
        </lightning-button>
        <lightning-button label="Do Something"
                        onclick={handleClick}>
        </lightning-button>
    </lightning-card>
</template>

Few notable points on the above HTML:

  • lightning-input-rich-text supports those format of font, size, image etc. Refer Documentation
  • value attribute of  lightning-input-rich-text shows initial data
  • Save as PDF button click event calls saveAsPdf method.
  • Clicking  on "Do Something" button, replaces any selected text with "Journey to Salesforce" with defined format with setRangeText() method, which is still in beta (Winter 21 release)
displayRichTextComponent.js

Entire code as below



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import { LightningElement } from 'lwc';
import generatePDF from '@salesforce/apex/DisplayRichTextHelper.generatePDF';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';

export default class DisplayRichTextComponent extends LightningElement {
    allowedFormats =  ['font', 'size', 'bold', 'italic', 'underline', 'strike',
    'list', 'indent', 'align', 'link', 'image', 'clean', 'table', 'header', 'color',
    'background','code','code-block'];

    //this method will display initial text
    get myVal() {
        return '**Generate PDF using LWC Component**';
    }

    attachment; //this will hold attachment reference

    /*This method extracts the html from input rich text 
        and pass this to apex class' method via implcit call
    */
    saveAsPdf(){
        const editor = this.template.querySelector('lightning-input-rich-text');
        
        //implicit calling apex method
        generatePDF({txtValue: editor.value})
        .then((result)=>{
            this.attachment = result;
                console.log('attachment id=' + this.attachment.Id);
                //show success message
                this.dispatchEvent(
                    new ShowToastEvent({
                        title: 'Success',
                        message: 'PDF generated successfully with Id:' + this.attachment.Id,
                        variant: 'success',
                    }),
                );
        })
        .catch(error=>{
            //show error message
            this.dispatchEvent(
                new ShowToastEvent({
                    title: 'Error creating Attachment record',
                    message: error.body.message,
                    variant: 'error',
                }),
            );
        })
    }
    
    /*
        This method updates the selected text with defined format
    */
    handleClick() {
        const editor  = this.template.querySelector('lightning-input-rich-text');
        const textToInsert = 'Journey to Salesforce'
        editor.setRangeText(textToInsert, undefined, undefined, 'select')
        editor.setFormat({bold: true, size:24, color: 'green', align: 'center',});
    }
}


Now, let's talk about visualforce page

renderAsPdf.page


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<apex:page controller="DisplayPDFController" renderAs="pdf"  
		   applyHtmlTag="false" showHeader="false" cache="true" readOnly="true" >
    <html>
        <head>
            <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
            <style>
                @page {
                    size: a4 portrait;    
                    padding-left: 2px;    
                    padding-right: 2px;
                }            
            </style>
        </head>
        <apex:outputText value = "{!displayText}" escape = "false"/>
    </html>
</apex:page>

You can see apex:outputText is used to display content, be sure to escape. I have added a style to display it as portrait with some padding option.

Now, see Visualforce Controller

DisplayPDFController.cls


1
2
3
4
5
6
7
8
public with sharing class DisplayPDFController {

    public String displayText {get; set;}
    public DisplayPDFController() {
        displayText = String.escapeSingleQuotes(
            ApexPages.currentPage().getParameters().get('displayText'));
    }
}

In the constructor, values are assigned to displayText. It can be done in page action method.

Finally, the Apex Class which is getting called from js file which actually creates the pdf file.

DisplayRichTextHelper.cls

Here, based on PageReference we are getting the page content which is being converted to pdf using getContentAspdf() method.

When we initially try to save attachment, we could face this below error if (cacheable=true) is used with @AuraEnabled

Too many DML statements: 1 out of 0 Error

which means that component is readonly and it doesn't allow to perform DML operation.

That's why cacheable=true is omitted. 

The file is getting attached to a contact record. For sake of brevity, error handling has been omitted and hardcoded Contact Id has been used.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public with sharing class DisplayRichTextHelper {
    
    @AuraEnabled
    public static Attachment generatePDF(String txtValue){
        
        Pagereference pg = Page.renderAsPDF;
        pg.getParameters().put('displayText', txtValue);

        Contact con = new Contact(Id='0032v00002ypAntAAE');
        Attachment objAttachment = new Attachment();
        objAttachment.Name = 'J2S.pdf';
        objAttachment.ParentId = con.Id;
        objAttachment.Body = pg.getContentaspdf();   
        objAttachment.IsPrivate = false;
        insert objAttachment;
        return objAttachment;
    }

}

Final pdf


The output is showing based on the element added into the rich text field.



meta files should include where this component will be available.

Create a Lightning App Builder page with one region and place this component and run the application, it will display the screen as above.

This concept can be leveraged easily at any project. For example, capturing fields from screen, then display results with some formats into the rich text box and finally generating pdf.

We are done and thanks for reading.


References



Further Reading


Monday, September 21, 2020

Display Google Map within a Flow using Lightning Web Components

Motivation behind this


Earlier I have written about the post on displaying Google maps either on Salesforce Community and through drag-and-drop functionality. From a long time, I want to experiment that on Flow and tried to achieve the same thing.

This way, we can extend the power of Lightning Web Components combining the configurable features of Flow.

Use Case


Business has a requirement to see the location of an Account in a Google Map, but only on a demand basis. Business doesn't want to see this map on Record Detail page every time. Rather, they want to view as and when required upon clicking on a button on Record Detail page and it will show as a pop up screen.

Developer wants to build this Google map component using Lightning Web Components which will be displayed as a pop up using Flow.

Expected Outcome




Clicking on Show Location quick action, the flow will be displayed as pop up screen to display the location.

Solution Approach


Salesforce provides lightning-map component which displays one or more locations. It inherits the styling from map of Lighting Design System. 

And, it is easier to display the component inside Flow, rather than using a separate pop-up component.

So, let's first prepare the component in LWC.

displayAccountMapInFlow.html

Simple page which holds lighting-map component.


<template>
    <lightning-map
        map-markers={mapMarkers}
        zoom-level={zoomLevel}>        
    </lightning-map>    
</template>

displayAccountMapInFlow.js

Few important points on the approach:
  • My earlier post talks about fetching the data using Apex classes. Here I have used ui*api Wire adapters. The benefits of using this is, we don't have a create separate Apex Class to fetch the record. Secondly, it recognizes access rights of the users meaning if the user doesn't have access to the field then error will return. If you are not sure about field access then you can use those fields as Optional.
  • We know that, we can get recordId using @api, but when the component is embed into flow then this recordId will not work. We need to pass recordId explicitly from Flow. This is tricky.
  • As this component can be reused to display in App page or record detail page so the recordId check has been done in connectedCallback method. 
Following way, you can check falsy values, no need to separately verify null or undefined.

this.recordId = (!!this.recordId) ? this.recordId: this.sfdcRecordId; 

Here is the way, through metadata config file.

<targetConfigs>
	<targetConfig targets="lightning__FlowScreen">
		<property name="sfdcRecordId" 
			type="string" 
			label="Pass record Id"
			description="Pass record Id"/>			   
	</targetConfig>
</targetConfigs> 

Entire .js as below


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/*
* Author: Santanu Boral
*/
import { LightningElement, api,track, wire } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { getRecord } from 'lightning/uiRecordApi';

//define the field values to be retrieved
const FIELDS = ['Account.Name', 'Account.BillingStreet',
                'Account.BillingCity', 'Account.BillingState',
                'Account.BillingPostalCode', 'Account.BillingCountry'
                ];

export default class DisplayAccountMapInFlow extends LightningElement {
    @api recordId;  //if this component is used other than flow then it will be used
    @api sfdcRecordId; //this is passed from flow
    @api zoomLevel; //this is passed from flow
    
    account; //internal variable to store the account data
    mapMarkers = [];  //this is used on HTML for attribute value 
    
    //This method check the values passed into the component
    connectedCallback(){
        this.recordId = (!!this.recordId) ? this.recordId: this.sfdcRecordId;  
        this.zoomLevel = (!!this.zoomLevel) ? this.zoomLevel: 6;        
    }

    //fetch record details based on recordId
    @wire(getRecord, { recordId: '$recordId', fields: FIELDS })
    wiredRecord({ error,data }) {
        if (data) {
            this.account = data;
            //prepare marker to display on map
            this.mapMarkers = [
                {
                    location: {
                        Street: this.account.fields.BillingStreet.value,
                        City: this.account.fields.BillingCity.value, 
                        State: this.account.fields.BillingState.value,
                        PostalCode: this.account.fields.BillingPostalCode.value,                         
                        Country: this.account.fields.BillingCountry.value
                    },    
                    icon: 'custom:custom26',
                    title: this.account.fields.Name.value,
                }                                    
            ];
            
        }
        else if (error){
            let message = 'Unknown error';
            if (Array.isArray(error.body)) {
                message = error.body.map(e => e.message).join(', ');
            } else if (typeof error.body.message === 'string') {
                message = error.body.message;
            }
            this.dispatchEvent(
                new ShowToastEvent({
                    title: 'Error loading Account',
                    message,
                    variant: 'error',
                }),
            );
        }
    }
}

displayAccountMapInFlow.js-meta.xml

You can see the zoomLevel has been passed along with recordId from Flow.


<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>49.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__RecordPage</target>
        <target>lightning__AppPage</target>
        <target>lightning__FlowScreen</target>
    </targets>
    <targetConfigs>
        <targetConfig targets="lightning__FlowScreen">
            <property name="zoomLevel" 
                type="string" 
                label="Enter zoom level of map"
                description="Enter zoom level of map"/>   
            <property name="sfdcRecordId" 
                type="string" 
                label="Pass record Id"
                description="Pass record Id"/>                   
        </targetConfig>
    </targetConfigs>        
</LightningComponentBundle>

Development on Flow Side


Overall flow will look like as below:


First, create recordId variable to capture the recordId of the current record.


Next, create a variable with text datatype with a name varRecordId.

Now, assignment to be done as follows:


Finally, the form screen as below


You can see that zoom level is passed as 16 and record Id has been passed with {!varRecordId}

Now, let's call this flow through quick action and expose that in the page layout.

That's it!.

For testing purpose, go to a record detail page and clicking on Show Location quick action, the flow will be opened to display the component.

Hope it helps and thanks for reading.

References



Further Reading




Friday, September 4, 2020

Salesforce JavaScript Developer I Certification Cheat Sheet - 6 Pager

 Motivation behind this

After passing JavaScript Developer I certification, I thought of preparing this cheat sheet which is a bridge between the syllabus and level of questions and can provide proper guidance to effectively prepare for this exam.

I didn't find this type of material during my preparation which motivates me to come up such a 6 pager cheat sheet which covers almost all important points and sections. This document mainly focuses on multiple choice exam. I have provided all the references at each section.

This will definitely help trailblazers to prepare and pass this exam.

To download entire cheat sheet in pdf version, scroll through at the end of the blog.

Here is each page will look like:

Page 1



Page 2


Page 3


Page 4

Page 5


Final Page



Download entire cheat sheet: Salesforce JavaScript Developer I Cheat Sheet - pdf

If you find this useful then post your feedback as comments and share it.

Happy to share and good luck for your exam.

Note: Since I love all those astro, codey etc characters that's why I have used those almost every pages.

Further Reading


Thursday, August 27, 2020

Tips for passing Salesforce certified JavaScript Developer I Certification

 Motivation behind this


Today (27th of August, 2020), I have successfully passed JavaScript Developer I Certification. I have been working on JavaScript from a long time along with Lightning Web Components and it has evolved a lot from last few years with ECMA script.

As Salesforce has come up with this exclusive certification with JavaScript language which drives me to learn and earn this which eventually will help me on LWC and other open source development.

This certification is on high demand now-a-days which motivates me to pass and share my tips.

I have given myself around 3 weeks of study time and happy to see me passed. By the way, this exam is tricky and actually challenges the depth of JavaScript knowledge.

Let's get started!



Exam Outline

This certification is comprised of two parts as below:


Multiple Choice Exam

  • Content: 60 multiple-choice/multiple-select questions and 5 non-scored questions 
  • Time allotted to complete the exam: 105 minutes
  • Passing score: 65% (approx 39 questions to be corrected)
  • Registration fee: 200 USD
  • Delivery options: Online proctored or onsite proctored delivery. 
  • Prerequisite: None

There were no questions from Lightning Web Components at MCQ.

Lightning Web Components Specialist Superbadge




Exclusive Trailmix and Trail:

Preparation


According to my personal choice, I have first targeted to pass MCQ exam and then worked on LWC superbadge.

I have attended Certification Preparation for Salesforce JavaScript Developer I (CRT-600) and finally gone through practice questions following explanations to solve those which immensely helped to pass this exam.


Topics to Consider for Exam


Most of the questions are lengthy code executions and output oriented. Here are following points to be consider along with the syllabus.

Variables, types and Collections
  • Different types of Primitive datatypes, Primitive Wrapper, Object variables
  • Truthy and falsey evaluations. For example 0, NaN, undefined are falsey
  • Difference between == and === operators
  • Scenarios of using var, let and const
  • Different ways of declaring objects
  • JSON transformations
  • Collections (Array, Map, Set) and their functions
  • String Functions
  • Different ways of creating arrays
  • Array methods, refer Array
Refer:  Grammar & Types

Objects, Functions and Classes
  • Function declarations, calling functions, handling recursions
  • Arrow functions
  • Create an instance of using Class, Function
  • Inheritance and use of super keyword
  • Use of Prototype,  Refer Object,Prototype
  • Use of this
  • Spread Syntax
  • High order functions like Sort, Map, Filter, Reduce
  •  
Refer: ObjectFunctions

Browser and Events
  • Accessing DOM elements
  • Use of getElementById, querySelector, querySelectorAll. Refer querySelector
  • Event propagation and effectively stopping that
  • Raising CustomEvent
Debugging and Error Handling
    Asynchronous Programming
    • Callback functions
    • Async functions with Promise 
    • Order of execution when Await with setTimeout, setInternal along with Promise methods and states are involved

     Server side JavaScript 
    • Importing (named, default) and using Node.js functions Refer Node.js API
    • Packaging files, checking dependencies, versioning (Major, Minor, Patch)
    Testing
    Hope it will help you to prepare and pass this exam.

    Further Reading