function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Cody Drennon 6Cody Drennon 6 

Lightning:datatable Infinite Scroll

Hello,

So i am trying to implement the "Infinite Scrolling to Load More Rows" in a datatable.  I am trying to implement the example found at https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/aura_compref_lightning_datatable.htm.  However there is a helper method called in the JS Controller called "fetchData" that is not there and i cant for the life of me make it myself.  I have tried everything.  Any help would be greatly appreciated.

Please see code below.

Component:
<aura:component controller="ContactController" implements="force:appHostable,flexipage:availableForAllPageTypes" access="global">
    <aura:attribute name="mydata" type="Object" />
    <aura:attribute name="mycolumns" type="List"/>
    <aura:attribute name="isLoading" type="Boolean" default="true"/>
    <aura:attribute name="contactId" type="Id" />
    <aura:attribute name="sortedBy" type="String" />
    <aura:attribute name="sortedDirection" type="String" /> 
    <aura:attribute name="enableInfiniteLoading" type="Boolean" default="true"/>
    <aura:attribute name="initialRows" type="Integer" default="10"/>
    <aura:attribute name="rowsToLoad" type="Integer" default="50"/>
    <aura:attribute name="totalNumberOfRows" type="Integer" default="300"/>
    <aura:attribute name="loadMoreStatus" type="String" default=""/>
    <aura:handler name="init" value="{! this }" action="{! c.init }"/>
    <div style="height: 500px">
        <lightning:datatable data="{! v.mydata}" 
                             columns="{! v.mycolumns }" 
                             keyField="id"
                             onsort="{!c.updateColumnSorting}"
                             sortedBy="{!v.sortedBy}"
                             sortedDirection="{!v.sortedDirection}"
                             enableInfiniteLoading="true"
                             onloadmore="{! c.loadMoreData }"/>
        
    </div>
    {! v.loadMoreStatus}
</aura:component>

JS Controller:

({
    init: function (cmp, event, helper) {

        var actions = [
            { label: 'Show details', name: 'show_details' },
            { label: 'Delete', name: 'delete' }
        ];
        cmp.set('v.mycolumns', [
            // Other column data here
            { type: 'action', typeAttributes: { rowActions: actions } }
        ]);
        cmp.set('v.mycolumns', [
            {label: 'Contact Name', fieldName: 'Name', type: 'string', sortable: 'true'},
            {label: 'Phone', fieldName: 'Phone', type: 'phone', sortable: 'true'},
            {label: 'Email', fieldName: 'Email', type: 'email', sortable: 'true'}
        ]);
        helper.getData(cmp);
    },
    
    getSelectedName: function (cmp, event) {
        var selectedRows = event.getParam('selectedRows');
        // Display that fieldName of the selected rows
        for (var i = 0; i < selectedRows.length; i++){
            alert("You selected: " + selectedRows[i].Name);
        }
    },
    
    handleRowAction: function (cmp, event, helper) {
        var action = event.getParam('action');
        var row = event.getParam('row');
        switch (action.name) {
            case 'show_details':
                alert('Showing Details: ' + JSON.stringify(row));
                cmp.set('v.contactId', row.Id);
                alert(cmp.get('v.contactId'));
                $A.util.removeClass(cmp.find("childDiv"),'toggle');
                $A.util.addClass(cmp.find("listDiv"),'toggle');
                var attribute1 = cmp.get('v.contactId');
                var childComponent = cmp.find('child');
                childComponent.reInit(attribute1);
                break;
            case 'delete':
                var rows = cmp.get('v.mydata');
                var rowIndex = rows.indexOf(row);
                rows.splice(rowIndex, 1);
                cmp.set('v.mydata', rows);
                break;
        }
    },
    
    //Client-side controller called by the onsort event handler
    updateColumnSorting: function (cmp, event, helper) {
        var fieldName = event.getParam('fieldName');
        var sortDirection = event.getParam('sortDirection');
        // assign the latest attribute with the sorted column fieldName and sorted direction
        cmp.set("v.sortedBy", fieldName);
        cmp.set("v.sortedDirection", sortDirection);
        helper.sortData(cmp, fieldName, sortDirection);     
        
        
    },
    
    loadMoreData: function (cmp, event, helper) {
        //Display a spinner to signal that data is being loaded
        event.getSource().set("v.isLoading", true);
        //Display "Loading" when more data is being loaded
        cmp.set('v.loadMoreStatus', 'Loading');
        helper.fetchData(cmp, cmp.get('v.rowsToLoad'))
            .then($A.getCallback(function (data) {
                if (cmp.get('v.mydata').length >= cmp.get('v.totalNumberOfRows')) {
                    cmp.set('v.enableInfiniteLoading', false);
                    cmp.set('v.loadMoreStatus', 'No more data to load');
                } else {
                    var currentData = cmp.get('v.mydata');
                    //Appends new data to the end of the table
                    var newData = currentData.concat(data);
                    cmp.set('v.mydata', newData);
                    cmp.set('v.loadMoreStatus', '');
                }
               event.getSource().set("v.isLoading", false);
            }));
    }
    
})

HELPER

This is where i need the help to come up with a helper method that is referenced in the JS controller.  THANK YOU!

 
Best Answer chosen by Cody Drennon 6
Raj VakatiRaj Vakati
Here is the code you can refer ... You have complete Datatable features are in this examples including infinity scrolling 
 
public class BookController {
    @AuraEnabled
    public static List<Book_Categories__c> getBooksByAllCategories(){
        
        List<Book_Categories__c> categeroy = [Select  Name,(Select Name, Author_Name__c, Book_Categories__c,Publisher_Id__c from Books__r) from Book_Categories__c];
        return categeroy;
    } 
    
    @AuraEnabled
    public static List<Book_Categories__c> getBooksCategories(){
        List<Book_Categories__c> categeroy = [Select  Name,Id from Book_Categories__c];
        return categeroy;
    }
    @AuraEnabled
    public static List<Books__c> getBooks(Integer limits , Integer offsets){
        System.debug('limits'+limits);
        System.debug('offsets'+offsets);
        Integer intlimits = integer.valueof(limits);
        Integer intoffsets = integer.valueof(offsets);
        
        List<Books__c> books = [Select Name,Is_Available__c,CreatedDate,publish_status__c,
                                URL__c ,Author_Name__c, Book_Categories__c,Publisher_Id__c from Books__c Order by Name Limit :intlimits Offset :intoffsets];
        return books;
    } 
    
    @AuraEnabled
    public static void setBookStatus(String status , List<Books__c> books){
        System.debug('--'+status);
        System.debug('--books --'+books);
        for(Books__c b :books){
            b.publish_status__c = status ;
        }
        
        update books;
    } 
    
    @AuraEnabled
    public static Integer getTotalCount(){
        AggregateResult results = [select  count(Id) total  from Books__c ];
        Integer total =(Integer)results.get('total') ; 
        return total;
    } 
    @AuraEnabled
    public static void deleteBooks(String ids ){
        Delete [Select id from Books__c where  id=:ids];
    } 
    
    
}
 
<aura:component controller="BookController" implements="force:appHostable">
    <!-- attributes -->
    <aura:attribute name="data" type="Object"/>
    <aura:attribute name="columns" type="List"/>
    <aura:attribute name="selectedRowsCount" type="Integer" default="0"/>
    <aura:attribute name="selectedRowsDetails" type="Object" />
    <aura:attribute name="selectedRowsList" type="List" />
    
    <aura:attribute name="maxRowSelection" type="Integer" default="5"/>
    <aura:attribute name="selectedRows" type="List" />
    
    <!--- enableInfiniteLoading  -->
    <aura:attribute name="enableInfiniteLoading" type="Boolean" default="true"/>
    <aura:attribute name="initialRows" type="Integer" default="30"/>
    <aura:attribute name="rowsToLoad" type="Integer" default="10"/>
    <aura:attribute name="totalNumberOfRows" type="Integer" default="10"/>
    <aura:attribute name="loadMoreStatus" type="String" default="Loading .... "/>
    <aura:attribute name="showRowNumberColumn" type="Boolean" default="false"/>
    <aura:attribute name="rowNumberOffset" type="Integer" default="0"/>
    <aura:attribute name="rowsToAdd" type="Integer" default="10"/>
    <aura:attribute name="currentCount" type="Integer" default="10"/>
    
    <aura:attribute name="activeFilter" type="string" default="All" description="The currently selected actions filter"/>
    
    <aura:attribute name="sortedBy" type="String"/>
    <aura:attribute name="sortedDirection" type="String"/>
    <aura:attribute name="defaultSortDirection" type="String"/>
    
    <!-- handlers-->
    <aura:handler name="init" value="{! this }" action="{! c.doInit }"/>
    <div class="my-custom-background">
        <lightning:button label="Complete"  variant="brand" onclick="{!c.handleSelect}"/>
        <lightning:button label="In Completed"  variant="brand" onclick="{!c.handleSelect}"/>
        <lightning:button label="Pre Order"  variant="brand" onclick="{!c.handleSelect}"/>
        
    </div>
    <!-- the container element determine the height of the datatable -->
    <div style="height: 600px">
        <h1> Total Rowns : {! v.totalNumberOfRows}</h1>
        <h1>Selected Rows: {! v.selectedRowsCount }</h1>
        <h1>Selected Objects: {! v.selectedRowsDetails }</h1>
        <h1>Current Offset : {! v.currentCount }</h1>
        
        
        <lightning:datatable columns="{! v.columns }"
                             data="{! v.data }"
                             keyField="id"
                             showRowNumberColumn="true"
                             rowNumberOffset="0"
                             onrowaction="{! c.handleRowAction }"
                             selectedRows="{! v.selectedRows }"
                             maxRowSelection="{! v.maxRowSelection }"
                             onrowselection="{! c.updateSelectedText }"
                             enableInfiniteLoading="true"
                             loadMoreOffset="{! v.loadMoreOffset }"
                             onheaderaction="{! c.handleHeaderAction }"
                             sortedBy="{! v.sortedBy }"
                             sortedDirection="{! v.sortedDirection }"
                             defaultSortDirection="{! v.defaultSortDirection }"
                             onsort="{! c.updateColumnSorting }"
                             onloadmore="{! c.loadMoreData }"/>
    </div>
    {! v.loadMoreStatus }
    
</aura:component>
 
({
    doInit : function(component, event, helper) {
        
        var totalCnt = component.get("c.getTotalCount");
        totalCnt.setCallback(this, function(a) {
            component.set("v.totalNumberOfRows", a.getReturnValue());
        });
        $A.enqueueAction(totalCnt);
        
        
        var actions = [
            { label: 'Show details', name: 'show_details' },
            { label: 'Delete', name: 'delete' }
        ];
        var headerActions = [
            {
                label: 'All',
                checked: true,
                name:'All'
            },
            {
                label: 'Completed',
                checked: false,
                name:'Completed'
            },
            {
                label: 'In Completed',
                checked: false,
                name:'In Completed'
            },
            {
                label: 'Pre Order',
                checked: false,
                name:'Pre Order'
            }
        ];
        
        component.set('v.columns', [
            {label: 'Name', fieldName: 'Name', type: 'text',sortable:true ,actions: headerActions},
            {label: 'URL', fieldName: 'URL__c', type: 'url',sortable:true,actions: headerActions},
            {label: 'Author Name', fieldName: 'Author_Name__c', type: 'text',sortable:true},
            {label: 'publish status', fieldName: 'publish_status__c', type: 'text',sortable:true},
            {label: 'Publisher Id', fieldName: 'Publisher_Id__c', type: 'text',sortable:true,actions: headerActions},
            { type: 'action', typeAttributes: { rowActions: actions } } 
        ]);
        helper.getData(component);
    },
    updateSelectedText : function(component, event, helper){
        var selectedRows = event.getParam('selectedRows');
        //  console.log('selectedRows'+selectedRows);
        component.set("v.selectedRowsCount" ,selectedRows.length );
        let obj =[] ; 
        for (var i = 0; i < selectedRows.length; i++){
            
            obj.push({Name:selectedRows[i].Name});
            
        }
        
        
        component.set("v.selectedRowsDetails" ,JSON.stringify(obj) );
        component.set("v.selectedRowsList" ,event.getParam('selectedRows') );
        
    },
    handleSelect: function (component, event, helper) {
        var arr = component.get('v.data');
        var obj =  component.get("v.selectedRowsList");
        console.log('obj '+JSON.stringify(obj) );
        var selectedButtonLabel = event.getSource().get("v.label");
        console.log('Button label: ' + selectedButtonLabel);
        var updateAction = component.get("c.setBookStatus");
        updateAction.setParams({ status : selectedButtonLabel , books: obj});
        updateAction.setCallback(this, function(a) {
            $A.get('e.force:refreshView').fire();
        });
        $A.enqueueAction(updateAction);
        
        
        
    },
    loadMoreData: function (component, event, helper) {
        //Display a spinner to signal that data is being loaded
        event.getSource().set("v.isLoading", true);
        //Display "Loading" when more data is being loaded
        component.set('v.loadMoreStatus', 'Loading');
        helper.fetchData(component, component.get('v.rowsToLoad')).then($A.getCallback(function (data) {
            if (component.get('v.data').length >= component.get('v.totalNumberOfRows')) {
                component.set('v.enableInfiniteLoading', false);
                component.set('v.loadMoreStatus', 'No more data to load');
            } else {
                var currentData = component.get('v.data');
                //Appends new data to the end of the table
                var newData = currentData.concat(data);
                component.set('v.data', newData);
                component.set('v.loadMoreStatus', 'Please wait ');
            }
            event.getSource().set("v.isLoading", false);
        }));
    },
    
    
    handleRowAction: function (cmp, event, helper) {
        var action = event.getParam('action');
        var row = event.getParam('row');
        switch (action.name) {
            case 'show_details':
                var navEvt = $A.get("e.force:navigateToSObject");
                navEvt.setParams({
                    "recordId": row.Id,
                    "slideDevName": "detail"
                });
                navEvt.fire();
                break;
            case 'delete':
                var rows = cmp.get('v.data');
                var rowIndex = rows.indexOf(row);
                console.log('rowIndex'+rowIndex);
                console.log('rowIndex row'+rows[rowIndex].Id);
                var deleteAct = cmp.get("c.deleteBooks");
                deleteAct.setParams({ ids : rows[rowIndex].Id });
                $A.enqueueAction(deleteAct);
                var toastEvent = $A.get("e.force:showToast");
                toastEvent.setParams({
                    "title": "Success!",
                    "message": "The record has been delete successfully."
                });
                toastEvent.fire();
                rows.splice(rowIndex, 1);
                cmp.set('v.data', rows);
                break;
        }
    },
    handleHeaderAction: function (cmp, event, helper) {
        
        // helper.getData(cmp);
        
        
        var actionName = event.getParam('action').name;
        var colDef = event.getParam('columnDefinition');
        var columns = cmp.get('v.columns');
        var activeFilter = cmp.get('v.activeFilter');
        console.log('activeFilter-->'+activeFilter);
        if (actionName !== activeFilter) {
            var idx = columns.indexOf(colDef);
            var actions = columns[idx].actions;
            console.log('actions'+actions)
            actions.forEach(function (action) {
                action.checked = action.name === actionName;
            });
            cmp.set('v.activeFilter', actionName);
            helper.updateBooks(cmp);
            cmp.set('v.columns', columns);
        }
    },
    
    // Client-side controller called by the onsort event handler
    updateColumnSorting: function (cmp, event, helper) {
        var fieldName = event.getParam('fieldName');
        var sortDirection = event.getParam('sortDirection');
        // assign the latest attribute with the sorted column fieldName and sorted direction
        cmp.set("v.sortedBy", fieldName);
        cmp.set("v.sortedDirection", sortDirection);
        helper.sortData(cmp, fieldName, sortDirection);
    },
    
    
    
    
    
})
 
({
    getData : function(component) {
        
        var action = component.get("c.getBooks");
        action.setParams({
            "limits": component.get("v.initialRows"),
            "offsets": component.get("v.rowNumberOffset")
        });
        action.setCallback(this, function(a) {
            component.set("v.data", a.getReturnValue());
            component.set("v.currentCount", component.get("v.initialRows"));
            
        });
        $A.enqueueAction(action);
    },
    sortData: function (cmp, fieldName, sortDirection) {
        var data = cmp.get("v.data");
        var reverse = sortDirection !== 'asc';
        //sorts the rows based on the column header that's clicked
        data.sort(this.sortBy(fieldName, reverse))
        cmp.set("v.data", data);
    },
    sortBy: function (field, reverse, primer) {
        var key = primer ?
            function(x) {return primer(x[field])} :
        function(x) {return x[field]};
        //checks if the two rows should switch places
        reverse = !reverse ? 1 : -1;
        return function (a, b) {
            return a = key(a), b = key(b), reverse * ((a > b) - (b > a));
        }
    },
    fetchData: function(component , rows){
        return new Promise($A.getCallback(function(resolve, reject) {
            var currentDatatemp = component.get('c.getBooks');
            var counts = component.get("v.currentCount");
            currentDatatemp.setParams({
                "limits": component.get("v.initialRows"),
                "offsets": counts 
            });
            currentDatatemp.setCallback(this, function(a) {
                resolve(a.getReturnValue());
                var countstemps = component.get("v.currentCount");
                countstemps = countstemps+component.get("v.initialRows");
                component.set("v.currentCount",countstemps);
                
            });
            $A.enqueueAction(currentDatatemp);
            
            
        }));
        
    } ,
    updateBooks: function (cmp) {
        var rows = cmp.get('v.data');
        var activeFilter = cmp.get('v.activeFilter');
        console.log('activeFilter Helper'+activeFilter)
        var filteredRows = rows;
        if (activeFilter == 'All') {
            return  rows; 
        }
        if (activeFilter !== 'All') {
            filteredRows = rows.filter(function (row) {
                console.log('Each Row'+row.publish_status__c);
                if(row.publish_status__c == activeFilter){
                    return  row; 
                }
                // return (activeFilter === 'In_Completed') ||(activeFilter === 'Pre_Order');
            });
        }
        cmp.set('v.data', filteredRows);
    },
    
})

 

All Answers

Raj VakatiRaj Vakati
Here is the code you can refer ... You have complete Datatable features are in this examples including infinity scrolling 
 
public class BookController {
    @AuraEnabled
    public static List<Book_Categories__c> getBooksByAllCategories(){
        
        List<Book_Categories__c> categeroy = [Select  Name,(Select Name, Author_Name__c, Book_Categories__c,Publisher_Id__c from Books__r) from Book_Categories__c];
        return categeroy;
    } 
    
    @AuraEnabled
    public static List<Book_Categories__c> getBooksCategories(){
        List<Book_Categories__c> categeroy = [Select  Name,Id from Book_Categories__c];
        return categeroy;
    }
    @AuraEnabled
    public static List<Books__c> getBooks(Integer limits , Integer offsets){
        System.debug('limits'+limits);
        System.debug('offsets'+offsets);
        Integer intlimits = integer.valueof(limits);
        Integer intoffsets = integer.valueof(offsets);
        
        List<Books__c> books = [Select Name,Is_Available__c,CreatedDate,publish_status__c,
                                URL__c ,Author_Name__c, Book_Categories__c,Publisher_Id__c from Books__c Order by Name Limit :intlimits Offset :intoffsets];
        return books;
    } 
    
    @AuraEnabled
    public static void setBookStatus(String status , List<Books__c> books){
        System.debug('--'+status);
        System.debug('--books --'+books);
        for(Books__c b :books){
            b.publish_status__c = status ;
        }
        
        update books;
    } 
    
    @AuraEnabled
    public static Integer getTotalCount(){
        AggregateResult results = [select  count(Id) total  from Books__c ];
        Integer total =(Integer)results.get('total') ; 
        return total;
    } 
    @AuraEnabled
    public static void deleteBooks(String ids ){
        Delete [Select id from Books__c where  id=:ids];
    } 
    
    
}
 
<aura:component controller="BookController" implements="force:appHostable">
    <!-- attributes -->
    <aura:attribute name="data" type="Object"/>
    <aura:attribute name="columns" type="List"/>
    <aura:attribute name="selectedRowsCount" type="Integer" default="0"/>
    <aura:attribute name="selectedRowsDetails" type="Object" />
    <aura:attribute name="selectedRowsList" type="List" />
    
    <aura:attribute name="maxRowSelection" type="Integer" default="5"/>
    <aura:attribute name="selectedRows" type="List" />
    
    <!--- enableInfiniteLoading  -->
    <aura:attribute name="enableInfiniteLoading" type="Boolean" default="true"/>
    <aura:attribute name="initialRows" type="Integer" default="30"/>
    <aura:attribute name="rowsToLoad" type="Integer" default="10"/>
    <aura:attribute name="totalNumberOfRows" type="Integer" default="10"/>
    <aura:attribute name="loadMoreStatus" type="String" default="Loading .... "/>
    <aura:attribute name="showRowNumberColumn" type="Boolean" default="false"/>
    <aura:attribute name="rowNumberOffset" type="Integer" default="0"/>
    <aura:attribute name="rowsToAdd" type="Integer" default="10"/>
    <aura:attribute name="currentCount" type="Integer" default="10"/>
    
    <aura:attribute name="activeFilter" type="string" default="All" description="The currently selected actions filter"/>
    
    <aura:attribute name="sortedBy" type="String"/>
    <aura:attribute name="sortedDirection" type="String"/>
    <aura:attribute name="defaultSortDirection" type="String"/>
    
    <!-- handlers-->
    <aura:handler name="init" value="{! this }" action="{! c.doInit }"/>
    <div class="my-custom-background">
        <lightning:button label="Complete"  variant="brand" onclick="{!c.handleSelect}"/>
        <lightning:button label="In Completed"  variant="brand" onclick="{!c.handleSelect}"/>
        <lightning:button label="Pre Order"  variant="brand" onclick="{!c.handleSelect}"/>
        
    </div>
    <!-- the container element determine the height of the datatable -->
    <div style="height: 600px">
        <h1> Total Rowns : {! v.totalNumberOfRows}</h1>
        <h1>Selected Rows: {! v.selectedRowsCount }</h1>
        <h1>Selected Objects: {! v.selectedRowsDetails }</h1>
        <h1>Current Offset : {! v.currentCount }</h1>
        
        
        <lightning:datatable columns="{! v.columns }"
                             data="{! v.data }"
                             keyField="id"
                             showRowNumberColumn="true"
                             rowNumberOffset="0"
                             onrowaction="{! c.handleRowAction }"
                             selectedRows="{! v.selectedRows }"
                             maxRowSelection="{! v.maxRowSelection }"
                             onrowselection="{! c.updateSelectedText }"
                             enableInfiniteLoading="true"
                             loadMoreOffset="{! v.loadMoreOffset }"
                             onheaderaction="{! c.handleHeaderAction }"
                             sortedBy="{! v.sortedBy }"
                             sortedDirection="{! v.sortedDirection }"
                             defaultSortDirection="{! v.defaultSortDirection }"
                             onsort="{! c.updateColumnSorting }"
                             onloadmore="{! c.loadMoreData }"/>
    </div>
    {! v.loadMoreStatus }
    
</aura:component>
 
({
    doInit : function(component, event, helper) {
        
        var totalCnt = component.get("c.getTotalCount");
        totalCnt.setCallback(this, function(a) {
            component.set("v.totalNumberOfRows", a.getReturnValue());
        });
        $A.enqueueAction(totalCnt);
        
        
        var actions = [
            { label: 'Show details', name: 'show_details' },
            { label: 'Delete', name: 'delete' }
        ];
        var headerActions = [
            {
                label: 'All',
                checked: true,
                name:'All'
            },
            {
                label: 'Completed',
                checked: false,
                name:'Completed'
            },
            {
                label: 'In Completed',
                checked: false,
                name:'In Completed'
            },
            {
                label: 'Pre Order',
                checked: false,
                name:'Pre Order'
            }
        ];
        
        component.set('v.columns', [
            {label: 'Name', fieldName: 'Name', type: 'text',sortable:true ,actions: headerActions},
            {label: 'URL', fieldName: 'URL__c', type: 'url',sortable:true,actions: headerActions},
            {label: 'Author Name', fieldName: 'Author_Name__c', type: 'text',sortable:true},
            {label: 'publish status', fieldName: 'publish_status__c', type: 'text',sortable:true},
            {label: 'Publisher Id', fieldName: 'Publisher_Id__c', type: 'text',sortable:true,actions: headerActions},
            { type: 'action', typeAttributes: { rowActions: actions } } 
        ]);
        helper.getData(component);
    },
    updateSelectedText : function(component, event, helper){
        var selectedRows = event.getParam('selectedRows');
        //  console.log('selectedRows'+selectedRows);
        component.set("v.selectedRowsCount" ,selectedRows.length );
        let obj =[] ; 
        for (var i = 0; i < selectedRows.length; i++){
            
            obj.push({Name:selectedRows[i].Name});
            
        }
        
        
        component.set("v.selectedRowsDetails" ,JSON.stringify(obj) );
        component.set("v.selectedRowsList" ,event.getParam('selectedRows') );
        
    },
    handleSelect: function (component, event, helper) {
        var arr = component.get('v.data');
        var obj =  component.get("v.selectedRowsList");
        console.log('obj '+JSON.stringify(obj) );
        var selectedButtonLabel = event.getSource().get("v.label");
        console.log('Button label: ' + selectedButtonLabel);
        var updateAction = component.get("c.setBookStatus");
        updateAction.setParams({ status : selectedButtonLabel , books: obj});
        updateAction.setCallback(this, function(a) {
            $A.get('e.force:refreshView').fire();
        });
        $A.enqueueAction(updateAction);
        
        
        
    },
    loadMoreData: function (component, event, helper) {
        //Display a spinner to signal that data is being loaded
        event.getSource().set("v.isLoading", true);
        //Display "Loading" when more data is being loaded
        component.set('v.loadMoreStatus', 'Loading');
        helper.fetchData(component, component.get('v.rowsToLoad')).then($A.getCallback(function (data) {
            if (component.get('v.data').length >= component.get('v.totalNumberOfRows')) {
                component.set('v.enableInfiniteLoading', false);
                component.set('v.loadMoreStatus', 'No more data to load');
            } else {
                var currentData = component.get('v.data');
                //Appends new data to the end of the table
                var newData = currentData.concat(data);
                component.set('v.data', newData);
                component.set('v.loadMoreStatus', 'Please wait ');
            }
            event.getSource().set("v.isLoading", false);
        }));
    },
    
    
    handleRowAction: function (cmp, event, helper) {
        var action = event.getParam('action');
        var row = event.getParam('row');
        switch (action.name) {
            case 'show_details':
                var navEvt = $A.get("e.force:navigateToSObject");
                navEvt.setParams({
                    "recordId": row.Id,
                    "slideDevName": "detail"
                });
                navEvt.fire();
                break;
            case 'delete':
                var rows = cmp.get('v.data');
                var rowIndex = rows.indexOf(row);
                console.log('rowIndex'+rowIndex);
                console.log('rowIndex row'+rows[rowIndex].Id);
                var deleteAct = cmp.get("c.deleteBooks");
                deleteAct.setParams({ ids : rows[rowIndex].Id });
                $A.enqueueAction(deleteAct);
                var toastEvent = $A.get("e.force:showToast");
                toastEvent.setParams({
                    "title": "Success!",
                    "message": "The record has been delete successfully."
                });
                toastEvent.fire();
                rows.splice(rowIndex, 1);
                cmp.set('v.data', rows);
                break;
        }
    },
    handleHeaderAction: function (cmp, event, helper) {
        
        // helper.getData(cmp);
        
        
        var actionName = event.getParam('action').name;
        var colDef = event.getParam('columnDefinition');
        var columns = cmp.get('v.columns');
        var activeFilter = cmp.get('v.activeFilter');
        console.log('activeFilter-->'+activeFilter);
        if (actionName !== activeFilter) {
            var idx = columns.indexOf(colDef);
            var actions = columns[idx].actions;
            console.log('actions'+actions)
            actions.forEach(function (action) {
                action.checked = action.name === actionName;
            });
            cmp.set('v.activeFilter', actionName);
            helper.updateBooks(cmp);
            cmp.set('v.columns', columns);
        }
    },
    
    // Client-side controller called by the onsort event handler
    updateColumnSorting: function (cmp, event, helper) {
        var fieldName = event.getParam('fieldName');
        var sortDirection = event.getParam('sortDirection');
        // assign the latest attribute with the sorted column fieldName and sorted direction
        cmp.set("v.sortedBy", fieldName);
        cmp.set("v.sortedDirection", sortDirection);
        helper.sortData(cmp, fieldName, sortDirection);
    },
    
    
    
    
    
})
 
({
    getData : function(component) {
        
        var action = component.get("c.getBooks");
        action.setParams({
            "limits": component.get("v.initialRows"),
            "offsets": component.get("v.rowNumberOffset")
        });
        action.setCallback(this, function(a) {
            component.set("v.data", a.getReturnValue());
            component.set("v.currentCount", component.get("v.initialRows"));
            
        });
        $A.enqueueAction(action);
    },
    sortData: function (cmp, fieldName, sortDirection) {
        var data = cmp.get("v.data");
        var reverse = sortDirection !== 'asc';
        //sorts the rows based on the column header that's clicked
        data.sort(this.sortBy(fieldName, reverse))
        cmp.set("v.data", data);
    },
    sortBy: function (field, reverse, primer) {
        var key = primer ?
            function(x) {return primer(x[field])} :
        function(x) {return x[field]};
        //checks if the two rows should switch places
        reverse = !reverse ? 1 : -1;
        return function (a, b) {
            return a = key(a), b = key(b), reverse * ((a > b) - (b > a));
        }
    },
    fetchData: function(component , rows){
        return new Promise($A.getCallback(function(resolve, reject) {
            var currentDatatemp = component.get('c.getBooks');
            var counts = component.get("v.currentCount");
            currentDatatemp.setParams({
                "limits": component.get("v.initialRows"),
                "offsets": counts 
            });
            currentDatatemp.setCallback(this, function(a) {
                resolve(a.getReturnValue());
                var countstemps = component.get("v.currentCount");
                countstemps = countstemps+component.get("v.initialRows");
                component.set("v.currentCount",countstemps);
                
            });
            $A.enqueueAction(currentDatatemp);
            
            
        }));
        
    } ,
    updateBooks: function (cmp) {
        var rows = cmp.get('v.data');
        var activeFilter = cmp.get('v.activeFilter');
        console.log('activeFilter Helper'+activeFilter)
        var filteredRows = rows;
        if (activeFilter == 'All') {
            return  rows; 
        }
        if (activeFilter !== 'All') {
            filteredRows = rows.filter(function (row) {
                console.log('Each Row'+row.publish_status__c);
                if(row.publish_status__c == activeFilter){
                    return  row; 
                }
                // return (activeFilter === 'In_Completed') ||(activeFilter === 'Pre_Order');
            });
        }
        cmp.set('v.data', filteredRows);
    },
    
})

 
This was selected as the best answer
Cody Drennon 6Cody Drennon 6
Raj V, Thank you so much! Worked like a charm!
Etienne DKEtienne DK
thanks, this was v useful
Shawn AbadieShawn Abadie
Hi, thanks for this.  I'm having a problem though:

-The loadMoreOffset attribute needs to be set in the component, correct?
-when I run this, using console logs, I can see that there is an infinite loop where the code is constantly calling "loadmoredata", even though the total number of records (2) is less than the rows to load limit (10).  It is constantly trying to load more data, and I cannot figure out why.
 
Pranav Parakh 4Pranav Parakh 4
Hi Shawn Abadie,

This is because you are never disabling Infinte scrolling.
Here's change to fix this refering the best answer.

For lightning:datatable in component replace enableInfiniteLoading="true"  to enableInfiniteLoading="{! v.enableInfiniteLoading}"
Shawn AbadieShawn Abadie
Thanks, Pranav.  I'm now running into a strange case where loadMoreData is being called immediately after the initial records are loaded, and it keeps on getting called until all teh records are loaded.  Instead of a scrollable table with 10 rows, I end up with all 42 rows loaded and displayed in the table and I can't seem to find the problem.
Pranav Parakh 4Pranav Parakh 4
I hope you have enclosed your datatable inside <div style="height: 600px"></div> so as to restrict the height of datatable. Moreover try to create more dummy data (around 150 records) to see if it works.
Narasimhan ChenduruNarasimhan Chenduru
Shawn, I am also facing a similar issue. Could you please share if you have found any solution to this.
Bhupendra Kshatri 6Bhupendra Kshatri 6
One of the best Article. Many Thanks.
Altough i have one observation : performin header Action : It loads the filtered record at the last line cmp.set('v.data', filteredRows);
but it also executes the infite loading causing other ten records to populate. SO using cmp.set('v.enableInfiniteLoading', false);
befoe the last line will solve the issue.

ANother issu eis once i have filtered on "Completed" i need to refresh the page to filter with another.
NeetikaNeetika
Did anyone face "Maximum SOQL offset allowed is 2000" error with the same functionality ? I have more than 12000 records to display with loadmore functinbality and getting this error in between. 
Prashanth MPrashanth M
@ Raj Vakati kindly upload the screenshot or details of book__c and  Book_Categories__c object, if anyone else can, pls do it
{tushar-sharma}{tushar-sharma}
While the answer works great, there are many user who is facing issue. So I have written a blog post where I have created same component which is dynamic and can be used any supported object. Which include many features including lazy loading, inlin edit and many more.

https://newstechnologystuff.com/2019/01/01/lightning-datatable-lazy-loading-with-inline-editing-and-actions/
Yogendra JangidYogendra Jangid
Hi, if you plan to build out a solution on lazy loading or infinite loading in LWC, check out on blog
https://inevitableyogendra.blogspot.com/2021/05/infinite-loading-in-lightning-datatable.html (https://inevitableyogendra.blogspot.com/2021/05/infinite-loading-in-lightning-datatable.html" target="_blank)
You will get many view and approaches to implement the solution.
Mario Quiroz 6Mario Quiroz 6
Im trying to replicate the solution but it is looping every time, its like  enableInfiniteLoading="{!v.enableInfiniteLoading}"   attribute doesnt stop, it loads the data in chunks automatically, not allowing the offset work properly, this is in aura, anyone has the explanation of this behaviour?