Category Archives: Salesforce

Attach Dynamic Attachment to Salesforce Email Template

Sometimes we need to attach dynamic attachment to a Salesforce email template. So, following are the steps to create dynamic attachments for an email template.

  • Create a Visualforce Page and an apex controller to generate a PDF document.
  • Create a Visualforce Page Component and an apex controller for Visualforce Email Template.

VF Page for PDF(ProposalPDF):

<apex:page controller="ProposalPDFController" applyHtmlTag="false" applyBodyTag="false" showHeader="false" sidebar="false">
    <html>
        Proposal Name:  <apex:outputText value="{!Opp.Name}" escape="false"/><br/>
        Proposal For:  <apex:outputText value="{!Opp.Account.Name}" escape="false"/><br/>
        Proposal Amount:  <apex:outputText value="{!Opp.Amount}" escape="false"/><br/>
    </html>
</apex:page>

Apex Controller for PDF VF Page(ProposalPDFController):

public class ProposalPDFController {
    
    public String OpportunityId {
        get{
            if(OpportunityId == null && ApexPages.currentPage().getParameters().get('id') != null){
                OpportunityId = ApexPages.currentPage().getParameters().get('id');
            }
            return OpportunityId;
        }
        set;
    }
    
    public Opportunity Opp {
        get{
            return [SELECT Id, Name, Account.Name, Amount  FROM Opportunity WHERE Id = :OpportunityId LIMIT 1];
        }
        set;
    }
}

VF Page Component for VF Page Email Template(ProposalAttachment):

<apex:component controller="ProposalAttachmentController" access="global">
    <apex:attribute name="oppId" description="Opportunity Record Id" assignTo="{!opportunityId}" type="Id" />
    <apex:outputText value="{!PagePDFContent}" escape="false" />
</apex:component>

Apex Controller for VF Page Component(ProposalAttachmentController):

global class ProposalAttachmentController {
    
    global String PagePDFContent{ get; set; }
    global String opportunityId{ 
        get; 
        set {
            UpdatePDFContent(value);
        } 
    }
    
    public void UpdatePDFContent(String opportunityId) {
        try {
            PageReference pageRef = Page.ProposalPDF;
            pageRef.getParameters().put('id', opportunityId);
            PagePDFContent = pageRef.getContent().toString().replace('<html style="display:none !important;">', '<html>');
        }catch(System.Exception ex){}
    }
}

Note: The Email Template can be used for Workflow Rule, Process Builder, Approval Process, Flow etc.

Lightning Custom Datatable With Pagination

Apex Class:

public class AccountController {
    
    @AuraEnabled//Get Account Records
    public static List<Account> getAccountList(Integer pageSize, Integer pageNumber){
        List<Account> accList = new List<Account>();
        ApexPages.StandardSetController ssc = new ApexPages.StandardSetController(Database.getQueryLocator('SELECT Id, Name, AccountNumber, Industry, Phone FROM Account'));
        ssc.setpagesize(pageSize);
        ssc.setPageNumber(pageNumber);
        accList = (List<Account>)ssc.getRecords();
        return accList;
    }
}

Lightning Component:

<aura:component implements="flexipage:availableForAllPageTypes,force:appHostable" controller="AccountController">
    
    <!--Declare Attributes-->
    <aura:attribute name="accList" type="Account[]"/>
    <aura:attribute name="pageNumber" type="Integer" default="1"/>
    <aura:attribute name="pageSize" type="Integer" default="10"/>
    <aura:attribute name="isLastPage" type="Boolean" default="false"/>
    <aura:attribute name="dataSize" type="Integer" default="0"/>
    <!--Declare Handler-->
    <aura:handler name="init" action="{!c.doInit}" value="{!this}"/>
    
    <!--Component Start-->
    <div class="slds-m-around--xxx-large">               
        
        <lightning:card>
            <aura:set attribute="title">
                Account
            </aura:set>
            <aura:set attribute="footer">
                <div class="slds-align_absolute-center"> 
                    <div class="slds-p-right_xx-small">
                        <lightning:button label="Prev"
                                          onclick="{!c.handlePrev}"
                                          disabled="{! v.pageNumber == 1}"
                                          variant="brand"
                                          iconName="utility:back"
                                          name="prev"/>
                    </div>
                    <span class="slds-badge slds-badge_lightest">
                        Page {!v.pageNumber} | Showing records from {! ((v.pageNumber-1)*v.pageSize)+' to '+((v.pageNumber-1)*v.pageSize+v.dataSize)}
                    </span>
                    <div class="slds-p-left_xx-small">
                        <lightning:button label="Next"
                                          disabled="{!v.isLastPage}"
                                          onclick="{!c.handleNext}" 
                                          variant="brand"
                                          iconName="utility:forward"
                                          iconPosition="right"
                                          name="next"/>
                    </div>
                </div>  
                
            </aura:set>
            <table class="slds-table slds-table_cell-buffer slds-table_bordered">
                <thead>
                    <tr class="slds-line-height_reset slds-text-title_caps">
                        <th  class="slds-is-resizable" scope="col">
                            <div class="slds-truncate" title="Name">
                                Name
                            </div>
                        </th>
                        <th  class="slds-is-resizable" scope="col">
                            <div class="slds-truncate" title="Account Number">
                                Account Number
                            </div>
                        </th>
                        <th class="slds-is-resizable" scope="col">
                            <div class="slds-truncate" title="Industry">
                                Industry
                            </div>
                        </th>
                        <th class="slds-is-resizable" scope="col">
                            <div class="slds-truncate" title="Phone">
                                Phone
                            </div>
                        </th>
                        <th scope="col">
                            <div class="slds-truncate" title="Actions">Actions</div>
                        </th>
                    </tr>
                </thead>
                <aura:if isTrue="{!v.accList.length > 0}">
                    <tbody>
                        <aura:iteration items="{!v.accList}" var="acc">
                            <tr class="slds-hint-parent">
                                
                                <th data-label="Name" scope="row">
                                    <div class="slds-truncate" title="{!acc.Name}">
                                        {!acc.Name}
                                    </div>
                                </th>
                                <td data-label="Account Number">
                                    <div class="slds-truncate" title="{!acc.AccountNumber}">{!acc.AccountNumber}</div>
                                </td>
                                <td data-label="Industry">
                                    <div class="slds-truncate" title="{!acc.Industry}">{!acc.Industry}</div>
                                </td>
                                <td data-label="Phone">
                                    <div class="slds-truncate" title="{!acc.Phone}">{!acc.Phone}</div>
                                </td>
                                <td data-label="Action">
                                    <lightning:buttonIcon name="{!acc.Id}" iconName="utility:delete" title="Delete" variant="bare" onclick="{!c.handleDeleteAccount}" alternativeText="Delete" />                                                                            
                                </td>
                                
                            </tr>
                        </aura:iteration>
                    </tbody>
                </aura:if>
            </table>
            <aura:if isTrue="{!empty(v.accList)}">
                <div class="slds-align_absolute-center">
                    No records found
                </div>
            </aura:if>
        </lightning:card>
    </div>
    <!--Component End-->
</aura:component>

Lightning JS Controller :

({
    doInit : function(component, event, helper) {        
        helper.getAccounts(component, event);
    },
    
    handleNext : function(component, event, helper) { 
        var pageNumber = component.get("v.pageNumber");
        component.set("v.pageNumber", pageNumber+1);
        helper.getAccounts(component, helper);
    },
    
    handlePrev : function(component, event, helper) {        
        var pageNumber = component.get("v.pageNumber");
        component.set("v.pageNumber", pageNumber-1);
        helper.getAccounts(component, helper);
    },
    
    handleDeleteAccount: function (component, event, helper) {
        alert('Selected Account to delete - ' + event.getSource().get("v.name"));
    },
})

Lightning JS Helper:

({
    getAccounts : function(component, event) {
        var action = component.get("c.getAccountList");
        action.setParams({
            'pageSize' : component.get("v.pageSize"),
            'pageNumber' : component.get("v.pageNumber")
        });
        action.setCallback(this,function(response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                var result = response.getReturnValue();
                if(result.length < component.get("v.pageSize") || result.length == 0){
                    component.set("v.isLastPage", true);
                } else{
                    component.set("v.isLastPage", false);
                }
                component.set("v.dataSize", result.length);
                component.set("v.accList", result);
            }
        });
        $A.enqueueAction(action);
    },
})

Subscribe to Change Events Using an Apex Trigger

We can subscribe to change events using Apex triggers. The change event trigger fires when one or a batch of change events is received. 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.

Here is an example on Lead object Change Event Trigger.

Enable Change Data Capture:

Go to Setup | Enter Change Data Capture in the Quick Find box, and click Change Data Capture | In Available Entities, select Lead object and click the > arrow |
Click Save.

Create Change Event Trigger:
Go to Developer Console
Select File | New | Apex Trigger
In the Name field, enter a name for the trigger. e.g. “LeadChangeTrigger”
From the dropdown, select the change event object for the Lead object “LeadChangeEvent”.
The trigger will be created with the after insert keyword.

trigger LeadChangeTrigger on LeadChangeEvent (after insert) {
    
    System.debug('Lead Change Event Trigger');
    
    //Iterate through each event message.
    for (LeadChangeEvent event : Trigger.New) {
        //Get event header fields
        EventBus.ChangeEventHeader header = event.ChangeEventHeader;
        
        switch on header.changeType {
            when 'CREATE'{
                //Craete logic
                System.debug('CREATE');
                break;
            }
            when 'UPDATE'{
                //Update logic
                System.debug('UPDATE');
                break;
            }
            when 'DELETE'{
                //Delete logic
                System.debug('DELETE');
                break;
            }
            when 'UNDELETE'{
                //Undelete logic
                System.debug('UNDELETE');
                break;
            }
        }
    }
}

Test Change Event Trigger:
To ensure that Salesforce record changes in a test method fire change event triggers, enable all entities for Change Data Capture by calling Test.enableChangeDataCapture() at the beginning of the test. This method enables all entities only for the test and doesn’t affect the Change Data Capture entity selections for the org.

After enabling Change Data Capture, perform some DML operations and then call the Test.getEventBus().deliver() method. The method delivers the event messages from the test event bus to the corresponding change event trigger and causes the trigger to fire.

@isTest
private class TestLeadChangeTrigger {
    
    static testmethod void testLeadChange() {
        //Enable all Change Data Capture entities for notifications.
        Test.enableChangeDataCapture();
        
        //Insert a Lead test record
        Insert new Lead(FirstName = 'Biswajeet',
                        LastName = 'Samal',
                        Company = 'Salesforce');
        //Call deliver to fire the trigger and deliver the test change event.
        Test.getEventBus().deliver();
        
        //Update Lead record
        Lead leadRecord = [SELECT Id, FirstName, LastName, Company FROM Lead LIMIT 1];
        leadRecord.Company = 'google';
        Update leadRecord;
        //Call deliver to fire the trigger for the update operation.
        Test.getEventBus().deliver();
        
        //Delete Lead record
        Delete leadRecord;
        //Call deliver to fire the trigger for the delete operation.
        Test.getEventBus().deliver();
        
        //Undelete Lead record
        Lead deletedLead = [SELECT Id, IsDeleted FROM Lead WHERE Id = :leadRecord.Id ALL ROWS];
        Undelete deletedLead;
        //Call deliver to fire the trigger for the undelete operation.
        Test.getEventBus().deliver();
    }
}  

Debug Change Event Trigger:
To enable debug logs for Change Event Trigger, we have to setup Entity Type as Automated Process then only we can view the debug log. Or we can deselect “Show My Current Log Only” checkbox in developer console to get the log in developer console.

Change Data Capture in Salesforce

A Change Data Capture event, or change event, is a notification that Salesforce sends when a change to a Salesforce record occurs as part of a create, update, delete, or undelete operation. The notification includes all new and changed fields, and header fields that contain information about the change. Change Data Capture can generate change events for all custom objects defined in your Salesforce org and a subset of standard objects.

When to use Change Data Capture:

  • 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.

Subscribe to Change Event Channel:
Salesforce offers multiple ways to subscribe to a change event channel. For external application to Salesforce, we can use Streaming API, or tools and libraries based on CometD, an open-source library that simulates push technology. Streaming API provides a subscription mechanism based on CometD.
To process data changes in Salesforce, we can write an Apex trigger for the change event. Change event triggers are called as Asynchronous Apex Trigger.

Example:
To get change notifications, we have to enable Change Data Capture for the required object. Here I have enabled the Lead Object Change Data Capture, for Change Notifications to listen to Lead record changes.

Go to Setup | Enter Change Data Capture in the Quick Find box, and click Change Data Capture | In Available Entities, select Lead object and click the > arrow |
Click Save.

Return Custom Error Messages From Apex Controller In Salesforce Lightning

Sample Code:

We can use System.AuraHandledException to return custom error message from server side apex controller to lightning component in Salesforce.

string errorMessage = 'Your Error Message';
AuraHandledException auraEx = new AuraHandledException(errorMessage);
auraEx.setMessage(errorMessage);
throw auraEx;