Bulk Approval or Rejection using Custom Lightning Component in Salesforce
- Amol Wagh
- Apr 1, 2021
- 4 min read
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
Can you please provide test class for this functionality.
This is super cool, I've added it as a component, the only place I'm stuck at (not being a developer, I can really just sort of read code), I am able to add more columns, but how would I go about adding the value from the related record to that column?