Amol Wagh

Apr 1, 20214 min

Bulk Approval or Rejection using Custom Lightning Component in Salesforce

Updated: Mar 1, 2023

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

    15222
    2