Salesforce Lightning Custom Path for Picklist Field

Now you can create your custom lightning path for picklist field of any standard and custom objects. In Winter’18 release, Salesforce introduced lightning:picklistPath component, it displays the progress of a process, which is based on the picklist field specified by the picklistFieldApiName attribute. The path is rendered as a horizontal bar with one chevron for each picklist item. Paths created by this component do not have key fields or guidance and do not display the Mark Complete button.

Here is an example to create a path for Application Custom Object records that’s based on the record Id and the Status__c picklist field.

Component:

<aura:component implements="flexipage:availableForAllPageTypes,force:hasRecordId" access="global">
    <lightning:notificationsLibrary aura:id="notifLib"/>
    <!--Picklist Field Attribute-->
    <aura:attribute name="picklistField" type="object"/>
    
    <!--Force Record Data to update picklist value-->
    <force:recordData aura:id="record"
                      layoutType="FULL"
                      recordId="{!v.recordId}"
                      targetFields="{!v.picklistField}"
                      mode="EDIT"/>
    
    <!--For changing from left-to-right use dir="ltr" and from to right-to-left use dir="rtl"-->
    <div dir="ltr">
        <!--Lightning Picklist Path For Applicatin Status-->
        <lightning:picklistPath recordId="{!v.recordId}"
                                variant="linear"
                                picklistFieldApiName="Status__c"
                                onselect="{!c.handleSelect}" />
    </div>
</aura:component>

Component JS Controller:

({
    handleSelect : function (component, event, helper) {
        //get selected Status value
        var selectStatus = event.getParam("detail").value;
        //set selected Status value
        component.set("v.picklistField.Status__c", selectStatus);
        
        component.find("record").saveRecord($A.getCallback(function(response) {
            if (response.state === "SUCCESS") {
                $A.get('e.force:refreshView').fire();
                component.find('notifLib').showToast({
                    "variant": "success",
                    "message": "Record was updated sucessfully",
                    "mode" : "sticky"
                });
            } else {
                component.find('notifLib').showToast({
                    "variant": "error",
                    "message": "There was a problem updating the record.",
                    "mode" : "sticky"
                });
            }
        }));
    }
})

Output:

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:

Lightning Component to Open URL in Browser New Tab on Button Click

Lightning Component:

<!--Sample.cmp-->
<aura:component implements="flexipage:availableForAllPageTypes, force:appHostable" access="global">
    <aura:attribute name="recordId" type="string" default="0019000001DEV5z"/>
    
    <div class="slds-m-around--xx-large">
        <!--Open URL in New Browser Tab-->
        <lightning:button label="Open in New Window" variant="brand" onclick="{!c.handleOpenInNewWindow}"/>
        <!--Open URL in New Browser Tab With Record Id-->
        <lightning:button label="Open in New Window With RecordId" variant="brand" onclick="{!c.handleOpenNewWindowWithRecordId}"/>
    </div>
</aura:component>

Lightning JS Controller:

({
    //Open URL in New Browser Tab
    handleOpenInNewWindow : function(component, event, helper) {
        window.open("https://www.salesforce.com", '_blank');
    },
    
    //Open URL in New Browser Tab With Record Id
    handleOpenNewWindowWithRecordId : function(component, event, helper) {
        var recordId = component.get('v.recordId');
        window.open('/' + recordId,'_blank');
    }
})

Custom Label in Lightning Component

Custom Label in Lightning Component:
$Label.c.labelName for the default namespace.
$Label.namespace.labelName if your org has a namespace, or to access a label in a managed package.

<aura:component implements="flexipage:availableForAllPageTypes">
    <div onclick="{!c.clickLabel}">
        <ui:outputText value="{!$Label.c.Message}" />
    </div>
</aura:component>

Custom Label in Javascript:
$A.get(“$Label.c.labelName”) for the default namespace.
$A.get(“$Label.namespace.labelName”) if your org has a namespace, or to access a label in a managed package.

({
    clickLabel : function(component, event, helper) {
        var label = $A.get("$Label.c.Message");
        alert(label);
    }
})

Salesforce Lightning Datatable With Pagination

Apex Class:

public class SampleController {
    
    //Get Account Records
    @AuraEnabled
    public static List<Account> getAccounts(String pageSize, String pageNumber){
        Integer psize = Integer.valueOf(pageSize);
        Integer pnumber = Integer.valueOf(pageNumber)-1;
        
        List<Account> accList = [SELECT Id, Name, AccountNumber, Industry, Phone
                                  FROM Account LIMIT :Integer.valueOf(psize) OFFSET :(psize*pnumber)];
        return accList;
    }
    
    //Delete Account
    @AuraEnabled
    public static void deleteAccount(Account acc){
        Delete acc;
    } 
}

Lightning Component:

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes" access="global" controller="SampleController">
    
    <aura:attribute name="data" type="Object"/>
    <aura:attribute name="columns" type="List"/>
    <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"/> 
    <aura:handler name="init" action="{!c.doInit}" value="{!this}"/>
    
    <div class="slds-m-around_xx-large">
        <div class="slds-clearfix">
            <div class="slds-page-header" role="banner">
                <p class="slds-page-header__title">Accounts</p>
            </div>
        </div>
        
        <lightning:datatable aura:id = "accDT"
                             columns = "{!v.columns}"
                             maxRowSelection="{!v.maxRowSelection}"
                             data = "{!v.data}"
                             keyField = "Id"
                             selectedRows = "{!v.selectedRowList}"
                             onrowaction="{!c.handleRowAction}"/>
        
        <div class="slds-clearfix">
            <div class="slds-page-header" role="banner">
                <div class="slds-float_right">            
                    <lightning:button label="Prev" iconName="utility:chevronleft" iconPosition="left"
                                      onclick="{!c.handlePrev}" disabled="{! v.pageNumber == 1}"/>
                    <lightning:button label="Next" iconName="utility:chevronright" iconPosition="right" 
                                      disabled="{! v.isLastPage}" onclick="{!c.handleNext}"/>
                </div>
                <p class="slds-page-header__title">Page {!v.pageNumber} | Showing records from {! ((v.pageNumber-1)*v.pageSize)+' to '+((v.pageNumber-1)*v.pageSize+v.dataSize)}</p>
            </div>
        </div>
    </div>
</aura:component>

Lightning JS Controller:

({
    doInit : function(component, event, helper) {        
        helper.getColumnAndAction(component);
        helper.getAccounts(component, helper);
    },
    
    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);
    },

    handleRowAction: function (component, event, helper) {
        var action = event.getParam('action');
        switch (action.name) {
            case 'edit':
                helper.editRecord(component, event);
                break;
            case 'delete':
                helper.deleteRecord(component, event);
                break;
            case 'view':
                helper.viewRecord(component, event);
                break;
        }
    },
})

Lightning JS Helper:

({
    getColumnAndAction : function(component) {
        var actions = [
            {label: 'Edit', name: 'edit'},
            {label: 'Delete', name: 'delete'},
            {label: 'View', name: 'view'}
        ];
        component.set('v.columns', [
            {label: 'Name', fieldName: 'Name', type: 'text'},
            {label: 'AccountNumber', fieldName: 'AccountNumber', type: 'text'},
            {label: 'Industry', fieldName: 'Industry', type: 'text'},
            {label: 'Phone', fieldName: 'Phone', type: 'phone'},
            {type: 'action', typeAttributes: { rowActions: actions } } 
        ]);
    },
    
    getAccounts : function(component, helper) {
        var action = component.get("c.getAccounts");
        var pageSize = component.get("v.pageSize").toString();
        var pageNumber = component.get("v.pageNumber").toString();
        
        action.setParams({
            'pageSize' : pageSize,
            'pageNumber' : pageNumber
        });
        action.setCallback(this,function(response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                var resultData = response.getReturnValue();
                if(resultData.length < component.get("v.pageSize")){
                    component.set("v.isLastPage", true);
                } else{
                    component.set("v.isLastPage", false);
                }
                component.set("v.dataSize", resultData.length);
                component.set("v.data", resultData);
            }
        });
        $A.enqueueAction(action);
    },
    
    viewRecord : 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();
    },
    
    deleteRecord : function(component, event) {
        var action = event.getParam('action');
        var row = event.getParam('row');
        
        var action = component.get("c.deleteAccount");
        action.setParams({
            "acc": 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);
    },
    
    editRecord : 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();
    },    
})

Output: