SF Wiseguys Intro

Hello Visitor !

We welcome you to our Tech Blog – Salesforce Wiseguys where we bestow knowledge, share resourceful content , aggrandise features and mostly just ramble and rant about all things Salesforce.

Meet Your SF Wiseguys –

Hi, I am Waseem “Wiseguy” Sabeel, been in the Salesforce realm for 8+ years and I am 22x Certified Salesforce Professional.
I’m a Passionate Trainer & Mentor, and aspiring Technical Architect.

My Trailhead Profile – https://trailblazer.me/id/wsabeel
On SFWiseguys, I have these roles to perform –
Co-Founder, President, Janitor and Watchman.

Hi, I am Aryakaran “Wiseguy” Gupta. I have been working in Salesforce Platform for over 8 Years now.
14x Certified and Salesforce Recognized Lightning Champion!

My Trailhead Profile –
https://trailblazer.me/id/aryakaran
On SFWiseguys, I have these roles to perform –
Co-Founder, Director, Customer Support and Plumber

Scan Barcodes and Create QR Code using Salesforce LWC

Welcome folks!

This blog post demonstrates the Salesforce Capabilities to Scan Barcodes as well as ability to Create QR codes out of a given text content, all using Lightning Web Components.

Salesforce had released a BarcodeScanner API which supports barcode scanning natively on LWC for mobile devices using the Salesforce Mobile App. Since it is supported natively, no need to worry about maintaining any 3rd party Javascript, Locker services, performance issues for reading a barcode.

This Salesforce LWC Component contains methods to Scan any of the Supported Bar Code Types by Salesforce and also, separately provides an ability via ES6 JavaScript file to Create a QR Code out of user-entered string content.

QR Code Generation can be done on Desktop or Mobile experience whereas the Bar Code scanning works in Salesforce Mobile App only.

Below are the App Screenshots on how this LWC works –

Mobile View below


Desktop View below (Scanning of barcode is disabled)

Examples of Scanned Bar Code examples on Mobile App below

The Codebase of this LWC can be obtained in the Github Repository shared below –

https://github.com/WaseemAliSabeel/lwc-barcode

The official documentation of the API is stated here .

Another interesting feature that it provides is to allow Continuous Bar Code scanning, along with Single Scan as well. More on it in the official documentation here

Thanks for reading!

Yours wisely,
Waseem

Movie Trivia Game built using LWC

Hello fellow Movie Enthusiasts!
Presenting to you – Trivia Time – a fun movie-guessing game built using Lightning Web Components and hosted for free on Heroku. The movies are taken from IMDb Top 250 List and is kept as a static set.

The Trivia game can be played by two teams numerous times and the simple rules are –

  • Guess the Movie Title from Plot Summary and other details
  • Correct Answer grants 10 points. Revealing Hints reduces points to be won by 5
  • First Team to score 100 points wins!

App Highlights –

  • The movies are randomly picked up from the static list , as sourced from the IMDb Top 250 chart. It contains foreign language and other critically acclaimed movies as well throughout the history of cinema, as governed by the Top 250 chart.
  • The game can be played innumerable times by resetting the scores and setting up different team members.
  • For curious Salesforce enthusiasts, this is nothing but a single LWC with a Static Resource powering the JSON Dataset. Can be exposed as a Custom Tab in your salesforce Developer Edition Org, and it this game is also readily hosted for free on Heroku , built using LWC Open Source and Salesforce Lightning Design System.
    If all the above sounds too much work, you can refer the built code in this Github Repository. If you’d like, feel free to fork this repo, host it on your Heroku account and edit the IMDB250Data.json file as per your preference. You can also modify the HTML & JS code in src/modules/my/wtt_triviagame web component to have your desired implementation.

So what are you waiting for ? Let the Movie Trivia Begin !
Game is Live here – https://movie-trivia-lwc.herokuapp.com/

Let us know your feedback / suggestions / opinions on this game.

Yours Wisely,
Waseem

Salesforce & Bing Maps Integration

Bing Maps REST API can easily be integrated within salesforce. In this article we will see how we can use different APIs of Bing and use it in our salesforce application. For this we will be creating a custom lightning component to test various APIs provided by Bing.


Authentication?

There is no specific type of authentication used. All we need is to set up a Bing maps key. You can request a free developer key from the maps portal.

Create a new basic key from the Application tab. There are two types of keys you can request: Basic or Enterprise. Basic is only used for testing and evaluation purposes and Enterprise is used for large scale business application which also comes with a support package. More information on keys and usage can be found here. For this article we will be using a Basic key.


Integration

Before we start writing code firstly we need to understand all the possible prerequisites. In the previous section we mentioned about setting a key. Store this key somewhere in a custom setting or metadata so that it is configurable. This example will have a direct place holder for the key reference in the apex class. Secondly we need to understand the endpoint URL and types of parameter which are mandatory. For this integration we will be using their standard rest endpoint:

https://dev.virtualearth.net/REST/v1/

Note: To perform any callouts with external system, The endpoint URL need to be added in Remote Sites. Please don’t forget to add the above URL on the remote sites!

So once we are all set. You have your key and the endpoint setup. Lets begin coding!

Below class contains callouts to 3 of the Bing REST API types. We will be using the Locations and Distance API:

Find Locations by Point:

To fetch the address by coordinates use this endpoint. Example request will look something like this:

http://dev.virtualearth.net/REST/v1/Locations/{point}?includeEntityTypes={entityTypes}&includeNeighborhood={includeNeighborhood}&include={includeValue}&key={BingMapsKey}

Details of the API Parameters can be found in the official documentation

Find Locations by Address:

Use this to fetch the coordinates if you know the address:

http://dev.virtualearth.net/REST/v1/Locations?countryRegion={countryRegion}&adminDistrict={adminDistrict}&locality={locality}&postalCode={postalCode}&addressLine={addressLine}&userLocation={userLocation}&userIp={userIp}&usermapView={usermapView}&includeNeighborhood={includeNeighborhood}&maxResults={maxResults}&key={BingMapsKey}

Details of the API Parameters can be found in the official documentation

Calculate Driving Distance:

To calculate distance between two points. Use the below request:

http://dev.virtualearth.net/REST/v1/Routes?wayPoint.1={wayPoint1}&viaWaypoint.2={viaWaypoint2}&waypoint.3={waypoint3}&wayPoint.n={waypointN}&heading={heading}&optimize={optimize}&avoid={avoid}&distanceBeforeFirstTurn={distanceBeforeFirstTurn}&routeAttributes={routeAttributes}&timeType={timeType}&dateTime={dateTime}&maxSolutions={maxSolutions}&tolerances={tolerances}&distanceUnit={distanceUnit}&key={BingMapsKey}

Details of the API Parameters can be found in the official documentation

public without sharing virtual class BingMapProvider {
private static String apiEndpoint = 'https://dev.virtualearth.net/REST/v1/';
private static String apiKey = 'YOUR_API_KEY_HERE';
/**
* @description
* Given decimal Lat/Lon coordinates – try to get address details
* makes a call like this:
* http://dev.virtualearth.net/REST/v1/Locations/47.64054,-122.12934?o=json&key={{BingMapsAPIKey}}
*
* @param latitude – Decimal Latitude, e.g. 47.64054
* @param longtitude – Decimal Longitude, e.g. -122.12934
*
* @return Response
* where: Response.responseData = parsed Resource object (with Address containing postal address) or null
*/
@AuraEnabled(cacheable=true)
public static Response getAddressByCoordinates(final Decimal latitude, final Decimal longtitude) {
final String endpointUrl = getApiEndpointUrl();
String params = '{0},{1}?';
final String paramsFormatted = String.format(params, new String[] {
EncodingUtil.urlEncode('' + latitude, 'UTF-8'),
EncodingUtil.urlEncode('' + longtitude, 'UTF-8')
});
final String url = endpointUrl + 'Locations/' + paramsFormatted;
return sendResourcesQuery(url);
}
/**
* @description
* same as getCoordinatesByAddress(Address) but address value is expected as JSON string
* this method is used to allow calling getCoordinatesByAddress from Aura/Lwc components
* @param addressJson serialied version of Address
*
* @return Response
* where: Response.responseData = parsed Resource object (with Address containing postal address) or null
*/
@AuraEnabled(cacheable=true)
public static Response getCoordinatesByAddressJson(final String addressJson) {
final Address address = (Address)JSON.deserialize(addressJson, Address.class);
return getCoordinatesByAddress(address);
}
/**
* @description
* Given an Address – try to get decimal Lat/Lon coordinates
* makes a call like this:
* http://dev.virtualearth.net/REST/v1/Locations?query={Address}&key=A{BingKey}
*
* @param Address – address details
*
* @return Response
* where: Response.responseData = parsed Resource object (with Point representing coordinates) or null
*/
public static Response getCoordinatesByQuery(final String strAddress) {
final String endpointUrl = getApiEndpointUrl();
String strEncodedAddress = EncodingUtil.urlEncode(''+strAddress, 'UTF-8');
final String url = endpointUrl + 'Locations?query='+strEncodedAddress;
return sendResourcesQuery(url);
}
/**
* @description
* Given an Address – try to get decimal Lat/Lon coordinates
* makes a call like this:
* http://dev.virtualearth.net/REST/v1/Locations?countryRegion={countryRegion}
* &locality={locality}&postalCode={postalCode}&addressLine={addressLine}&key={BingMapsKey}
*
* @param Address – address details
*
* @return Response
* where: Response.responseData = parsed Resource object (with Point representing coordinates) or null
*/
public static Response getCoordinatesByAddress(final Address address) {
final String endpointUrl = getApiEndpointUrl();
address.countryRegion = address.countryRegion == null ? '' : address.countryRegion;
address.locality = address.locality== null ? '': address.locality;
address.postalCode = address.postalCode == null ? '': address.postalCode;
address.addressLine = address.addressLine == null ? '' : address.addressLine;
String params = '?countryRegion={0}&locality={1}&postalCode={2}&addressLine={3}';
final String paramsFormatted = String.format(params, new String[] {
EncodingUtil.urlEncode('' + address.countryRegion, 'UTF-8'),
EncodingUtil.urlEncode('' + address.locality, 'UTF-8'),
EncodingUtil.urlEncode('' + address.postalCode, 'UTF-8'),
EncodingUtil.urlEncode('' + address.addressLine, 'UTF-8')
});
final String url = endpointUrl + 'Locations/' + paramsFormatted;
return sendResourcesQuery(url);
}
/**
* @description
* same as getDrivingDistance(Point, Point, DistanceUnit) but parameters are passed as a single object encoded in JSON string
* this method is used to allow calling getDrivingDistance from Aura/Lwc components
* @param paramsJson is a string containing JSON object of the following format
* {
* 'wayPoint0_latitude': 12.34,
* 'wayPoint0_longtitude': -23.45,
* 'wayPoint1_latitude': 13.44,
* 'wayPoint1_longtitude': -24.56,
* 'distanceUnit': 'mi'
* }
* Note: distanceUnit can be either 'mi' or 'km'
*
* @return distance between two points
* where: Response.responseData = parsed Resource object (with travelDistance representing the driving distance) or null
*/
@AuraEnabled(cacheable=true)
public static Response getDrivingDistanceJson(final String paramsJson) {
System.assertNotEquals(null, paramsJson, 'Non NULL parameter paramsJson is required');
final Map<String, Object> valueByKey = (Map<String, Object>)JSON.deserializeUntyped(paramsJson);
final Decimal wayPoint0_latitude = (Decimal)valueByKey.get('wayPoint0_latitude');
final Decimal wayPoint0_longtitude = (Decimal)valueByKey.get('wayPoint0_longtitude');
final Decimal wayPoint1_latitude = (Decimal)valueByKey.get('wayPoint1_latitude');
final Decimal wayPoint1_longtitude = (Decimal)valueByKey.get('wayPoint1_longtitude');
final String distanceUnitStr = (String)valueByKey.get('distanceUnit');
final Point wayPoint0 = new Point(wayPoint0_latitude, wayPoint0_longtitude);
final Point wayPoint1 = new Point(wayPoint1_latitude, wayPoint1_longtitude);
final DistanceUnit distanceUnit = 'mi' == distanceUnitStr? DistanceUnit.MI : DistanceUnit.KM;
return getDrivingDistance(wayPoint0, wayPoint1, distanceUnit);
}
/**
* @description
* retrieve driving distance between two points
*
* @param wayPoint0 – lat,lon of the starting point
* @param wayPoint1 – lat,lon of the finishing point
* @param DistanceUnit – 'mi' or 'km'
*
* @return distance between two points
* where: Response.responseData = parsed Resource object (with travelDistance representing the driving distance) or null
*/
public static Response getDrivingDistance(final Point wayPoint0, final Point wayPoint1,
final DistanceUnit distanceUnit) {
System.assertNotEquals(null, wayPoint0, 'Non NULL parameter wayPoint0 is required');
System.assertNotEquals(null, wayPoint1, 'Non NULL parameter wayPoint1 is required');
System.assertNotEquals(null, distanceUnit, 'Non NULL parameter distanceUnit is required');
final String endpointUrl = getApiEndpointUrl();
String params = 'wayPoint.0={0}&wayPoint.1={1}&routeAttributes=routeSummariesOnly&distanceUnit={2}';
final String paramsFormatted = String.format(params, new String[] {
wayPoint0.toUrlParameter(),
wayPoint1.toUrlParameter(),
EncodingUtil.urlEncode(distanceUnit.name().toLowerCase(), 'UTF-8')
});
final String url = endpointUrl + 'Routes?' + paramsFormatted;
return sendResourcesQuery(url);
}
private static String getApiEndpointUrl() {
return apiEndpoint;
}
private static String getApiKey(){
return apiKey;
}
/**
* generic method for processing HTTP response error
*/
private static Response processError(final System.HttpResponse httpResponse, final Response response) {
if (String.isBlank(httpResponse.getBody())) {
// https://docs.microsoft.com/en-us/bingmaps/rest-services/status-codes-and-error-handling#handling-empty-responses
response.errorData = 'Bing Map API returned empty response.' +
' Check HTTP header X-MS-BM-WS-INFO.' +
' If it is set to 1 – it is best to wait a few seconds and try again';
} else {
response.errorData = httpResponse.getBody();
}
return response;
}
/**
* @return Resource or null (if failed to extract Resource from given valueByKey)
*/
private static Resource parseLocationResult(final Map<String, Object> valueByKey) {
final Object[] resourceSets = (Object[])valueByKey.get('resourceSets');
if (null != resourceSets && !resourceSets.isEmpty()) {
final Map<String, Object> resourceSet = (Map<String, Object>)resourceSets[0];
final Object[] resources = (Object[])resourceSet.get('resources');
if (null != resources && !resources.isEmpty()) {
final String resourceStr = JSON.serialize( resources[0] );
final Resource resource = (Resource)JSON.deserialize(resourceStr, Resource.class);
return resource;
}
}
return null;
}
/**
* makes a call like this:
* http://dev.virtualearth.net/REST/v1/Locations/…&o=json&userIp=127.0.0.1&key={BingApiKey}'
*
* @param full request URL, excluding Bing Map API key, userIp and output format
*
* @returns Response
* where: Response.responseData = parsed Resource object (with Point and Address) or null
* generic method for sending requests to Bing Map API "Locations/" endpoint
*/
private static Response sendResourcesQuery(final String url) {
final HttpRequest req = new HttpRequest();
final String key = getApiKey();
final String completeUrl = url + '&o=json&userIp=127.0.0.1&key=' + key + '&incl=ciso2';
System.debug('agX complete URL: ' + completeUrl);
req.setEndpoint(completeUrl);
req.setMethod('GET');
final Http http = new Http();
final System.HttpResponse httpResponse = http.send(req);
final Response response = new Response();
response.isSuccess = 200 == httpResponse.getStatusCode() && String.isNotBlank(httpResponse.getBody());
if (response.isSuccess) {
final Map<String, Object> valueByKey = (Map<String, Object>)JSON.deserializeUntyped(httpResponse.getBody());
final Resource res = parseLocationResult(valueByKey);
if (null == res) {
response.isSuccess = false;
response.errorData = 'Failed to extract Resource from API response. ' + httpResponse.getBody();
} else {
response.responseData = res;
}
} else {
processError(httpResponse, response);
}
return response;
}
////////////////////////////////////////////////////////////////////////
public class Resource {
@AuraEnabled public String name;// e.g. '1 Microsoft Way, Redmond, WA 98052'
@AuraEnabled public Point point;
@AuraEnabled public Address address;
@AuraEnabled public String confidence; //e.g. 'Medium'
@AuraEnabled public String entityType; //e.g. 'Address'
@AuraEnabled public String distanceUnit; //e.g. 'Kilometer'
@AuraEnabled public Decimal travelDistance; //e.g. 1312.142
}
public class Point {
public Point(final Decimal latitude, final Decimal longtitude) {
coordinates = new Decimal[]{latitude, longtitude};
}
@AuraEnabled public Decimal[] coordinates;
@AuraEnabled public Decimal getLatitude() {
return null != coordinates && !coordinates.isEmpty()? coordinates[0] : null;
}
@AuraEnabled public Decimal getLongtitude() {
return null != coordinates && coordinates.size() > 1 ? coordinates[1] : null;
}
public String toUrlParameter() {
return EncodingUtil.urlEncode('' + getLatitude(), 'UTF-8') + ',' +
EncodingUtil.urlEncode('' + getLongtitude(), 'UTF-8');
}
}
public class Address {
@AuraEnabled public String addressLine; // Street
@AuraEnabled public String formattedAddress; // output parameter, not required for input
@AuraEnabled public String countryRegion; //e.g. US
@AuraEnabled public String countryRegionIso2;
@AuraEnabled public String locality; // City, e.g. Redmond
@AuraEnabled public String postalCode;
@AuraEnabled public String adminDistrict;
}
public enum DistanceUnit {MI, KM}
/**
* @description
* use this to return response result of HTTP callout in a generic way
**/
public class Response {
@AuraEnabled
public Boolean isSuccess;
@AuraEnabled
public Object responseData;
@AuraEnabled
public Object errorData;
}
}
Our Service Class

Once the class is set we create our custom lightning component to the test the APIs:

import { LightningElement, track } from 'lwc';
import getAddressByCoordinatesApex from '@salesforce/apex/BingMapProvider.getAddressByCoordinates';
import getCoordinatesByAddressApex from '@salesforce/apex/BingMapProvider.getCoordinatesByAddressJson';
import getDrivingDistanceApex from '@salesforce/apex/BingMapProvider.getDrivingDistanceJson';
export default class BingMapTest extends LightningElement {
@track isLoading = false;
// address by coordinates
@track latitude = 47.64054;
@track longtitude = -122.12934;
@track queryAddressResult;
// coordinates by address
@track addressLine = 'Microsoft Way';
@track countryRegion = 'US';
@track locality = 'Redmond';
@track postalCode = '98052';
@track queryCoordinatesResult;
// driving distance
@track fromLatitude = 47.64054;
@track fromLongtitude = -122.12934;
@track toLatitude = 37.779160067439079
@track toLongtitude = -122.42004945874214;
@track distanceUnit = 'mi'; //'km' or 'mi'
@track queryDistanceResult;
handleLatitudeChange(event) {
this.latitude = event.target.value;
}
handleLongtitudeChange(event) {
this.longtitude = event.target.value;
}
handleAddressLineChange(event) {
this.addressLine = event.target.value;
}
handleCountryRegionChange(event) {
this.countryRegion = event.target.value;
}
handleLocalityChange(event) {
this.locality = event.target.value;
}
handlePostalCodeChange(event) {
this.postalCode = event.target.value;
}
handleQueryAddressClick(event) {
this.isLoading = true;
this.queryAddressResult = '';
const _this = this;
getAddressByCoordinatesApex({'latitude': this.latitude, 'longtitude': this.longtitude})
.then(response => {
if (true === response.isSuccess) {
let data = response.responseData;
this.queryAddressResult = JSON.stringify(response, null, 2);
console.log("response.responseData=" + JSON.stringify(data));
} else {
this.queryAddressResult = 'ERROR: ' + JSON.stringify(response.errorData);
console.log("response.errorData=" + JSON.stringify(response.errorData));
}
return this.queryAddressResult;
})
.catch(error => {
const message = 'Error received: code' + error.errorCode + ', ' + 'message ' + error.body.message;
this.queryAddressResult = message;
})
.finally(function() { _this.isLoading = false; });
}
handleQueryCoordinatesClick(event) {
this.isLoading = true;
this.queryCoordinatesResult = '';
const _this = this;
const address = {
'addressLine': this.addressLine,
'countryRegion': this.countryRegion,
'locality': this.locality,
'postalCode': this.postalCode,
};
getCoordinatesByAddressApex( { 'addressJson': JSON.stringify(address) })
.then(response => {
if (true === response.isSuccess) {
let data = response.responseData;
this.queryCoordinatesResult = JSON.stringify(response, null, 2);
console.log("response.responseData=" + JSON.stringify(data));
} else {
this.queryCoordinatesResult = 'ERROR: ' + JSON.stringify(response.errorData);
console.log("response.errorData=" + JSON.stringify(response.errorData));
}
return this.queryCoordinatesResult;
})
.catch(error => {
const message = 'Error received: code' + error.errorCode + ', ' + 'message ' + error.body.message;
this.queryCoordinatesResult = message;
})
.finally(function() { _this.isLoading = false; });
}
handleDrivingDistanceInputChange(event) {
let value = event.target.value;
switch(event.target.label) {
case 'fromLatitude':
this.fromLatitude = value;
break;
case 'fromLongtitude':
this.fromLongtitude = value;
break;
case 'toLatitude':
this.toLatitude = value;
break;
case 'toLongtitude':
this.toLongtitude = value;
break;
case 'distanceUnit':
this.distanceUnit = value;
break;
default:
// code block
}
}
handleQueryDrivingDistanceClick(event) {
this.isLoading = true;
this.queryDistanceResult = '';
const _this = this;
const paramsJson = {
'wayPoint0_latitude': Number(this.fromLatitude),
'wayPoint0_longtitude': Number(this.fromLongtitude),
'wayPoint1_latitude': Number(this.toLatitude),
'wayPoint1_longtitude': Number(this.toLongtitude),
'distanceUnit': this.distanceUnit
};
getDrivingDistanceApex( { 'paramsJson': JSON.stringify(paramsJson) })
.then(response => {
if (true === response.isSuccess) {
let data = response.responseData;
this.queryDistanceResult = JSON.stringify(response, null, 2);
console.log("response.responseData=" + JSON.stringify(data));
} else {
this.queryDistanceResult = 'ERROR: ' + JSON.stringify(response.errorData);
console.log("response.errorData=" + JSON.stringify(response.errorData));
}
return this.queryDistanceResult;
})
.catch(error => {
const message = 'Error received: code' + error.errorCode + ', ' + 'message ' + error.body.message;
this.queryDistanceResult = message;
})
.finally(function() { _this.isLoading = false; });
}
}
view raw bingMapTest.js hosted with ❤ by GitHub
LWC JS File
<template>
<template if:true={isLoading}>
<lightning-spinner variant="brand" size="large" alternative-text="Please wait…"></lightning-spinner>
</template>
<div class="slds-card">
<lightning-layout>
<lightning-layout-item padding="around-small">
<lightning-input type="text" label="Latitude" value={latitude} onchange={handleLatitudeChange}></lightning-input>
</lightning-layout-item>
<lightning-layout-item padding="around-small">
<lightning-input type="text" label="Longtitide" value={longtitude} onchange={handleLongtitudeChange}></lightning-input>
</lightning-layout-item>
<lightning-layout-item padding="around-small">
<lightning-button variant="brand" label="Query Address" title="Query Address" onclick={handleQueryAddressClick} class="slds-m-left_x-small"></lightning-button>
</lightning-layout-item>
</lightning-layout>
{queryAddressResult}
<!– Coordinates by Address –>
<lightning-layout>
<lightning-layout-item padding="around-small">
<lightning-input type="text" label="addressLine" value={addressLine} onchange={handleAddressLineChange}></lightning-input>
</lightning-layout-item>
<lightning-layout-item padding="around-small">
<lightning-input type="text" label="countryRegion" value={countryRegion} onchange={handleCountryRegionChange}></lightning-input>
</lightning-layout-item>
<lightning-layout-item padding="around-small">
<lightning-input type="text" label="locality" value={locality} onchange={handleLocalityChange}></lightning-input>
</lightning-layout-item>
<lightning-layout-item padding="around-small">
<lightning-input type="text" label="postalCode" value={postalCode} onchange={handlePostalCodeChange}></lightning-input>
</lightning-layout-item>
<lightning-layout-item padding="around-small">
<lightning-button variant="brand" label="Query Coordinates" title="Query Coordinates" onclick={handleQueryCoordinatesClick} class="slds-m-left_x-small"></lightning-button>
</lightning-layout-item>
</lightning-layout>
{queryCoordinatesResult}
<!– Driving Distance by Coordinates –>
<lightning-layout>
<lightning-layout-item padding="around-small">
<lightning-input type="text" label="fromLatitude" value={fromLatitude} onchange={handleDrivingDistanceInputChange}></lightning-input>
</lightning-layout-item>
<lightning-layout-item padding="around-small">
<lightning-input type="text" label="fromLongtitude" value={fromLongtitude} onchange={handleDrivingDistanceInputChange}></lightning-input>
</lightning-layout-item>
<lightning-layout-item padding="around-small">
<lightning-input type="text" label="toLatitude" value={toLatitude} onchange={handleDrivingDistanceInputChange}></lightning-input>
</lightning-layout-item>
<lightning-layout-item padding="around-small">
<lightning-input type="text" label="toLongtitude" value={toLongtitude} onchange={handleDrivingDistanceInputChange}></lightning-input>
</lightning-layout-item>
<lightning-layout-item padding="around-small">
<lightning-input type="text" label="distanceUnit" value={distanceUnit} onchange={handleDrivingDistanceInputChange}></lightning-input>
</lightning-layout-item>
<lightning-layout-item padding="around-small">
<lightning-button variant="brand" label="Query Driving Distance" title="Query Driving Distance" onclick={handleQueryDrivingDistanceClick} class="slds-m-left_x-small"></lightning-button>
</lightning-layout-item>
</lightning-layout>
{queryDistanceResult}
</div>
</template>
LWC HTML File
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>50.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__RecordPage</target>
<target>lightning__HomePage</target>
<target>lightning__Tab</target>
</targets>
</LightningComponentBundle>
view raw bingMapTest.xml hosted with ❤ by GitHub
LWC XML File

Once we are done with the components lets begin some tests. Following is the screenshot of the API tests done from the lightning component for each of the above API mentioned:

Easy to integrate isn’t it? There are other variations on the various types of the API you can check. Full list here. Next time we will be integrating their Map UI on a custom page with cool interactive features.

Till then Happy Integrating!

Resume Builder (LWC)

Presenting to you all – Resume Builder – a responsive website enabling users to create their own Resume, for any profession.
Users can edit the resume content, print it and can use this to create their Online Portfolio and host it for free as per their choice.

Resume Builder

Resume Builder Highlights

  1. Edit the JSON data to personalize this Resume as per your needs and you can print the content too!
  2. The Resume Preview is available in Light and Dark Mode and can be toggled from the Top Panel
  3. Users can choose to toggle the visibility of the display picture from the Top Panel. The image source is hosted in my Github repository. Users can choose to fork the repo and personalize this as needed.
  4. The site is responsive and works well on Mobiles, Tablets and Desktop browsers.
  5. Since users need to edit the JSON content and this can be prone to errors and typos, we suggest using JSON Validators (like – https://jsonlint.com/) to ensure the JSON schema integrity is maintained upon parsing it from a string value.

Showcasing Responsiveness and Dark Mode below-

It is built using LWC Open Source and Salesforce Lightning Design System and it is hosted for free on Heroku

The resume format and design approach is greatly inspired from the JSON Resume project – All resume content to display is inspired from its standardized JSON schema. “For Developers, by Developers.

How to Build one yourself

This project is built using Salesforce’s Lightning Web Components Open Source Framework.

Check out the documentation for using create-lwc-app and lwc-services here.

If you want to see Lightning Web Components in action – check out https://recipes.lwc.dev.

All the Basics of creating your LWC OSS App are described well in this trailhead module.

A few steps to note in particular on using SLDS styles, fonts, icons in your OSS project

1.Install the lightning-base-components via npm install command line interface

npm install lightning-base-components

2. Ensure that your lwc.config.json has the npm dependency specified

 { "modules": [ 
   { "dir": "src/modules" }, 
   { "npm": "lightning-base-components" }
  ]
 }

3. Install the SLDS using npm install

npm install @salesforce-ux/design-system --save-dev

4. Ensure you have lwc-services.config.js properly configured as shown below –

module.exports = {
   resources: [{ from: 'src/resources/', to: 'dist/resources/' },
   {
     from: 'node_modules/@salesforce-ux/design-system/assets',
     to: 'src/SLDS'
   },
  {
    from: 'node_modules/@salesforce-ux/design-system/assets',
    to: 'dist/SLDS'
   }
  ]
};

5. In your index.html file, add the SLDS stylesheet in the head tag –

<link rel="stylesheet" href="/SLDS/styles/salesforce-lightning-design-system.min.css" />

6. In your index.js file, add this statement at the first line-

import '@lwc/synthetic-shadow';

If all the above sounds too much work, you can refer the built code in this github repository.
If you’d like, feel free to fork this repo and edit the resume.json file as per your content.
You can modify the HTML & JS code in src/modules/my/resume LWC to have your desired implementation.

For hosting this, I have preferred to create an app on Heroku platform and linked it to my Github Repository, such that whenever I commit my changes to the main branch, an automated build will run and that would deploy it to Heroku. You can see the Live LWC OSS App hosted at – https://resume-builder-lwc.herokuapp.com/

Gear up for the New Year, with a New Salesforcy Resume.

Let us know your feedback / suggestions / opinions on this utility tool.

Yours Wisely,
Waseem

LWC Navigation Scenarios with Examples

A lot of good many LWC Navigation examples and documentation are available scattered across different sources , so we thought of aggregating and providing it all here covering most of the navigation use cases.

To navigate in Lightning Experience, Lightning communities, and the Salesforce app, use the navigation service, lightning/navigation to navigate to many different page types, like records, list views, custom tabs, and objects and even open files.

Instead of a URL, the navigation service uses a PageReference. A PageReference is a JavaScript object that describes the page type, its attributes, and the state of the page. Using a PageReference insulates your component from future changes to URL formats. It also allows your component to be used in multiple applications, each of which can use different URL formats.

NOTE – The type (String) and attributes (Object) are required parameters in all cases.
state (Object) is an optional parameter.

Let’s jump into the code

To use the Navigation Service in your LWC , import it in your JS file-

import { NavigationMixin } from 'lightning/navigation';

And apply the NavigationMixin function to your Component’s base class –

export default class My_lwc extends NavigationMixin(LightningElement) {
}

The NavigationMixin adds two APIs to your component’s class. Since these APIs are methods on the class, they must be invoked with this reference.

  • [NavigationMixin.Navigate](pageReference, [replace]): A component calls this[NavigationMixin.Navigate] to navigate to another page in the application.
  • [NavigationMixin.GenerateUrl](pageReference): A component calls this[NavigationMixin.GenerateUrl] to get a promise that resolves to the resulting URL. The component can use the URL in the href attribute of an anchor. It can also use the URL to open a new window using the window.open(url) browser API.

Navigation Service Examples

  1. RECORD PAGES :
// Navigate to View Account Record Page
    navigateToViewAccountPage() {
        this[NavigationMixin.Navigate]({
            type: 'standard__recordPage',
            attributes: {
                recordId: this.yourRecordId,
                objectApiName: 'Account',
                actionName: 'view'
            },
        });
    }
// Navigate to Edit Account Record Page
    navigateToEditAccountPage() {
        this[NavigationMixin.Navigate]({
            type: 'standard__recordPage',
            attributes: {
                recordId: this.yourRecordId,
                objectApiName: 'Account',
                actionName: 'edit'
            },
        });
    }
// Navigate to Clone Account Record Page
    navigateToCloneAccountPage() {
        this[NavigationMixin.Navigate]({
            type: 'standard__recordPage',
            attributes: {
                recordId: this.yourRecordId,
                objectApiName: 'Account',
                actionName: 'clone'
            },
        });
    }
//Communities don’t support the actionName values clone or edit.

2. OBJECT PAGES

// Navigate to New Account Page
navigateToNewAccountPage() {
    this[NavigationMixin.Navigate]({
        type: "standard__objectPage",
        attributes: {
            objectApiName: "Account",
            actionName: "new"
        },
    });
}

// Navigation to Account List view(recent)
navigateToAccountRecentListView() {
    this[NavigationMixin.Navigate]({
        type: "standard__objectPage",
        attributes: {
            objectApiName: "Account",
            actionName: "list"
        },
        state: {
            filterName: "Recent"
        },
    });
}
//filterName =  StringID or developer name of object"s list view.


// Navigate to a New Account page with default field values:
//    Name: Salesforce,
//    OwnerId: 005XXXXXXXXXXXXXXX,
//    AccountNumber: ACXXXX,
//    NumberOfEmployees: 35000
navigateToAccountDefault() {
    this[NavigationMixin.Navigate]({
        type: “standard__objectPage”,
        attributes: {
            objectApiName: "Account",
            actionName: "new"
        },
        state: {
            defaultFieldValues = "AccountNumber=ACXXXX,Name=Salesforce,NumberOfEmployees=35000,OwnerId=005XXXXXXXXXXXXXXX",
            nooverride: "1"
        }
    });
}


// Navigation to Case object home page
navigateToCaseHome() {
    this[NavigationMixin.Navigate]({
        type: "standard__objectPage",
        attributes: {
            objectApiName: "Case",
            actionName: "home"
        }
    });
}


//Navigate to Reports Standard Tab
navigateToReports() {
    this[NavigationMixin.Navigate]({
        type: "standard__objectPage",
        attributes: {
            objectApiName: "Report",
            actionName: "home"
        },
    });
}
//Navigate to Files Home/Standard tab
navigateToFilesHome() {
    this[NavigationMixin.Navigate]({
        type: "standard__objectPage",
        attributes: {
            objectApiName: "ContentDocument",
            actionName: "home"
        },
    });
}
//In communities, actionName = list and home are the same.
//In managed package, prefix the custom object with ns__

3. RECORD RELATIONSHIP PAGE
A page that interacts with a relationship on a particular record in the org. Only related lists are supported.

// Navigation to Contact related list of Account
    navigateToContactRelatedList() {
        this[NavigationMixin.Navigate]({
            type: 'standard__recordRelationshipPage',
            attributes: {
                recordId: this.recordId,
                objectApiName: 'Account',
                relationshipApiName: 'Contacts',
                actionName: 'view'
            },
        });
    }
//actionName = Only view is supported.
//relationshipApiName = The API name of the object’s relationship field.

4. APP PAGES

//Navigate to a Standard App
navigateToSalesApp() {
    this[NavigationMixin.Navigate]({
        type: 'standard__app',
        attributes: {
            appTarget: 'standard__Sales',
        }
    });
}

//Navigate to a Custom App
navigateToMyCustomApp() {
    this[NavigationMixin.Navigate]({
        type: 'standard__app',
        attributes: {
            appTarget: 'c__MyCustomApp',
        }
    });
}
// Pass either the appId or appDeveloperName to the
// appTarget. The appId is the DurableId field on the AppDefinition object.
// For standard apps, the namespace is standard__. For custom
// apps, it’s c__. For managed packages, it’s the namespace
// registered for the package.

//Navigate to App Record Page in an App
navigateToAppRecordPage() {
    this[NavigationMixin.Navigate]({
        type: 'standard__app',
        attributes: {
            appTarget: 'standard__LightningSales',
            pageRef: {
                type: 'standard__recordPage',
                attributes: {
                    recordId: '001xx000003DGg0XXX',
                    objectApiName: 'Account',
                    actionName: 'view'
                }
            }
        }
    });
}

5. LIGHTNING COMPONENT PAGES :

To make an addressable LWC, embed it in an Aura component that implements the lightning:isUrlAddressable interface.

// Navigate to Lightning Component with Params
navigateToLC() {
    this[NavigationMixin.Navigate]({
        type: "standard__component",
        attributes: {
            //Here myCustomAura is name of Aura component
            //which implements lightning:isUrlAddressable
            componentName: "c__myCustomAura"
        },
        state: {
            c__counter: '5',
            c__recId: 'XXXXXXXXXXXXXXXXXX'
        }
    });
}
//You can pass any key and value in the state object. The key
//must include a namespace, and the value must be a string.

In Aura component, retrieve the sent params using component.get in the init controller method –

component.get("v.pageReference").state.c__counter);
component.get("v.pageReference").state.c__recId);

Retrieval of params in LWC is described in a detailed section below.

6. CUSTOM TABS :

navItemPage – A page that displays the content mapped to a custom tab. Visualforce tabs, Web tabs, Lightning Pages, and Lightning Component tabs are supported.

// Navigate to Custom Tab - VF tabs, Web tabs, Lightning Pages, and Lightning Component Tabs
navigateToTab() {
    this[NavigationMixin.Navigate]({
        type: 'standard__navItemPage',
        attributes: {
            apiName: 'MyCustomTabAPIName'
        },
        state: {
            c__counter: '5',
            c__recId: this.recId
        }
    });
}

7. WEB PAGES

// Navigate to an External URL
navigateToWebPage() {
    this[NavigationMixin.Navigate]({
        type: 'standard__webPage',
        attributes: {
            url: 'http://salesforce.com'
        }
    });
}
//Navigate to a Visualforce page
    navigateToVF() {
        this[NavigationMixin.GenerateUrl]({
            type: 'standard__webPage',
            attributes: {
                url: '/apex/myVFPage?id=' + this.recId
            }
        }).then(generatedUrl => {
            window.open(generatedUrl);
        });
    }
// Here we use NavigationMixin.GenerateUrl to form the URL and then navigate to it in the promise.

8. KNOWLEDGE ARTICLE :

// Navigate to Knowledge Article record
navigateToKnowledgeArticle() {
    this[NavigationMixin.Navigate]({
        type: 'standard__knowledgeArticlePage',
        attributes: {
            articleType: 'MyArticleType',
            urlName: 'Mar-2020'
        }
    });
}
//The articleType is API name of the Knowledge Article record.
//The urlName is the article's URL.

9. NAMED PAGES (standard) & FILE PREVIEW

// Navigate to Named Pages - A standard page with a unique name.
navigateToNamedPage() {
    this[NavigationMixin.Navigate]({
        type: 'standard__namedPage',
        attributes: {
            pageName: 'home'
        }
    });
}
// pageName possible Values - home , chatter, today, dataAssessment, filePreview


// Navigate to filePreview Page
navigateToFilePreview() {
    this[NavigationMixin.Navigate]({
        type: "standard__namedPage",
        attributes: {
            pageName: "filePreview"
        },
        state: {
            // assigning single ContentDocumentId
            selectedRecordId: this.id
        }
    });
}
// recordIds: '069xx0000000005AAA,069xx000000000BAAQ',
// selectedRecordId:'069xx0000000005AAA'

10. NAMED PAGES & LOGIN PAGE (community)

// Navigate to standard page used in Lightning communities
navigateToCommPage() {
    this[NavigationMixin.Navigate]({
        type: 'comm__namedPage',
        attributes: {
            name: 'Home'
        }
    });
}
// Supported pages in communities - Home, Account Management, Contact Support,
// Error, Login, My Account, Top Articles, Topic Catalog, Custom page

// Navigate to Authentication page in Lightning communities
navigateToLoginPage() {
    this[NavigationMixin.Navigate]({
        type: 'comm__loginPage',
        attributes: {
            actionName: 'login'
        }
    });
}
// Supported actionName = login, logout

Now that we have seen what all kinds of Navigation scenarios are provided, let’s also look at another feature, which is the URL generation done by NavigationMixin.GenerateUrl , as shown below-

recordPageUrl; // variable to be associated to anchor tag.

// Generate a URL to a User record page
generateURLforLink() {
    this[NavigationMixin.GenerateUrl]({
        type: 'standard__recordPage',
        attributes: {
            recordId: '005B0000001ptf1XXX',
            actionName: 'view',
        },
    }).then(generatedUrl => {
        this.recordPageUrl = generatedUrl;
    });
}
// NavigationMixin.GenerateUrl returns the Generated URL in the promise.
// We can even use this in  window.open(generatedUrl) command


Retrieving Params in LWC

We use CurrentPageReference to get a reference to the current page in Salesforce. Page URL formats can change in future releases. Hence, to future proof your apps, use page references instead of URLs.

import { CurrentPageReference } from 'lightning/navigation';
@wire(CurrentPageReference)
pageRef;

The key-value pairs of the PageReference state property are serialized to URL query parameters. 

Example of an LWC encoding defaultFieldValues in state and then navigating to another LWC and decoding them –

// navSenderLWC.js
import { LightningElement } from 'lwc';
import { NavigationMixin } from 'lightning/navigation';
import { encodeDefaultFieldValues } from 'lightning/pageReferenceUtils';

export default class navSenderLWC extends NavigationMixin(LightningElement) {

    navigateWithParams() {
        const encodedValues = encodeDefaultFieldValues({
            FirstName: 'Waseem',
            LastName: 'Sabeel'
        });

        this[NavigationMixin.Navigate]({
            type: 'standard__objectPage',
            attributes: {
                objectApiName: 'Contact',
                actionName: 'new'
            },
            state: {
                defaultFieldValues: encodedValues
            }
        });
    }
}

And we use the Wired PageReference on the navigated LWC, and decode the field values as below –

// navReceiverLWC.js
import { LightningElement, wire } from 'lwc';
import { CurrentPageReference } from 'lightning/navigation';
import { decodeDefaultFieldValues } from 'lightning/pageReferenceUtils';

export default class navReceiverLWC extends LightningElement {

    @wire(CurrentPageReference)
    setCurrentPageRef(pageRef) {
        if (pageRef.state.defaultFieldValues) {
            const decodedValues = decodeDefaultFieldValues(pageRef.state.defaultFieldValues);
        }
    }
}

Note – All encoded default field values in the state are passed as strings.

So that was all about Lightning Navigation Scenarios and Examples.

A Note on Limitations –

The lightning/navigation service is supported only in Lightning Experience, Lightning communities, and the Salesforce app. It isn’t supported in other containers, such as Lightning Components for Visualforce, or Lightning Out. This is true even if you access these containers inside Lightning Experience or the Salesforce app.

For more detailed information , kindly refer to the Official Documentation provided here –

  1. All PageReference Types with attribute details
  2. Developer Guide
  3. lightning-navigation Component reference

Thank You!

Yours Wisely,
Waseem

Salesforce: Mobile Extension Toolkit for Field Service Lightning

Image for post

As of Winter 21 release, Salesforce offers developers to create their own HTML based mobile extensions for native FSL App. This feature is currently in pilot so we can expect some more changes and updates from salesforce until it fully goes GA. In this post I am going to show you can host the HTML app as an FSL action and how different elements and functions integrate together.

Why do we use this feature?

As we all know whenever we work with salesforce mobile app there are certain considerations and limitations we have to take into account whenever we design a use case. In FSL the end users have a wide variety of choices to perform various types of actions which are built natively on salesforce using flows or standard quick actions. However we cannot build a fully customizable flow which runs a lightning web component or aura components in the background. This is currently a limitation on FSL. So if you want to use your own custom screens with fully customizable user interface flows Mobile extension toolkit is the best fit here.

Example

Let’s take a very simple example of creating a very simple HTML based application which uses standard FSL methods for fetching the record, getting the picklist values and run a SOQL Query. These are some of the very common methods you may use while building your own extensions. There are other capabilities which are also available but for this example I will use these 3 functions. Steps →

Step 1 → Create a native HTML app on your machine with index.html file as a root. This file is a must as this is the entry point of your extension.

Image for post

Step 2 → Once your index file is ready, you can start forming your application structure by adding scripts and other features you would like to add. For this example I created a scripts folder to store my FSL scrips which will be executed from the root HTML file during runtime

Before we move the step 3, This how my page currently looks like on desktop.

Image for post
Desktop View

Obviously it won’t work as it is only intended for FSL app on Mobile. You can refer the code from this GitHub Repo.

Step 3 → Now once our native HTML extension is all set, Its time to test! Navigate to salesforce setup → Mobile Extension Toolkit (Pilot) → Upload your HTML extension as a zip (make sure index.html is always present on the root)

Image for post
Navigate to salesforce setup → Mobile Extension Toolkit (Pilot)
Image for post
Upload Mobile Extensions by clicking on “New” button
Image for post
Provide your extension details

One thing you need to keep in mind is before you upload this in salesforce, Have the index file load on your local browser to make sure its error free. FSL functions wont work as the browser doesn’t recognize it. Its only available once the extension is completely loaded on the FSL App.

Step 4 → Once your mobile extension is successfully uploaded to your org we now need to reference it in our global or quick actions in order to trigger the extension. For this example I have created a simple quick action button on Work Order object which will appear on the detail page.

Image for post
Quick Action Referring the Mobile Extension we uploaded in Step 3

Make sure to add this quick action in Salesforce Mobile and Lightning Experience Actions section of the page layout

Step 5 → Now comes the most important bit. To test our extension in the FSL app. As mentioned above I am using 3 basic functions: Show a record info, Getting picklist value and run a basic SOQL query.

Image for post
Result when Fetch Picklist button is clicked
Image for post
Result when Execute SOQL is clicked
Image for post
Result when Get Record is clicked

FSL Functions used in the above example –

Get Record → fsl.query.getRecord

SOQL → fsl.query.executeSoqlQuery

Fetch Picklist values → fsl.metadata.getPicklistValues

Other capabilities include calling Rest API, Context API, Create Record, Update Record, Delete Record, Barcode Scanner, ObjectInfo, Geolocation API, Calling Apex Rest Services etc.

Limitations

No native debugging and testing mechanism present. Debugging can only be performed from Mobile extension WebView. Desktop testing is not supported.

No support for calling native apex methods. Apex methods can only be called if they are exposed as rest service in apex rest resource.

No support for native lightning data service & lightning elements. Only pre defined FSL methods are supported

Reference of 3rd party links are restricted. 3rd party scripts are required to be downloaded and added in the current zip artifacts to get it working.

Guidelines and Best Practices to be considered

Usage of Mobile extension WebView for debugging is recommended

Scripts, CSS and other components should be maintained in dedicated folders of the native HTML5 app

As Mobile extension does not offer any native JS code testing it is recommended that the developers perform an offline JS test code at their end before uploading the zip in salesforce

Testing should be carried out in both Android and IOS devices for both iPad and iPhone as native HTML elements may behave differently in different devices.

Keep us posted on your findings and insights on this feature too !
We are waiting for it to be GA soon 🙂

Yours wisely

Ballot Box – An LWC Voting App

Hello Fellas. Presenting to you- Ballot Box- A Lightning Web Component App for carrying out Voting among Team Members, exposed as a Public Site in Salesforce.

App Highlights

This Fun App provisions an Admin to create a Team with relevant Candidates who can Vote among themselves on Titles- All built on Salesforce!

This App, when exposed as a public Salesforce Site, allows Users to cast their Vote using their configured Email Id in the Candidate records, without any Login hassle!

The pre-built Voting rules are –

  • Voter can cast only one Vote per Title
  • Voter cannot vote for oneself in any of the Titles
  • Few Titles may have a pre-filtered list of Nominees, others will display all the Team Members

Our proposed way is to share the Public Salesforce Site URL with the Users all at once to ensure Simultaneous Votes are being cast and the Salesforce Admin can then view the results in a Wall of Fame Ballot Box Dashboard as shown below –

Get Ballot Box App in your Org

Installation & Site Setup

The Complete Guide to Install and Configure this App can be found in our Github Repo here – https://github.com/WaseemAliSabeel/BallotBox


Request you to go through the Salesforce Site Setup steps therein.

So what are you waiting for ? Let the Voting Begin !


App Author

PS: There is a subtle UI-related Easter Egg in this LWC component.
Can you spot it ?
Let us know in the comments!

Thank you.

JavaScript Developer I Certification: Preparation


This exam’s experience was very different compared to our previous certifications. Quite Challenging and Quite Enjoyable as well.

The certification is divided into two parts:

  1. Lightning Web Component Super Badge
  2. JavaScript Developer I Multiple choice exam

Which Part to Attempt First?

Both of the above requisites can be done in any order. Doing the MCQ exam first won’t make any difference as the exam doesn’t really focus on lightning web components (as of my time of writing this post). I believe that’s why Salesforce has introduced a super badge for this. What I would recommend is doing the super badge first will give you a good headstart to prepare for the MCQ exam as some of the steps of super badge are tricky which involves writing some pure JavaScript. So you will definitely go out of your way to get some steps working and learn some cool features of JavaScript. But it’s totally up to you which part you want to attempt first. If you are from a JavaScript background looking to get into Salesforce development, then giving the multiple choice exam first is a good fit.


LWC Superbadge

Most of the information and steps are given in Trailhead for obtaining the badge. You can access the super badge Here. You need to complete all 4 modules on Trailhead in order to unlock the superbadge-


The Superbadge has some good challenging steps and covers all the scenarios of web component development similar to a real word project! I would recommend not to really rush through the steps. Try to understand what is the ask, how many components you would need, Make a model of the components and then attempt the superbadge as a whole. If you have understood and followed the requirements correctly, you will eventually be able to clear all the steps. Few references and example recipes that may come useful during the superbadge, in case you aren’t already familiar-

Web Component Reference Guide

Example Recipes


JavaScript Developer I Multiple Choice Exam

As mentioned above, this exam is different from usual Salesforce exams. The Trailmix is a good start but most of its references focuses towards the superbadge. The exam consists of 60 questions with additional 5 non scored questions. You have total of 65 questions to be attempted in 105 minutes. The passing score is 65% which is about 40 correctly answered questions.

IMPORTANT! Before we jump on the topics, Do make sure to go through the exam guide and make a note of the suggested resources there. Those are primary ones. We also found this preparation trail very helpful as it has example flash cards with questions and suggested references. This module gives you an idea how the questions will look like. Make sure to take a note of all the suggested references there.

Topic Breakdown & Main Focus Areas:

Variables, Types and Collections 23%
Object, Functions, and Classes 25%
Browser and Events 17%
Debugging and Error Handling 7%
Asynchronous Programming 13%
Server Side JavaScript 8%
Testing 7%


Variables, Types and Collections (23%)

Data Types in JavaScript — Primitive, Composites and Special
Create and Initialize variables correctly
Demonstrate awareness of type coercion and its effects
Array methods, Data manipulations with arrays
JSON objects and methods
Sets and Maps
TypeOf , InstanceOf Operators
Strict mode — Behavior differences in strict vs non strict mode
Grammar types and Template literals
Decorators

Few Questions were on Array functions chained together – like map(), followed by filter()
Understand block-scoping & function-scoping differences between var, let, const
Lot of Questions on Type Coercions involving + , * , NaN etc
Understand typeof. eg- typeof(null) = object. and typeof(typeof (null)) is a string.
‘use strict’ considerations in a code snippet , JSON.stringify() & JSON.parse() scenarios


Objects, Functions and Classes (25%)

Objects — Iteration, Enumeration, Object Properties such as Object Prototypes, this keyword, Optional chaining, Object to primitive conversion, Global objects
Functions — Regular function declaration vs arrow functions, Function expressions, declarations
Classes — Inheritance, Static methods and properties, private and protected, Mixins, built in classes
Import & Export modules in JS
Loops and Iterations

Few Questions were on Object.Prototype, Object.freeze on a const.
Hoisting scenarios, import * as, forEach loop – break scenario etc.
for…of loop regarding Arrays. for…in loop regarding Objects


Browser and Events (17%)

Event Listeners
History API — popstate, pushstate, replaceState etc.
Events — Event propagation, DOM visibility, Differences between bubbles composed
DOM Structure, DOM manipulation
Window API, Location API, Navigator API, Screen API
setTimeout, setInterval
Page Events

Questions on addEventListener() , onclick() and different combinations of setTimeout with setInterval in same snippet.
DOM manipulation like adding style classes by querying particular tags etc


Debugging and Error Handling (7%)

Error Handling in JS — Try, Catch, Finally
Custom Errors — Overriding the Error class
Debugging
Control error handling flow
Debugging in browser with breakpoints, Debugger statement
String substitutions

Many questions involving console.assert() success & fail scenarios,
use of debugger statement


Asynchronous Programming (13%)

Promise basics
Using Promises, Promise methods
Asynchronous function
Combination of async-await
Promise chaining
Callback stack

Most challenging topic as per my experience. Learn all fundamentals about Promise and Async/Await. Write code snippets and practically implement them.
Questions will be on scenarios involving Promises with setTimeout etc.


Server Side JavaScript (8%)

NPM Modules, Package.JSON structure
NodeJS Basics
Package Version Naming conventions
Frameworks — Which frameworks is suitable – Frontend vs Backend
CLI

Know versioning- Major.Minor.Patch. npm install, inspect commands related questions. Have an idea of which are the famous Backend and Frontend frameworks having a community.


Testing (7%)

Console commands
Assertions
Testing Implementation — False positives vs False negatives
Black Box Testing vs White Box Testing
Jest basics


Recommended References

I found MDN and JavaScript.info the most helpful in our preparation. The exam guide on Trailhead majorly have references to these two sites. But it’s your personal choice which site you feel most comfortable with.

This Youtube playlist by Salesforce Troop is also quite detailed and covers the whole JS syllabus. I watched the videos at 2X speed to conveniently grasp all the content. The Practice test in the end is quite helpful for revision.

Another special mention to This Github Repo by Lydia Hallie for plenty of revision Questions on JavaScript. Just don’t get overwhelmed 🙂

Make sure to go through the official Exam GuideTrailmix and Study Trail for sure.


Conclusion

If you are still unsure on how specific functionalities work in real time scenario, it is always better to implement those in your browser’s console and refer some videos on YouTube, or Blogs with detailed examples. It took me some time to thoroughly understand Events and Asynchronous Javascript so I decided to create some sample apps in my local and test various combinations. At the end of the day you have to make sure there is absolutely no confusion in understanding.

As far as the exam goes most of the questions will be code based, so you really need to manage time as well as stay focused till the very end. Try to understand the scenario, look at the options and then try to filter down the best possible choices. Please don’t rush through the preparation and take your time in understanding the fundamentals. 

More than the credential, Learning matters!

And another piece of advice- Keep practicing JavaScript.
You’ll only master this skill if you keep learning, practicing and evolving with it.

Good Luck to You.

Yours wisely,

Image for post

LWC with VF in Iframe – Bidirectional Communication

This post will demonstrate the case where we have a Visualforce Page nested into a Lightning Web Component and how the communication between the two is established.

There are two important things you need to know about Visualforce pages running in Lightning Experience:

  • Different DOMs. A Visualforce page hosted in Lightning Experience is loaded in an iframe. In other words, it’s loaded in its own window object which is different from the main window object where the Lightning Components are loaded.
  • Different Origins. Visualforce pages and Lightning Components are served from different domains.

In case of Developer Edition, Sandboxes and Production,
Lightning Components are loaded from a domain that looks like this:
yourdomain.lightning.force.com

Visualforce pages are loaded from a domain that looks like this:
yourdomain–c.visualforce.com
(in Trailhead Playground Org, I witnessed the domain is in the form of –
yourdomain-dev-ed–c.na35.visual.force.com )

The browser’s same-origin policy prevents a page from accessing content or code in another page loaded from a different origin (protocol + port + host).

In our case, that means that a Visualforce page can’t use the parent window reference to access content or execute code in the Lightning Component wrapper. Similarly, the Lightning component can’t use the iframe’s contentWindow reference to access content or execute code in the Visualforce page it wraps.

These restrictions are enforced for very good reasons. But fortunately, there is also an API (otherWindow.postMessage()) that provides a secure approach (when used properly) to exchange messages between different window objects with content loaded from different origins.

window.postMessage() is a standard.

In the scenario below, we will look at different examples illustrating how postMessage() can be used to communicate between LWC and Visualforce page

Let’s Jump Into Code

The Installation URL or the Deployment process of the complete App LWC Comms is available in the Github Repo –
https://github.com/WaseemAliSabeel/LWCComms

LWC to VF

We have an LWC that wraps a VF page using the iframe tag, and we want the LWC to send messages to the wrapped VF page. 

The first argument of postMessage() is the data you want to pass to the other window. It can be a primitive data type or an object.

The second argument of postMessage() is the origin (protocol + port + host) of the window you send the message to (vfWindow in this case). The event will not be sent if the page in vfWindow at the time postMessage() is called wasn’t loaded from vfOrigin.

LWC HTML :

<template>
    <lightning-card title="LWC with VF in iFrame" icon-name="custom:custom9">
        <lightning-layout multiple-rows>
            <lightning-layout-item size="12" padding="around-small">
            </lightning-layout-item>
            <lightning-layout-item size="6" padding="around-small">
                <div class="slds-m-around_medium">
                    <lightning-input label="Message to send to VF" type="text" value={msg} onchange={handleChange}>
                    </lightning-input>
                    <lightning-button label="Send to VF" variant="brand" onclick={handleFiretoVF}></lightning-button>
                </div>

            </lightning-layout-item>
            <lightning-layout-item size="6" padding="around-small">
                <template if:true={receivedMessage}>
                    <p> Message Received from VF = </p>
                    <div class="slds-box">
                        <lightning-formatted-text value={receivedMessage}></lightning-formatted-text>
                    </div>
                </template>
            </lightning-layout-item>

            <lightning-layout-item size="12" padding="around-small">
                <p>VF page below in an iFrame</p>
                <iframe height="400px" width="100%" src="/apex/POV_VFiframe"></iframe>
            </lightning-layout-item>

        </lightning-layout>
    </lightning-card>
</template>

LWC JS :

import { LightningElement,wire} from 'lwc';

import getVFOrigin from '@salesforce/apex/POV_Controller.getVFOrigin';

export default class pov_lwc_vfiframe extends LightningElement {
    msg = '';
    receivedMessage = '';
    error;

    // Wire getVFOrigin Apex method to a Property
    @wire(getVFOrigin)
    vfOrigin;

    /*****Called on LOAD of LWC  *****/
    connectedCallback() {
        // Binding EventListener here when Data received from VF
        window.addEventListener("message", this.handleVFResponse.bind(this));
    }

    handleVFResponse(message) {
        if (message.origin === this.vfOrigin.data) {
            this.receivedMessage = message.data;
        }
    }

    handleChange(event) {
        this.msg = event.detail.value;
    }

    handleFiretoVF() {
        let message = this.msg;
        //Firing an event to send data to VF
        this.template.querySelector("iframe").contentWindow.postMessage(message, this.vfOrigin.data);
    }

In here, We use an Apex method to get us the Dynamic Origin  URL ( vfOrigin) as shown below –

@AuraEnabled(cacheable=true)
    public static string getVFOrigin() {
      string vfOrigin = '';
    string baseURL = URL.getOrgDomainUrl().toExternalForm(); // Expected Format = https://domain.my.salesforce.com

    // Expected Format for DE, Sandbox & Production ORgs = https://domain--c.vf.force.com
    vfOrigin = baseURL.split('.my.')[0] + '--c.' + 'vf.force.com';

   // Please note the DOMAIN mismatch error in your console logs , if any. 
   // Earlier it used to end with  --c.visualforce.com
   // Now, it is found to work successfully when ended with --c.vf.force.com


    /* ********* Below Odd Discrepancy was found while implementing this in a Trailhead Playground ***********
    Organization oOrg = [SELECT InstanceName, IsSandbox, OrganizationType FROM Organization LIMIT 1];
    if(oOrg.OrganizationType == 'Developer Edition'){
      // Expected Format for Trailhead Playground DE Org = https://domain--c.ap4.visual.force.com
      vfOrigin = baseURL.split('.my.')[0]+'--c.'+oOrg.InstanceName.toLowercase()+'.visual.force.com';

    } else {
      // Expected Format for personal DE, Sandbox & Production Orgs = https://domain--c.visualforce.com
      vfOrigin = baseURL.split('.my.')[0]+'--c.'+'visualforce.com';
    }  */

    return vfOrigin;
    }

VF to LWC-

VF Page –

<apex:page lightningStylesheets="true" controller="POV_Controller">
    <apex:slds />
    <div class="slds-grid slds-gutters slds-p-around_medium">
        <div class="slds-col">
            <p>Message To Send to Parent LWC</p>
            <input type="text" id="vfMessage" />
            <br/>
            <button class="slds-button slds-button_outline-brand" onclick="firetoLWC()">Send to LWC</button>
            <br/>
        </div>
        <div class="slds-col">
            <p>Message Received from Parent LWC</p>
            <div id="output" class="slds-box" />
        </div>
    </div>

    <script>
         // Obtaining LEX origin URL from Apex to fire to parent & match the source upon receiving message
         var lexOrigin = '{!lexOrigin}';

        /*** EventListener to GET response from LWC  ***/
        window.addEventListener("message", function (event) {
            if (event.origin === lexOrigin) {
                var receivedfromLWC = event.data;
                var output = document.querySelector("#output");
                output.innerHTML = receivedfromLWC;
            }
        });

        /*** Method to Fire Event to LWC ***/
        function firetoLWC() {
            var message = document.getElementById('vfMessage').value;
            window.parent.postMessage(message, lexOrigin);
        }
    </script>
</apex:page>

This also uses an Apex class getter method to dynamically get the LWC origin (lexOrigin) as shown below –

public string lexOrigin {get{ 
return URL.getOrgDomainUrl().toExternalForm().split('.my.')[0]+'.lightning.force.com';
} set;}
  // Expected Format = https://domain.lightning.salesforce.com
 

 event.origin is the actual origin of the window that sent the message at the time postMessage() was called. You should always verify that the actual origin and the expected origin match, and reject the message if they don’t.

event.data is the message sent from the other window

When you send a message from a Lightning component to the iframe it wraps using contentWindow.postMessage(), there can only be one Visualforce page loaded in that contentWindow. In other words, that Visualforce page is the only place where you can set up a message event listener and get the messages sent by the Lightning component in that fashion. This is a one-to-one messaging scheme.

When you send a message from an iframed Visualforce page to its Lightning component wrapper using parent.postMessage()parent is a reference to your main window in Lightning Experience where other Lightning components may be loaded. If other Lightning components loaded in the same window object set up a message event listener, they will receive the Visualforce messages as well. This is a one-to-many messaging scheme, and it’s something to account for both when you send and receive messages. For example, you could name messages to allow Lightning components to filter incoming messages and only handle messages they are interested in.

Important Security Consideration – window.postMessage() is a standard web API that is not aware of the Lightning and Locker service namespace isolation level. As a result, there is no way to send a message to a specific namespace or to check which namespace a message is coming from. Therefore, messages sent using postMessage() should be limited to non sensitive data and should not include sensitive data such as user data or cryptographic secrets.

Using this secure and standard-based approach, you can tightly integrate Visualforce pages in Lightning Experience and support all your communication requirements: Lightning to Visualforce, Visualforce to Lightning, and even Visualforce to Visualforce inside Lightning Experience.

An Important highlight to share here is the Dynamic generation of Lightning Origin and VF Origin using Apex, which takes separate values for a sandbox, production and developer edition.

This communication is preferred when VF pages are present in an iFrame inside a Lightning Component.
If VF Pages and Lightning Components are separately present on a Lightning page, then LMS should be the preferred mode of communication.

Salesforce Blog for Reference –

https://developer.salesforce.com/blogs/developer-relations/2017/01/lightning-visualforce-communication.html

Lightning Communication with Platform Events

  • You can publish platform events using Apex, REST API, Flows, and Processes. Also, you can subscribe to platform events using Apex triggers, flows, processes, a Lightning component, and a CometD-based tool.
  • But by using platform events, you benefit from an event-based programming model and a standard way of programming across your apps.
  • Platform events enable the flow of event messages within Salesforce and to or from external apps. Apps on the Salesforce platform use an Apex method to publish events and an Apex trigger or the empApi Lightning component to consume events. As an alternative to code, you can publish events with declarative tools, such as Process Builder and Flow Builder. External apps publish events using the sObject API and consume events using CometD.
  • You can use platform events in native Salesforce platform apps and external apps alike. Use platform events in the following cases:
    • To send and receive custom event data with a predefined schema
    • To publish or subscribe to events in Apex
    • For the flexibility of publishing and processing events on and off the Salesforce platform

Let’s Jump into Code

The Installation URL or the Deployment process of the complete App LWC Comms is available in our Github Repo – https://github.com/sfwiseguys/LWCComms

Create a Platform Event

In Salesforce Setup -> Platform Events, you can define a Platform Event with the set of fields. When you create a platform event, the system appends the __e suffix to create the API name of the event. 

A platform event defined with the Publish After Commit behavior is published only after a transaction commits successfully. Define an event with this option if subscribers rely on data that the publishing transaction commits. These can be rolled back

A platform event defined with the Publish Immediately behavior is published when the publish call executes. Select this option if you want the event message to be published regardless of whether the transaction succeeds. These cannot be rolled back.

Publish Platform Event Via Apex

In below example, we use an LWC to fire a Platform Event via Imperative Apex call on a button click.
And another LWC will be subscribed to this Platform event using empApi

Here, we use publishEvent() with the parameter message to fire our created Platform event-

@AuraEnabled
    public static void publishEvent(String message) {
    POV_Platform_Event__e event = new POV_Platform_Event__e(Message__c = message);

    Database.SaveResult result = EventBus.publish(event);

    if (!result.isSuccess()) {
      for (Database.Error error : result.getErrors()) {
        System.debug('Error returned: ' +error.getStatusCode() +' - ' +error.getMessage());
      }
    }
  }

Subscribe in LWC

The lightning/empApi module provides access to methods for subscribing to a streaming channel and listening to event messages. This component requires API version 44.0 or later. The lightning/empApi module uses a shared CometD connection.

In a component’s Javascript file, import methods from the lightning/empApi module using this syntax.

Note the Comments below Suitably-

import {LightningElement} from 'lwc';
import {subscribe, unsubscribe, onError} from 'lightning/empApi';

export default class Pov_emp_lwc extends LightningElement {
    channelName = '/event/POV_Platform_Event__e';
    isSubscribeDisabled = false;
    isUnsubscribeDisabled = !this.isSubscribeDisabled;
    receivedMessage;

    subscription = {};

    // Initializes the component
    connectedCallback() {
        // Register error listener       
        this.registerErrorListener();
       
    }

    // Handles subscribe button click
    handleSubscribe() {

        // ARROW function is very important here. We have to use arrow function as it does not have its own scope
        const messageCallback = (response) => {
            this.handleResponse(response);
        }

        // Invoke subscribe method of empApi. Pass reference to messageCallback
        subscribe(this.channelName, -1, messageCallback).then(response => {
            // Response contains the subscription information on subscribe call
            console.log('Subscription request sent to: ', JSON.stringify(response.channel));
            this.subscription = response;
            this.toggleSubscribeButton(true);
        });

        
    }

    handleResponse(response){
        this.receivedMessage = response.data.payload.Message__c;
    }

    
    // Handles unsubscribe button click
    handleUnsubscribe() {
        this.toggleSubscribeButton(false);

        // Invoke unsubscribe method of empApi
        unsubscribe(this.subscription, response => {
            console.log('unsubscribe() response: ' + JSON.stringify(response));
            // Response is true for successful unsubscribe
        });
    }

    toggleSubscribeButton(enableSubscribe) {
        this.isSubscribeDisabled = enableSubscribe;
        this.isUnsubscribeDisabled = !enableSubscribe;
    }

    registerErrorListener() {
        // Invoke onError empApi method
        onError(error => {
            console.log('Received error from server: ', JSON.stringify(error));
            // Error contains the server-side error
        });
    }
}

As per this demonstration of using Platform Events for communication, a Lightning Component can subscribe and listen to the Platform Events being fired from Internal or External sources-  Apex, REST API, Flows, and Processes.

Please note – This is not the ideal prescribed way of communicating between multiple lightning components on a single page as such, but For the Components to subscribe and listen to Platform Events in the org.

Thank You and Good Luck !

Lightning Message Service – Implementation Examples

Now that Salesforce’s Latest introduction – Lightning Message Service (LMS) is generally available Summer ’20 onwards, we will be sharing how we can leverage it to communicate between Visualforce and Lightning Components (Aura & LWC) anywhere on a Lightning Page.

Features

  • LMS positions itself as the standard publish-subscribe library in the UI, enabling components in any part of the DOM to communicate.
  • Similar to Aura Application Events, communication happens between components regardless of where they are in the DOM. LMS provides a standard means for LWC to communicate with Aura Components as well as Visualforce Pages, including components in a utility bar. All interactions have to originate from the same Lightning Experience application instance — i.e. same browser tab.
  • Lightning message service is available in Lightning Experience only. You can also use Lightning message service to communicate with softphones via Open CTI.
  • Lightning Message Service is based on a new type of metadata: Lightning Message Channels. These are new lightweight, packageable components that you can create in your org and at runtime, publish and subscribe to messages on them

In this Demonstration, we have used a Single LMS Channel and based on the Subscribed Listener components on a Flexi Page, established communication between them.

Let’s Jump into the Code

The Installation URL or the Deployment process of the complete App LWC Comms is available in our Github Repo – https://github.com/sfwiseguys/LWCComms

Step 1 – Create a Message Channel

In VS Code, use the LightningMessageChannel metadata type and append it with __c. The message channel isn’t a custom object, it just uses the same suffix. To deploy a LightningMessageChannel into your org, create an SFDX project. Include the XML definition in the force-app/main/default/messageChannels/ directory. The LightningMessageChannel file name follows the format messageChannelName.messageChannel-meta.xml. To add it to your scratch org, run sfdx force:source:push. To add it to another type of org, such as a sandbox or a Developer Edition org, run sfdx force:source:deploy.

<?xml version="1.0" encoding="UTF-8"?>
<LightningMessageChannel xmlns="http://soap.sforce.com/2006/04/metadata">
    <description>This is for demonstration</description>
    <isExposed>true</isExposed>
    <masterLabel>POVMessageChannel</masterLabel>
</LightningMessageChannel>

Step 2 – Create Components to Publish to Message Channel

LWC Publisher –

import {LightningElement, track, wire} from 'lwc';
import {MessageContext, APPLICATION_SCOPE, publish} from 'lightning/messageService';
import POVMC from "@salesforce/messageChannel/POVMessageChannel__c";
export default class pov_lwc_publisher extends LightningElement {
    @track msg = '';

    // Wired message Context
    @wire(MessageContext)
    context;

    handleChange(event) {
        this.msg = event.detail.value;
    }

    handlePublish() {
            let payload = {
                source: "LWC",
                messageBody: this.msg
            };
            publish(this.context, POVMC, payload);
        
    }
}

Aura Publisher –

In Aura, we need to use  lightning:messageChannel component in the container Aura Component to fire

<lightning:messageChannel type="POVMessageChannel__c" aura:id="SENDPOVMC" scope="APPLICATION" />

and in the controller.js use the publish method –

//PUBLISHER
    handlePublish: function(component, event, helper) {
        let sendingmsg = component.get("v.message");
        const payload = {
            source: "Aura",
            messageBody: sendingmsg
        };
        component.find("SENDPOVMC").publish(payload);
    },

Visualforce Publisher –

In VF, we need to write a script and use the sforce.one.publish() method as shown below which is called on button click-

<apex:page lightningStylesheets="true">
   <h4>VF Page Publisher</h4>
    <div>
        <p>Message To Send</p>
        <input type="text" id="vfMessage" /> 
        <button class="slds-button" onclick="publishMessage()">Publish</button> 
    </div>
    <script> 
        // Load the MessageChannel token in a variable
        var POVMC = "{!$MessageChannel.POVMessageChannel__c}";
       function publishMessage() {
            const payload = {
                source: "Visualforce",
                messageBody: document.getElementById('vfMessage').value
            };
            sforce.one.publish(POVMC, payload);
        } 
    </script>
</apex:page>

Step 3 – Subscribe to a Message Channel

By default, communication over a message channel can occur only between components in an active navigation tab, an active navigation item, or a utility item. Utility items are always active. A navigation tab or item is active when it’s selected. Navigation tabs and items include:

  • Standard navigation tabs
  • Console navigation workspace tabs
  • Console navigations subtabs
  • Console navigation items

To receive messages on a message channel from anywhere in the application, use lightning:messageChannel’s optional parameter, scope. Set scope to the value “APPLICATION”

LWC Listener

The createMessageContext and releaseMessageContext functions are unique to LWC. The context object provides contextual information about the Lightning Web Components using LMS. In disconnectedCallback, we recommend calling releaseMessageContext, which will unregister any subscriptions associated with the context.

Call createMessageContext in a service component that doesn’t extend LightningElement.
In a service component, you can’t use @wire(MessageContext) to create a MessageContext object. Instead, call the createMessageContext and then, pass context into the subscribe() function. MessageContext isn’t automatically released for service components. Instead, call releaseMessageContext(context) to remove any subscriptions associated with your LWC’s MessageContext.

import {LightningElement, track} from 'lwc';
import {createMessageContext, releaseMessageContext, APPLICATION_SCOPE, subscribe, unsubscribe} from 'lightning/messageService';
import POVMC from "@salesforce/messageChannel/POVMessageChannel__c";
export default class pov_lwc_listener extends LightningElement {
    @track receivedMessage = '';
    @track subscription = null;

    // NOT Using Wired MessageContext.
    // Instead using createMessageContext,releaseMessageContext to subscribe and unsubscribe
    // @wire(MessageContext)
    // context;

    context = createMessageContext();

    handleSubscribe() { 
        if (this.subscription) {
            return;
        }
        this.context = createMessageContext();
        this.subscription = subscribe(this.context, POVMC, (message) => {
            this.handleMessage(message);
        }, {scope: APPLICATION_SCOPE});
    }

    handleMessage(event) {
        if (event) {
            let message = event.messageBody;
            let source = event.source;
            this.receivedMessage = 'Message: ' + message + '.\n \n Sent From: ' + source;
        }
    }

    handleUnsubscribe() {
        unsubscribe(this.subscription);
        this.subscription = undefined;
        releaseMessageContext(this.context);
    }

    get subscribeStatus() {
        return this.subscription ? 'TRUE' : 'FALSE';
    }
}

Aura Listener –

In Aura, we need to use  lightning:messageChannel component in the container Aura Component with scope=”APPLICATION”, which upon receiving the message, fires handleReceiveMessage() controller method

<aura:attribute type="String" name="receivedMessage" />
<lightning:messageChannel type="POVMessageChannel__c" 
aura:id="POVMC" onMessage="{!c.handleReceiveMessage}" 
 scope="APPLICATION" />

// LISTENER
    handleReceiveMessage: function (component, event, helper) {
        if (event != null) {
            const message = event.getParam('messageBody');
            const source = event.getParam('source');
            component.set("v.receivedMessage", 'Message: ' + message + '.\n\n Sent From: ' + source);
        }
    }

Visualforce Listener-

In VF, upon subscribing we need to specify the method to be called. In this example below, onMCPublished() is fired

<apex:page lightningStylesheets="true">
   <div>
        <p>Subscribe To Recieve Messages</p>
        <button onclick="subscribeMC()">Subscribe</button>
        <button onclick="unsubscribeMC()">Unsubscribe</button>
        <p>Received Message =</p>
        <div id="output” />
    </div>
    <script> 
        // Load the MessageChannel token in a variable
        var POVMC = "{!$MessageChannel.POVMessageChannel__c}";
        var subscriptionToMC;

        // SUBSCRIBE
        function subscribeMC() {
            if (!subscriptionToMC) {
                subscriptionToMC = sforce.one.subscribe(POVMC, onMCPublished, { scope: "APPLICATION" });
            }
        }
        // Handle Message upon receiving the Published message
        function onMCPublished(message) {
            var textArea = document.querySelector("#output");
            textArea.innerHTML = message ? 'Message: ' + message.messageBody + '\n \n Sent From: ' + message.source : 'no message payload';
        }
        // UNSUBSCRIBE
        function unsubscribeMC() {
            if (subscriptionToMC) {
                sforce.one.unsubscribe(subscriptionToMC);
                subscriptionToMC = null;
            }
        }
    </script>
</apex:page>

Limitations of LMS

  • LMS doesn’t currently support these Salesforce experiences and tools-
    • Salesforce Mobile app
    • Lightning Out
    • Lightning Communities
  • LMS cannot be used to communicate with VF page contained in an iframe in Aura/LWC.
  • When including message channels in packages that you publish on AppExchange, only 1GP packages are supported.
  • A message is a serializable JSON object.

Examples of data that you can pass in a message include strings, numbers, booleans, and objects. A message cannot contain functions and symbols. The lightning:messageChannel component is only available in Lightning Experience.

  • Aura Components that don’t render aren’t Supported

Lightning message service only supports Aura components that render. You can’t use lightning:messageChannel in an Aura component that uses the background utility item interface. Similarly, Aura components that use lightning:messageChannel can’t call Lightning message service methods in the init lifecycle handler because the component hasn’t rendered.

lightning:messageChannel Must Be a Child of aura:component

Contrasting LMS with PubSub

Use Lightning Message Service– To communicate between components within a single Lightning page or across multiple pages, use Lightning message service. Lightning message service communicates over a Lightning message channel, and message channels aren’t restricted to a single page. Any component in a Lightning Experience application that listens for events on a message channel updates when it receives a message. It works between Lightning web components, Aura components, and Visualforce pages in any tab or in any pop-out window in Lightning Experience. It also works across namespaces.

Use the pubsub Module– In containers that don’t support Lightning Messaging Service, use the pubsub module. You need to Download the module separately from https://github.com/developerforce/pubsub

The pubsub module restricts events to a single page. In a publish-subscribe pattern, one component publishes an event. Other components subscribe to receive and handle the event. Every component that subscribes to the event receives the event. For example, if you add two components to a Lightning page in Lightning App Builder, use the pubsub module to send events between them.

This complete App LWC Comms is available in our Github Repo – https://github.com/sfwiseguys/LWCComms

We would recommend you to get started in using LMS for Communication among components, where appropriate.

Always refer to Salesforce Documentation for further details –

https://developer.salesforce.com/docs/component-library/bundle/lightning-message-service/documentation

Thank You & Good Luck!