February 26, 2021

Channel Menu setup in Salesforce Experience Site(Community Cloud) with Pre-Chat form pre-filled and linking the Chat to Account, Contact and Case

1. Create an Apex Class to fetch currently logged in user details.

public class PreChatValuesController {
    
    @AuraEnabled
    public static user fetchUserDetails() {
        
       User u = [ SELECT Id, FirstName, LastName, Email, Phone, Account.Name
                 FROM User
                 WHERE Id =: userInfo.getUserId() ];
       return u;
        
    }

}


2. Create a Lightning Component.

HTML:
<aura:component implements="forceCommunity:availableForAllPageTypes" access="global" controller="PreChatValuesController">
    <aura:handler name="init" value="this" action="{!c.doInit}"/>
    <aura:attribute name="userInfo" type="user"/>     
    <div id="chatFName" hidden="true">
        {!v.userInfo.FirstName}
    </div>    
    <div id="chatLName" hidden="true">
        {!v.userInfo.LastName}
    </div>    
    <div id="chatEmail" hidden="true">
        {!v.userInfo.Email}
    </div>    
    <div id="chatPhone" hidden="true">
        {!v.userInfo.Phone}
    </div>    
    <div id="chatAccNm" hidden="true">
        {!v.userInfo.Account.Name}
    </div>   
</aura:component>

JavaScript:
({
        
    doInit : function(component, event, helper) {
        
    var action = component.get( "c.fetchUserDetails" );
        action.setCallback( this, function( response ) {
            var state = response.getState();
            if ( state === "SUCCESS" ) {
                
                var storeResponse = response.getReturnValue();
                component.set( "v.userInfo", storeResponse );
                
            }
        });
        $A.enqueueAction( action );
        
    }
    
})


3. Create a Static Resource with the below JavaScript code. The file should have .js extension.
Note:
Here Chat is the Menu Item name in the Channel Menu.
 

window._snapinsSnippetSettingsFile = ( function() {

    console.log( "Code from Static Resource loaded" );
    let chatFName = document.getElementById( "chatFName" ).innerHTML;
    let chatLName = document.getElementById( "chatLName" ).innerHTML;
    let chatEmail = document.getElementById( "chatEmail" ).innerHTML;
    let chatPhone = document.getElementById( "chatPhone" ).innerHTML;
    let chatAccNm = document.getElementById( "chatAccNm" ).innerHTML;
    console.log(
        'User details from Static Resource are ' +
        'FirstName - ' + chatFName +
        ', LastName - ' + chatLName +
        ', Email - ' + chatEmail +
        ', Phone - ' + chatPhone +
        ', Account Name - ' + chatAccNm
    );
    embedded_svc.menu.snippetSettingsFile = {
        Chat: {
            settings: {
                /* Pre-populating pre-chat form */
                prepopulatedPrechatFields: { "FirstName" : chatFName, "LastName" : chatLName, "Email" : chatEmail, "Phone" : chatPhone,
                                             "SuppliedCompany" : chatAccNm },
                /* Linking Chat to Account, Contact and Case */
                extraPrechatInfo : [ {
                    "entityName":"Contact",
                    "showOnCreate":true,
                    "linkToEntityName":"Case",
                    "linkToEntityField":"ContactId",
                    "saveToTranscript":"ContactId",
                    "entityFieldMaps": [ {
                        "isExactMatch":true,
                        "fieldName":"FirstName",
                        "doCreate":true,
                        "doFind":true,
                        "label":"First Name"
                    }, {
                        "isExactMatch":true,
                        "fieldName":"LastName",
                        "doCreate":true,
                        "doFind":true,
                        "label":"Last Name"
                    }, {
                        "isExactMatch":true,
                        "fieldName":"Email",
                        "doCreate":true,
                        "doFind":true,
                        "label":"Email"
                    }]
                }, {
                    "entityName":"Case",
                    "showOnCreate":true,
                    "saveToTranscript":"CaseId",
                    "entityFieldMaps": [ {
                        "isExactMatch":false,
                        "fieldName":"Subject",
                        "doCreate":true,
                        "doFind":false,
                        "label":"Subject"
                      } ]
                }, {
                    "entityName":"Account",
                    "showOnCreate":true,
                    "linkToEntityName":"Case",
                    "linkToEntityField":"AccountId",
                    "saveToTranscript":"AccountId",
                    "entityFieldMaps": [ {
                        "isExactMatch":true,
                        "fieldName":"Name",
                        "doCreate":false,
                        "doFind":true,
                        "label":"Web Company"
                      } ]
                } ]
            }
        }
    };
 
}

)();



4. Go to Channel Menu and edit the Code Settings.


5. Add the Static Resource added in Step 3. Get the name.


6. Add the Lightning component created in the Experience Builder.

 
7. Add the Channel Menu Component in the Experience Builder. Use the name from Step 5.


Output:
 

 

February 24, 2021

How to collapse all apex:pageBlockSection inside apex:repeat in Salesforce?

1. Define the id attribute for apex:pageBlock.

2. Iterate using for loop based on the list size using JavaScript and use the below to collapse by default.
twistSection(document.getElementById('{!$Component.pb}').getElementsByTagName('img')[i]);
 
Sample Code:

Visualforce Page:
<apex:page controller="AccordionController">    
    <apex:pageBlock title="Accounts and related Contacts" tabStyle="Account" id="pb">
        <apex:repeat value="{!listAccounts}" var="acc">
            <apex:pageBlockSection collapsible="true" title="{!acc.Name}">
                <apex:pageBlockSectionItem>
                    <apex:repeat value="{!acc.Contacts}" var="con">
                        {!con.FirstName} {!con.LastName}<br/>
                    </apex:repeat>
                </apex:pageBlockSectionItem>
            </apex:pageBlockSection>
        </apex:repeat>
        <!-- Collapsing all the Page Block Sections by default -->
        <script>
            console.log( 'Account List Size ' + {!listAccounts.size} );
            for ( let i = 0; i < {!listAccounts.size}; i++ ) {
                twistSection(document.getElementById('{!$Component.pb}').getElementsByTagName('img')[i]);
            }
        </script>

    </apex:pageBlock>
</apex:page>

Apex Class:
public with sharing class AccordionController {
    
    public List < Account > listAccounts {get;set;}
    
    public AccordionController() {
        
        listAccounts = [ SELECT Id, Name, Industry, ( SELECT Id, FirstName, LastName, Email FROM Contacts ) FROM Account ORDER BY Name LIMIT 10 ];
        
    }
    
}
 
Output:
 
 

How to pop up Chat after sometime using Automated Invitation in Salesforce Chat?

Use Sending Rule to pop up Chat after sometime using Automated Invitation in Salesforce Chat.
 
Customize the criteria for your sending rule to define how and when automated invitations are sent to customers. Use filter logic to fine-tune how these criteria are applied using logical operators.
 
 
If the Popup is immediately popping up, use the below link to troubleshoot.

February 23, 2021

Channel Menu setup in Salesforce Experience Site(Community Cloud) with Pre-Chat form pre-filled

1. Setup Routing Configuration.


2. Create a Queue.


3. Create a Chat Button with the Queue created in Step 2.


4. Create a Chat Deployment.


5. Create Embedded Service Deployment.
 

6. Enable "Digital Experiences " by navigating to Settings under Digital Experiences section in Setup.

7. Create a simple Site(Community). I used Partner Central template for this implementation.

8. Create a Channel Menu.


9. Set up menus for the Channel Menu.


10. Add the Channel Menu to the Experience Site.


11. Allow the Embedded Service Chat endpoints in the Experience Builder.


12. Create the below LWC for custom Pre-Chat.

HTML:
<template>
    <template if:true={backgroundImgURL}>
        <img src={backgroundImgURL}/>
    </template>
    <lightning-card title="Prechat Form">        
        <template class="slds-m-around_medium" for:each={fields} for:item="field">
            <div key={field.name}>
                <lightning-input
                    key={field.name}
                    name={field.name}
                    label={field.label}
                    value={field.value}
                    max-length={field.maxLength}
                    required={field.required}>
                </lightning-input>
            </div>
        </template>
    </lightning-card>
    <lightning-button
        label="Start Chat"
        title="Start Chat"
        onclick={handleStartChat}
        class="slds-m-left_x-small"
        variant="brand">
    </lightning-button>
</template>

JavaScript:
import BasePrechat from 'lightningsnapin/basePrechat';
import { api, wire } from 'lwc';
const FIELDS = [ 'User.FirstName', 'User.LastName', 'User.Email', 'User.Phone' ];
import Id from '@salesforce/user/Id';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { getRecord } from 'lightning/uiRecordApi';

export default class PreChatLwc extends BasePrechat  {

    @api prechatFields;
    @api backgroundImgURL;
    fields;
    namelist;
    objUser;
    error;
    userId = Id;

    @wire( getRecord, { recordId: '$userId', fields: FIELDS } )
    wiredRecord({ error, data }) {

        if ( error ) {

            let message = 'Unknown Error';

            if ( Array.isArray( error.body ) )
                message = error.body.map(e => e.message).join( ', ' );
            else if ( typeof error.body.message === 'string' )
                message = error.body.message;
                
            this.dispatchEvent(
                new ShowToastEvent( {
                    title: 'Error fetching User details',
                    message,
                    variant: 'error',
                } )
            );

        } else if ( data ) {

            console.log( 'User ' + JSON.stringify( data ) );
            this.objUser = data.fields;            
            
            this.fields = this.fields.map( field => {

                console.log( 'Field is ' + JSON.stringify( field ) );

                if ( field.name === 'FirstName' )                
                    field.value = this.objUser.FirstName.value;
                else if ( field.name === 'LastName' )                
                    field.value = this.objUser.LastName.value;
                else if ( field.name === 'Email' )                
                    field.value = this.objUser.Email.value;
                else if ( field.name === 'Phone' )                
                    field.value = this.objUser.Phone.value;
                
                console.log( 'Field after passing value is ' + JSON.stringify( field ) );
                return field;
    
            });

        }

    }

    connectedCallback() {
                
        this.fields = this.prechatFields.map( field => {

            const { label, name, value, required, maxLength } = field;
            return { label, name, value, required, maxLength };

        });
        this.namelist = this.fields.map( field => field.name );
        console.log( 'Name list is ' + this.namelist );

    }

    /*
        Focus on the first input after this component renders.
    */
    renderedCallback() {

        this.template.querySelector("lightning-input").focus();

    }

    /*
        On clicking the 'Start Chatting' button, send a chat request.
    */
    handleStartChat() {

        this.template.querySelectorAll( "lightning-input" ).forEach( input => {
            this.fields[ this.namelist.indexOf( input.name ) ].value = input.value;
        });
        if ( this.validateFields( this.fields ).valid ) {

            let isValid = true;

            for ( let field of this.fields ) {

                if ( field.required && !field.value ) {

                    isValid = false;
                    break;
                
                }

            }

            if( isValid )
                this.startChat(this.fields);
        
        } else {
            
            this.dispatchEvent(
                new ShowToastEvent( {
                    title: 'An Error occured',
                    message: 'Error while initiating the Chat. Reach out to us via Phone',
                    variant: 'error',
                } )
            );

        }

    }

}

CSS:
:host {
    display: flex;
    flex-direction: column;
}

lightning-button {
    padding-top: 1em;
}

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

13. Configure the LWC in the Embedded Service Deployment.


14. Initiate a Chat from Salesforce Digital Experience Site.


15. The pre-chat form will be auto populated based on the logged in user information.


Calling Apex method from a Quick Action in Salesforce Lightning using Aura component

Sample Code:

Apex Class:
public class AccountController {
    
    @AuraEnabled  
    public static void updateAccount( String recId ) {
        
        update new Account( Id = recId, Description = String.valueOf( System.now() ) );
        
    }
    
}

Aura Component:
<aura:component implements="force:hasRecordId,force:lightningQuickAction" controller="AccountController">
    <aura:handler name = "init" value = "{!this}" action = "{!c.onInit}"/>
</aura:component>

JavaScript Controller:
({  
    
    onInit : function( component, event, helper ) {    
        
        let action = component.get( "c.updateAccount" );  
        action.setParams({  
            recId: component.get( "v.recordId" )
        });  
        action.setCallback(this, function(response) {  
            let state = response.getState();  
            if ( state === "SUCCESS" ) {  
                
                $A.get("e.force:closeQuickAction").fire();  
                $A.get('e.force:refreshView').fire();   
                
            }  else {
                
                let showToast = $A.get( "e.force:showToast" );
                showToast.setParams({
                    title : 'Testing Toast!!!',
                    message : 'Record Not Saved due to error.' ,
                    type : 'error',
                    mode : 'sticky',
                    message : 'Some error occured'
                });
                showToast.fire();
                
            }
        });  
        $A.enqueueAction( action );         
        
    }
    
})
 
Quick Action:
 
 
Page layout configuration:
The Quick Action should be added to the "Salesforce Mobile and Lightning Experience Actions" section in the page layout.
 

Output:


February 21, 2021

How to connect Heroku Postgres from pgAdmin?

1. Install pgAdmin if you haven't installed it. 

2. Go to Resources Tab and click "Heroku Postgres".


3. Click Settings. Click Credentials.
 
4.  Select Add New Server in pgAdmin.


5. In General Tab, enter a name. Then, select Connection Tab.
 
a. Host from Heroku should be the Host Name/Address.
b. User from Heroku should be the Username.
c. Password from Heroku should be the Password.
d. Database from Heroku should be Maintenance Database.

6. Click "Save" button.

7. Find your Database and right click the Table and select Query Tool.


8. Run sample SQL like below.

 
Note:
Restart your PC if you face any issues.

February 20, 2021

How to insert data into Heroku Postgres table from Visual Studio Code and write back to Salesforce?

To connect Heroku Postgres from Visual Studio Code, check the below link.
 
To insert data into Heroku Postgres table from Visual Studio Code and write back to Salesforce, follow the below steps:
 
1. Enable  "Write database updates to Salesforce using" from the Object Mapping.


2. Sample Codes:
 
1. Inserting into case table with Subject, Origin and Priority.
INSERT INTO salesforce.case( subject, origin, priority )
VALUES ( 'test from Heroku', 'Web', 'High' );

2. Inserting into case table with Subject, Origin, Priority and OwnerId.
INSERT INTO salesforce.case( subject, origin, priority, ownerid )
VALUES ( 'test from Heroku1', 'Web', 'High', '0054x000003WYaSAAW' )
RETURNING id;
 

 
3. Check the data in Salesforce.


February 18, 2021

Collapsible and Expandable sections using Visualforce Page and Apex Class in Salesforce

Sample Code:

Visualforce Page:
<apex:page controller="AccordionController">    
    <apex:pageBlock title="Accounts and related Contacts" tabStyle="Account" id="pb">
        <apex:repeat value="{!listAccounts}" var="acc">
            <apex:pageBlockSection collapsible="true" title="{!acc.Name}">
                <apex:pageBlockSectionItem>
                    <apex:repeat value="{!acc.Contacts}" var="con">
                        {!con.FirstName} {!con.LastName}<br/>
                    </apex:repeat>
                </apex:pageBlockSectionItem>
            </apex:pageBlockSection>
        </apex:repeat>
        <!-- Collapsing all the Page Block Sections by default -->
        <script>
            console.log( 'Account List Size ' + {!listAccounts.size} );
            for ( let i = 0; i < {!listAccounts.size}; i++ ) {
                twistSection(document.getElementById('{!$Component.pb}').getElementsByTagName('img')[i]);
            }
        </script>
    </apex:pageBlock>
</apex:page>

Apex Class:
public with sharing class AccordionController {
    
    public List < Account > listAccounts {get;set;}
    
    public AccordionController() {
        
        listAccounts = [ SELECT Id, Name, Industry, ( SELECT Id, FirstName, LastName, Email FROM Contacts ) FROM Account ORDER BY Name LIMIT 10 ];
        
    }
    
}
 
Output:
 
 

February 17, 2021

How to connect Heroku Postgres from Visual Studio Code?

1. Install PostgreSQL extension in Visual Studio Code.
https://marketplace.visualstudio.com/items?itemName=ckolkman.vscode-postgres

2. Restart the Visual Studio Code.

3. Create a Folder. Open the Folder from Visual Studio Code. You can save the queries for future references here.
 
4. Go to Resources Tab in Heroku App and click "Heroku Postgres".


5. Click Settings. Click Credentials.
 
6.  Use PostgreSQL: Add Connection to connect to Heroku Postgres.

a. Host from Heroku should be the Name of the Host.
b. User from Heroku should be the Name of the User.
c. Password from Heroku should be the Password of the User.
d. Database from Heroku should be Database(though optional enter it).
e. Use Secure Connection type.

7. Use PostgreSQL: Focus on PostgreSQL view to open the query explorer.


8. Right Click on the connection and select "New Query".


Sample Query:
SELECT name, phone FROM salesforce.account LIMIT 5;

9. Select the Query. Right Click and click "Run Query".


February 15, 2021

Session not found - Session Cache not supported Salesforce

If your data is accessed by asynchronous Apex, it can’t be stored in a cache that is based on the user’s session.

The session cache isn’t available when an active session isn’t present, such as in asynchronous Apex or code called by asynchronous Apex. For example, if batch Apex causes an Apex trigger to execute, the session cache isn’t available in the trigger because the trigger runs in asynchronous context.

Use isAvailable() to determine whether the session cache is available for use. 

How to connect Heroku Postgres from Azure Data Studio?

1. Install Azure Data Studio if you haven't installed it. 

2. Install the PostgreSQL extension for Azure Data Studio.

3. Go to Resources Tab and click "Heroku Postgres".


4. Click Settings. Click Credentials.

5. In Azure Data Studio
1. Connection Type should be PostgreSQL.
2. Server Name should be Host from Heroku Postgres.
3. Authentication Type should be Password.
4. User name should be User from Heroku Postgres.
5. Password should be Password from Heroku Postgres.
6. Database name should be Database from Heroku Postgres.
 
Note:
Restart your PC if you face any issues.

February 14, 2021

Securing Lightning Web Components

1. Locker
Lightning Locker is enabled for all custom Lightning web components. LWC uses Lightning Locker, a powerful security architecture for Lightning components that enhances security by isolating Lightning components in separate namespaces. 


2. LWC offers its own built-in security features, such as sanitization of malformed HTML code and blocking Content Security Policy (CSP)-incompatible code.

Load Assets Correctly
To import a third-party JavaScript or CSS library, use the platformResourceLoader module.

Blocking Inline Script
As part of our CSP implementation, LWC applications inside of Salesforce are blocked from loading an inline script, or calling a script from a template error or event handler. 

Filtering a Dynamic Script
To aid developers with building complex applications on the Salesforce ecosystem, dynamic script evaluation through eval() is enabled.

Add Third-Party APIs to Allowlist
In order to add third-party APIs to an allowlist, you must first add them to CSP Trusted Sites. This option can be found under Setup in your Salesforce org.

Heroku Connect Setup to fetch data from Salesforce to Herokup Postgres

1. Create an Heroku App.


2. Install the Heroku Connect and Heroku Postgres.
Heroku Connect - https://elements.heroku.com/addons/herokuconnect

Heroku Postgres - https://elements.heroku.com/addons/heroku-postgresql

3. Click Heroku Connect on the Overview page.

 
4. Click Setup Connection.

 
5. Click the Database Config Vars. Fill "Enter schema name:". Click "Next" button.

6. Select Environment, Version and click "Authorize" button.

7. Login with your Salesforce Credentials and click "Allow Access".

8. Go to Mapping Tab. Click "Create Mapping".


9. Select Object.

10. Select Fields(Don't select unnecessary fields). Click "Save" button.

11. Click Explorer tab to view the data.
 

February 10, 2021

How to override Edit button in Lightning Experience with a Visualforce Page?

 1. Sample VF page for Account object.

<apex:page standardController="Account"> 
    Account Edit Page 
</apex:page>

2. Edit button in Account object.


Output:


How to get URL Parameters in Visualforce Page?

Syntax:
{!$CurrentPage.parameters.Paramtervalue}

Help Article - https://developer.salesforce.com/docs/atlas.en-us.pages.meta/pages/pages_variables_global_currentpage.htm

Sample Code:

<apex:page >
    
    {!$CurrentPage.parameters.recId}<br/>
    {!$CurrentPage.parameters.checkBool}
    
</apex:page>
 
Syntax:
 
 

How to navigate to Lightning Component Tab/Web Tab using LWC?

Sample Code:
import { api, LightningElement } from 'lwc';
import { NavigationMixin } from 'lightning/navigation';

export default class RecordPage extends NavigationMixin( LightningElement ) {

    @api recordId;

    openLightningTab() {

        console.log( 'Inside the Open Tab' );
        this[NavigationMixin.Navigate]({
            type: 'standard__navItemPage',
            attributes: {
                apiName: 'Sample_LWC'
            },
            state: {
                c__strInput: 'testing',
                c__recId: this.recordId
            }
        });

    }

}

February 9, 2021

Salesforce Formula Field for Date time difference

Sample Formula:

IF(
CONTAINS( TEXT( End_Date_Time__c - Start_Date_Time__c ), '.' ),
IF(
DATEVALUE( End_Date_Time__c ) = DATEVALUE( Start_Date_Time__c ),
'0',
MID( TEXT( End_Date_Time__c - Start_Date_Time__c ), 0, FIND( '.', TEXT( End_Date_Time__c - Start_Date_Time__c ) ) - 1 )
)
& ' Day(s) ' &
TEXT( ROUND( VALUE( MID( TEXT( End_Date_Time__c - Start_Date_Time__c ), FIND( '.', TEXT( End_Date_Time__c - Start_Date_Time__c ) ), 18 ) ) * 24, 2 ) )
& ' Hour(s)',
TEXT( End_Date_Time__c - Start_Date_Time__c ) & ' Day(s) 0 Hours'
)
 
Output: