Thursday 26 May 2022

Setting up merge fields and sending emails using email template on the custom object through apex


SCENARIO

While working on one of the requirements for a Service sector project for a client based out of Atlanta, GA, there was a requirement to send an email using an email template where the related entity type was the custom object.

CHALLENGE

In SingleEmailMessage, if we set template-id (using setTemplateId), then target object id is required. setTargetObjectId() can accept contact, lead, user or person account id only. We cannot pass the custom object id in the parameter of setTargetObjectId() - reference link. Also, the custom object is not having any relationship to any of these standard objects.

APPROACH

With a little magical Apex hand-waving, we can indeed send emails using custom email templates.

The key thing we used here is Salesforce doesn't send an email immediately when the sendEmail() method is executed, instead, Salesforce waits for the very end of the transaction. If we roll back the transaction, Salesforce doesn't send the email at all.

Below is the text value of the email template having some merge fields that we want to populate automatically,

Hi {{{Sourcing__c.Full_Name__c}}},

Good afternoon, I hope you are having a great day, and that this email finds you well. 

I just wanted to reach out to follow up with you about your application for one of
our Entry-Level IT Career Opportunities. We are very interested in speaking with you.

Our initial phone call takes less than 10 minutes. You can reach me on my direct line,
{{{Sender.Phone}}} If for some reason I do not answer please leave a voice mail
and reply to this email. 

Regards,
{{{Sender.Signature}}}

Below is a code we used for sending emails,

public class SendEmailSourcing{
    
    public void sendEmailMessage(){
	// Fetching email template
        EmailTemplate emailTemplate = [SELECT Id, DeveloperName, Subject, HtmlValue, Body
                                       FROM EmailTemplate WHERE Name = '1st Call - Email' LIMIT 1];
									   
	// Picking a dummy contact where email is not null
        Contact con = [SELECT Id, Email FROM Contact WHERE email <> NULL LIMIT 1];    
        
        List<Messaging.SingleEmailmessage> emailMessages = new List<Messaging.SingleEmailMessage>();
        List<Messaging.SingleEmailmessage> emailMessagesToSend = new List<Messaging.SingleEmailMessage>();
        Messaging.SingleEmailmessage email = new Messaging.SingleEmailmessage();  
        
	// For every sourcing record, creating email message and adding it to the list of email messages
        for (Sourcing__c sourcing :  sourcingList) {                
		email = new Messaging.SingleEmailmessage();
		email.setTemplateId(emailTemplate.Id);
		email.setTargetObjectId(con.Id);
		email.setWhatId(sourcing.Id);
		email.setToAddresses(new List<String>{sourcing.Email__c});
		email.setTreatTargetObjectAsRecipient(false);
		email.setUseSignature(false);
		emailMessages.add(email);
	}       
        
	// Setting save point to rollback the transaction after sending email message
        Savepoint sp = Database.setSavepoint();
        Messaging.sendEmail(emailMessages);
        Database.rollback(sp);
        
	// Copying content of the each email message that we just sent using sendEmail() and rolled back
	// and sending these new messages
        for (Messaging.SingleEmailMessage singleEmail : emailMessages) {
		email = new Messaging.SingleEmailMessage();
		email.setToAddresses(singleEmail.getToAddresses());
		email.setPlainTextBody(singleEmail.getPlainTextBody());
		email.setHTMLBody(singleEmail.getHTMLBody());
		email.setSubject(singleEmail.getSubject());
		email.setWhatId(singleEmail.getWhatId());
		email.setUseSignature(false);
		email.setSaveAsActivity(true);
		emailMessagesToSend.add(email);
        }
		
        Messaging.sendEmail(emailMessagesToSend);  
    }
	
}

In the above code, first, we created email message for each sourcing record and added it to the list of email messages. Then trying to send email messages in a transaction that can be rolled back.

After rolling back the transaction, we are iterating through all the emails we just sent and are copying the content of those emails into another list of emails.

That newly created list is then used to send the emails.

CONCLUSION

Using Apex effectively and rolling back the transaction, we are able to set merge fields and send email messages using email templates where a related entity type is a custom object.

If you have any questions you can reach out our Salesforce Consulting team here.

Thursday 19 May 2022

Lightning Web Component - Making custom dual list box with same functionality as standard Lightning-dual-listbox

SCENARIO

While working on one of the requirements for a Banking sector project for a client based out of London, UK there was a requirement to make a custom-made dual list box instead of the standard lightning-dual-listbox.

CHALLENGE

The client wanted a custom dual listbox instead of a standard because they wanted to implement different functionality on each button which was not possible in the standard lightning-dual-listbox.

APPROACH

First, We created a custom dual list box having the same UI as standard lightning-dual-listbox having 2 list boxes and 4 buttons (right, left, up and down) using the code provided by the standard lightning design system.

We made sure that multiple values can be added from the available to the selected list box and vice-versa.

Refer to the below code that we used to create custom dual listbox.

customDualListBox.html

<template>
    <lightning-card title="Custom Dual List Box" icon-name="custom:custom63">
        <div class="slds-form-element slds-box" role="group" aria-labelledby="picklist-group-label">
            <span id="picklist-group-label" class="slds-form-element__label slds-form-element__legend">Select
                Options</span>
            <div class="slds-form-element__control">
                <div class="slds-dueling-list">
                    <div class="slds-assistive-text" id="drag-live-region" aria-live="assertive"></div>
                    <div class="slds-assistive-text" id="option-drag-label">Press space bar when on an item, to move it
                        within the list. Cmd/Ctrl plus left and right arrow keys, to move items between lists.</div>
                    <div class="slds-box">
                        <!-- Available Options -->
                        <div class="slds-dueling-list__column slds-border_right">
                            <span class="slds-form-element__label" id="label-107">Available Options</span>
                            <div class="slds-dueling-list__options slds-border_right">
                                <ul aria-describedby="option-drag-label" aria-labelledby="label-107"
                                    aria-multiselectable="true" class="slds-listbox slds-listbox_vertical"
                                    role="listbox">
                                    <template for:each="{availableOptions}" for:item="option">
                                        <li class="slds-listbox__item" data-value="{option.value}" key="{option.value}"
                                            onclick="{handleSelectionForAvailable}">
                                            <div data-val="{option.value}"
                                                class="slds-listbox__option slds-listbox__option_plain slds-media slds-media_small slds-media_inline">
                                                <span class="slds-media__body">
                                                    <span title="English" class="slds-truncate">{option.label}</span>
                                                </span>
                                            </div>
                                        </li>
                                    </template>
                                </ul>
                            </div>
                        </div>
                        <!-- Right left buttons -->
                        <div class="slds-dueling-list__column">
                            <button class="slds-button slds-button_icon slds-button_icon-container"
                                title="Move Selection to Second Category" onclick="{handleRightClick}">
                                <lightning-icon icon-name="utility:right" size="x-small" alternative-text="right"
                                    title="right"></lightning-icon>
                                <span class="slds-assistive-text">Move Selection to Second Category</span>
                            </button>
                            <button class="slds-button slds-button_icon slds-button_icon-container"
                                title="Move Selection to First Category" onclick="{handleLeftClick}">
                                <lightning-icon icon-name="utility:left" size="x-small" alternative-text="left"
                                    title="left"></lightning-icon>
                                <span class="slds-assistive-text">Move Selection to First Category</span>
                            </button>
                        </div>
                        <!-- Selected Options -->
                        <div class="slds-dueling-list__column">
                            <span class="slds-form-element__label" id="label-108">Selected Options</span>
                            <div class="slds-dueling-list__options">
                                <ul aria-describedby="option-drag-label" aria-labelledby="label-108"
                                    aria-multiselectable="true" class="slds-listbox slds-listbox_vertical"
                                    role="listbox">
                                    <template for:each="{selectedOptions}" for:item="option">
                                        <li class="slds-listbox__item" data-value="{option.value}" key="{option.value}"
                                            onclick="{handleSelectionForSelected}">
                                            <div data-val="{option.value}"
                                                class="slds-listbox__option slds-listbox__option_plain slds-media slds-media_small slds-media_inline">
                                                <span class="slds-media__body">
                                                    <span title="selected" class="slds-truncate">{option.label}</span>
                                                </span>
                                            </div>
                                        </li>
                                    </template>
                                </ul>
                            </div>
                        </div>
                        <!-- Up down buttons -->
                        <div class="slds-dueling-list__column">
                            <button class="slds-button slds-button_icon slds-button_icon-container"
                                title="Move Selection up" onclick="{handleUpClick}">
                                <lightning-icon icon-name="utility:up" size="x-small" alternative-text="up" title="up">
                                </lightning-icon>
                                <span class="slds-assistive-text">Move Selection up </span>
                            </button>
                            <button class="slds-button slds-button_icon slds-button_icon-container"
                                title="Move Selection down" onclick="{handleDownClick}">
                                <lightning-icon icon-name="utility:down" size="x-small" alternative-text="down"
                                    title="down"></lightning-icon>
                                <span class="slds-assistive-text">Move Selection down </span>
                            </button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </lightning-card>
</template>

customDualListBox.js

import { LightningElement, track } from "lwc";
export default class customDualListBox extends LightningElement {
    @track availableOptions = [
        { label: "Option 1", value: "o1" },
        { label: "Option 2", value: "o2" },
        { label: "Option 3", value: "o3" },
        { label: "Option 4", value: "o4" },
        { label: "Option 5", value: "o5" },
        { label: "Option 6", value: "o6" },
        { label: "Option 7", value: "o7" },
    ];

    @track selectedAvailableOptions = [];
    @track selectedOptions = [];
    @track selectedSelectedOptions = [];

    handleSelectionForAvailable(event) {
        if (this.selectedAvailableOptions.filter((option) => option === event.currentTarget.dataset.value).length) {
            this.selectedAvailableOptions = this.selectedAvailableOptions.filter((option) => option !== event.currentTarget.dataset.value);
            event.currentTarget.setAttribute("aria-selected", "false");
        } else {
            this.selectedAvailableOptions.push(event.currentTarget.dataset.value);
            event.currentTarget.setAttribute("aria-selected", "true");
        }
    }

    handleSelectionForSelected(event) {
        if (this.selectedSelectedOptions.filter((option) => option === event.currentTarget.dataset.value).length) {
            this.selectedSelectedOptions = this.selectedSelectedOptions.filter((option) => option !== event.currentTarget.dataset.value);
            event.currentTarget.setAttribute("aria-selected", "false");
        } else {
            this.selectedSelectedOptions.push(event.currentTarget.dataset.value);
            event.currentTarget.setAttribute("aria-selected", "true");
        }
    }

    handleRightClick() {
        var tempSelectedOptions = JSON.parse(JSON.stringify(this.selectedAvailableOptions));
        if (tempSelectedOptions.length) {
            for (let selectedOption of tempSelectedOptions) {
                this.selectedOptions.push(...this.availableOptions.filter((option) => option.value === selectedOption));
                this.availableOptions = this.availableOptions.filter((option) => option.value !== selectedOption);
            }
        }
        this.selectedAvailableOptions = [];
    }

    handleLeftClick() {
        var tempSelectedOptions = JSON.parse(JSON.stringify(this.selectedSelectedOptions));
        if (tempSelectedOptions.length) {
            for (let selectedOption of tempSelectedOptions) {
                this.availableOptions.push(...this.selectedOptions.filter((option) => option.value === selectedOption));
                this.selectedOptions = this.selectedOptions.filter((option) => option.value !== selectedOption);
            }
        }
        this.selectedSelectedOptions = [];
    }

    handleUpClick() {
        var tempSelectedOptions = JSON.parse(JSON.stringify(this.selectedSelectedOptions));
        if (tempSelectedOptions.length == 1) {
            for (let i = 0; i < this.selectedOptions.length; i++) {
                if (i != 0 && this.selectedOptions[i].value === tempSelectedOptions[0]) {
                    [this.selectedOptions[i], this.selectedOptions[i - 1]] = [this.selectedOptions[i - 1], this.selectedOptions[i]];
                    break;
                }
            }
        }
    }

    handleDownClick() {
        var tempSelectedOptions = JSON.parse(JSON.stringify(this.selectedSelectedOptions));
        if (tempSelectedOptions.length == 1) {
            for (let i = 0; i < this.selectedOptions.length; i++) {
                if (i != this.selectedOptions.length - 1 && this.selectedOptions[i].value === tempSelectedOptions[0]) {
                    [this.selectedOptions[i], this.selectedOptions[i + 1]] = [this.selectedOptions[i + 1], this.selectedOptions[i]];
                    break;
                }
            }
        }
    }
}

  • handleSelectionForAvailable: This function is used to select/unselect value(s) under the available options section.
  • handleSelectionForSelected: This function is used to select/unselect value(s) selected options section.
  • handleLeftClick: This function is responsible to add the selected value(s) in the selected options section and remove values from available options section.
  • handleRightClick: This function is responsible to add the selected value(s) in the available options section and remove values from selected options section.
  • handleDownClick: This function is used to move values one step down in the selected options section.
  • handleUpClick: This function is used to move values one step up in the selected options section.

customDualListBox.xml

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


OUTPUT


CONCLUSION

By using the above code, We are able to make a custom dual list box with the same functionality as the standard Lightning-dual-listbox.


If you have any questions you can reach out our Salesforce Consulting team here.

Thursday 12 May 2022

Select the Right Report Type for New Reports Smoothly

As with every Salesforce release, there are new features rolled out across the platform. With Spring ’22 there are some specific enhancements to Reports. Let’s take a look at it.

Easily Find the Right Report Type for New Reports

While creating a Report take the uncertainty out of selecting a report type. With the release of Spring ’22 enhanced report type selector, quickly access recently used report types and view the included fields and objects. Quickly identify which report types are standard and custom and hide those that you don't need.

Let's look into the Report tab

Start a new report on the Reports tab. In the Choose Report Type windows, you can still select report type categories on the left and search for report types by name in the search bar.

But now there’s much more.

Choose Report Type window

If you want to create a report which is the same as one that you have created in the previous week, but you can’t remember what report type you used. No worry! scroll through the Recently Used list to find the type that you used and when you used it.

Recently Used Reports

To see the details of the Report Type, Click on the search report or recently used report list.

Detail Panel

In the detail panel following details display the Report

  1. The Report Created By.
  2. Report Created By Others in your org.
  3. The List of Objects used in the report type.
  4. Quick Lookup in the Fields used in Report.

As Salesforce Classic, You can also hide the report type from the report type list. hidden report types do not appear in the search results and all the category-specific lists, and you can’t use them to create reports. You can see the hidden report type under the Hidden Report Type tab and available to see again if needed.

Hidden Report Types

Click Here to see the release notes on report type selector. 



If you have any questions you can reach out our Salesforce Consulting team here.

Friday 6 May 2022

reRender Parent VF page data when Child VF page in pop-up window is getting closed.

OUTLINE 
While working on one of the requirements for a Health sector solutions customer based out in Atlanta, Georgia, there was a requirement to reRender Parent Visualforce page data when another Child Visualforce page in Pop-up window is getting closed. 

CHALLENGE 
Our main challenge was to call the JavaScript function in the Parent VF page from the child VF page in a pop-up window. 

APPROACH
  • Here the requirement was to open a VF page with the click of a button that creates a Contact record for a particular account, and after creating the contact record modify the contact table with the newly created contact.
  • To implement this requirement, we used custom JavaScript function and apex:actionFunction

REFERENCE CODE

ContactList.vfp
<apex:page Controller="ContactListController" lightningStylesheets="true">
    <apex:slds />
    
    <apex:form >
        <apex:commandbutton value="Add Contact"
                 oncomplete="window.open('/apex/AddContact?id={!accountId}', '_blank', 
                            'left=500, top=100, height=500px, width=500px');" />
        <br/><br/><br/>
        <apex:pageBlock id="ContactBlock">
            <apex:pageBlockTable value="{!contactList}" var="cnt" id="ContactList">
                <apex:column value="{!cnt.Name}"/>
                <apex:column value="{!cnt.phone}"/>
                <apex:column value="{!cnt.Email}"/>
            </apex:pageBlockTable>
        </apex:pageBlock>
        <apex:actionfunction name="reRenderContactListAF" 
                             action="{!retrieveContacts}" 
                             reRender="ContactBlock,ContactList" />
        
        <script>
        
        function reRenderContactList(){
            reRenderContactListAF();
        }
        </script>
    </apex:form>
</apex:page>

  • In the above code, we used custom JavaScript and actionFunction to retrieve the newly created contact and reRender the components.

AddContact.vfp
<apex:page Controller="ContactListController" lightningStylesheets="true">
    <apex:slds />
    
    <apex:form >
    	<apex:pageBlock title="Contact Form">           
        
        <apex:pageBlockSection columns="2">
            
            <apex:inputField value="{!newContact.FirstName}"/>
            <apex:inputField value="{!newContact.LastName}"/>
            <apex:inputField value="{!newContact.Email}"/>
            <apex:inputField value="{!newContact.Phone}"/>
            
        </apex:pageBlockSection>                       
        
        <apex:pageBlockButtons location="bottom">
            
            <apex:commandButton action="{!saveContact}" value="Save"
                         onclick="this.value='Saving ...';this.disabled=true"
                         oncomplete="this.value='Save';this.disabled=false;winClose()" />

        </apex:pageBlockButtons>
        
    </apex:pageBlock>
    <script>
    	function winClose(){
            window.opener.reRenderContactList();
            window.close();
    	}
    </script>
    </apex:form>
</apex:page>

  • In the above code, winClose() function will be executed on click of Save button. And winClose() JavaScript function will call the reRenderContactList() JavaScript function of the opener VF page. 

ContactListController.apxc
public class ContactListController {   
    
    public Contact newContact{get; set;} 
    public List<Contact> contactList{get; set;}
    public string accountId{get; set;}
    
    public ContactListController(){        
        accountId = ApexPages.CurrentPage().getParameters().get('id');                    
        newContact = new Contact(AccountId=accountId);
        retrieveContacts();
    }
    
    public void retrieveContacts(){
        contactList = [select Id, Name, Email, Phone 
                       from contact where AccountId=:accountId];
    }  
    
    public void saveContact(){ 
        upsert newContact;
    }
}


HOW IT WORKS  
  • So basically, whenever the save button gets clicked on VF page in the pop-up, it calls the JavaScript function winClose() and this function calls the JavaScript function of the Parent VF page.
  • JavaScript function of Parent VF page calls the apex:actionFunction which calls the controller method on its action attribute. 
  • After completion of controller method execution, apex:actionFunction rerenders some elements in the same component.

OUTPUT




CONCLUSION 

By using <apex:actionFunction> and JavaScript function effectively, we are able to reRender the contact table with the newly created contact. 

If you have any questions you can reach out our Salesforce Consulting team here.