Tag Archives: Lightning

Handle Trigger and Validation Rule Error in Lightning Component

Sometimes we can have a requirement to display validation rule error or apex trigger error messages, on save of record in Lightning Component. We can handle those errors from apex controller and can throw messages to Lightning Component. In lightning response object though the state value comes as ERROR but the message in the error object always says ‘Internal Server Error”. Here is an example to handle the Trigger and Validation Rule Error in apex controller to show error messages in Lightning Component.

In below example I’ve used Lead object FirstName, LastName, Email & Company Field in lightning component to create record. And I’ve created a validation rule and a trigger validation on Lead object.

1. Validation rule for Company field (Company cannot be Test Company).

2. In below Trigger I’m checking if FirstName field value is “Test” then it will throw error.

trigger LeadTrigger on Lead (before insert, before update) {
    
    for(Lead obj :Trigger.new){
        if(obj.FirstName == 'Test'){
            obj.FirstName.addError('First name cannot be test');
        }
    }
}

Apex Controller:

public class SampleAuraController {
    
    @AuraEnabled
    Public static void createLead(Lead objLead){
        String msg = '';
        try{
            //Insert Lead Record
            insert objLead; 
            
        }catch(DmlException e){
            //Any type of Validation Rule error message, Required field missing error message, Trigger error message etc..
            //we can get from DmlException
            
            //Get All DML Messages
            for (Integer i = 0; i < e.getNumDml(); i++) {
                //Get Validation Rule & Trigger Error Messages
                msg =+ e.getDmlMessage(i) +  '\n' ;
            }
            //throw DML exception message
            throw new AuraHandledException(msg);
            
        }catch(Exception e){
            //throw all other exception message
            throw new AuraHandledException(e.getMessage());
        }
        finally {
        }
    } 
}

Lightning Component:

<!--Sample.cmp--> 
<aura:component controller="SampleAuraController" implements="flexipage:availableForAllPageTypes,force:appHostable">
    
    <!--Declare Attributes-->
    <aura:attribute name="isSpinner" type="boolean" default="false"/>
    <aura:attribute name="objLead" type="Lead" default="{'sobjectType':'Lead', 
                                                        'FirstName': '',
                                                        'LastName': '',
                                                        'Email': '', 
                                                        'Company': ''}"/>
    
    
    <!--Component Start--> 
    <div class="slds-m-around--xx-large">
        <div class="container-fluid">
            <div class="form-group">
                <lightning:input name="fname" type="text" maxlength="50" required="true" label="First Name" value="{!v.objLead.FirstName}" />
            </div>
            <div class="form-group">
                <lightning:input name="lname" type="text" maxlength="50" required="true" label="Last Name" value="{!v.objLead.LastName}" />
            </div>
            <div class="form-group">
                <lightning:input name="emailId" type="email" maxlength="100" required="true" label="Email" value="{!v.objLead.Email}" />
            </div>
            <div class="form-group">
                <lightning:input name="company" type="text" maxlength="50" required="true" label="Company" value="{!v.objLead.Company}" />
            </div>
        </div>
        <br/>
        <lightning:button variant="brand" label="Submit" onclick="{!c.handleLeadSave}" />        
        <lightning:button label="Cancel" onclick="{!c.handleCancel}"/>        
    </div>
    <!--Component End-->
</aura:component>

Lightning JS Controller:

({    
    //Handle Lead Save
    handleLeadSave : function(component, event, helper) {
        var objLead = component.get("v.objLead");
        var action = component.get("c.createLead");
        action.setParams({
            objLead : objLead
        });
        action.setCallback(this,function(a){
            var state = a.getState();
            if(state === "SUCCESS"){
                alert('Record is Created Successfully');
            } else if(state === "ERROR"){
                var errors = action.getError();
                if (errors) {
                    if (errors[0] && errors[0].message) {
                        alert(errors[0].message);
                    }
                }
            }else if (status === "INCOMPLETE") {
                alert('No response from server or client is offline.');
            }
        });       
        $A.enqueueAction(action);
    }
})

Output:

Trigger Error Message:

Validation Rule Error Message:

Get Picklist Values Dynamically From sObject In Lightning Component

In below example I’m retrieving “Account” object “Industry” picklist values and populating on lightning component using lightning:select and creating account record.

Apex Controller:

public class SampleAuraController {
    
    @AuraEnabled //Save Account Data
    Public static void createAccount(Account objacc){
        try{
            //Insert Account Record
            insert objacc; 
            
        }catch(Exception e){
            //throw exception message
            throw new AuraHandledException(e.getMessage());
        }
        finally {
        }
    }
    
    @AuraEnabled //get Account Industry Picklist Values
    public static Map<String, String> getIndustry(){
        Map<String, String> options = new Map<String, String>();
        //get Account Industry Field Describe
        Schema.DescribeFieldResult fieldResult = Account.Industry.getDescribe();
        //get Account Industry Picklist Values
        List<Schema.PicklistEntry> pList = fieldResult.getPicklistValues();
        for (Schema.PicklistEntry p: pList) {
            //Put Picklist Value & Label in Map
            options.put(p.getValue(), p.getLabel());
        }
        return options;
    }
}

Lightning Component:

<!--Sample.cmp--> 
<aura:component controller="SampleAuraController" implements="flexipage:availableForAllPageTypes,force:appHostable">
    
    <!--Declare Attributes-->
    <aura:attribute name="industryMap" type="Map"/>
    <aura:attribute name="acc" type="Account" default="{'sobjectType':'Account', 
                                                       'Name': '',
                                                       'AccountNumber': '',
                                                       'Email': '',
                                                       'Phone': '', 
                                                       'Industry': ''}"/>
    
    <!--Declare Handler-->
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>  
    
    <!--Component Start--> 
    <div class="slds-m-around--xx-large">
        <div class="container-fluid">
            <div class="form-group">
                <lightning:input name="accName" type="text" required="true" maxlength="50" label="Account Name" value="{!v.acc.Name}" />
            </div>
            <div class="form-group">
                <lightning:input name="accNumber" type="text" required="true" maxlength="10" label="Account Number" value="{!v.acc.AccountNumber}" />
            </div>
            <div class="form-group">
                <lightning:input name="accEmail" type="email" required="true" maxlength="100" label="Email" value="{!v.acc.Email}" />
            </div>
            <div class="form-group">
                <lightning:input name="accPhone" type="phone" required="true" maxlength="10" label="Phone" value="{!v.acc.Phone}" />
            </div>
            <div class="form-group">
                <lightning:select aura:id="industryPicklist" value="{!v.acc.Industry}" onchange="{!c.handleCompanyOnChange}" name="industryPicklist" label="Industry" required="true">
                    <option value="">--None--</option>
                    <aura:iteration items="{!v.industryMap}" var="ind" indexVar="key">
                        <option text="{!ind.value}" value="{!ind.key}" selected="{!ind.key==v.acc.Industry}" />
                    </aura:iteration>
                </lightning:select>
            </div>
        </div>
        <br/>
        <lightning:button variant="brand" label="Submit" onclick="{!c.handleAccountSave}" />              
    </div>
    <!--Component End-->
</aura:component>

Lightning JS Controller:

({
    //Load Account Industry Picklist
    doInit: function(component, event, helper) {        
        helper.getIndustryPicklist(component, event);
    },
    
    //handle Account Save
    handleAccountSave : function(component, event, helper) {
        helper.saveAccount(component, event);
    },
    
    //handle Industry Picklist Selection
    handleCompanyOnChange : function(component, event, helper) {
        var indutry = component.get("v.acc.Industry");
        alert(indutry);
    }
})

Lightning JS Helper:

({
    //get Industry Picklist Value
    getIndustryPicklist: function(component, event) {
        var action = component.get("c.getIndustry");
        action.setCallback(this, function(response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                var result = response.getReturnValue();
                var industryMap = [];
                for(var key in result){
                    industryMap.push({key: key, value: result[key]});
                }
                component.set("v.industryMap", industryMap);
            }
        });
        $A.enqueueAction(action);
    },
    
    //handle Account Save
    saveAccount : function(component, event) {
        var acc = component.get("v.acc");
        var action = component.get("c.createAccount");
        action.setParams({
            objacc : acc
        });
        action.setCallback(this,function(response){
            var state = response.getState();
            if(state === "SUCCESS"){
                alert('Record is Created Successfully');
            } else if(state === "ERROR"){
                var errors = action.getError();
                if (errors) {
                    if (errors[0] && errors[0].message) {
                        alert(errors[0].message);
                    }
                }
            }else if (status === "INCOMPLETE") {
                alert('No response from server or client is offline.');
            }
        });       
        $A.enqueueAction(action);
    }
})

Output:

Lightning OverlayLibrary Modal

In Spring 18 Salesforce has provided lightning:overlayLibrary tag to displays messages via modals and popovers. This component requires API version 41.0 and later. lightning:overlayLibrary is not supported in the lightning application. It’s supported only on salesforce lightning experience Lightning, Console, Communities.

In this example, I’m using below components:

Main Component (Sample.cmp) – Will contain lightning:overlayLibrary tag with aura:id attribute and a lightning:button
Modal Content (OverlayLibraryModal.cmp) – The component responsible for containing all the content to show in the Modal as content.

Sample.cmp:

<!--Sample.cmp--> 
<aura:component  implements="flexipage:availableForAllPageTypes,flexipage:availableForRecordHome" access="global">
    <!--Declare Attribute-->
    <aura:attribute name="FirstName" type="String"/> 
    <aura:attribute name="LastName" type="String"/> 
    
    <lightning:overlayLibrary aura:id="overlayLib"/>
    
    <!--Component Start-->    
    <div class="slds-m-around--xx-large">
        <lightning:input name="fname" label="First Name" value="{!v.FirstName}" />
        <lightning:input name="lname" label="Last Name" value="{!v.LastName}" />
        <br/>
        <lightning:button variant="brand" label="Show Modal" onclick="{!c.handleShowModal}"/>
    </div>
    <!--Component End-->
</aura:component>

SampleController.js:

({
    handleShowModal: function(component) {
        var fName = component.get("v.FirstName");
        var lName = component.get("v.LastName");
        $A.createComponent("c:OverlayLibraryModal",
                           {
                               "FirstName" : fName,
                               "LastName" : lName
                           },
                           function(content, status) {
                               if (status === "SUCCESS") {
                                   var modalBody = content;
                                   component.find('overlayLib').showCustomModal({
                                       header: "Welcome to Biswajeet Samal's Blog",
                                       body: modalBody, 
                                       showCloseButton: true,
                                       closeCallback: function(ovl) {
                                           console.log('Overlay is closing');
                                       }
                                   }).then(function(overlay){
                                       console.log("Overlay is made");
                                   });
                               }
                           });
    }
})

OverlayLibraryModal.cmp:

<aura:component access="global">
    <!--Declare Attribute-->
    <aura:attribute name="FirstName" type="String"/> 
    <aura:attribute name="LastName" type="String"/> 
    <lightning:overlayLibrary aura:id="overlayLib"/>
    
    <!--Component Start-->  
    <div class="slds-m-around--xx-large">
        <p>{!v.FirstName} &nbsp; {!v.LastName} </p>
        <br/>
        <lightning:button variant="brand" label="Cancel" onclick="{!c.handleCloseModal}"/>
    </div>
    <!--Component End-->
</aura:component>

OverlayLibraryModalController.js:

({
    handleCloseModal: function(component, event, helper) {
        //Close the Modal Window
        component.find("overlayLib").notifyClose();
    }
})

Output:

Salesforce Lightning Datatable

Apex Controller:

public class SampleAuraController {
    
    //Get Contact List
    @AuraEnabled
    public static List<Contact> getContactList(Integer recordLimit, Integer recordOffset){
        Integer intLimit = Integer.valueof(recordLimit);
        Integer intOffset = Integer.valueof(recordOffset);
        List<Contact> conList = new List<Contact>();
        conList = [SELECT Id, Name, Email, Phone FROM Contact LIMIT :intLimit Offset :intOffset];
        return conList;
    }
    
    //Get Total Number of Contacts
    @AuraEnabled
    public static Integer getTotalContacts(){
        AggregateResult results = [SELECT Count(Id) TotalContacts  From Contact];
        Integer totalContacts = (Integer)results.get('TotalContacts') ; 
        return totalContacts;
    } 
    
    //Delete Contact
    @AuraEnabled
    public static void deleteContact(Contact con){
        Delete con;
    } 
}

Lightning Component:

<!--Sample.cmp--> 
<aura:component controller="SampleAuraController" implements="flexipage:availableForAllPageTypes,force:appHostable">
    
    <!--Declare Attributes-->
    <aura:attribute name="data" type="Object"/>
    <aura:attribute name="columns" type="List"/>
    <aura:attribute name="selectedRowsCount" type="Integer" default="0"/>
    <aura:attribute name="selectedRowsDetails" type="Object" />
    <aura:attribute name="selectedRowsList" type="List" />
    <aura:attribute name="maxRowSelection" type="Integer" default="10"/>
    <aura:attribute name="selectedRows" type="List" />
    <aura:attribute name="enableInfiniteLoading" type="Boolean" default="true"/>
    <aura:attribute name="initialRows" type="Integer" default="10"/>
    <aura:attribute name="rowsToLoad" type="Integer" default="10"/>
    <aura:attribute name="totalNumberOfRows" type="Integer" default="10"/>
    <aura:attribute name="loadMoreStatus" type="String" default="Please scroll down to load more data"/>
    <aura:attribute name="showRowNumberColumn" type="Boolean" default="false"/>
    <aura:attribute name="rowNumberOffset" type="Integer" default="0"/>
    <aura:attribute name="rowsToAdd" type="Integer" default="10"/>
    <aura:attribute name="currentCount" type="Integer" default="10"/>
    <aura:attribute name="sortedBy" type="String"/>
    <aura:attribute name="sortedDirection" type="String"/>
    <aura:attribute name="defaultSortDirection" type="String"/>
    
    <!--Declare Handlers-->
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    
    <!--Component Start-->
    <div class="slds-m-around_xx-large" style="height: 300px">
        <lightning:datatable columns="{!v.columns}"
                             data="{!v.data}"
                             keyField="Id"
                             showRowNumberColumn="true"
                             rowNumberOffset="0"
                             onrowaction="{!c.handleRowAction}"
                             selectedRows="{!v.selectedRows}"
                             maxRowSelection="{!v.maxRowSelection}"
                             onrowselection="{!c.handleSelectedRow}"
                             enableInfiniteLoading="true"
                             loadMoreOffset="{!v.loadMoreOffset}"
                             sortedBy="{!v.sortedBy}"
                             sortedDirection="{!v.sortedDirection}"
                             defaultSortDirection="{!v.defaultSortDirection }"
                             onsort="{!c.handleColumnSorting}"
                             onloadmore="{!c.handleLoadMoreContacts}"/>
        <br/>
        <div class="slds-float_left">
            <strong>Total Rows : {!v.totalNumberOfRows}</strong>
            &nbsp;&nbsp;
            <strong>Selected Rows: {!v.selectedRowsCount }</strong>
        </div>
        <div class="slds-float_right">
            <strong>{!v.loadMoreStatus}</strong>
        </div>
        <br/>
        <br/>
        <div class="slds-float_left">
            <lightning:button label="Get Selected Contacts"  variant="brand" onclick="{!c.handleSelectedRows}"/>    
        </div>
    </div>
    <!--Component End-->
</aura:component>

Lightning Component JS Controller:

({
    doInit : function(component, event, helper) {
        helper.getTotalNumberOfContacts(component);
        helper.getColumnAndAction(component);
        helper.getContacts(component);
    },
    
    handleLoadMoreContacts: function (component, event, helper) {
        event.getSource().set("v.isLoading", true);
        component.set('v.loadMoreStatus', 'Loading....');
        helper.getMoreContacts(component, component.get('v.rowsToLoad')).then($A.getCallback(function (data) {
            if (component.get('v.data').length == component.get('v.totalNumberOfRows')) {
                component.set('v.enableInfiniteLoading', false);
                component.set('v.loadMoreStatus', 'No more data to load');
            } else {
                var currentData = component.get('v.data');
                var newData = currentData.concat(data);
                component.set('v.data', newData);
                component.set('v.loadMoreStatus', 'Please scroll down to load more data');
            }
            event.getSource().set("v.isLoading", false);
        }));
    },
    
    handleSelectedRows: function (component, event, helper) {
        var data = component.get('v.data');
        var selectedRowList =  component.get("v.selectedRowsList");
        console.log('selectedRowList-' + selectedRowList);
    },
    
    handleSelectedRow: function(component, event, helper){
        var selectedRows = event.getParam('selectedRows');
        component.set("v.selectedRowsCount", selectedRows.length);
        let obj =[] ; 
        for (var i = 0; i < selectedRows.length; i++){
            obj.push({Name:selectedRows[i].Name});
        }
        component.set("v.selectedRowsDetails", JSON.stringify(obj) );
        component.set("v.selectedRowsList", event.getParam('selectedRows'));
    },
    
    handleRowAction: function (component, event, helper) {
        var action = event.getParam('action');
        switch (action.name) {
            case 'new':
                helper.createContactRecord(component, event);
                break;
            case 'edit':
                helper.editContactRecord(component, event);
                break;
            case 'delete':
                helper.deleteContactRecord(component, event);
                break;
            case 'view':
                helper.viewContactRecord(component, event);
                break;
        }
    },
    
    handleColumnSorting: function (component, event, helper) {
        var fieldName = event.getParam('fieldName');
        var sortDirection = event.getParam('sortDirection');
        component.set("v.sortedBy", fieldName);
        component.set("v.sortedDirection", sortDirection);
        helper.sortData(component, fieldName, sortDirection);
    },
})

Lightning Component JS Helper:

({
    getContacts : function(component) {
        var action = component.get("c.getContactList");
        action.setParams({
            "recordLimit": component.get("v.initialRows"),
            "recordOffset": component.get("v.rowNumberOffset")
        });
        action.setCallback(this, function(response) {
            var state = response.getState();
            if (state === "SUCCESS" ) {
                var resultData = response.getReturnValue();
                component.set("v.data", resultData);
                component.set("v.currentCount", component.get("v.initialRows"));
            }
        });
        $A.enqueueAction(action);
    },
    
    getTotalNumberOfContacts : function(component) {
        var action = component.get("c.getTotalContacts");
        action.setCallback(this, function(response) {
            var state = response.getState();
            if (state === "SUCCESS" ) {
                var resultData = response.getReturnValue();
                component.set("v.totalNumberOfRows", resultData);
            }
        });
        $A.enqueueAction(action);
    },
    
    getColumnAndAction : function(component) {
        var actions = [
            {label: 'New', name: 'new'},
            {label: 'Edit', name: 'edit'},
            {label: 'Delete', name: 'delete'},
            {label: 'View', name: 'view'}
        ];
        component.set('v.columns', [
            {label: 'Name', fieldName: 'Name', type: 'text', sortable:true},
            {label: 'Email', fieldName: 'Email', type: 'email', sortable:true},
            {label: 'Phone', fieldName: 'Phone', type: 'phone', sortable:true},
            {type: 'action', typeAttributes: { rowActions: actions } } 
        ]);
    },
    
    getMoreContacts: function(component , rows){
        return new Promise($A.getCallback(function(resolve, reject) {
            var action = component.get('c.getContactList');
            var recordOffset = component.get("v.currentCount");
            var recordLimit = component.get("v.initialRows");
            action.setParams({
                "recordLimit": recordLimit,
                "recordOffset": recordOffset 
            });
            action.setCallback(this, function(response) {
                var state = response.getState();
                if(state === "SUCCESS"){
                    var resultData = response.getReturnValue();
                    resolve(resultData);
                    recordOffset = recordOffset+recordLimit;
                    component.set("v.currentCount", recordOffset);   
                }                
            });
            $A.enqueueAction(action);
        }));
    },
    
    sortData: function (component, fieldName, sortDirection) {
        var data = component.get("v.data");
        var reverse = sortDirection !== 'asc';
        data.sort(this.sortBy(fieldName, reverse))
        component.set("v.data", data);
    },
    
    sortBy: function (field, reverse, primer) {
        var key = primer ?
            function(x) {return primer(x[field])} :
        function(x) {return x[field]};
        reverse = !reverse ? 1 : -1;
        return function (a, b) {
            return a = key(a), b = key(b), reverse * ((a > b) - (b > a));
        }
    },
    
    viewContactRecord : function(component, event) {
        var row = event.getParam('row');
        var recordId = row.Id;
        var navEvt = $A.get("event.force:navigateToSObject");
        navEvt.setParams({
            "recordId": recordId,
            "slideDevName": "detail"
        });
        navEvt.fire();
    },
    
    deleteContactRecord : function(component, event) {
        var action = event.getParam('action');
        var row = event.getParam('row');
        
        var action = component.get("c.deleteContact");
        action.setParams({
            "con": row
        });
        action.setCallback(this, function(response) {
            var state = response.getState();
            if (state === "SUCCESS" ) {
                var rows = component.get('v.data');
                var rowIndex = rows.indexOf(row);
                rows.splice(rowIndex, 1);
                component.set('v.data', rows);
                
                var toastEvent = $A.get("e.force:showToast");
                toastEvent.setParams({
                    "title": "Success!",
                    "message": "The record has been delete successfully."
                });
                toastEvent.fire();
            }
        });
        $A.enqueueAction(action);
    },
    
    editContactRecord : function(component, event) {
        var row = event.getParam('row');
        var recordId = row.Id;
        var editRecordEvent = $A.get("e.force:editRecord");
        editRecordEvent.setParams({
            "recordId": recordId
        });
        editRecordEvent.fire();
    },
    
    createContactRecord : function (component, event) {
        var createRecordEvent = $A.get("e.force:createRecord");
        createRecordEvent.setParams({
            "entityApiName": "Contact"
        });
        createRecordEvent.fire();
    }
})

Output:

Salesforce Lightning Button Menu

Lightning Component:

<!--Sample.cmp--> 
<aura:component implements="flexipage:availableForAllPageTypes, force:appHostable">
    <div class="slds-m-around_xx-large">
        <lightning:buttonMenu  iconName="utility:settings" alternativeText="Action" onselect="{!c.handleMenuSelect}">
            <lightning:menuItem label="New" iconName="utility:new" value="new"/>
            <lightning:menuItem label="Edit" iconName="utility:edit" value="edit"/>
            <lightning:menuItem label="Delete" iconName="utility:delete" value="delete"/>
        </lightning:buttonMenu>
    </div>
</aura:component>

Lightning Component JS Controller:

({    
    handleMenuSelect: function(component, event, helper) {
        var selectedMenu = event.detail.menuItem.get("v.value");
        console.log('selectedMenu-' + selectedMenu);
        switch(selectedMenu) {
            case "new":
                //do create
                break;
            case "edit":
                //do edit
                break;
            case "delete":
                //do delete
                break;
        }
    }
})

Output:

Reference: lightning:buttonMenu lightning:menuItem