Pages

Showing posts with label salesforce. Show all posts
Showing posts with label salesforce. Show all posts

Thursday, May 5, 2022

Understanding of Async/Await in LWC - Extension to Promise




In the last post, we discussed about Promise function How to chain Asynchronous actions in LWC using Promise? While they provide easy control to Asynchronous JavaScript, but Async/Await which builds on Promises allowing them to be used in a way that much more closely resembles synchronous JavaScript.

Async

It is simply a keyword we put in front of a function definition. Async functions are instances of the AsyncFunction constructor.

async function performAction() {
   // do some asynchronous action
   return 'result of an action';
}

An Async function always returns a Promise. That promise will contain the value that you defined.

If you look at the below code script

function start() {
    console.log('start');
    console.log(performAction());
    console.log('stop');
}

start();

You might expect the below result

start
result of an action
stop

But in reality the result will be 

start
stop
result of an action

This is because performAction() was executed asynchronously.

Now, to make the above code work like synchronously or to execute all calls in sequential order we need to use Await function.

Await

The await expression causes the async function to pause until a Promise is returned for the async function. So, in simple words we are essentially telling our code to wait for the result of the asynchronous function to complete going any further.

await always expects a Promise from the async function, and parses the result automatically.

Lets consider the above code snippet

async function performAction() {
   // do some asynchronous action
   return 'result of an action';
}

async function start() {
    console.log('start');
    console.log(await performAction());
    console.log('stop');
}

start();

Now the result will be

start
result of an action
stop

This is because we are waiting the function performAction() to be completed before proceeding further.

NOTE: await can only be used within Async function.

Usage in LWC

Problem Statement : I need to re-render the validation message whenever the user checks Another amount as a radio button, but for me the DOM is not getting rendered properly on UI. So, I need to make a wait before I check for the selected radio button and checks the validity and reset it. So, to make that happen I need to put my change action in async/await.

Below is the code snippet:

payment.html

<!--
  @description       : 
  @author            : @tandonprateek
  @group             : 
  @last modified on  : 04-15-2022
  @last modified by  : @tandonprateek
-->
<template>
	<div class="slds-grid slds-gutters slds-var-m-bottom_small">
		<div class="slds-col slds-size_1-of-3 slds-nowrap">
			{labels.component.amountToPay}
		</div>
		<div class="slds-col">
			<lightning-radio-group 
                name="amountToPay"
                label={labels.component.amountToPay} 
                variant="label-hidden"
				options={amountToPayOptions} 
                value={amountToPaySelection} 
                type="radio" 
                onclick={handleAmountToPayChange}
				onchange={handleAmountToPayChange} 
                class="slds-var-m-bottom_small requiredInput" required>
			</lightning-radio-group>
			<lightning-input 
                type="number" 
                name="anotherAmount" 
                label="Another Amount" 
                variant="label-hidden"
				value={anotherAmount} 
                min="1" onfocus={handleAnotherAmountFocus} 
                onchange={handleAnotherAmountChange}
				formatter="currency" 
                class="inputAmount anotherAmount requiredInput" required={isAnotherAmountRequired}>
			</lightning-input>
		</div>
	</div>
</template>

payment.js

/**
 * @description       : 
 * @author            : @tandonprateek
 * @group             : 
 * @last modified on  : 05-05-2022
 * @last modified by  : @tandonprateek
**/
import { LightningElement } from 'lwc';

const ANOTHER_AMOUNT_OPTION = 'anotherAmount';
const AMOUNT_DUE_TODAY_OPTION = 'amountDueToday';


export default class Payment extends LightningElement {

    labels = {
        toast: {
            processingErrorTitle: 'Processing Error'
            , validationErrorTitle: 'Input Validation Error'
            , validationErrorMessage: 'Please provide values for all required fields'
        },
        component: {
            paymentHeader: 'Payment'
            , amountToPay: 'Amount to Pay'
            , amountDueToday: 'Amount Due Today '
            , remainingBalance: 'Remaining Balance '
            , anotherAmount: 'Another Amount'
        }
    }

    amountToPaySelection;
    anotherAmount;
    isAnotherAmountRequired = false;

    connectedCallback() {
        this.amountToPaySelection = AMOUNT_DUE_TODAY_OPTION
    }

    /**
        This method will wait for the Promise to be resolved and will then reset the validity.
    **/
    async handleAmountToPayChange(event) {
        this.amountToPaySelection = event.detail.value;
        const anotherAmountField = this.template.querySelector('.anotherAmount');
        if(this.amountToPaySelection !== ANOTHER_AMOUNT_OPTION) {
            this.anotherAmount = undefined;
            await Promise.resolve();
            anotherAmountField.reportValidity();
        } else {
          anotherAmountField.focus();
          anotherAmountField.reportValidity();
        }
        this.isAnotherAmountRequired = this.amountToPaySelection === ANOTHER_AMOUNT_OPTION;
    }
    
    handleAnotherAmountChange(event) {
        const value = event.detail.value;
        this.anotherAmount = value;
    }
    
    handleAnotherAmountFocus(event) {
        this.amountToPaySelection = ANOTHER_AMOUNT_OPTION;
        this.isAnotherAmountRequired = this.amountToPaySelection === ANOTHER_AMOUNT_OPTION;
    }
    
    get amountToPayOptions() {
        const theOptions = [];
        theOptions.push({ label: this.labels.component.amountDueToday, value: AMOUNT_DUE_TODAY_OPTION});
    
        let balanceLabel = this.labels.component.remainingBalance;
    
        theOptions.push({ label: balanceLabel, value: 'remainingBalance'});
        
        theOptions.push({ label: this.labels.component.anotherAmount, value: ANOTHER_AMOUNT_OPTION });
    
        return theOptions;
    }

}




Error Handling 

To handle error we will be using old try/catch block.

async function doSomething() {
   // do something asyncronous
   return 'something';
}

async function start() {
    try {
        console.log('start');
        console.log(await performAction());
        console.log('end');
    } catch(e) {
        console.error(e);
    } finally {
        console.log('Do something no matter what');
    }
}

start();


Tuesday, April 19, 2022

How to chain Asynchronous actions in LWC using Promise?



As we are aware that Javascript can interpret only one statement at a time. With this, Javascript also doesn't face concurrency issue like other language faces while executing threads. Having said that, in Javascript, you can execute "Asynchronous Methods". These Asynchronous Methods are "Callbacks, Promises, Async/Await".


First, let's understand the difference between Callbacks and Promises before using Asynchronous actions in LWC.


Callbacks:


A callback is a function that is to be executed after another function has finished executing. Async callbacks are functions that are passed as arguments. and when that function is called it will start executing code in the background. When the background code finishes running, it calls the callback function to let you know the work is done. We use these callbacks because we want to avoid executing things out of order. If we want to wait for something in JavaScript, we need to use a callback.


Let's make Pizza from scratch using callbacks:


Synchronous Code:


 const makePizza = () => {

  const pizzaBase = makePizzaBase();

  const veggies = chopVeggies();

  const spreads = makePizzaSpreads();

  const cheese = grateCheese();

  const pizza = preparePizza(pb, veg, spread, cheese);

  return pizza;

 }

 makePizzaBase() = () => {

  //do something

 }


This synchronous makePizza() function runs in order, one function after another but what if we had a function that needed to be run first and other functions couldn't be running until after this function finishes. Let’s think of making pizza from scratch, you can’t put the veggies, spreads, and cheese on the pizza base until it’s made so you must wait until it's done. With synchronous code, it doesn’t wait it just does it. So, to fix this issue we have to Async callbacks.


Asynchronous Code:


const makePizza = () => {

  makePizzaBase(function(){

   chopVeggies(function(..arguments){

    makePizzaSpreads(function(arguments){

     grateCheese(function(arguments){

      preparePizza(){

       //pizza is ready

      }

     });

    });

   });

  });


Let’s consider the above asynchronous code which has a lot of code to execute. This code execution can cause an issue because we can have enough nested callbacks inside one another. This leads to what we call callback hell. Callback hell can puzzle the code with bugs that are illusory. For this reason, we need a way to get rid of so many nested callbacks.


Promises:


A promise is a returned object where you attach callbacks, instead of passing callbacks into a function. You can attach the callback after the successful completion of a task is called, .then(). inside this, you can pass a callback. Promises allow us to avoid callback hell by chaining multiple .then() on each other which avoids nested callbacks and a cleaner code. For the failure of completing a task, you can pass it through a .catch() block.


Let’s convert the above asynchronous callback code to Promise:


const makePizza = () => {

  return makePizzaBase()

   .then(veggies = > chopVeggies(vegetables))

   .then(pizzaSpreads => makePizzaSpreads(sauces))

   .then(cheese => grateCheese(cheese))

   .then(pizza => constructPizza());

  catch((bad pizza));

 }


In the above code, we turned our code into Promise. First, we will make the pizza base and on completion, we are returning the promise that will be passed to the next callback. Next, we did chaining on other functions that will execute one after the other in order by using .then(), making an async function. As we have observed that code looks cleaner and we avoided callback hell.


How we can use Promise in LWC:


A promise can have three stages

- Pending - Default state, not fulfilled, not rejected.

- Fulfilled - Operation completed.

- Rejected - Operation failed.


Syntax :


var promise = new Promise(function(resolve, reject){

     //do something

});

 

Example:


promiseExample.html


<!--
  @description       : 
  @author            : @tandonprateek
  @group             : 
  @last modified on  : 04-19-2022
  @last modified by  : @tandonprateek
-->
<template>
    <lightning-input type="text" label="Input1" value={val1} onchange={val1Change}></lightning-input>
    <lightning-input type="text" label="Input2" value={val2} onchange={val2Change}></lightning-input>
    <br/>
    <lightning-button variant="brand" label="Demo Promise Example" title="" onclick={executePromise} class="slds-m-left_x-small"></lightning-button>
    <div>Promise status : {status}</div>
</template>


promiseExample.js


/**
 * @description       : 
 * @author            : @tandonprateek
 * @group             : 
 * @last modified on  : 04-19-2022
 * @last modified by  : @tandonprateek
**/
import { LightningElement } from 'lwc';

export default class PromiseExample extends LightningElement {
    val1; 
    val2;
    status;

    val1Change(event) {
        this.val1= event.target.value;
    }

    val2Change(event) {
        this.val2= event.target.value;
    }
    
  promiseDemoExample(arg1, arg2) {
    return new Promise((resolve, reject) => {
      if (arg1 == arg2) resolve();
      else reject();
    });
  }

  executePromise() {
    this.promiseDemoExample(this.val1, this.val2)
      .then((result) => {
        this.status = "Resolved and Fulfilled";
      })
      .catch((result) => {
        this.status = "Rejected and Failed";
      });
  }
}


The promiseDemoExample method returns a Promise which will resolve in the future. When we call the promiseDemoExample method with 2 parameters, it will check if they are equal, if they are equal promise will resolve, or else it will reject the promise.


In executePromise method when a promise is resolved or rejected it will call either .then() method (when resolved) or .catch() method (when rejected). 


Every time a promise is resolved it will go in .then() block and when a promise is rejected it will go into .catch() block. Along with it, you can also create .finally() block which will execute no matter what happens to promise.


In .then() or .catch() method you can chain other promise as well. Using chaining, you can execute the asynchronous method in sequential order.


For Example:


executePromise() {
    this.promiseDemoExample(this.val1, this.val2)
      .then((result) => {
        //do something or add new promise
      })
      .then((result) => {
        //do something or add new promise
      })
      .catch((result) => {
        //rejected
      });
  }


In the next post, I will discuss the Async/Await methods, as these methods are extensions of Promises. So stay tuned till then.




Friday, June 5, 2020

Let's Communicate !!! Lightning Message Service | Message Channel | Salesforce

Salesforce introduced a new feature Lightning Message Service (LMS) in Winter 20 and is generally available in Summer 20. This feature allows developers to easily communicate between VF and Lightning. This feature provides a quick channel to which consumers can consume and get both updates and convenient tags/modules, so developers don't have to worry about CometD resources and/or complicated Javascript.

In this blog, I will share with you how to create an LMS channel and then write code in LWC and VF both to send messages.

Use Case: I want to display selected account record details on the VF Page, I will select the account record on the LWC component and for that account, all information will be displayed on VF Page.

Before we start writing our code, we have to create a Message Channel. As of right now, the only way to leverage this is by using Metadata API.

How we can create Message Channel?

Message Channel can be created using VS Code. You need to follow the below steps to create Message Channel.

  1. Create a folder under force-app/menu/default folder and the folder name will be "messageChannels"
  2. Once you have created the folder, under that folder you need to create a file with naming convention as "SampleMessageChannel.messageChannel-meta.xml".
  3. Once you have created the file we need to add some XML code into that file and will look something like below

<?xml version="1.0" encoding="UTF-8"?>
<LightningMessageChannel xmlns="http://soap.sforce.com/2006/04/metadata">
   <masterLabel>PassRecordId</masterLabel>
   <isExposed>true</isExposed>
   <description>This Lightning Message Channel sends information from LWC to VF.</description>
   <lightningMessageFields>
       <fieldName>recordIdToSend</fieldName>
       <description>Record Id to be send</description>
   </lightningMessageFields>
   <lightningMessageFields>
       <fieldName>objectName</fieldName>
       <description>Identifies for which object we are sending the record id</description>
   </lightningMessageFields>
</LightningMessageChannel>

Where the "lightningMessageFields" tag is used to create a parameter that we need to send via LMS. We can create as many fields as we want per requirement. In my case, I have created "recordIdToSend" as a parameter which will hold Account record Id and "objectName" will hold the object name as 'Account'.
 
    4. Now, we need to add the entry for LMS in package.xml

<types>
        <members>*</members>
        <name>LightningMessageChannel</name>
    </types>


Let's create the LWC component and VF Page to publish and subscribe to the LMS.

VF Page code

To subscribe to the LMS on the VF page we use sforce.one API.
If you see in the below code I have subscribed the channel using sforce.one.subscribe method.

<!--
  @File Name          : LMS_AccountDetails.page
  @Description        : 
  @Author             : @tandonprateek
  @Group              : 
  @Last Modified By   : @tandonprateek
  @Last Modified On   : 6/6/2020, 7:52:36 AM
  @Modification Log   : 
  Ver       Date            Author      		    Modification
  1.0    6/6/2020   @tandonprateek     Initial Version
-->

<apex:page controller="LMS_AccountDetailsController">
    <script>
        var PASSRECORDID = "{!JSENCODE($MessageChannel.PassRecordId__c)}";
        var subscriptionToMC;
        if (!subscriptionToMC) {
            subscriptionToMC = sforce.one.subscribe(PASSRECORDID, onPublished, {scope: "APPLICATION"});
} function onPublished(message) { console.log('recordId on VF page ==== ' + message.recordIdToSend); getAccountData(message.recordIdToSend); } </script> <apex:form> <apex:actionFunction name="getAccountData" action="{!fetchAccountDetailRecord}" reRender="accountDetailsBlock"> <apex:param name="recordId" value=""></apex:param> </apex:actionFunction> <apex:pageBlock id="accountDetailsBlock" title="VF Page Display Account Details"> <apex:pageBlockSection id="accountSection"> <apex:outputField value="{!accDetailObject.Name}"></apex:outputField> <apex:outputField value="{!accDetailObject.Phone}"></apex:outputField> </apex:pageBlockSection> </apex:pageBlock> </apex:form> </apex:page>

Apex Class

/**
 * @File Name          : LMS_AccountDetailsController.cls
 * @Description        : 
 * @Author             : @tandonprateek
 * @Group              : 
 * @Last Modified By   : @tandonprateek
 * @Last Modified On   : 6/6/2020, 7:53:48 AM
 * @Modification Log   : 
 * Ver       Date            Author      		    Modification
 * 1.0    6/6/2020   @tandonprateek     Initial Version
**/

public with sharing class LMS_AccountDetailsController {

    public Account accDetailObject {get;set;}

    
    /**
    * @description 
    * @author @tandonprateek | 6/6/2020 
    * @return Pagereference 
    **/
    public Pagereference fetchAccountDetailRecord(){
        String accId = Apexpages.currentPage().getParameters().get('recordId');
        accDetailObject = [Select Id, Name, Phone from Account where Id =: accId];
        return null;
    }
}


LWC Component code

<!--
   @File Name          : accountInfoComponent.html
   @Description        : 
   @Author             : @tandonprateek
   @Group              : 
   @Last Modified By   : @tandonprateek
   @Last Modified On   : 6/6/2020, 7:51:35 AM
   @Modification Log   : 
   Ver       Date            Author      		    Modification
   1.0    6/6/2020   @tandonprateek     Initial Version
   -->
<template>
   <div class="slds-page-header">
      <div class="slds-grid">
         <div class="slds-col slds-has-flexi-truncate">
            <p class="slds-text-title_caps slds-line-height_reset">LWC Component Account Info</p>
            <h1 class="slds-page-header__title slds-m-right_small slds-align-middle slds-truncate"  title="AccountInfo">Acount Info</h1>
         </div>
      </div>
   </div>
   <lightning-button label="Get All Accounts" onclick={handleClick}></lightning-button>
   <lightning-card title="Account Result Data" icon-name="custom:custom3">
      <div class="slds-m-around_medium">
         <ul>
            <template for:each={finalData} for:item="acc">
               <li key={acc.Id} >
                  <a hrefv="javascript:void(0);" data-record-id={acc.Id} onclick={handleClickonAccountName}>{acc.Name}</a>
               </li>
            </template>
         </ul>
      </div>
   </lightning-card>
</template>


To use LMS in LWC we need to first import the methods from lightning/MessageChannel library and import the message channel, in my case, I need to add the below code to my LWC js file.

import { publish,createMessageContext,releaseMessageContext, subscribe, unsubscribe } from 'lightning/messageService';
import PassRecordId from "@salesforce/messageChannel/PassRecordId__c";

Below is my LWC js file

/**
 * @File Name          : accountInfoComponent.js
 * @Description        : 
 * @Author             : @tandonprateek
 * @Group              : 
 * @Last Modified By   : @tandonprateek
 * @Last Modified On   : 6/6/2020, 7:51:50 AM
 * @Modification Log   : 
 * Ver       Date            Author             Modification
 * 1.0    6/6/2020   @tandonprateek     Initial Version
**/
import { LightningElement, track } from "lwc";
import { searchAccountByName, searchAccountById, searchAccountsByIds, fetchAccounts, fetchAllAccounts } from "c/accountService";
import { publish,createMessageContext,releaseMessageContext, subscribe, unsubscribe } from 'lightning/messageService';
import PassRecordId from "@salesforce/messageChannel/PassRecordId__c";

export default class AccountInfoComponent extends LightningElement {
    searchKey = '';
    searchKeyText = '';
    data = [];
    context = createMessageContext();

    handleClick() {
        fetchAllAccounts()
            .then(result => {
                this.data = result;
                console.log(result);
            })
            .catch(error => {
                console.log(error.message);
            });
    }

    get finalData(){
        return this.data;
    }

    handleClickonAccountName(event){
        //let recordId = event.currentTarget.getAttribute('key');
        //let recordId = event.target.value;
        let recordId = event.target.dataset.recordId;
        console.log('recordId ==== ' + recordId);
        const payload = {
            recordIdToSend: recordId,
            objectName: 'Account'
        };

        publish(this.context, PassRecordId, payload);
    }

}

Here I have used Service Components to get the account records, you can refer to my previous blog about Service Component in LWC.
Now, to publish the LMS I need to call the publish method to call the LMS channel and pass the payload to the channel.

Below is the demo:



In summary, LMS provides us a very easy and quick way to move complex data across the DOM between LWC and VF Page, allowing for better interoperability.

References:

https://releasenotes.docs.salesforce.com/en-us/winter20/release-notes/rn_lc_message_channel.htm

https://newstechnologystuff.com/2020/03/22/lightning-message-service-quick-demo/

Saturday, May 30, 2020

Useful VS Code Extensions

I, like many others, are fans of extensions which helps in boosting productivity while coding.  I am constantly looking for such plugins, settings, themes, and productive tips to enhance my coding experience.

Since I started using VS Code for Salesforce development I was looking for some cool plugins which increase my productivity and also help me to gain my coding experience.

I am guessing most people are already aware of some extensions or maybe using on a daily basis because these extensions are useable.

Therefore, I decided to create a list with some of my favorite or maybe useful VS Code extensions. So without further ado lets list out the extensions.

1. Salesforce Extension Pack

This extension is very important if you are doing Salesforce development. This tool will provide features for working with development orgs (scratch orgs, sandboxes and DE orgs), Apex, Aura components, Lightning Web Components, Visualforce.

2. Salesforce Documenter

This extension helps in facilitating proper and structured code documenting for Salesforce related files. This tool is very useful for the developers as it makes the code more readable and easy to understand by automating tasks such as adding File headers as well as Method headers. Also, this tool is very useful for lazy 😴 developers like me who usually add one-liners as method headers.

After installing this extension to VS Code, go to any Apex class and select method and right-click you will see the option as 'Generate Apex Method Header', click on it and you will get the header added automatically on the method.


Also, if you see there is another option available as 'Insert File Header', this will add the headers on the Apex class.

3. Salesforce Package.xml Generator

This extension provides a User Interface to choose metadata components for the Package.xml file for development against sandboxes or DE orgs. This is similar to the Eclipse Force.com IDE Add/Remove Metadata Components option.

4. Local History

A visual code extension for maintaining the local history of files. This extension is a savior for me many times.
This extension will help you out when you change a file or delete the file by accident 😈. Every time you make changes to a file, a copy of the old content of that file is stored in local history. With the help of this tool, at any time you can compare the existing code with any older version of the file from history.

5. Highlight Matching Tag

As the name suggests this extension highlights the matching closing tags, be it on the same line or far down the editor. This is especially useful when you have nested tags and several lines of codes to read.


In the above image as you can see this tool highlighted the start and end 'div' tag. 

6. Indented Block Highlighting

This one is my most favorite extension as it exactly does the same thing as the name suggests. This tool highlights the indented block which you are working with. In my case, it highlights the block within if


So this is my list of VS Code extensions that I use on a daily basis. Some of the extensions are not that popular but they got the job done.

Sunday, May 24, 2020

Lightning Web Component : What are Service Components & How are they Useful?

As we keep working on complex LWC applications, it’s inevitable that complexity will increase. With increased complexity in code, we will need to write a modular reusable code. In this blog, I will try to explain how we can create a reusable LWC code using Service Components.

Let's try to define What is Service Components?

A service component is a component that provides a set of functionalities in a single component. Ideally, the service should be specialized, generic, and reusable. Also, this component does not have a markup, i.e. this is not visible by default.
This helps in reducing code duplication and simplifies the component's code. This, in turn, makes the code more robust and easier to maintain.

How we can Create a Service Component?

I have taken an Account object for my example and I will be creating a service component for my Account object.

AccountService.cls as Apex Class:

public with sharing class AccountService {
    
    public AccountService() {

    }

    @AuraEnabled
    public static List<Account> searchAccountByName(String accountName) {
        List<Account> accounts = [SELECT Id, Name FROM Account WHERE Name=:accountName];
        if (accounts.size() > 0) {
            return accounts;
        }
        return null;
    }

    @AuraEnabled
    public static List<Account> fetchAccountById(String accountId) {
        List<Account> accounts = [SELECT Id, Name FROM Account WHERE Id=:accountId];
        if (accounts.size() > 0) {
            return accounts;
        }
        return null;
    }

    @AuraEnabled
    public static List<Account> fetchAccountsByIds(List<Id> accountIds) {
        List<Account> accounts = [SELECT Id, Name FROM Account WHERE Id in: accountIds];
        if (accounts.size() > 0) {
            return accounts;
        }
        return null;
    }

    @AuraEnabled
    public static List<Account> searchAccounts(String accountName) {
        String query = 'Select Id, Name from Account where Name like ' + '\'%' + accountName + '%\'';
        List<Account> accounts = Database.query(query);
        if (accounts.size() > 0) {
            return accounts;
        }
        return null;
    }

    @AuraEnabled
    public static List<Account> fetchAllAccounts(List<Id> accountIds) {
        List<Account> accounts = [SELECT Id, Name FROM Account LIMIT 10];
        if (accounts.size() > 0) {
            return accounts;
        }
        return null;
    }
}


accountService as Service Component:

import getAccountByName from "@salesforce/apex/AccountService.searchAccountByName";
import getAccountById from "@salesforce/apex/AccountService.fetchAccountById";
import getAccountsByIds from "@salesforce/apex/AccountService.fetchAccountsByIds";
import getAccounts from "@salesforce/apex/AccountService.searchAccounts";
import getAllAccounts from "@salesforce/apex/AccountService.fetchAllAccounts";

const searchAccountByName = async accountName => {
   const response = await getAccountByName({ accountName: accountName });
   return response;
};

const searchAccountById = async accountId => {
    const response = await getAccountById({ accountId: accountId });
    return response;
};

const searchAccountsByIds = async accountIds => {
    const response = await getAccountsByIds({ accountIds: accountIds });
    return response;
};

const fetchAccounts = async accountName => {
    const response = await getAccounts({ accountName: accountName });
    return response;
 };

const fetchAllAccounts = () => {
   return new Promise(resolve => {
    getAllAccounts()
         .then(data => {
            resolve(data);
         })
         .catch(error => {
            resolve(error);
         });
   });
};

export { searchAccountByName, searchAccountById, searchAccountsByIds, fetchAccounts, fetchAllAccounts };

In the above component, I have exposed the below services

  • searchAccountByName: it accepts the search key as accountName and will return the account record with the exact account name
  • searchAccountById: it accepts the search key as accountId and will return the account with the exact account id
  • searchAccountsByIds: it accepts the set of account ids and will return the list of account
  • fetchAccounts: it accepts the search key as accountName and will return the list of Account records which contains name as search keyword.
  • fetchAllAccounts: this returns the list of accounts.
You will also notice that I have followed the Singleton pattern to develop this component i.e. this component has only one instance.
Now to use the above services, I have created one component which will consume these services.

accountInfo as LWC Component:

accountInfoComponent.html

<template>
        <lightning-button label="Get All Accounts" onclick={handleClick}></lightning-button>
      
        <lightning-layout vertical-align="end" class="slds-m-bottom_small">
            <lightning-layout-item flexibility="grow">
                <lightning-input type="search" onchange={handleKeyChange} label="Search By Exact Name" value={searchKey} ></lightning-input>
            </lightning-layout-item>
            <lightning-layout-item class="slds-p-left_xx-small">
                <lightning-button label="Search By Exact Name" onclick={handleSearchAccountByName}></lightning-button>
            </lightning-layout-item>
        </lightning-layout>

        <lightning-layout vertical-align="end" class="slds-m-bottom_small">
            <lightning-layout-item flexibility="grow">
                <lightning-input type="search" onchange ={handleKeyTextChange} label="Filter By Name" value={searchKeyText} ></lightning-input>
            </lightning-layout-item>
            <lightning-layout-item class="slds-p-left_xx-small">
                <lightning-button label="Filter By Name" onclick={handleSearchAccountsByName}></lightning-button>
            </lightning-layout-item>
        </lightning-layout>

        
            <lightning-card title="Account Result Data" icon-name="custom:custom3">
                <div class="slds-m-around_medium">
                        <ul>
                            <template for:each={finalData} for:item="acc">
                                <li key={acc.Id}>{acc.Name}
                                    
                                </li>
                            </template>
                        </ul>
                </div>
            </lightning-card>

        
      </template>

accountInfoComponent.js

import { LightningElement, track } from "lwc";
import { searchAccountByName, searchAccountById, searchAccountsByIds, fetchAccounts, fetchAllAccounts } from "c/accountService";


export default class AccountInfoComponent extends LightningElement {
    searchKey = '';
    searchKeyText = '';
    data = [];


    handleKeyChange(event) {
        this.searchKey = event.target.value;
    }

    //get all the accounts
    handleClick() {
        fetchAllAccounts()
            .then(result => {
                this.data = result;
                console.log(result);
            })
            .catch(error => {
                console.log(error.message);
            });
    }

    //get Account by Name
    handleSearchAccountByName() {
        this.fetchAccountByName(this.searchKey);
    }

    async fetchAccountByName(accountName) {
        try {
            let account = await searchAccountByName(accountName);
            this.data = account;
        } catch (err) {
            console.log(err);
        }
    }

    //get Account by Id
    handleSearchAccountById() {
        this.fetchAccountById(this.accountId);
    }

    async fetchAccountById(accountId) {
        try {
            let account = await searchAccountById(accountId);
            this.data = account;
        } catch (err) {
            console.log(err);
        }
    }

    //get Accounts by Ids
    handleSearchAccountsByIds() {
        this.fetchAccountsByIds(this.accountIds);
    }

    async fetchAccountsByIds(accountIds) {
        try {
            let accounts = await searchAccountsByIds(accountIds);
            this.data = accounts;
        } catch (err) {
            console.log(err);
        }
    }

    handleKeyTextChange(event){
        this.searchKeyText = event.target.value;
    }

    //get accounts by name with a search text on name
    handleSearchAccountsByName() {
        this.fetchAccountsByName(this.searchKeyText);
    }

    async fetchAccountsByName(accountName) {
        try {
            let accounts = await fetchAccounts(accountName);
            this.data = accounts;
        } catch (err) {
            console.log(err);
        }
    }

    get finalData(){
        console.log('wwww ' + JSON.stringify(this.data))    ;
        return this.data;
    }

}

accountInfoComponent.js-met.xml:

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



Wednesday, May 20, 2020

Part II : Change Data Capture ... Learn How To Subscribe Change Data Capture to External Source

In my previous blog, I have discussed Salesforce Change Data Capture and how this feature helps to publish change events that represent changes to records. I have taken an example to display change event records or messages on the Lightning Web Component using Platform Events. If your interested to have a look at my previous blog, please feel free to visit on below link:

Understanding Change Data Capture using Asynchronous Apex Triggers and Handling Platform Event in Lightning Web Components

In this blog, we will discuss how we can subscribe to Change Data Capture events and get the changed records that we can easily import to the external source.

To explain this I will be taking MuleSoft as a middleware tool that receives the message from the Subscribe Channel from salesforce and convert the message to the required format of another tool, so I will be passing change event records to MuleSoft using Subscription Channel and I will be fetching all the changed Case records in MuleSoft.

First, we need to set up the Change Data Capture and select the object for which we need to receive notifications for record changes. For my example, I will be selecting the Case object.


Once you have selected the object and saved it, salesforce will create a Subscription Channel with a name as CaseChangeEvent. To understand more on Subscription Channel, please click here. Also, in the previous blog, I have discussed little regarding Subscription Channel and naming conventions.

We are all done from the Salesforce side, now we need to move to the Mule platform and see how we can subscribe to the streaming channel provided by Salesforce.

So, in MuleSoft we will be selecting Salesforce connector and we will be using the Subscription Channel component, to receive events from Salesforce.
We will do the required Connector Configuration and provide the Streaming channel name. In this example, I have used the 'Basic Username and Password' connection and the channel name will be '/data/CaseChangeEvent'.

Below will be the MuleSoft flow:


Configuration for Subscriber Channel component and Salesforce Connector:








Now to read the response from the subscribed channel we will be using the Transform Message component and Logger component to generate the log for the payload which we have received from the channel.


Here, you can transform the payload into JSON object or Array of an object as per your convenience or whatever format is supported by another tool where you want to import the data.



Finally, its time to see the payload which we have got in the logger.


If you see the payload carefully which we have received it gives the complete information about the record that is changed in salesforce. Please refer to the change event message structure.


{
  "data": {
    "schema": "<schema_ID>", 
    "payload": {
      "ChangeEventHeader": {
         "entityName" : "...",
         "recordIds" : "...",
         "changeType" : "...",
         "changedFields": [...],
         "changeOrigin" : "...",
         "transactionKey" : "...",
         "sequenceNumber" : "...",
         "commitTimestamp" : "...",
         "commitUser" : "...",
         "commitNumber" : "..."
      }, 
     "field1":"...",
     "field2":"...",
     . . .
    }, 
    "event": {
      "replayId": <replayID>
    }
  }, 
  "channel": "/data/<channel>"
}

Please check out the descriptions of the fields which change event message contains from the below link:
Change Event Message Structure

So instead of doing periodic exports or imports of data or repeated API, we can easily make use of Change Data Capture to update in the external system. Capturing changes with Change Data Capture event notifications ensures that your external data can be updated in real-time and stays fresh.

I hope this will help to understand how we can subscribe to the change events from Salesforce using Change Data Capture to External Source.

References:


Looking forward to everyone's suggestions and comments!!!