Visualforce Notifications Lightning Style

Lightning Component/Web Component can use build in technique to display toast message
Visualforce page cannot use it, but still we can easily imitate this functionality with simply Javascript/CSS.

Following is Visual Component that used to display toast notifications in Visualforce Page.
It simply have empty div "snackbar" and JS code that set their text message + few css elements that display it with fade in/out.

I set some of the values as parameters to have different message if needed.


 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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
<apex:component access="global" controller="NotificationComponentController">
   <apex:attribute type="Integer" name="hideNotificationAfter" assignTo="{!hideAfter}" description="hide notification after X seconds" default="5000"/>
    <apex:attribute type="String" name="notificationLocation" assignTo="{!location}" description="location either top or bottom" default="top"/>
    <apex:attribute type="String" name="backgroundColor" assignTo="{!bgColor}" description="message background color" default="#706e6b"/>
    <apex:attribute type="String" name="fontColor" assignTo="{!fnColor}" description="message font color" default="#FFFFFF"/>
    <apex:attribute type="Integer" name="movementPrecentage" assignTo="{!mvmPrecentage}" description="precentage to move into the layout" default="17"/>
    
    <div id="snackbar">
        <div id="snakckbartext"></div>
        <a onclick="closeSnackbar();" class="closeNotification">&times;</a>    
    </div>
    
    
<script>
$j = jQuery.noConflict();

function closeSnackbar(){
    $j('#snackbar').removeClass('show');
}
function showNotificationVF(txt){
    $j('#snakckbartext').text(txt);
    $j('#snackbar').addClass('show');
    
    setTimeout(function(){     
        $j('#snackbar').removeClass('show');
    }, '{!hideNotificationAfter}');
}
</script>
<style>
#snackbar {
    visibility: hidden;
    {!location}: {!mvmPrecentage}%;
    align-items: center;
    position: fixed;
    color: {!fnColor};
    background: {!bgColor};
    font-weight: 300;
    border-radius: .25rem;
    margin: .5rem;
    padding: .75rem 3rem .75rem 1.5rem;
    min-width: 30rem;
    text-align: center;
    justify-content: flex-start;
    width: 50%;
    left: 25%;
    font-weight: bold;
    z-index: 999;
}

#snackbar.show {
  visibility: visible;
  -webkit-animation: fadein 0.5s ,fadeout 0.5s 2.5s;
  animation: fadein 0.5s;
}

@-webkit-keyframes fadein {
  from {{!location}: 0; opacity: 0;} 
  to {{!location}: {!mvmPrecentage}%; opacity: 1;}
}

@keyframes fadein {
  from {{!location}: 0; opacity: 0;}
  to {{!location}: {!mvmPrecentage}%; opacity: 1;}
}

@-webkit-keyframes fadeout {
  from {{!location}: {!mvmPrecentage}%; opacity: 1;} 
  to {{!location}: 0; opacity: 0;}
}

@keyframes fadeout {
  from {{!location}: {!mvmPrecentage}%; opacity: 1;}
  to {{!location}: 0; opacity: 0;}
}

.closeNotification {
    color: {!fnColor};
    float: right;
    font-size: 28px;
    font-weight: bold;
    position: absolute;
    right: 4%;
    top: 0;
}
</style>
</apex:component>

Notice that it have 3 parameters that assigned to controller variables.
The controller is must if you want to be able to set the parameters from the caller, otherwise the JS/CSS will always use the default values.


1
2
3
4
5
6
7
8
public class NotificationComponentController{

    public Integer hideAfter {get; set;}
    public String location {get; set;}
    public String bgColor {get; set;}
    public String fnColor {get; set;}
    public Integer mvmPrecentage {get; set;}
}

Finally, simple Visualforce page demo how to use it



 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
<apex:page >

<apex:includeScript value="{!URLFOR($Resource.jquery_1_11_1_min)}" />
<c:NotificationComponent notificationLocation="top" hideNotificationAfter="3000" movementPrecentage="7"/>

<apex:form >
    <apex:sectionHeader title="Notification Example"/>

    <apex:pageBlock >
        <apex:pageBlockSection>
            <apex:pageBlockSectionItem>
                <apex:inputTextarea id="textAreaId"/>
                <apex:commandButton value="Click me" onclick="showNotification(); return false;"/>
            </apex:pageBlockSectionItem>
        </apex:pageBlockSection>
    </apex:pageBlock>
</apex:form>    


<script>
$j = jQuery.noConflict();

function showNotification(){
    var txt = $j("textarea[id*='textAreaId']").val();
    showNotificationVF(txt);
}
</script>

</apex:page>






Check User Field Level Security Before DML

According to Salesforce best practice we must check the current user FLS (field level security) before running any DML action. It's sometimes annoying and in many cases in enterprise company no one really enforce it but if you want to develop something to the appexchange and go through the security review then it is must.

Following utility class/method can simplify the process and help understanding how to run this check.


 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
public with sharing class SecurityChecker {

    //Check object access and FLS
    public static set<String> isUserAllowed(String action, list<SObject> l_records){
        set<String> s_errors = new set<String>();
        
        SObjectType objType = l_records.get(0).getSObjectType();
        
        if(action == 'delete' && objType.getDescribe().isDeletable() == false){
            s_errors.add('Missing delete permission on object : ' + objType);
        }
        else if(action == 'insert'){
            if (objType.getDescribe().isCreateable() == false) {
                s_errors.add('Missing create permission on object : ' + objType);
            }
            else {
                Map<String, Schema.SObjectField> fieldMap = objType.getDescribe().fields.getMap();
                
                for (SObject soRecord : l_records) {
            
                    for (String fieldName : soRecord.getPopulatedFieldsAsMap().keySet()) {
                        if (fieldName == 'Id') continue;
                        
                        Schema.SObjectField myField = fieldMap.get(fieldName);
                        if (myField == null) continue;
                        
                        Schema.DescribeFieldResult dfr = myField.getDescribe();
                        
                        if (dfr.isCalculated() == false && dfr.isPermissionable() == true && dfr.isUpdateable() == false) {
                            s_errors.add('Missing create permission on object/field : ' + objType + '/' + myField);
                        } 
                    }
                }
            }
        }
        else if(action == 'update'){
            if (objType.getDescribe().isUpdateable() == false) {
                s_errors.add('Missing update permission on object : ' + objType);
            }
            else {
                Map<String, Schema.SObjectField> fieldMap = objType.getDescribe().fields.getMap();
                
                for (SObject soRecord : l_records) {
            
                    for (String fieldName : soRecord.getPopulatedFieldsAsMap().keySet()) {
                        if (fieldName == 'Id') continue;
                        
                        Schema.SObjectField myField = fieldMap.get(fieldName);
                        if (myField == null) continue;
                        
                        Schema.DescribeFieldResult dfr = myField.getDescribe();
                        
                        if (dfr.isCalculated() == false && dfr.isPermissionable() == true && dfr.isUpdateable() == false) {
                            s_errors.add('Missing update permission on object/field : ' + objType + '/' + myField);
                        } 
                    }
                }
            }
        }
        
        return s_errors;
    }
}



Basically, having this utility class, all you need to do is to invoke it before update.
For instance:



1
2
3
4
5
6
7
8
set<String> s_errors = SecurityChecker.isUserAllowed('update', accountList);

if(s_errors.isEmpty()){
 update accountList;
}
else{
 //missing permissions. print errors
}



Hope this is helpful 😯

Apex Trigger Framework Based on Custom Metadata

If you are using best practice, you should have up to 1 trigger per object and most of the code itself is not inside the trigger. The trigger only called apex classes that hold the logic.
However, even when using those guidelines sometimes apex triggers can have too much logic and many processes which increase the regression when adding changes in 1 of the processes.

Apex Trigger Framework can assist on this area.

The following framework provide some useful benefits:
-minimize the code inside trigger
-separation between processes
-easy debugging and error reporting
-stop/start execution from setup

It contains 3 simple components:
1.Apex class - IRunTrigger
Interface class that every process that should be running from trigger must implement.


public interface IRunTrigger {

    TriggerHandler.TriggerRunResult  run();
    
}
2.Apex class - TriggerHandler
Each trigger call the main function in this handler in order to execute the processes.


 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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
public with sharing class TriggerHandler {
    
    private static Set<String> bypassProcess = new Set<String>();
    
    private static String userName;
    private static String userProfileName;
    
    static {
        String profileId = userInfo.getProfileId();
        userProfileName = [select Id,Name from Profile where Id = :profileId limit 1].Name;
        
        userName = UserInfo.getUserName();
    }
   
    public static void runTrigger (String objectType) {
   
       String triggerType = trigger.isBefore ? 'Before ' : 'After '; 
       triggerType += trigger.isInsert ? 'Insert' : (trigger.isUpdate ? 'Update' : (trigger.isDelete ? 'Delete' : ''));
       
       System.debug('Start trigger for ' + objectType + '. Context: ' + triggerType);
       
       list<Trigger_Execution__mdt> l_mdTrigger = [select Type__c, Execution_Order__c, Class_Runner__c, Half_for_Users__c, Halt_for_Profiles__c
                                                    from Trigger_Execution__mdt 
                                                    where Object_Type__c = :objectType and Halt_Process__c = false and Type__c = :triggerType
                                                    order by Execution_Order__c];
                                                    
        list<TriggerRunResult> l_results = new list<TriggerRunResult>();
                                  
        for(Trigger_Execution__mdt mdRecord : l_mdTrigger){
            try{
                if(! isSkipHandler(mdRecord.Class_Runner__c, mdRecord.Halt_for_Profiles__c, mdRecord.Half_for_Users__c)) {
                    Type typeClass = Type.forName(mdRecord.Class_Runner__c);
                    IRunTrigger runTrigger = (IRunTrigger) typeClass.newInstance();
                    TriggerRunResult res = runTrigger.run();
                    l_results.add(res);
                }
            }
            catch(Exception ex){
                //Add general error to the error list 'Unhandle exception in class 'class name'
                System.debug('Exception during execution: ' + ex.getMessage());
            }
        }
        
        //flush all errors to DB
        for(TriggerRunResult runResult : l_results){
            if(runResult.status != 'success'){
                //report error to log list
            }
        }
    }
   
   
    public static boolean isSkipHandler(
        String handlerName,
        String excludeProfiles,
        String excludeUsers){
    
        return bypassProcess.contains(handlerName)
            || (! String.isBlank(excludeUsers) && excludeUsers.contains(userName))
            || (! String.isBlank(excludeProfiles) && excludeProfiles.contains(userProfileName));
    
    }
   
    
    
    public static void addBypass(String handlerName) {
        bypassProcess.add(handlerName);
    }
    public static void clearBypass(String handlerName) {
        bypassProcess.remove(handlerName);
    }
    
    private String getHandlerName() {
        return String.valueOf(this).substring(0,String.valueOf(this).indexOf(':'));
    }
    private String getObjectType(){
        return trigger.isDelete ? trigger.old[0].getsObjectType().getDescribe().getName() : 
                        trigger.new[0].getsObjectType().getDescribe().getName();
    }

    
    
    public class TriggerRunResult {
    
        public String status;
        public String message;

    }
}
3.Custom Metadata - Trigger Execution
Each Custom MD record represent process that should be run from trigger in specific context.




Assume that you want to implement trigger on Contact object that not allow to save if some fields are empty (this can be done also with validation rule, but I add it in code for the demo).

Then 2-3 things need to be done:
1.If there is already trigger on Contact object - skip this step, if not implement the following trigger:


trigger ContactTrigger on Contact (before insert, before update, before delete,
                                    after insert, after update, after delete) {

    TriggerHandler.runTrigger ('Contact');

}

2.Write apex class that implement the interface IRunTrigger and add your logic


 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
public with sharing class SetContactDefaultValues implements IRunTrigger {


    public TriggerHandler.TriggerRunResult run(){
    
        TriggerHandler.TriggerRunResult result = new TriggerHandler.TriggerRunResult();
        
        try{
            for(Contact contactRecord : (list<Contact>) trigger.new){
        
                if(contactRecord.Email == null){
                    contactRecord.addError('Please setup the contact email.');
                }
            }
            
            result.status = 'ok';
            result.message = 'No Errors';
        }
        catch(Exception ex){
            result.status = 'failed';
            result.message = ex.getMessage();
        }
        
        return result;
    
    }
}

3.Last thing - new custom metadata records that instruct the handler when this process should run
If the process need to run in several contexts - for example: before update and before insert - add 2 metadata records.

And that's all.
The great benefits is that your trigger code is minimal and you don't need any changes it in the trigger in future when adding/changing processes.

In addition there are few more option from the custom metadata to halt specific process.
-Halt Process checkbox to stop process completely (can also delete the metadata record in such case).
-Halt for Profiles field - stop the process for specific profiles (can add several profiles and use any separator).
-Halt for Users - to stop the process for specific users.

Last additional useful feature is to skip specific processes by adding then to the bypassprocess list in the trigger handler.
This can done using the command:
TriggerHandler.addBypass(processToSkip);










Custom Loader Image in Lightning Component

When using lightning component you can use the standard build in spinner easily. But what if you want to use custom image? As you will find out in this post, it is also quite easy.

Just follow those steps:
1.Find the image you want to use. There are plenty of free site were you can search nice gif and download the one that suite you (for example: http://icon-library.com/icon/loading-icon-animated-gif-26.html)
After downloading the file upload it in Salesforce as static resource.

2.In your lightning component add 2 things
First, add boolean variable 'showLoader'. This will be used to turn on and off the loader

 <aura:attribute name="showLoader" type="boolean" default="false" />  



Second, add section that show the loader image itself.
LoadingProgress is the name of the static resource, therefore rename it if you used different name.

 <aura:if isTrue="{!v.showLoader}">  
     <section role="dialog" aura:id="modalBlockUI" tabindex="-1" aria-labelledby="modal-heading-01" aria-modal="true" aria-describedby="modal-content-id-1" class="slds-modal slds-fade-in-open">  
       <div class="slds-modal__container">  
         <div class="slds-modal__content slds-p-around_medium" style="background-Color: transparent; border: 0;">  
           <img src="{!$Resource.LoadingProgress}" />  
         </div>  
       </div>  
           </section>      
     <div class="slds-backdrop slds-backdrop--open" aura:id="customModal-Back"></div>  
   </aura:if>  


3.Whenever you call server function from the controller, you should first set the showLoader variable as true and once complete (usually it will be in the callback) set it back to false.

For example:

 ({  
     sendOpportunityAlert : function(component, event, helper) {  
     component.set("v.showLoader", true);  
     var oppAlertAction = component.get("c.sendAlert");  
     oppAlertAction.setParams({"oppId" : component.get("v.recordId")});  
     oppAlertAction.setCallback(this, function(response){  
       component.set("v.showLoader", false);    
     });  
     $A.enqueueAction(oppAlertAction);  
      }  
 })  


In the demo example, I simply added lightning component with button 'Send Alert' on opportunity layout. Pressing the button start the loader.

Automatically Refresh Lightning Record Page

Salesforce implementers/developers often encounter a case where several processes, some of them background processes, update the data that t...