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?