Pages

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!!!


Monday, May 18, 2020

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

Salesforce introduced Change Data Capture and Asynchronous Apex Triggers in Summer 19 release. Change Data Capture publishes change events, which represents changes to Salesforce records. Changes include the creation of a new record, updates to an existing record, deletion of a record, and undeletion of a record.

Available in: both Salesforce Classic and Lightning Experience
Available in: Enterprise, Performance, Unlimited, and Developer editions


Use of Change Data Capture events to:
  • Receive notifications of Salesforce record changes, including create, update, delete, and undelete operations.
  • Capture field changes for all records.
  • Get broad access to all data regardless of sharing rules.
  • Get information about the change in the event header, such as the origin of the change, which allows ignoring changes that your client generates.
  • Perform data updates using transaction boundaries.
  • Use a versioned event schema.
  • Subscribe to mass changes in a scalable way.
  • Get access to retained events for up to three days
Supported Objects

Change events are available for all custom objects defined in your Salesforce org and a subset of standard objects. To see the complete list of supported standard objects, please click here.


Understanding the Flow for Change Data Capture

So when a record is created or updated, Change Data Capture publishes an event and a change event trigger can then process that event asynchronously. Above is a simple example to understand the flow better. Once any record completes its execution then change data publish an event and then Asynchronous Apex Trigger starts processing.


Setup Change Data Capture

First, we need to enable Change Data Capture for the object. In Setup Search for Change data Capture and open it.



Once we have enabled it, we need to subscribe to the change events. To subscribe to the change events we will be using Subscribe with Apex Triggers. But before diving to apex triggers we need to understand the naming conventions for the channels which will be subscribing to the change.


Subscription Channels

Use the subscription channel that corresponds to the change notifications you want to receive. The channel name is case-sensitive.

All Change Events
/data/ChangeEvents

A Standard Object
/data/(Standar Object Name)ChangeEvent

For example, the channel to subscribe to change events for Case records is:
/data/CaseChangeEvent

A Custom Object
/data/(Custom Object Name)__ChangeEvent

For example, the channel to subscribe to change events for Candidate__c custom object records is:
/data/Candidate__ChangeEvent

If you want to learn more about Change Data Capture, please click here.


Subscription Using Apex Triggers

The change event trigger fires when one or a batch of change events is received. The change event trigger is not like object triggers, it runs asynchronously after the database transaction is completed. The asynchronous execution makes change event triggers ideal for processing resource-intensive business logic while keeping transaction-based logic in the object trigger. Change event triggers can help reduce transaction processing time.

Below is the example:




trigger CaseChangeEventTrigger on CaseChangeEvent (after insert) {

    List<CaseChangeEvent> changes = Trigger.new;
    
    Set<String> caseIds = new Set<String>();
    
    for (CaseChangeEvent change : changes) {
        // Get all Record Ids for this change and add to the set
        List<String> recordIds = change.ChangeEventHeader.getRecordIds();
        caseIds.addAll(recordIds);
    }
    
    // Publish platform events for predicted red accounts
    List<DemoLight12__Case_Change_Event__e> caseChangeEvents = new List<DemoLight12__Case_Change_Event__e>();
    for (Case caseObj : [Select Id,CaseNumber from Case where Id in: caseIds]) {
        caseChangeEvents.add(new DemoLight12__Case_Change_Event__e(DemoLight12__CaseNumber__c=caseObj.CaseNumber));
    }
    
    System.debug('RED_ACCT: ' + caseChangeEvents);
    if (caseChangeEvents.size() > 0) {
        EventBus.publish(caseChangeEvents);
    }
}

In the above code, I have created a platform event as Case_Change_Event__e, and by using EventBus.publish method I have published an event message.

If you want to learn more about Platform Events, please click here.


Handling Platform Events in Lightning Components

The lightning/empApi module provides access to methods for subscribing to a streaming channel and listening to event messages. All streaming channels are supported, including channels for platform events, PushTopic events, generic events, and Change Data Capture events.

Note:
  • This component is available in the 44.0 API version or later.
  • This is available in Lightning Experience and only supported in desktop browsers.
Use case: I want to display a message on the Case Detail record page that particular case has been updated by others and the logged-in person is working on different cases, so on the case detail page logged in person will get a message displayed at the top which case has been updated. Below are the components I have used to achieve my use case:

  • Setup Change Data Capture on Case object explained above.
  • Create a Platform Event as Case_Change_Event__e and one custom field as Casenumber__c within the platform event object to store the CaseNumber which got updated.
  • Create an Async Apex trigger as CaseChangeEventTrigger (explained above) to publish the platform event.
  • Custom LWC component as caseChangeEvent which will subscribe to the platform event and will update the message on UI.


Below is the code for the LWC component which will be added on the Case Detail record page and will subscribe to the platform event and display the message of any records got updated.


caseChangeEvent.html


<template>
    <lightning-card title="Pinned Updates on Cases" icon-name="custom:custom14">
        <div class="slds-m-around_medium">
            {finalMessage}
        </div>
        
    </lightning-card>
</template>


caseChangeEvent.js



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

export default class EmpApiLWC extends LightningElement {
    channelName = '/event/DemoLight12__Case_Change_Event__e';
    isSubscribeDisabled = false;
    isUnsubscribeDisabled = !this.isSubscribeDisabled;

    message;

    renderedCallback() {
        const messageCallback = (response) => {
            console.log('New message received : ', JSON.stringify(response));
            // Response contains the payload of the new message received
            console.log('New message received : ', response.data.payload.DemoLight12__CaseNumber__c);
            this.message = 'Case ' + response.data.payload.DemoLight12__CaseNumber__c + ' has been updated';
        };

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

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

    get finalMessage(){
        return this.message;
    }
}

If you observe in the above code I have imported lightning/empApi which provides below method

  • subscribe - Subscribes to a given channel
  • unsubscribe - Unsubscribes from the channel using the given subscription object
  • onError - Registers a listener to errors that the server returns.
  • setDebugFlag - Set to true or false to turn console logging on or off respectively.
  • isEmpEnabled - Returns a promise that holds a Boolean value. The value is true if the EmpJs Streaming API library can be used in this context; otherwise false.
To learn more about lightning/empApi, please click here.

caseChangeEvent.js-meta.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__RecordPage</target>
   </targets>
</LightningComponentBundle>

Live Demo:



I hope this will help to understand platform events handling in lightning components. 

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