Apex Tip: Trigger Handler/Helper Framework

Apex triggers are an essential part of development in Salesforce. Since triggers can get very complicated, I’ll be sharing a framework that follows the best practices with a little twist.

To understand the basics, I recommend reading the documentation Salesforce has for apex triggers. For those that are more experienced with developing on the Salesforce platform, you know that the best practice is to have one trigger per object. Another good practice is to have a handler class that holds all of the logic for a single trigger. (More information on best practices for triggers can be found here.)

Well, I’m going to suggest one more step to enhance the Trigger Framework. My version consists of three components:

  • Object Trigger
  • Trigger Handler Class
  • Trigger Helper Class

The Explanation

The object trigger will remain as a logic-less class. All it will do is determine the type of trigger, and redirect the data to the right function in the handler class.

The handler class will also be a logic-less class. This is key to this framework setup. The purpose of the handler class is to determine the flow of the logic. In the example below, I have set up the framework for the Opportunity object. You will see two functions: one that updates the opportunity’s owner, and another that updates the opportunity’s type.

With this new framework, it’s very clear to see what exactly is happening when a new opportunity is created just by looking at the handler class. The name of the function describes what it is doing, and it will be easy to modify the flow, or disable a function. Let’s say, you need to update the type before updating the owner, just swap the two lines and done. Or, you need to stop re-assigning the opportunities, just comment out the call and done. Pretty easy, right?

The last piece is the helper class. This will hold all of the logic for the trigger. Each function should only be created for a single purpose. If a function is too complicated, or is performing multiple tasks, then it should be split up.

Now that you have all of that, let’s see an example!

The Code

OpportunityTrigger
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* Name: OpportunityTrigger
* Type: Trigger
* Description: Handles all trigger events of insert and update
*              for before and after for the Opportunity object.
*/

trigger OpportunityTrigger on Opportunity (before insert, before update, after insert, after update) {
   if(Trigger.isBefore)
   {
      if(Trigger.isInsert)
      {
         OpportunityTriggerHandler.onBeforeInsert(Trigger.new, Trigger.newMap);
      }
      else if(Trigger.isUpdate)
      {
         // make a call to OpportunityTriggerHandler here for on before update
      }
   }
   else
   {
      if(Trigger.isInsert)
      {
         // make a call to OpportunityTriggerHandler here for on after insert
      }
      else if(Trigger.isUpdate)
      {
         // make a call to OpportunityTriggerHandler here for on after update
      }
   }
}
OpportunityTriggerHandler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Name: OpportunityTriggerHandler
* Type: Class
* Description: This class is used by OpportunityTrigger to call
*              the helper functions depending on the type of
*              event that was triggered.
*/

public class OpportunityTriggerHandler
{
   public static void onBeforeInsert(List<Opportunity> newOpptys,
                                    Map<Id, Opportunity> newOpptysMap)
   {
      OpportunityTriggerHelper.matchOpptyOwnerToAcc(newOpptys);

      OpportunityTriggerHelper.setTypeBasedOnOwner(newOpptys, newOpptysMap);
   }
}
OpportunityTriggerHelper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
* Name: OpportunityTriggerHelper
* Type: Class
* Description: This class is used by OpportunityTriggerHandler
*              and holds all of the functions that will be used
*              in a trigger event.
*/


public class OpportunityTriggerHelper
{

   /* Updates the owner of the new opportunities to match
    * the owner of the account that the opportunities belong to.
    */

   public static void matchOpptyOwnerToAcc(List<Opportunity> newOpptys)
   {
      // get the account ids from the new opportunities
      Set<Id> accIds = new Set<Id>();
      for(Opportunity opp : newOpptys)
      {
         if(opp.AccountId != NULL)
            accIds.add(opp.AccountId);
      }

      // get the account records related to the new opportunities
      Map<Id, Account> accMap = new Map<Id, Account>(
                        [SELECT OwnerId FROM Account WHERE Id = :accIds]);

      for(Opportunity opp : newOpptys)
      {
         // if the oppty does not have an account, skip
         if(opp.AccountId == NULL)
            continue;

         Account acc = accMap.get(opp.AccountId);

         opp.OwnerId = acc.OwnerId;
      }
   }

   /* Sets the type of the opportunity based on the profile
    * of the user that owns the opportunity.
    */

   public static void setTypeBasedOnOwner(List<Opportunity> newOpptys)
   {
      // get the owner ids from the new opportunities
      Set<Id> ownerIds = new Set<Id>();
      for(Opportunity opp : newOpptys)
      {
         ownerIds.add(opp.OwnerId);
      }

      Map<Id, User> userMap = new Map<Id, User>(
                  [SELECT ProfileId FROM User WHERE Id = :ownerIds]);

      for(Opportunity opp : newOpptys)
      {
         User owner = userMap.get(opp.OwnerId);

         if(owner.ProfileId == CustomConstants.PartnerProfileId)
            opp.Type = 'Partners';
         else if(owner.ProfileId == CustomConstants.CSRepProfileId)
            opp.Type = 'Renewal';
         else
            opp.Type = 'New';
      }
   }
}

Quick Tip

You may have noticed that I used a class called CustomConstants. For more information on how to use that, check it out here.

As always, if you have any questions or comments, please let me know!

Apex Tip: Custom Constants

As Salesforce Developers, we have to deal with numerous business rules that apply to different profiles and roles. As a result, this may require hardcoding ids, role names, or even email addresses. At first, this may not seem like a bad idea. Ids never change, right? And how often do role names really get updated?

A couple of issues:

  • Ids are hard to read. Sure you could comment what Id you’re using, but comments are not always maintained properly. If you bring on a new developer, you want them to hit the ground running. Using self-explanatory custom constants will help with this.
  • What if you have to use something other than ids? Such as a name, or an email address which could change over time. You will have to go update a whole bunch of classes anytime the value changes. Your supervisor is going to want quick results when they ask for such a simple change.

Simply put, hardcoding isn’t a good practice as that will become difficult to maintain over time. So, what should we do then? Well, there’s a handy trick called Custom Constants. With this trick, instead of updating numerous classes, you only have to update one.

The Explanation

Now, how does this work? It’s actually very simple, you will create an Apex class called CustomConstants (or CC if you want to keep it short). Next, think of some ids or strings that you may need to hardcode. For example, id of a profile, email address, name of a role, etc.

Once you have your list, go ahead and write them all down into the class. I recommend splitting up the constants into their own groups (e.g. profile ids, role ids) so that it’s easier to read/maintain.

The “public static final” portion is important. This lets any Apex class use the value without creating an instance of the class. This will also ensure that the value of the variable never changes during code execution.

And that’s it! Take a look at the code below, and if you have any questions or comments, please let me know.

The Code

1
2
3
4
5
6
7
8
9
10
11
public class CustomConstants {
   /*** Profile Ids ***/
   public static final String SalesRepProfileId = '00e...';
   public static final String SalesRepAssistProfileId = '00e...';

   /*** Role Ids ***/
   public static final String DirectorRoleId = '00E...';

   /*** Organization-Wide Email Addresses ***/
   public static final String NoReplyEmailId = '0D2...';
}

How to Use

1
2
3
// Grab the list of accounts owned by users with the Sales Rep profile
List<Account> accs = [SELECT Id FROM Account
                     WHERE Owner.ProfileId = :CustomConstants.SalesRepProfileId];

Quick Note

Be sure to use a descriptive name for each constant. For example, Profile1Id is confusing, but SalesRepProfileId is quite informative. This will make your code a lot easier to read.

Quick Note 2

An alternative to this is to use Custom Settings. The major downside is that it requires more lines of code to access Custom Settings. Whereas the Custom Constants approach only takes 2 lines of code to use. It’s up to you though!

Display sObject Records Dynamically in a Lightning Component

Back in the good old days of Salesforce Classic, we had something called “Dynamic Visualforce Bindings.” It allowed you to display information about a record without knowing which fields to display. Unfortunately, Salesforce has yet to provide this feature for Lightning Components.

Fortunately, I have found a workaround! Though to admit, it’s not as good as what Visualforce has, but it’s better than nothing, right?

There’s quite a bit of code to go through, so I’ll be putting my explanation here. If you have any questions or comments, please let me know in the comments!

The Explanation

I’m using my approach on a custom lightning component that can be added to any page. The user can type in which object, and which fields to display in the component. The component will then load the records using the user’s input.

In this scenario, dynamic field binding would be extremely useful. However, it’s not available yet so we must put in a workaround. The workaround can be found in the Lightning Component Controller.

The workaround takes the records list, and converts each sObject record map into a list of values. The end result is a list of list of record values. The order of the values will be the same order as the list of fields provided by the user. There will be another list that holds the field labels taken from the records list.

That’s all this workaround takes! Hopefully you will find this useful for your application. For those curious, the CSS styling for the Lightning Component is from the Lightning Design System (extremely useful website to have as a bookmark).

The Code

Lightning Component
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<aura:component controller="DisplayRecordsController"
                implements="flexipage:availableForAllPageTypes"
                access="global">
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    <aura:attribute name="obj" type="String" access="global" />
    <aura:attribute name="fields" type="String" access="global" />
   
    <aura:attribute name="fieldsDisplayList" type="List" />
    <aura:attribute name="recordsList" type="List[]" />
    <aura:attribute name="errorMsg" type="String" default="" />
   
    <!-- DISPLAY ERROR IF NEEDED -->
    <div style="color:red;font-weight:bold">{!v.errorMsg}</div>
   
    <!-- BEGIN TABLE -->
    <table class="slds-table slds-table_bordered slds-table_cell-buffer">
        <!-- TABLE HEADER -->
        <thead>
            <tr class="slds-text-title_caps">
                <aura:iteration items="{!v.fieldsDisplayList}" var="field">
                    <th scope="col">
                        <div class="slds-truncate" title="{!field}">{!field}</div>
                    </th>
                </aura:iteration>
            </tr>
        </thead>
        <!-- TABLE BODY -->
        <tbody>
            <!-- FOR EACH ROW -->
            <aura:iteration items="{!v.recordsList}" var="recordList">
                <tr>
                    <!-- FOR EACH COLUMN -->
                    <aura:iteration items="{!recordList}" indexVar="i" var="data">
                        <!-- FIRST COLUMN -->
                        <aura:if isTrue="{!i == 0}">
                            <th scope="row">
                                <div class="slds-truncate" title="{!data}">
                                    {!data}
                                </div>
                            </th>
                            <!-- END FIRST COLUMN -->
                            <!-- SECOND AND ONWARD COLUMN -->
                            <aura:set attribute="else">
                                <td>
                                    <div class="slds-truncate" title="{!data}">
                                        {!data}
                                    </div>
                                </td>
                            </aura:set>
                            <!-- END SECOND AND ONWARD COLUMN -->
                        </aura:if>
                    </aura:iteration>
                </tr>
            </aura:iteration>
        </tbody>
    </table>
    <!-- END TABLE -->
</aura:component>
Lightning Component Controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
({
    doInit : function(cmp, event, helper)
    {
        var action = cmp.get("c.getRecords");
        action.setParams({
            objName : cmp.get("v.obj"),
            fieldsApiStr : cmp.get("v.fields")
        });

        // Create a callback that is executed after
        // the server-side action returns
        action.setCallback(this, function(response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                var response = response.getReturnValue();

                // If the response is null, display error message.
                // User will need to double-check API names of
                // object and fields.
                if(response == null)
                {
                    cmp.set("v.errorMsg", "Unable to retrieve records.");
                    return;
                }
               
                // If the length is zero, there are no records to
                // display.
                if(response.length == 0)
                {
                    cmp.set("v.errorMsg", "No records to display.");
                    return;
                }
               
                // This will grab the name of the fields to display.
                var fieldsDisplayList = Object.keys(response[0]);
                cmp.set("v.fieldsDisplayList", fieldsDisplayList);
               
               
                // This will build the records list.
                var recordsList = [];
               
                for(var i = 0; i < response.length; i++)
                {
                    var record = Object.values(response[i]);
                   
                    recordsList.push(record);
                }
                cmp.set("v.recordsList", recordsList);
               
            }
        });
       
        $A.enqueueAction(action);
    }
})

 

Lightning Component Design
1
2
3
4
5
6
<design:component label="Record List">
   <design:attribute name="obj" label="Object API" required="true"
                description="The API Name of the Object to display." />
   <design:attribute name="fields" label="Fields API" required="true"
                description="The list of API Name for each field to display (separated by comma)." />
</design:component>

 

Apex Controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class DisplayRecordsController {
   /* Simple function to grab records with the API names
    * of the object and the fields provided
    *
    * @objName - The API name of the object to grab records from
    * @fieldsApiStr - List of field API names to display, comma-separated
    */

   @AuraEnabled
   public static List<sObject> getRecords(String objName,
                               String fieldsApiStr)
   {
      // build the query
      String query = 'SELECT {0} FROM {1} LIMIT 10';
      List<String> queryArgs = new List<String> {
            fieldsApiStr,
            objName
         };
     
      query = String.format(query, queryArgs);
     
      // get the records
      List<sObject> records = NULL;
     
      try
      {
         records = Database.query(query);
      }
      catch(Exception ex)
      {
         system.debug('Error: '+ex.getMessage());
         return NULL;
      }
     
      return records;
   }
   
}

If you have any questions or comments, please let me know in the comments!

Time for the Midnight Donut

Hi there!

This is the first official post of the Midnight Donut blog. My goal for this blog is to be focused mostly on programming, but I may stray from the path every now and then… Which is fine, right? Got to keep things interesting somehow!

As far as programming go, I’ve been focusing on Salesforce and Python development which means my posts will be mostly focusing on that. I’m hoping to get out at least two posts a week. Stick around, and you might see a useful tip, workaround, or a post that rants about something that you also care about 😎

If everything you read so far sounds interesting to you, then great! If not, well… I hope you keep reading anyways 😀

Now then, you may have noticed a donut here or there. Well, this site is based on my love for donuts. I always seem to want one around the time of midnight. I actually stopped by my local donut shop earlier today, and got a couple of my favorites. Took them home, took some photos, and now here they are, looking wonderfully delicious on this blog. Go ahead, take a look. If you’re craving for a donut, I definitely recommend getting one 😉

I’ll go ahead and wrap this post up now since I’m sure you’re on your way out to grab a donut. Thanks for reading, and I hope you enjoy this blog!