Salesforce Lighting Web Component datatable rows selection across pagination

Salesforce Lighting Web Component datatable rows selection across pagination

We can achieve rows selections across multiple pages when using pagination in lightning-datatable, by storing the selected rows in a variable.

Sample Apex Class:

public class SampleLightningWebComponentController {
    
    @AuraEnabled
    public static Map < String, Object > fetchAccounts(
        Integer intCount,
        String firstAccountId,
        String lastAccountId,
        String sortDirection,
        String strAccountName
    ) {
        
        Integer intLimit = 5;
        String strSOQL = 'SELECT Id, Name, Industry, Type FROM Account';
        Map < String, Object > resultMap = new Map < String, Object >();

        if ( String.isNotBlank ( strAccountName ) ) {

            strSOQL +=  ' WHERE Name LIKE \'%' + strAccountName + '%\'';

            if ( sortDirection == 'ASC' ) {

                if ( String.isNotBlank( lastAccountId ) ) {

                    strSOQL +=  ' AND Id > \'' + lastAccountId + '\'';
                
                }
                
            } else if ( sortDirection == 'DESC' ) {
                
                if ( String.isNotBlank( firstAccountId ) ) {

                    strSOQL +=  ' AND Id < \'' + firstAccountId + '\'';
                
                }

            }

            if ( intCount == 0 ) {
                
                String strCountSOQL = 'SELECT COUNT() FROM Account';
                strCountSOQL +=  ' WHERE Name LIKE \'%' + strAccountName + '%\'';
                intCount = Database.countQuery( strCountSOQL );
                resultMap.put( 'intCount', intCount );

            }

            strSOQL +=  ' ORDER BY Id ' + sortDirection + ' LIMIT ' + intLimit;
            System.debug(
                'SOQL is ' +
                strSOQL
            );
            List < Account > listAccounts = Database.query( strSOQL );

            if ( listAccounts.size() > 0 ) {

                resultMap.put( 'accounts', listAccounts );
                resultMap.put( 'message', 'Successfully fetched Accounts' );

                if ( sortDirection == 'ASC' ) {

                    resultMap.put( 'firstAccountId', listAccounts.get( 0 ).Id );
                    resultMap.put( 'lastAccountId', listAccounts.get( listAccounts.size() - 1 ).Id );
                    
                } else if ( sortDirection == 'DESC' ) {

                    resultMap.put( 'lastAccountId', listAccounts.get( 0 ).Id );
                    resultMap.put( 'firstAccountId', listAccounts.get( listAccounts.size() - 1 ).Id );
    
                }

            } else {

                resultMap.put( 'message', 'No matching Accounts found' );

            }

        } else {

            resultMap.put( 'message', 'Account name is missing' );

        }

        return resultMap;
         
    }

}

Sample Lightning Web Component:

HTML:

<template>      
    <template lwc:if={isLoaded}>
        <lightning-spinner 
            alternative-text="Loading" 
            size="large">
        </lightning-spinner>
    </template>
    <lightning-card 
        title="Search Accounts" 
        icon-name="standard:account">  
        <lightning-layout vertical-align="center">
            <lightning-layout-item padding="around-small">
                <lightning-input
                    type="text"
                    label="Account Name"
                    class="slds-p-around_medium"
                    value={accountName} 
                    onchange={handleChange} >
                </lightning-input>
            </lightning-layout-item>
            <lightning-layout-item padding="around-small">
                <br/>
                <lightning-button 
                    variant="brand" 
                    label="Search Accounts" 
                    class="slds-p-around_medium"
                    icon-name="utility:record_lookup" 
                    onclick={handleAccountsSearch} >
                </lightning-button>
            </lightning-layout-item>
        </lightning-layout>
    </lightning-card>
    <template if:true={accounts}>      
        <lightning-card 
            title="Searched Accounts" 
            icon-name="standard:account">  
            <lightning-datatable
                key-field="Id"
                data={accounts}
                columns={columns}
                onrowselection={handleSelected}
                selected-rows={selectedAccountIds}>
            </lightning-datatable>  
            <div slot="footer">
                <lightning-button
                    label="Previous"
                    class="slds-p-around_medium"
                    onclick={handleAccountsSearch}
                    disabled={previousButtonDisabled}>
                </lightning-button>
                <lightning-button
                    label="Next"
                    class="slds-p-around_medium"
                    disabled={nextButtonDisabled}
                    onclick={handleAccountsSearch} >
                </lightning-button>
            </div>
        </lightning-card>
    </template>    
    <template if:true={selectedAccounts}>  
        <lightning-card 
            title="Selected Accounts"
            icon-name="standard:account">   
            <lightning-datatable
                key-field="Id"
                columns={columns}
                data={selectedAccounts}
                hide-checkbox-column
                show-row-number-column>
            </lightning-datatable> 
        </lightning-card>
    </template>  
</template>

JavaScript:

import { LightningElement } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import fetchAccounts from '@salesforce/apex/SampleLightningWebComponentController.fetchAccounts';
 
const COLUMNS = [   
    { label: 'Name', fieldName: 'Name' },
    { label: 'Industry', fieldName: 'Industry' },
    { label: 'Type', fieldName: 'Type' }
];

export default class SampleLightningWebComponent extends LightningElement {
     
    error;
    accounts;
    accountName;
    intCount = 0;
    intOffset = 0;
    lastAccountId;
    firstAccountId;
    isLoaded = false;
    selectedAccounts;
    columns = COLUMNS;
    selectedAccountIds = [];
    nextButtonDisabled = true;
    previousButtonDisabled = true;
    currentAccountIds = [];
    currentSelectedAccounts = [];
    currentSelectedAccountIds = [];

    handleChange( event ) {

        if ( event.target.label == 'Account Name' ) {

            this.accountName = event.target.value;

        } 

    }

    handleAccountsSearch( event ) {

        this.isLoaded = true;
        let sortDirection = 'ASC';
        let buttonName = event.target.label;
        this.accounts = undefined;
        console.log(
            'Button Clicked is',
            buttonName
        );
        console.log(
            'Account Name is',
            this.accountName
        );

        if ( buttonName == 'Search Accounts' ) {
            
            this.intOffset = 0;
            this.lastAccountId = '';
            this.selectedAccounts = [];
            this.selectedAccountIds = [];

        } else if ( buttonName == 'Previous' ) {

            sortDirection = 'DESC';
            
        } else if ( buttonName == 'Next' ) {
            
        }

        fetchAccounts( { 
            intCount : this.intCount,
            firstAccountId : this.firstAccountId,
            lastAccountId : this.lastAccountId, 
            sortDirection : sortDirection,
            strAccountName : this.accountName 
        } )
        .then( result => {

            console.log(
                'result is',
                JSON.stringify( result )
            );

            if ( result.accounts ) {

                if ( result.accounts.length > 0 ) {

                    this.accounts = result.accounts.sort( ( a, b ) => {
                        a = a[ 'Name' ] ? a[ 'Name' ].toLowerCase() : 'z'; 
                        b = b[ 'Name' ] ? b[ 'Name' ].toLowerCase() : 'z';
                        return a > b ? 1 : -1;
                    });

                    this.currentAccountIds = [];
                    this.accounts.forEach( ( row ) => {
                        this.currentAccountIds.push( row.Id );
                    } );

                    this.error = undefined;

                    if ( buttonName == 'Previous' ) {
            
                        this.intOffset -= 5;
                        
                    } else {
                        
                        this.intOffset += 5;

                    }

                    if ( result.firstAccountId ) {

                        this.firstAccountId = result.firstAccountId;
                        
                    }

                    if ( result.lastAccountId ) {

                        this.lastAccountId = result.lastAccountId;
                        
                    }

                    if ( result.intCount ) {

                        this.intCount = result.intCount;
                        
                    }

                    console.log(
                        'Offset is',
                        this.intOffset
                    );
                    console.log(
                        'Count is',
                        this.intCount
                    );

                    if ( this.intCount > this.intOffset ) {
                        
                        this.nextButtonDisabled = false;

                    } else {
                        
                        this.nextButtonDisabled = true;
                        
                    }

                    if ( this.intOffset > 5 ) {
                        
                        this.previousButtonDisabled = false;

                    } else {
                        
                        this.previousButtonDisabled = true;
                        
                    }
        
                } else {
        
                    this.dispatchEvent(
                        new ShowToastEvent( {
                            title: 'Error',
                            message: 'No matching Accounts found',
                            variant: 'error'
                        } )
                    );   
                    this.accounts = undefined;
        
                }

            } else {

                this.dispatchEvent(
                    new ShowToastEvent( {
                        title: 'Error',
                        message: result.message,
                        variant: 'error'
                    } )
                );   
                this.accounts = undefined;    
                this.error = undefined;        

            }

            this.isLoaded = false;

        } )
        .catch( error => {

            console.log(
                'Error is',
                JSON.stringify( error )
            );
            this.dispatchEvent(
                new ShowToastEvent( {
                    title: 'Error!!',
                    message: JSON.stringify( error ),
                    variant: 'error',
                    mode: 'sticky'
                } )
            );     
            this.accounts = undefined;    
            this.error = undefined;   
            this.isLoaded = false;

        } )

    }

    handleSelected( event ) {
        
        this.isLoaded = true;
        this.currentSelectedAccounts = [];
        this.currentSelectedAccountIds = [];
        let tempSelectedAccountIds = this.selectedAccountIds;
        let tempSelectedAccounts = this.selectedAccounts;
        console.log(
            'Selected Rows are',
            JSON.stringify(
                event.detail.selectedRows
            )
        );

        event.detail.selectedRows.map( row => {
            
            if ( row.Type !== 'Prospect' ) {

                this.currentSelectedAccounts.push( row );
                this.currentSelectedAccountIds.push( row.Id );

            } else {

                this.dispatchEvent(
                    new ShowToastEvent( {
                        variant: 'error',
                        title: 'Account not Available', 
                        message: 'Please select Non-Prospect Accounts only'
                    } )
                );

            }

        } );

        console.log(
            'Current Selected Accounts are',
            JSON.stringify(
                this.currentSelectedAccounts
            )
        );
        console.log(
            'Current Selected Account Ids are',
            JSON.stringify(
                this.currentSelectedAccountIds
            )
        );

        if ( 
            tempSelectedAccountIds.length == 0 && 
            this.currentSelectedAccountIds.length > 0 
        ) {

            console.log(
                'Inside Selected Accounts length 0 & current length > 0'
            );
            tempSelectedAccounts = this.currentSelectedAccounts;
            tempSelectedAccountIds = this.currentSelectedAccountIds;

        } else if ( 
            this.selectedAccountIds.length == 0 && 
            this.currentSelectedAccountIds.length == 0 
        ) {

            this.selectedAccounts = [];
            this.selectedAccountIds = [];

        } else if ( 
            this.selectedAccountIds.length > 0 && 
            this.currentSelectedAccountIds.length > 0 
        ) {

            event.detail.selectedRows.forEach( ( row ) => {
            
                if ( 
                    !tempSelectedAccountIds.includes( row.Id ) &&
                    this.currentSelectedAccountIds.includes( row.Id )
                ) {

                    console.log(
                        'Inside selected Account Ids not include' +
                        ' and Current Selection include'
                    );

                    if ( row.Id ) {

                        tempSelectedAccounts.push( row );
                        tempSelectedAccountIds.push( row.Id );

                    } 

                } 
                
            } );

        }

        this.currentAccountIds.forEach( ( rowId ) => {

            console.log(
                'Inside current Account Ids iteration',
                rowId
            );

            if ( 
                tempSelectedAccountIds.includes( rowId ) &&
                !this.currentSelectedAccountIds.includes( rowId ) 
            ) {

                console.log(
                    'Inside selected Account Ids include' +
                    ' and Current Selection not include'
                );

                if ( rowId ) {

                    tempSelectedAccounts.splice( 
                        tempSelectedAccountIds.indexOf( rowId ), 1 
                    );
                    tempSelectedAccountIds.splice( 
                        tempSelectedAccountIds.indexOf( rowId ), 1 
                    );

                } 

            }

        } );

        console.log(
            'tempSelectedAccountIds are',
            JSON.stringify( tempSelectedAccountIds )
        );

        if ( tempSelectedAccountIds.length > 0 ) {

            this.selectedAccounts = [ ...tempSelectedAccounts ];
            this.selectedAccountIds = [ ...tempSelectedAccountIds ];

        } else {

            this.selectedAccounts = [];
            this.selectedAccountIds = [];

        }

        console.log(
            'Selected Accounts are',
            JSON.stringify(
                this.selectedAccounts
            )
        );
        console.log(
            'Selected Account Ids are',
            JSON.stringify(
                this.selectedAccountIds
            )
        );
        this.isLoaded = false;

    }

}

js-meta.xml:

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>62.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__Tab</target>
    </targets>
</LightningComponentBundle>

Output:

Leave a Reply