Sunday, July 28, 2019

Display Google Map Lightning Web Component at Salesforce Communities

Motivation behind this


After posting my previous blog post Display Google Map using Salesforce Lightning Web Components leveraging modern JavaScript at Linkedin (My Post), there are few community members asked me if that component could be available in Salesforce Communities, which drives me to do one more proof-of-concept on how that component can be available at Communities.


Use Case


How to expose Lightning Web Component at Salesforce Community.


Expected Outcome


After adding LWC component at Community Page, it will look like this:



Solution Approach


Let's break down few steps to be performed:

1. Changes in Configuration File displayLWCMap.js-meta.xml

a) Need to add targets where this component will be  exposed. lightningCommunity__Page and lightningCommunity__Default 

b) Need to add targetConfigs which will contain property to be passed.

So, entire file will look like this:



<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="displayLWCMap">
    <apiVersion>45.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
    <target>lightning__AppPage</target>
    <target>lightning__RecordPage</target>
    <target>lightning__HomePage</target>
    <target>lightningCommunity__Page</target>
    <target>lightningCommunity__Default</target>
  </targets>
  <targetConfigs>
        <targetConfig targets="lightningCommunity__Default">
            <property name="accountNameParam" type="String" default="myValue"></property>            
        </targetConfig>
    </targetConfigs>
  
</LightningComponentBundle>

If the defined property is not exposed in js Controller we will receive error.

So, here I have used accountNameParam which is public reactive property (@api).

There will not be any other changes in HTML and JS controller files.

2. Enabling Community and use your own template.

For more information, refer Build a Custom Theme Layout Component for Lightning Communities

3. Adding Component to the Community Builder

When we try to add this component inside the page, we will see following alert message.





Solving Security Issues


Navigate to Builder à Settings à Security and find following settings:



Add the blocked URL as Whitelist URL.

We can also add trusted sites in the following screen.




Add the displayLWCMap component to the layout, publish the changes and our job is done.


Related Reference




Further Reading


Display Google Map using Salesforce Lightning Web Components leveraging modern JavaScript

Motivation behind this


Currently I am exploring Lightning Web Components (LWC) and trying to display Google map using LWC leveraging modern JavaScript (ES6 Standard). Since modern JavaScript has new functions like Spread function, Arrow function which are well blended into LWC JavaScript controller and hence, drives me to come out with this proof-of-concept.

Use Case


Business has a requirement to display their various office locations in the Google Map which will get retrieved from Salesforce Account Object.

Developer wants to build this requirement using Lightning Web Components.


Expected Outcome





In the above picture I am displaying our company office locations (Bangaluru and Noida) in Google Map. I have taken only 2 locations for simplicity.

Solution Approach


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

So, it is better to use it.

Syntax for using lightning-map component


<template>
    <lightning-map
 map-markers={mapMarkers}
 markers-title="Our Company Location">
  </lightning-map>
</template>

A marker contains
  • Location Information: A coordinate pair of latitude and longitude, or an address composed of address elements.
  • Descriptive Information: Optional title, description, and an icon. These are relevant to the marker but not specifically related to location.
For more information, refer lightning-map documentation

Little bit homework for preparing data

Since data to be displayed dynamically from database, so created 2 Account records with a name starts with Tavant providing complete BillingAddress information. I have not given Latitude and Longitude as Location which can also be used alternatively.

Let's first construct the Apex class.

DisplayMapController.cls

getOfficeLocations method accepts accountNameInitial  as input parameter and returns List of Accounts based on SOQL query. We are not doing any data transformation which can be recognized in map display, rather let it be handled by LWC js controller will be mentioned below.

Note: 
@AuraEnabled (cacheable=true) used as it will be called from js controller and @wire is calling the method
Secondly, use of bind (:str) variables to get rid of SOQL injection.



public with sharing class DisplayMapController {
    
    @AuraEnabled (cacheable=true)
    public static List<Account> getOfficeLocations(String accountNameInitial){
        String str = accountNameInitial + '%';
        return [SELECT Id, Name, BillingStreet, BillingCity, BillingState, BillingPostalCode, BillingCountry 
                FROM Account 
                WHERE Name LIKE :str];       
    }
}


Let's develop Lightning Web Components

Create a VSCode project with a menu selection SFDX: Create Lightning Web Component and provide a name displayLWCMap, which creates 3 files html (UI) , js (Controller) and xml (Configuration) at your chosen directory.

displayLWCMap.html

Inside the template lightning-map component has been used and also zoom-level has been used as additional attribute apart from map-markers and markers-title.

<template>
    <div class="slds-m-around_medium">
        <h1 class="slds-text-heading_small">Tavant Technologies</h1>
        <p class="slds-text-body_regular">Office locations</p>
    </div>
    <lightning-map 
        map-markers={mapMarkers}
        markers-title={markersTitle}
        zoom-level={zoomLevel}>
    </lightning-map>   
</template>

displayLWCMap.js

Good thing of LWC is handling all attributes as js controller level which is opposed to Aura and this class is most important for implementing this functionality.

Follow these steps:
  • import LightningElement, api, wire, track which will be used in this class.
  • import getOfficeLocations as we will be using getOfficeLocations method of DisplayMapController apex class.
  • At the class level, 
    • accountNameParam which is public reactive property decorated with @api
    • error, mapMarkers, markersTitle and zoomLevel are private reactive properties
  • @wire to function i.e getOfficeLocations and passing parameter '$accountNameParam' for accountName (here, parameter has been hard coded, though it can passed from html)
    • This is function, retrieve the data and prepare mapMarkers array with location, icon and title which should be recognized and used in the lightning-map.


 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
import { LightningElement , wire, track, api} from 'lwc';

import getOfficeLocations from '@salesforce/apex/DisplayMapController.getOfficeLocations';

export default class DisplayLWCMap extends LightningElement {

    @api accountNameParam; 
    accountNameParam = 'Tavant';
    @track error;   //this holds errors

    @track mapMarkers = [];
    @track markersTitle = 'Tavant Technologies';
    @track zoomLevel = 4;
    /* Load address information based on accountNameParam from Controller */
    @wire(getOfficeLocations, { accountNameInitial: '$accountNameParam'})
    wiredOfficeLocations({ error, data }) {
        if (data) {            
            data.forEach(dataItem => {
                this.mapMarkers = [...this.mapMarkers ,
                    {
                        location: {
                            City: dataItem.BillingCity,
                            Country: dataItem.BillingCountry,
                        },
        
                        icon: 'custom:custom26',
                        title: dataItem.Name,
                    }                                    
                ];
              });            
            this.error = undefined;
        } else if (error) {
            this.error = error;
            this.contacts = undefined;
        }
    }

}

displayLWCMap.js-meta.xml


This is configuration file which states where this component will be available.
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="displayLWCMap">
    <apiVersion>45.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
    <target>lightning__AppPage</target>
    <target>lightning__RecordPage</target>
    <target>lightning__HomePage</target>
  </targets>
</LightningComponentBundle>


Leveraging Modern JavaScript


Arrow Function

You can observe that following statement has been used which doesn't have function() and return statement.

data.forEach(dataItem => {
  //method body without return statement
}

For example, earlier we use write this way to using function and return statement.

let result = function (a,b) {
  return a+b; 
}
console.log(result(1,2));

Now, with Arrow function, it can be written as:

let result = (a,b) => return a+b; 

console.log(result(1,2))

Also, Arrow function resolves some confusion when dealing with this keyword, when nested functions are involved. Functions have a special variable called this, often referred to as the “dynamic this,” which refers to the object used to invoke the function.

For more information, Refer Understand JavaScript Functions

Spread Operator

Spread operator helps to expand the array which we have used for creating mapMarkers array. Here is an example:


let arr = ['a','b']; 
let arr2 = [...arr,'c','d']; 
  
console.log(arr2); // [ 'a', 'b', 'c', 'd' ] 

We can see, arr values has been appended first and then c and d have been added to the same array.

Similarly, we can append array values at end the array too.


let arr = ['a','b']; 
let arr2 = ['c','d',...arr]; 
  
console.log(arr2); // [ 'c', 'd', 'a', 'b'] 

Monday, July 22, 2019

Salesforce Lightning Web Components Cheat Sheet

Motivation behind this


Lately, I am exploring on Lightning Web Components and trying to find out one handy document which I didn't find on net. That's why I thought of preparing a cheat sheet on this. 

I have prepared 8 pager cheat sheet which covers almost all most important features and functionalities. (To download entire document scroll through the bottom).

Hope it will be helpful for Salesforce community members who are learning, exploring and working on Lightning Web Components.

Page 1



Page 2

Page 3

Page 4

Page 5

Page 6

Page 7 



Final Page




Download entire cheat sheet

Salesforce Lightning Web Components Cheat Sheet (Unofficial)

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

Happy to share!

Further Reading


Monday, July 15, 2019

Display Combobox values by Lightning Web Components and propagating events to Parent Aura Components

Motivation behind this


I have started exploring on Lightning Web Components (aka. LWC) lately and tried to answer this question at StackExchange Lightning Web Component Combobox in a different way. During my proof of concepts I have faced few issues in array handling on LWC js controller and thought of sharing this solution.

I have come up with a use case which could help many of us who are learning LWC. Now a days, there are lot of Lightning upgrade projects are going on. Most of companies are also transforming Aura Components to Lightning Web Components. Also, in some cases, Aura Components will still exist as Parent Component of LWC child components. So, communication from Child LWC to Parent Aura Component is also necessary which has been depicted as part of this use case.

Use Case


In a Salesforce instance there is a Aura Component which displays Account specific information and all types of DML operation are getting performed from this component. There is a requirement to display list of Contacts pertaining to that Account which should be done on Lightning Web Components (as there could many other Contact specific information or calculations can be performed) and selection of the Contact from the dropdown should fire an event and send the ContactId to the Parent Aura Component.

End output will look like this:


Solution Approach


We can break down the solutions into the smaller steps as follows:

1. Create a Lightning Web Component which will display list of Contacts from database. It will also take AccountId as parameter to this component.

2. Create a Aura Component which will have Account information and it will embed Child LWC and pass AccountId to the child.

3. Final step will be event propagation which will be from Child LWC to Parent Aura component. Here, for sake of simplicity, selecting Contact from comboxbox will fire Component event and send ContactId of the selected item and pass it as part of event. This will be displayed into Parent Aura Component as a text.

Child Web Components

displayContact.html

As part of step 1, create this LWC component and place lightning-combo component which attributes. Note that, value, options attributes and onchange event handler.

<template>
    <lightning-card title="List of Contacts - Lightning Web Component" icon-name="custom:custom63">
        <lightning-combobox
            name="contacts"
            label="Contacts"
            placeholder="Choose Contact"
            value={value}
            onchange={handleChange}
            options={statusOptions}>
        </lightning-combobox>
    </lightning-card>
</template>

displayContact.js

This is most important for implementing this functionality.

There are the points to meet Step 2, to populate and display combobox values.

  • import LightningElement, api, wire, track which will be used in this class.
  • import getContacts as we will be using getContacts method of AccountController apex class.
  • At the class level, 
    • recordId which is public property decorated with @api
    • error, items array, value are private tracked properties
  • @wire to function i.e getContacts and passing parameter '$recordId' for accountId
    • This is function, retrieve the data and prepare items array with value and label which should be recognized and used in the combobox.
    • Mind the syntax for storing values in array: this.items = [...this.items ,{value: data[i].Id , label: data[i].Name}]
  • getter property for statusOptions which displays options taking items array.

To meet Step 3, we have used handleChange(event) it fires based on changing combobox item selection.

  • Collect combobox value as selectedOption.
  • Create a Custom Event, Lightning web component fires a custom filterchange event in its JavaScript file.
  • And finally, dispatchEvent which helps to communicate from child to parent.


 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
/* eslint-disable no-console */
/* eslint-disable no-alert */
/* eslint-disable no-unused-vars */
import { LightningElement, api, wire, track } from 'lwc';

import getContacts from '@salesforce/apex/AccountController.getContacts';
let i=0;

export default class DisplayContact extends LightningElement {
     /** Id of record to display. */
    @api recordId; //this captures AccountId which is passed from Parent Component
    @track error;   //this holds errors

    @track items = []; //this holds the array for records with value & label

    @track value = '';  //this displays selected value of combo box

    /* Load Contacts based on AccountId from Controller */
    @wire(getContacts, { accountId: '$recordId'})
    wiredContacts({ error, data }) {
        if (data) {
            for(i=0; i<data.length; i++) {
                console.log('id=' + data[i].Id);
                this.items = [...this.items ,{value: data[i].Id , label: data[i].Name}];                                   
            }                
            this.error = undefined;
        } else if (error) {
            this.error = error;
            this.contacts = undefined;
        }
    }
   
    //getter property from statusOptions which return the items array
    get statusOptions() {
        console.log(this.items);
        return this.items;
    }

    handleChange(event) {
        // Get the string of the "value" attribute on the selected option
        const selectedOption = event.detail.value;
        console.log('selectedOption=' + selectedOption);

        //This is for event propagation
        
        const filterChangeEvent = new CustomEvent('filterchange', {
            detail: { selectedOption },
        });

        
        // Fire the custom event
        this.dispatchEvent(filterChangeEvent);
    }
    
}

AccountController.cls

getContacts method returns List of Contacts based on SOQL query. We are not doing any data transformation which can be recognized in combobox display, rather let it be handled by LWC js controller as specified above.


public with sharing class AccountController{
    
    @AuraEnabled (cacheable=true)
    public static List<Contact> getContacts(String accountId){
        return [SELECT Id, Name FROM Contact WHERE AccountId =:accountId];        
    }
}

displayContact.js-meta.xml

This is nothing fancy as such.

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="DisplayContact">
    <apiVersion>45.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
    <target>lightning__AppPage</target>
    <target>lightning__RecordPage</target>
    <target>lightning__HomePage</target>
  </targets>
</LightningComponentBundle>

Now its time to prepare Parent Aura Component and related files.

ParentAccount.cmp

Following points to be noted, it meets Step 3:

  • force:hasRecordId has been implemented to get recordId of Account.
  • That has been passed as recordId which is captured in Child LWC controller through @api public property
  • The enclosing Aura component wrapper adds a handler for the custom event. Notice that the event handler, onfilterchange, matches the event name with “on” prefixed to it. That is, use onfilterchange to handle the event named filterchange.


<aura:component implements="flexipage:availableForAllPageTypes,force:hasRecordId">
    <aura:attribute name="message" type="String" default="Select a Contact from dropdown"/>
    <lightning:card title="Account - Aura Component" iconName="custom:custom30">
        <div class="slds-m-around_medium">
            <lightning:layout>
                <lightning:layoutItem size="4">
                    <!-- This is an LWC component -->
                    <c:displayContact recordId="{!v.recordId}"  onfilterchange="{!c.handleFilterChange}"/>
                </lightning:layoutItem>
                <lightning:layoutItem size="8" class="slds-p-left_medium">
                    {!v.message}
                </lightning:layoutItem>
            </lightning:layout>
        </div>
    </lightning:card>
</aura:component>


ParentAccountController.js

The Aura component’s controller receives the event. You can handle the event however you’d like. This component operates on the filters array passed in the details property of the Lightning web component event. You can optionally fire a new Aura event to communicate with other Aura components.


({
    handleFilterChange: function(component, event) {
        var filters = event.getParam('selectedOption');
        console.log('filters=' + filters);
        component.set('v.message', filters.length > 0 ? 
                      'Selected ContactId from Child LWC: ' + filters : 
                      'No selection from Child LWC');
    },
});

So, we are done.

After deployment, place ParentAccount component on the page layout of Account and our desired functionality will be achieved.

Note: The concepts for preparing array with items not only applicable for combobox, but also for, lightning-checkbox-group, lightning-radio-grouplightning-dual-listbox, lightning-datatable etc. So the above approach can be leveraged for all those purposes.

References