Sunday, September 22, 2024

What are events in LWC? How Communications happens in LWC?

In Lightning Web Components (LWC), events are used for communication between components, particularly for passing information or triggering actions. 

In Lightning Web Components (LWC), there are three primary approaches for communication between components using events:

  1. Parent-to-Child Communication: This allows a parent component to pass data or trigger actions in a child component via public properties or methods.

  2. Child-to-Parent Communication (Custom Events): Child components can send data or trigger actions in their parent component by dispatching custom events.

  3. Publish-Subscribe Pattern (LMS): This pattern enables communication between two components that do not have a direct parent-child relationship, using the Lightning Message Service (LMS) to broadcast and subscribe to events across the application. 

Event Phase in LWC:

Lightning web components use only the bubbling phase. Dispatching events or adding listeners to the capture phase isn't supported. Simply think of the event’s path as starting with your component and then moving to its parent, and then grandparent, and so on.

Parent-to-Child Communication:

In Lightning Web Components, data can be passed from a parent to a child component using the following two methods:

1. Public Properties: These are reactive properties defined in the child component that the parent component can set directly.


2. Public Methods: These are methods in the child component that the parent component can invoke to pass data or trigger specific behavior.


Child-to-Parent Communication (Custom Events)

Child to Parent communication is achieved by triggering custom event from child LWC and handling it parent LWC as explained in blog: https://www.sfdc-lightning.com/2020/01/how-to-pass-data-from-child-component-to-parent-component-in-lightning-web-component.html

We will see the use of Publish-Subscribe Pattern (LMS) in our next blog.

Monday, September 16, 2024

What is Shadow DOM in LWC?

In Lightning Web Components (LWC), encapsulation refers to the isolation of a component’s internal structure, styles, and behavior from the rest of the application. This is primarily achieved using the Shadow DOM, which ensures that the component’s logic remains self-contained and unaffected by external factors, allowing it to function independently.The styles defined inside a component are scoped to that component and do not affect other components or the global application.The component’s internal DOM is hidden from the outside world, ensuring that its structure and behavior cannot be directly manipulated by external code.

In the below example, Shadow DOM in Lightning Web Components (LWC) is used to encapsulate the styles and DOM structure of the parent and child components, preventing style leakage between them:

The parent component (`ParentShadowDOM`) defines a `<p>` tag with blue text and large font, which applies only to the parent due to Shadow DOM encapsulation.

The child component (`childLWCComp`) has its own `<p>` tag styled with red text and small font.

Since both components use Shadow DOM, the styles of the parent component do not affect the child component, and vice versa, ensuring complete isolation of their DOM structure and styles. This maintains a clear separation between the two components, allowing each to have its own independent styling and behavior.

parentShadowDOM.html

<template>

    <div><p>I am parent comp</p></div>

    <div>

  <c-child-lwc-comp></c-child-lwc-comp>

    </div>

</template>

parentShadowDOM.js

import { LightningElement } from 'lwc';

export default class ParentShadowDOM extends LightningElement {}

parentShadowDOM.js-meta.xml

<?xml version="1.0" encoding="UTF-8"?>

<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">

    <apiVersion>61.0</apiVersion>

    <isExposed>false</isExposed>

</LightningComponentBundle>

parentShadowDOM.css

p{

    color: blue;

    font-size: large;

}

childLWCComp.html

<template>

    <div><p>I am child comp</p></div>

</template>

childLWCComp.js

import { LightningElement } from 'lwc';


export default class ChildLwcComp extends LightningElement {}

childLWCComp.js-meta.xml

<?xml version="1.0" encoding="UTF-8"?>

<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">

    <apiVersion>61.0</apiVersion>

    <isExposed>false</isExposed>

</LightningComponentBundle>

childLWCComp.css

p{

    color: red;

    font-size: small;

}

Let us now preview this component for testing by adding it in Aura App as shown below.

<aura:application>

<c:parentShadowDOM></c:parentShadowDOM>

</aura:application>


What is Shadow DOM in LWC?

Saturday, July 27, 2024

Update Number Of Child Contacts on Account for Contact changes

 he UpdateContactCountOnAccount trigger ensures that the custom field Number_Of_Child_Contacts__c on the parent Account is updated with the correct count of associated Contacts. It runs after insert, after update, after delete, and after undelete on the Contact object. The trigger collects Account IDs for Contacts being processed, queries all related Contacts, manually counts them, and updates the custom field on the corresponding Accounts. 


trigger UpdateContactCountOnAccount on Contact (after insert, after update, after delete, after undelete) {

    Set<Id> accountIdsToUpdate = new Set<Id>();

    if (Trigger.isInsert || Trigger.isUndelete) {

        for (Contact c : Trigger.new) {

            accountIdsToUpdate.add(c.AccountId);

        }

    } else if (Trigger.isUpdate) {

        for (Contact c : Trigger.new) {

            accountIdsToUpdate.add(c.AccountId);

        }

        for (Contact c : Trigger.old) {

            accountIdsToUpdate.add(c.AccountId);

        }

    } else if (Trigger.isDelete) {

        for (Contact c : Trigger.old) {

            accountIdsToUpdate.add(c.AccountId);

        }

    }

    // Map to hold accountId and corresponding contact count

    Map<Id, Integer> accountContactCountMap = new Map<Id, Integer>();

    // Initialize the map with account IDs

    for (Id accountId : accountIdsToUpdate) {

        accountContactCountMap.put(accountId, 0);

    }

    // Query all contacts for the accounts to be updated

    List<Contact> contacts = [

        SELECT AccountId 

        FROM Contact 

        WHERE AccountId IN :accountIdsToUpdate

    ];

    // Count the number of contacts for each account

    for (Contact contact : contacts) {

        if (accountContactCountMap.containsKey(contact.AccountId)) {

            accountContactCountMap.put(contact.AccountId, accountContactCountMap.get(contact.AccountId) + 1);

        } else {

            accountContactCountMap.put(contact.AccountId, 1);

        }

    }

    // Prepare the list of accounts to be updated

    List<Account> accountsToUpdate = new List<Account>();

    for (Id accountId : accountContactCountMap.keySet()) {

        accountsToUpdate.add(new Account(Id = accountId, Number_Of_Child_Contacts__c = accountContactCountMap.get(accountId)));

    }

    // Update the accounts

    if (!accountsToUpdate.isEmpty()) {

        update accountsToUpdate;

    }

}


Trigger to prevent deletion of account if it has associated contact in salesforce

The PrevAccountDeletion trigger on the Account object is designed to prevent the deletion of any account that has associated contacts. It runs before the account deletion operation. The trigger collects the IDs of the accounts being deleted and checks for associated contacts by querying the Contact object. If any account has associated contacts, it adds an error message to the account record, stopping the deletion process. This ensures that accounts with related contacts cannot be removed, maintaining data integrity within the Salesforce system.

trigger PrevAccountDeletion on Account (before delete) {

    // Hold IDs of accounts that have associated contacts

    Set<Id> accountIdsWithContacts = new Set<Id>();

    // IDs of accounts being deleted

    Set<Id> accountIdsToDelete = Trigger.oldMap.keySet();

    // Find contacts associated with the accounts being deleted

for (Contact contact : [SELECT AccountId FROM Contact WHERE AccountId IN :accountIdsToDelete]) {

        accountIdsWithContacts.add(contact.AccountId);

    }

    // prevent deletion if it has associated contacts

    for (Account acc : Trigger.old) {

        if (accountIdsWithContacts.contains(acc.Id)) {

            acc.addError('This account has associated contacts and cannot be deleted.');

        }

    }

}

Sunday, April 21, 2024

OAuth 2.0 Web Server Flow to integrate two salesforce orgs

This flow is used to integrate an external web app with the Salesforce API, it implements the OAuth 2.0 authorization code grant type. With this flow, the server hosting the web app must be able to protect the connected app’s identity, defined by the client ID and client secret.

Throughout this topic, we'll dive into the key concepts of the Web Server Flow,  and provide a step-by-step guide on how to implement it between two Salesforce orgs say ORG A and ORG B.

By the end of this session, you'll have a clear understanding of how to leverage the Web Server Flow to create robust integrations that facilitate smooth collaboration and data sharing between Salesforce environments. Let's get started!

The below steps explain the process involved in webserver flow.

  • Request an Authorization Code
  • Authenticate the User and Grant Access to the App
  • Receive a Callback
  • Request an Access Token
  • Receive an Access Token

  • Post Request


Let's start,

ACTIVITIES WE NEED TO DO IN ORG B.

1) Create a Connected App in ORG B.

2) Create a Webservice in ORG B which will be invoked from ORG A.

Create A Connected App In ORG B:

The connected app configured above under "How to configure a Connected App for the OAuth 2.0 Client Credentials Flow?" will work for web server flow as well as we have also enabled "Require Secret for Web Server Flow" check box. The below image also highlights callback URL of ORG A.






Create A Webservice As Shown Below In ORG B:

The createAccountRecord class is an Apex REST service that allows external systems to create Account records in a Salesforce org. It is exposed as a REST resource with the URL mapping '/createAccountRecord/*', meaning it can be accessed via HTTP requests.

This service defines a POST method, createAccount(), which expects JSON data containing information about the Accounts to be created. Upon receiving a request, the method deserializes the JSON payload, creates Account records based on the provided data, and attempts to insert them into the database.

If the insertion is successful, the method returns 'Success'. Otherwise, it returns 'Failure'.

The incoming JSON payload should adhere to the structure defined by the responseWrapper inner class, which includes fields such as Name, Description, Type, and Industry for each Account record.

This REST service provides a simple yet effective means for external systems to programmatically create Account records in the Salesforce org, facilitating seamless integration and data synchronization.

@RestResource(urlMapping='/createAccountRecord/*')

   global with sharing class createAccountRecord {

     @HttpPost

      global Static string createAccount(){

      RestRequest req = RestContext.request;

      RestResponse res = Restcontext.response;

      string jsonString=req.requestBody.tostring();

      system.debug('jsonString'+jsonString);

      boolean successCheck;

      List<responseWrapper> wRespList=(List<responseWrapper>) JSON.deserialize(jsonString,List<responseWrapper>.class);

      Account obj=new Account();

      List<Account> accList= new List<Account>();

      for(responseWrapper wResp: wRespList){

      obj.Name=wResp.Name;

      obj.Description=wResp.Description;

      obj.Type=wResp.Type;

      obj.Industry=wResp.Industry;

      accList.add(obj);

      }

      try{

      if(accList.size() > 0){

      Insert accList;

      successCheck=true;

      }

      }

      catch(Exception e){

      successCheck=false;

      }

      if(successCheck)

      return 'Success';

      else

      return 'Failure';

      }

 

      global class responseWrapper{

 

       global string Name;

       global string Description;

       global string Type;

       global string Industry;

 

      }

 

   }


ACTIVITIES WE NEED TO DO IN ORG A.

Request an Authorization Code:

Hit the below URL to get the authorization code.

https://farukh-dev-ed.lightning.force.com/services/oauth2/authorize?
client_id=Client_Id_Of_ConnectedApp_From_OrgB&
redirect_uri=https://myknowndomain-dev-ed.my.salesforce.com/services/oauth2/callback&
response_type=code

If you’re not familiar with these types of calls, don’t worry. Let’s break it down into its individual components.

Component 1:

https://farukh-dev-ed.lightning.force.com/services/oauth2/authorize

This address is the Salesforce instance’s OAuth 2.0 authorization endpoint of ORG B. It’s the endpoint where your connected apps send OAuth authorization requests.

Component 2:

client_id=Client_Id_Of_ConnectedApp_From_OrgB

The client ID is the connected app’s consumer key from ORG B.

Component 3:

redirect_uri=https://myknowndomain-dev-ed.my.salesforce.com/services/oauth2/callback

The redirect URI is where users are redirected after a successful authorization. In our case it corresponds to ORG A callback URL. The redirect URI is the connected app’s callback URL, which you can also find on the connected app’s Manage Connected Apps page.

This image shows the callback URL that corresponds with the code samples.



Component 4:

response_type=code

The response type tells Salesforce which OAuth 2.0 grant type the connected app is requesting. The response type of code indicates that the connected app is requesting an authorization code.

After you successfully hit the URL, you will be asked to login into Salesforce ORG B. One you logged in with ORG B credentials you will be redirected to below URL as shown below. As you can see the below URL has auth code as highlighted.

https://myknowndomain-dev-ed.my.salesforce.com/services/oauth2/callback?code=aPrxsh*********************************************8Xg%3D%3D

Now, next step is to get the access token and do callout to ORG B to POST account creation request using the auth code obtained above.

The provided code demonstrates an integration scenario where an Apex class interacts with Salesforce REST APIs to perform various operations, such as obtaining an access token, querying data, and making callouts to another Salesforce org.

Here's a breakdown of the key functionalities:

responseWrapper Class: Defines a wrapper class to represent the response from the OAuth token request. It includes fields such as id, access_token, and instance_url.

cKey and cSecret: Variables to store the client ID and client secret obtained from a Custom Metadata Type named Store_Cred__mdt.

Connection Parameter Retrieval: Retrieves the client ID and client secret from the Store_Cred__mdt Custom Metadata Type.

Authentication Code and URI: Defines the authorization code and redirect URI required for the OAuth token request.

Request Body Construction: Constructs the request body for the OAuth token request including the grant type, client ID, client secret, authorization code, and redirect URI.

OAuth Token Request: Sends a POST request to the OAuth token endpoint to obtain an access token using the constructed request body.

Deserialization: Deserializes the response from the OAuth token request into the responseWrapper class.

API Callout: Constructs a callout request to another Salesforce org's Apex REST endpoint (createAccountRecord) to create an Account record.

Setting Headers and Body: Sets the necessary headers (Authorization and Content-Type) and request body for the callout.

Sending Callout Request: Sends the callout request to create an Account record in the target Salesforce org.

Handling Response: Checks the response status code to determine if the callout was successful.

This code demonstrates a typical integration flow where Salesforce Apex code interacts with Salesforce REST APIs to authenticate, exchange data, and perform operations across different Salesforce orgs.

Copy paste the below code in execute anonymous window in ORG A and replace the authCode with the authCode obtained above during authorization request.

public class responseWrapper {

        public string id;

        public string access_token;

        public string instance_url;

    }

public string cKey;

public string cSecret;

List < Store_Cred__mdt > connectionParam = [SELECT Id, MasterLabel, client_id__c, client_secret__c from Store_Cred__mdt];

        if(connectionParam.size() >0){

        cKey=connectionParam[0].client_id__c;

        cSecret=connectionParam[0].client_secret__c;

        }

        System.debug('Store_Cred__mdt' + connectionParam);

        String authCode= 'aPrxsh*********************************************8Xg%3D%3D';

         // The below URI is the callback URL of ORG A

        String uri='https://myknowndomain-dev-ed.my.salesforce.com/services/oauth2/callback';

        String reqBody = 'grant_type=authorization_code&client_id=' + cKey + '&client_secret=' + cSecret + '&code=' + authCode + '&redirect_uri=' + uri ;

        Http h = new Http();

        HttpRequest req = new HttpRequest();

        req.setBody(reqBody);

        req.setMethod('POST');

       // Token end point of  ORG B

        req.setEndpoint('https://farukh-dev-ed.my.salesforce.com/services/oauth2/token');

        HttpResponse hresp = h.send(req);

        System.debug('hresp is'+hresp);

        System.debug('hresp body is'+hresp.getBody());

        responseWrapper wResp = (responseWrapper) JSON.deserialize(hresp.getBody(), responseWrapper.class);

        system.debug('reqBody '+reqBody );

        system.debug('wResp'+wResp );

        system.debug('Instance url' + wResp.instance_url);

        system.debug('session id' + wResp.access_token);

 

         List<Account> accList = new List<Account>();

// End point of ORG B where we will send POST request for account record creation

string endPoint = 'https://farukh-dev-ed.my.salesforce.com'+'/services/apexrest/createAccountRecord';

             accList=[SELECT Id,Name,Description,Type,Industry from account Order By createddate DESC Limit 1];

         Http h1 = new Http();

            HttpRequest req1 = new HttpRequest();

            req1.setHeader('Authorization', 'Bearer ' + wResp.access_token);

            req1.setHeader('Content-Type', 'application/json');

            req1.setMethod('POST');

            req1.setEndpoint(endPoint);

            req1.setBody(JSON.serialize(accList));

            HttpResponse hresp1 = h1.send(req1);

            system.debug('hresp1'+hresp1 );

            system.debug('hresp1.getStatusCode()'+hresp1.getStatusCode());

             system.debug('hresp1.getBody()'+hresp1.getBody());

            if (hresp1.getStatusCode() == 200) {

                system.debug('Callout Success');

            }


After executing the above code from developer console, the below image shows the log. You can also verify the account record will be created in ORG B.


Account record created in ORG B.