Help Center
  • What is GridMate
  • 📌Getting Started
    • Package Setup
    • Appexchange
  • 🎬Product Tour
    • Related List Grid
    • Parent Related List Grid
    • List View Grid
    • Kanban List View Grid
    • Field Set Grid
    • User Grid
    • Pivot Grid
    • Report Table
    • Multi Calendar
    • Object Timeline
    • File Explorer
    • Record Layout
    • Record KPI
    • Field Path
    • Map Record
    • Map List
    • Utility Bar Grid
    • Record App Switcher
    • Flow Grids
    • Compact Calendar
  • 🚀Advanced Guides
    • Grid - Advanced Configuration
    • Grid - Mass/Record Actions
    • Grid - Advanced Filtering
    • Grid - Inline Components
    • Grid - Mass Edit Button
    • Grid - Enhanced Filter Builder
    • Grid - Data Import Wizard
    • Grid - Dynamic Formula Field
    • Grid - Grid Explorer
    • Grid - Dynamic Interaction
    • Grid - Dynamic FieldSet Grid
    • Grid - Dynamic Record Card
    • Grid - Custom Action
    • Grid - Interactive Filters
    • Grid - Bulk Action
    • Grid - Custom Inline Component
    • Grid - Config Checker
    • Grid - Admin Cockpit
    • User Grid - Split View
    • User Grid - Data Filtering
    • User Grid - Deployment Process
    • Map List - Search Around Setup
    • Salesforce Classic Setup
  • 📦Package Reference
    • Components Library
      • GM - RelatedList Grid
      • GM - FieldSet Grid
      • GM - ListView Grid
      • GM - FieldSet Kanban
      • GM - ListView Kanban
      • GM - Parent RelatedList Grid
      • GM - RelatedList Tabs
      • GM - RelatedList Accordion
      • GM - RelatedList Cards
      • GM - Record Layout
      • GM - Record Layout (LWC)
      • GM - Record Card
      • GM - Dynamic Tabs
      • GM - Dynamic Accordion
      • GM - Flow Layout
      • GM - Field Path
      • GM - Multi Calendar
      • GM - FieldSet Pivot
      • GM - Flow View Grid
      • GM - Flow Edit Grid
      • GM - Record App Switcher
      • GM - Map Record
      • GM - Map List
      • GM - Report Table
      • GM - Object Timeline
      • GM - User Grid
      • GM - File Explorer
      • GM - Dynamic FieldSet Grid
      • GM - Dynamic Record Card
      • GM - User Grid Split View
      • GM - Compact Calendar
      • GM - Interaction Logger
    • Javascript Formulas
    • DataGrid Settings
  • Tools
    • SF Cli Plugin
    • Chrome Extension
  • 📬TROUBLESHOOTING
    • ⚙️Config Snippets
      • Layout - basic setup
      • Layout with read only field
      • Layout with field visibility
      • Layout with section visibility
      • Layout with autocomplete
      • Inline FieldSet Grid
      • Inline RelatedList Grid
      • Inline Record Layout
      • Inline Chatter Feed
      • Multiple Inline Components
      • Calendar - Extra Activities
      • Field Path Stages
      • Dynamic Tabs
      • Compact Calendar
      • Object Timeline
    • ❓FAQ
  • 📋Release Notes
Powered by GitBook

Links

  • Appexchange
  • Pricing
  • Solution

Social

  • Youtube
  • LinkedIn
  • X

2025 GridMate

On this page
  • Bulk Action Monitor Setup
  • Bulk Action Implementation
  • Aura Action Component
  • Apex Controller Classes
  • Bulk Action Configuration
  • Source Code

Was this helpful?

  1. Advanced Guides

Grid - Bulk Action

PreviousGrid - Interactive FiltersNextGrid - Custom Inline Component

Last updated 1 year ago

Was this helpful?

When it comes to updating the data from a grid, we might hit the limits like a CPU time, memory, DML.... To bypass those limitations, the updates should be executed in asynchronous mode using queueables or batches jobs.

To support this type of transactions, we provide GM - Bulk Action. With Bulk Action you get a framework to track the running tasks from a developer perspective. We also provide a monitor to visualise background tasks and let the users see the outcomes including the occurred errors.

Bulk Action Monitor Setup

To track the running bulk actions, the GM - Bulk Action Monitor should be configured on the Lightning App. Each app requiring this type of tracking should have this utility bar configured.

When the utility item is added, it should be configured as below. The width and the height should adjusted based on the label of actions, screen resolution...

Start automatically should be always checked for the monitor. If not we may loose initiated jobs before opening the monitor at the first time.

The cleanup delay defines the number of second before removing a successfully completed job. If you want your users to get a chance, put a long delay.

When a job is completed, the user get success toast message if the job is completed without any error. Otherwise he will get an error notification.

Bulk Action Implementation

Aura Action Component

Our monitor is configured, let's go ahead and start our first bulk action. Let's say that we want to move to close date by one day for the whole scope defined by the user.

This action will be triggered from an Opportunity grid. Let's create an Aura component TestBulkActionActionComponent. The component has all the required properties including:

  • Grid Name : Grid name coming from the configuration,

  • Grid Label : Grid label coming from the configuration,

  • Action Name : Action name coming from the configuration,

  • Action Label : Action label coming from the configuration,

  • Query : The SOQL query defining the scope

The component should also fire onsucess or oncancel based on the user flow. Finally we will use bulkActionPublisherLWC utility component to publish the job to the monitor. It's a useful component to streamline the implementation process.

<aura:component implements="force:hasRecordId" controller="TestBulkActionController" access="global">
    <!-- Action properties coming from GridMate-->
    <aura:attribute name="relatedObjectName" type="String" access="global" />    
    <aura:attribute name="gridLabel" type="String" access="global" />
    <aura:attribute name="gridName" type="String" access="global" />
    <aura:attribute name="gridCode" type="String" access="global" />
    <aura:attribute name="actionName" type="String" access="global" />
    <aura:attribute name="actionLabel" type="String" access="global" />

    <!-- Soql Query coming from GridMate-->
    <aura:attribute name="query" type="String" access="global" />

    <!-- Internal flag to control the spinner-->
    <aura:attribute name="isWorking" type="Boolean" access="global" />

    <aura:registerEvent name="onsuccess" type="gmpkg:DataGridActionEvent" />
    <aura:registerEvent name="oncancel" type="gmpkg:DataGridActionEvent" />

    <!-- overlayLib API -->
    <lightning:overlayLibrary aura:id="overlayLib" />
        
    <!-- Bulk Action Publisher -->
    <gmpkg:bulkActionPublisherLWC aura:id="bulkActionPublisher" />
    
    <div class="slds-theme_default">
        <div class="content-wrapper">
            <span> {! v.query }</span>
        </div>

        <div class="slds-modal__footer actions-wrapper">
            <button class="slds-button slds-button--neutral" onclick="{!c.handleCancel}">Cancel</button>
            <button class="slds-button slds-button--brand" onclick="{!c.handleSubmit}">Submit</button>
        </div>

        <aura:if isTrue="{! v.isWorking }">
            <lightning:spinner variant="brand" alternativeText="Processing" style="background: transparent" />
        </aura:if>
    </div>
</aura:componen
({
    handleSubmit: function (component, event, helper) {
        component.set('v.isWorking', true);

        //Submit the action to the Salesforce
        let action = component.get('c.submitBulkAction');
        action.setParams({
            gridLabel: component.get('v.gridLabel'),
            gridName: component.get('v.gridName'),
            actionLabel: component.get('v.actionLabel'),
            actionName: component.get('v.actionName'),
            query: component.get('v.query'),
            url: window.location.href
        });

        action.setCallback(this, function (res) {
            component.set('v.isWorking', false);
            if (res.getState() === 'SUCCESS') {
                //Publish the JobId for the monitor
                let bulkActionPublisher = component.find('bulkActionPublisher');
                bulkActionPublisher.publish(res.getReturnValue());

                //Show a toast for the end user
                let toastEvent = $A.get('e.force:showToast');

                toastEvent
                    .setParams({
                        title: 'Success',
                        type: 'success',
                        message: 'Action Executed Successfully'
                    })
                    .fire();

                component
                    .getEvent('onsuccess')
                    .setParams({
                        action: 'ActionExecuted'
                    })
                    .fire();

                //Close the modal
                component.find('overlayLib').notifyClose();
            } else if (res.getState() === 'ERROR') {
                helper.handleServerErr(res);
            }
        });

        $A.enqueueAction(action);
    },
    handleCancel: function (component, event, helper) {
        component
            .getEvent('oncancel')
            .setParams({
                action: 'ActionCancelled'
            })
            .fire();

        component.find('overlayLib').notifyClose();
    }
});

Apex Controller Classes

Our Aura component is ready, let's go ahead and implement our Apex controller. In our example, the job of the controller is straightforward; creates a Bulk Action Job and starts an Apex batch. To get the number of records to process, we just replace the Id with Count().

The batch class track the progress of the job and keep track of the occurred errors. At the end of the batch, we close the job.

When a job is closed, the monitor is notified and the toast message is displayed to the user.

public with sharing class TestBulkActionController {
    @AuraEnabled
    public static Id submitBulkAction(
        String gridLabel,
        String gridName,
        String actionLabel,
        String actionName,
        String query,
        String url
    ) {
        String countQuery = query.replaceAll('Select Id ', 'Select count()');

        //Submit the job to the action manager
        Id jobId = gmpkg.BulkActionManager.createJob(
            new gmpkg.BulkActionManager.BulkActionRequest(
                gridLabel,
                gridName,
                actionLabel,
                actionName,
                query,
                url,
                database.countQuery(countQuery)
            )
        );

        //Start the batch to process all the opportunities
        Database.executeBatch(new TestBulkActionBatch(jobId, query));
        return jobId;
    }
}
public with sharing class TestBulkActionBatch implements Database.Batchable<sObject>, Database.Stateful {
    public Id jobId; // Monitored jobId
    public String scopeQuery; // Query coming from the action
    public Integer totalProgress = 0; // Current progress of the job

    public TestBulkActionBatch(Id jobId, String scopeQuery) {
        this.jobId = jobId;
        this.scopeQuery = scopeQuery;
    }

    public Database.QueryLocator start(Database.BatchableContext bc) {
        //Just replace the Id with the expected columns for the our batch
        return Database.getQueryLocator(this.scopeQuery.replace('Id', 'Id, Name, StageName, CloseDate'));
    }

    public void execute(Database.BatchableContext bc, List<Opportunity> opportunities) {
        if (opportunities.size() > 0) {
            for (Opportunity opp : opportunities) {
                opp.CloseDate += 1;
            }

            //We prepare the list of BulkActionError to keep track of save errors 
            List<gmpkg.BulkActionManager.BulkActionError> errorList = new List<gmpkg.BulkActionManager.BulkActionError>();
            List<Database.SaveResult> resultList = database.update(opportunities, false);
            for (Database.SaveResult res : resultList) {
                if (!res.isSuccess()) {
                    errorList.add(new gmpkg.BulkActionManager.BulkActionError(res.getId(), JSON.serialize(res.getErrors())));
                }
            }

            //If we have errors, we report them back to the monitor
            if (errorList.size() > 0) {
                gmpkg.BulkActionManager.reportErrors(this.jobId, errorList);
            }

            //We increase the progress and we report it back to the monitor
            this.totalProgress += opportunities.size();
            gmpkg.BulkActionManager.reportProgress(this.jobId, this.totalProgress);
        }
    }

    public void finish(Database.BatchableContext bc) {
        //At the end, we close the job
        gmpkg.BulkActionManager.closeJob(this.jobId);
    }
}

Bulk Action Configuration

Our Aura component and Apex Classes are ready for use. Let's go ahead and configure the grid action as below:

[
    {
        "name": "Move",
        "label": "Move",
        "bulkAction": true,
        "confirmationRequired": true,
        "component": "c:TestBulkActionComponent"
    }
]

To trigger a Bulk Action, bulkAction flag should be set to true. If not the action will be considered as synchronous action and the query will not be passed to your component.

If confirmationRequired is set to true, the user will be asked to select the scope of the action. It could be either the selected records or the whole filter scope. See the screenshot below.

Source Code

The full source code of this example is available here

🚀
👇
https://github.com/GridMate/gridmate-examples/tree/main
Action Monitor Setup #1
Action Monitor Setup #2
Bulk Action Scope
Bulk Action Progress