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

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

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.

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!