top of page

Bulk Approval or Rejection using Custom Lightning Component in Salesforce

Updated: Mar 1

In this post, we are going to see an example for Bulk Approval or Rejection using Lightning Component in Salesforce.


Follow the below steps for developing Lightning Component:


Step1: Create Lightning Component- “MassApprovalRejectionComponent.cmp”


<aura:component controller="MassApprovalRejectionController" implements="flexiPage:availableForAllPageTypes,force:appHostable">
    
    <!-- Call Javescript Method on Load -->
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/> 
    
    <!-- Functional Attributes -->
    <aura:attribute name="columns" type="list" description="Captures the value of column names of the data table"/>
    <aura:attribute name="data" type="object" description="Captures the records displayed in the data table"/>
    <aura:attribute name="sortedBy" type="string" description="Captures the column name used for sorting"/>
    <aura:attribute name="sortedDirection" type="string" description="Captures the direction of sorting(ascending or descending)"/>
 
    <!-- Spinner -->
    <lightning:spinner aura:id="spinnerId" variant="brand" size="medium"/>
    <!-- Spinner -->
    
    <!-- Page Header -->
    <div class="slds-page-header" role="banner">
    	<span class="slds-page-header__title">Approval page</span>
    </div>
    <!-- Page Header -->
    
    <!--Approve and Reject buttons are disabled by default.Whenever a record is selected, buttons will be enabled-->
    <div class="slds-m-vertical_medium">
    	<lightning:button aura:id="approvalButtonId" label="Approve" variant="success" disabled="true"
                          onclick="{!c.handleApproveAction}"/>
        <lightning:button aura:id="rejectButtonId" label="Reject" variant="destructive" disabled="true"
                          onclick="{!c.handleRejectAction}"/>
    </div>
     <!--Approve and Reject buttons are disabled by default.Whenever a record is selected, buttons will be enabled-->
    
    <!-- Page Body -->
    <div>
    	<lightning:datatable aura:id="approvalRecordsTableId"
                             keyField="workItemId"
                             columns="{!v.columns}"
                             data="{!v.data}"
                             sortedBy="{!v.sortedBy}"
                             sortedDirection="{!v.sortedDirection}"
                             onsort="{!c.handleSortingOfRows}"
                             onrowselection="{!c.handleRowSelection}"/>
 
    </div>
    <!-- Page Body -->


Step2: Create Javascript Controller- “MassApprovalRejectionComponent.js”


({
    //Method call on load of Lightning Component
    doInit : function(component,event,helper){
        helper.doInitHelper(component,event,helper);
    },
    
    //Method to handle sorting of records
    handleSortingOfRows : function(component,event,helper){
        helper.handleSortingOfRows(component,event);
    },
    
    //Method to enable or disable Approve and Reject button
    handleRowSelection : function(component,event,helper){
        helper.handleRowSelection(component,event,helper);
    },
    
    //Method to Approve the selected records
    handleApproveAction : function(component,event,helper){
        helper.processSelectedRecords(component,event,helper,'Approve');
    },
    
    //Method to Reject the selected records
    handleRejectAction : function(component,event,helper){
        helper.processSelectedRecords(component,event,helper,'Reject');
    }
})


Step3: Helper Class: “MassApprovalRejectionComponent.helper”


({
    //Method call on load of Lightning Component
    doInitHelper : function(component,event){
        //Initialize the columns for data table
        component.set('v.columns',[
            {
                label : 'Name',
                fieldName : 'recordId',
                type : 'url',
                typeAttributes : {label:{fieldName:'recordName'},target:'_blank'}
            },
            {
                label : 'Related to',
                fieldName : 'relatedTo',
                type : 'text',
                sortable : true
            },
            {
                label : 'Submitted by',
                fieldName : 'submittedBy',
                type : 'text',
                sortable : true
            },
            {
                label : 'Submitted date',
                fieldName : 'submittedDate',
                type : 'date',
                typeAttributes : {year:"2-digit",month:"short",day:"2-digit"},
                sortable : true
            }
        ]);
        this.getData(component,event);
    },
    
    //Method to fetch data
    getData : function(component,event){
        //show spinner till data is loaded from server
        var spinner = component.find("spinnerId");
        $A.util.toggleClass(spinner, "slds-hide");
        var toastRef = $A.get('e.force:showToast');
        var action = component.get('c.getSubmittedRecords');
        action.setCallback(this,function(response){
            var state = response.getState();
            if(state == 'SUCCESS'){
                var records = response.getReturnValue();
                records.forEach(function(record){
                   record.recordId = '/'+record.recordId;
                });
                $A.util.toggleClass(spinner, "slds-hide");
                if(records.length == 0){
                    toastRef.setParams({
                        'type' : 'error',
                        'title' : 'Error',
                        'message' : 'No records found for approve/reject',
                        'mode' : 'sticky'
                    });
					toastRef.fire();
                }
                component.set('v.data',records);
            }
        });
        $A.enqueueAction(action);
    },
    
    //Method to handle sorting of records
    handleSortingOfRows : function(component,event){
        //Set field name and direction of sorting
        var sortedBy = event.getParam('fieldName');
        var sortedDirection = event.getParam('sortDirection');
        component.set('v.sortedBy',sortedBy);
        component.set('v.sortedDirection',sortedDirection);
        this.sortRecords(component,event,helper,sortedBy,sortedDirection);
    },
    
    //Method to handle sorting of records
    sortRecords : function(component,event,helper,sortedBy,sortedDirection){
        var records = component.get('v.data');
        var direction = sortedDirection == 'asc' ? 1 : -1;
        var fieldValue = function(record){ return record[sortedBy]; }//returns the field value(field used for sorting) for each record
        records.sort(function(record1,record2){
            var fieldValue1 = fieldValue(record1);
            var fieldValue2 = fieldValue(record2);
            return direction * (fieldValue1 > fieldValue2) - (fieldValue2 > fieldValue1);//For asc,return value of -1 sorts the record,1 or 0 keeps the order intact.
        });
        component.set('v.data',records);
    },
    
    //Method to enable or disable Approve and Reject button
    handleRowSelection : function(component,event,helper){
        //To enable or disable Approve, Reject button based on row selection
        var rowsSelected = event.getParam('selectedRows');
        if(rowsSelected.length > 0){
            component.find('approvalButtonId').set('v.disabled',false);
            component.find('rejectButtonId').set('v.disabled',false);
        }
        else{
            component.find('approvalButtonId').set('v.disabled',true);
            component.find('rejectButtonId').set('v.disabled',true);
        }
    },
    
    //Method to Approve or Reject the selected records
    processSelectedRecords : function(component,event,helper,processType){
        //To approve, reject selected records based on 'processType' variable
        component.find('approvalButtonId').set('v.disabled',true);
        component.find('rejectButtonId').set('v.disabled',true);
        var selectedRows = component.find('approvalRecordsTableId').get('v.selectedRows');
        var action = component.get('c.processRecords');//Calling server side method with selected records
        action.setParams({
            lstWorkItemIds : selectedRows,
            processType : processType
        });
        action.setCallback(this,function(response){
            var state = response.getState();
            var toastRef = $A.get('e.force:showToast');
            if(state == 'SUCCESS'){
                var message = response.getReturnValue();
                if(message.includes('success')){
                    toastRef.setParams({
                        'type' : 'success',
                        'title' : 'Success',
                        'message' : message,
                        'mode' : 'dismissible'
                    });
                }
                else{
                   toastRef.setParams({
                        'type' : 'error',
                        'title' : 'Error',
                        'message' : message,
                        'mode' : 'sticky'
                    });
                }
                toastRef.fire();
                $A.get('e.force:refreshView').fire();
            }
        });
        $A.enqueueAction(action);
    }
})




Step4: Create Custom Apex Controller- “MassApprovalRejectionController”


public class MassApprovalRejectionController {

    //Method to fetch all the records which are submitted for approval
    @AuraEnabled
    public static List<SubmittedRecordsWrapper> getSubmittedRecords(){
        List<SubmittedRecordsWrapper> lstSubmissionWrapper = new List<SubmittedRecordsWrapper>();
        //Process instance stores the info of records submitted for approval,
        // Process instance work item are the records an approver sees while approving/rejecting, Process instance step stores approved/rejected record including approva;/rejection comments
        for(ProcessInstance ps : [SELECT Id,TargetObjectId,TargetObject.Name,CreatedDate,
                                 (SELECT ID FROM WorkItems WHERE OriginalActorId = : UserInfo.getUserId()),
                                 (SELECT OriginalActor.Name FROM Steps WHERE StepStatus = 'Started') FROM ProcessInstance]){
            if(!ps.WorkItems.isEmpty()){
                SubmittedRecordsWrapper objSubmittedRecordsWrapper = new SubmittedRecordsWrapper();
                objSubmittedRecordsWrapper.workItemId = ps.WorkItems[0].Id;
                objSubmittedRecordsWrapper.recordId = ps.TargetObjectId;
                objSubmittedRecordsWrapper.recordName = ps.TargetObject.Name;
                objSubmittedRecordsWrapper.relatedTo = getObjectName(ps.TargetObjectId);//get the object name using the record id
                objSubmittedRecordsWrapper.submittedDate = Date.newInstance(ps.CreatedDate.year(),ps.CreatedDate.month(),ps.CreatedDate.day());
                if(!ps.steps.isEmpty()){
                    objSubmittedRecordsWrapper.submittedBy = ps.steps[0].OriginalActor.Name;
                	lstSubmissionWrapper.add(objSubmittedRecordsWrapper);
                }
            }
        }
        return lstSubmissionWrapper;
    }
    
    public static String getObjectName(String recordId){
        //To get the label of the object name using Schema methods
        String keyPrefix = recordId.subString(0,3);
        String objectName = '';
        Map<String,Schema.SObjectType> sobjectTypeMap = Schema.getGlobalDescribe();
        for(String obj : sobjectTypeMap.keySet()){
            Schema.DescribeSObjectResult sobjectResult = sobjectTypeMap.get(obj).getDescribe();
            if(sobjectResult.getKeyPrefix() == keyPrefix){
                objectName = sobjectResult.getLabel();
                break;
            }
        }
        return objectName;
    }
    
    //Method to Approve or Reject the selected records
    @AuraEnabled
    public static String processRecords(List<String> lstWorkItemIds,String processType){
        String message = '';
        Integer recordsProcessed = 0;
        String comments = processType == 'Approve' ? 'Approved' : 'Rejected';
        List<Approval.ProcessWorkitemRequest> lstWorkItemRequest = new List<Approval.ProcessWorkitemRequest>();//ProcessWorkitemRequest class has methods to programmatically process submitted records
        for(String workItemId : lstWorkItemIds){
            Approval.ProcessWorkitemRequest objWorkItemRequest = new Approval.ProcessWorkitemRequest();
            objWorkItemRequest.setComments(comments);
            objWorkItemRequest.setAction(processType);//approve or reject
            objWorkItemRequest.setWorkitemId(workItemId);
            lstWorkItemRequest.add(objWorkItemRequest);
        }
        Approval.ProcessResult[] lstProcessResult = Approval.process(lstWorkItemRequest,FALSE);//process method is used for approving/rejecting records depending on setAction attribute
        for(Approval.ProcessResult processResult : lstProcessResult){
            if(processResult.isSuccess()){
                recordsProcessed++;
            }
            else{
                for(Database.Error error : processResult.getErrors()){
                    message += error.getMessage();
                }
            }
        }
        if(recordsProcessed == lstWorkItemIds.size()){
            message = 'All records are '+comments+' successfully';
        }
        return message;
    }
    
    //Wrapper class to store the column values of data table
    public class SubmittedRecordsWrapper{
        @AuraEnabled public Id workItemId;
    	@AuraEnabled public String recordId;
        @AuraEnabled public String relatedTo;
        @AuraEnabled public String recordName;
        @AuraEnabled public String submittedBy;
        @AuraEnabled public Date submittedDate;
    }
}



That's it you have developed a custom lighting component for Bulk approval.,

Create one Tab in your org and link to the above-created Component to see a list of record for Bulk approval




1,176 views2 comments
bottom of page