
Custom Data Table With Pagination And Checkbox Functionality In Salesforce Lightning
Hello guys, today in this post we are going to learn how we can create custom lightning data table with client side JavaScript pagination buttons and row level checkbox functionality using salesforce lightning component. In this component we’ll also persist checkbox checked state on pagination.
Component Features Highlights :
- Client Side JavaScript Pagination.
- Inline Checkbox Functionality.
- Get Selected Rows in Controller.
- Total Records Count
- Selected Records Count
- Select/Deselect All Checkbox

Apex Controller : dataTableCtrl
/* API : 49.00 Author : Rahul Joshi Source : sfdcMonkey.com Date : Dec,2020 */ public class dataTableCtrl{ @AuraEnabled public static List<accountListWrapper> fetchAccountWrapper(){ List<accountListWrapper> lstaccountListWrapper = new List<accountListWrapper>(); // query account records and create 'accountListWrapper' class instance for each record. for(Account acc : [Select id,Name,Type,Phone From Account Limit 1000]){ // by default checkbox should be false lstaccountListWrapper.add(new accountListWrapper(false,acc)); } // return the 'lstaccountListWrapper' list return lstaccountListWrapper; } /* wrapper class */ public class accountListWrapper { @AuraEnabled public boolean isChecked {get;set;} @AuraEnabled public account objAccount{get;set;} public accountListWrapper(boolean isChecked, account objAccount){ this.isChecked = isChecked; this.objAccount = objAccount; } } }
- Check code comments.
Lightning Component : tableWithPC.cmp
<!-- API : 49.00 Author : Rahul Joshi Source : sfdcMonkey.com Date : Dec,2020 --> <aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" access="global" controller="dataTableCtrl"> <!-- call doInit method on component load --> <aura:handler name="init" value="{!this}" action="{!c.doInit}"/> <!-- aura attributes to store data/values --> <aura:attribute name="listOfAllAccounts" type="list"/> <aura:attribute name="PaginationList" type="list"/> <aura:attribute name="selectedCount" type="integer" default="0" description="selected Records Count"/> <aura:attribute name="startPage" type="Integer" /> <aura:attribute name="endPage" type="Integer"/> <aura:attribute name="totalRecordsCount" type="Integer"/> <aura:attribute name="pageSize" type="Integer" default="4" description="number of records to be display on per page"/> <aura:attribute name="currentPage" type="integer" default="1"/> <aura:attribute name="totalPagesCount" type="integer"/> <aura:attribute name="bNoRecordsFound" type="boolean"/> <aura:if isTrue="{!v.bNoRecordsFound}"> <!--display error message if there is no records available --> <div class="slds-notify slds-notify_alert slds-theme_alert-texture slds-theme_info" role="alert"> <span class="slds-assistive-text">error</span> <h2>No record found.</h2> </div> <aura:set attribute="else"> <!-- lightning:button to get selected rows data --> <div class="slds-clearfix slds-m-around_small"> <div class="slds-clearfix"> <div class="slds-float_right"> <lightning:button variant="destructive" label="Get Selected Records" onclick="{! c.getSelectedRecords }" disabled="{!v.selectedCount == 0}"/> </div> </div> </div> <!-- display total record and selected record count --> <p class="slds-m-around_small"> <span class="slds-badge slds-badge_lightest" style="display:inline-block"> Total Records : {!v.selectedCount > 0 ? v.selectedCount + '/' : ''} {!v.totalRecordsCount} </span> </p> <!-- data table start--> <table class="slds-table slds-table_bordered slds-table_cell-buffer"> <thead> <tr class="slds-text-title_caps"> <!--header checkbox for select all--> <th style="width:3.25rem;" class="slds-text-align_right"> <div class="slds-form-element"> <div class="slds-form-element__control"> <label class="slds-checkbox"> <ui:inputCheckbox disabled="{!v.totalRecordsCount == 0}" aura:id="selectAllId" change="{!c.selectAllCheckbox}"/> <span class="slds-checkbox_faux"></span> <span class="slds-form-element__label"></span> </label> </div> </div> </th> <th scope="col"> <div class="slds-truncate" title="Name">Name</div> </th> <th scope="col"> <div class="slds-truncate" title="Phone">Phone</div> </th> <th scope="col"> <div class="slds-truncate" title="Type">Type</div> </th> </tr> </thead> <tbody> <aura:iteration items="{!v.PaginationList}" var="obj"> <tr> <th scope="row" class="slds-text-align_right" style="width:3.25rem;"> <div class="slds-form-element"> <div class="slds-form-element__control"> <label class="slds-checkbox"> <ui:inputCheckbox text="{!obj.objAccount.Id}" value="{!obj.isChecked}" change="{!c.checkboxSelect}"/> <span class="slds-checkbox_faux"></span> <span class="slds-form-element__label text"></span> </label> </div> </div> </th> <th scope="row"> <div class="slds-truncate" title="{!obj.objAccount.Name}"> {!obj.objAccount.Name} </div> </th> <th scope="row"> <div class="slds-truncate" title="{!obj.objAccount.Phone}"> <lightning:formattedPhone value="{!obj.objAccount.Phone}"/> </div> </th> <th scope="row"> <div class="slds-truncate" title="{!obj.objAccount.Type}"> {!obj.objAccount.Type} </div> </th> </tr> </aura:iteration> </tbody> </table> <!-- DataTable End --> <br/> <!-- Pagination Buttons Start --> <div class="slds-align_absolute-center"> <lightning:button label="Previous" disabled="{!v.startPage == 0}" onclick="{!c.navigation}" variant="brand" iconName="utility:back" name="previous"/> <span class="slds-badge slds-badge_lightest" style="margin-right: 10px;margin-left: 10px;"> Page {!v.currentPage} out of {!v.totalPagesCount} </span> <lightning:button label="Next" disabled="{!(v.endPage + 1) >= v.totalRecordsCount}" onclick="{!c.navigation}" variant="brand" iconName="utility:forward" iconPosition="right" name="next"/> </div> <!-- Pagination Buttons End --> </aura:set> </aura:if> </aura:component>
- Check code comments.
JavaScript Controller : tableWithPCController.js
({ doInit: function(component, event, helper) { helper.doInitHelper(component, event); }, /* javaScript function for pagination */ navigation: function(component, event, helper) { var sObjectList = component.get("v.listOfAllAccounts"); var end = component.get("v.endPage"); var start = component.get("v.startPage"); var pageSize = component.get("v.pageSize"); var whichBtn = event.getSource().get("v.name"); // check if whichBtn value is 'next' then call 'next' helper method if (whichBtn == 'next') { component.set("v.currentPage", component.get("v.currentPage") + 1); helper.next(component, event, sObjectList, end, start, pageSize); } // check if whichBtn value is 'previous' then call 'previous' helper method else if (whichBtn == 'previous') { component.set("v.currentPage", component.get("v.currentPage") - 1); helper.previous(component, event, sObjectList, end, start, pageSize); } }, selectAllCheckbox: function(component, event, helper) { var selectedHeaderCheck = event.getSource().get("v.value"); var updatedAllRecords = []; var updatedPaginationList = []; var listOfAllAccounts = component.get("v.listOfAllAccounts"); var PaginationList = component.get("v.PaginationList"); // play a for loop on all records list for (var i = 0; i < listOfAllAccounts.length; i++) { // check if header checkbox is 'true' then update all checkbox with true and update selected records count // else update all records with false and set selectedCount with 0 if (selectedHeaderCheck == true) { listOfAllAccounts[i].isChecked = true; component.set("v.selectedCount", listOfAllAccounts.length); } else { listOfAllAccounts[i].isChecked = false; component.set("v.selectedCount", 0); } updatedAllRecords.push(listOfAllAccounts[i]); } // update the checkbox for 'PaginationList' based on header checbox for (var i = 0; i < PaginationList.length; i++) { if (selectedHeaderCheck == true) { PaginationList[i].isChecked = true; } else { PaginationList[i].isChecked = false; } updatedPaginationList.push(PaginationList[i]); } component.set("v.listOfAllAccounts", updatedAllRecords); component.set("v.PaginationList", updatedPaginationList); }, checkboxSelect: function(component, event, helper) { // on each checkbox selection update the selected record count var selectedRec = event.getSource().get("v.value"); var getSelectedNumber = component.get("v.selectedCount"); if (selectedRec == true) { getSelectedNumber++; } else { getSelectedNumber--; component.find("selectAllId").set("v.value", false); } component.set("v.selectedCount", getSelectedNumber); // if all checkboxes are checked then set header checkbox with true if (getSelectedNumber == component.get("v.totalRecordsCount")) { component.find("selectAllId").set("v.value", true); } }, getSelectedRecords: function(component, event, helper) { var allRecords = component.get("v.listOfAllAccounts"); var selectedRecords = []; for (var i = 0; i < allRecords.length; i++) { if (allRecords[i].isChecked) { selectedRecords.push(allRecords[i].objAccount); } } alert(JSON.stringify(selectedRecords)); } })
- check code comments
JavaScript Helper : tableWithPCHelper.js
({ /* doInitHelper funcation to fetch all records, and set attributes value on component load */ doInitHelper : function(component,event){ var action = component.get("c.fetchAccountWrapper"); action.setCallback(this, function(response) { var state = response.getState(); if (state === "SUCCESS"){ var oRes = response.getReturnValue(); if(oRes.length > 0){ component.set('v.listOfAllAccounts', oRes); var pageSize = component.get("v.pageSize"); var totalRecordsList = oRes; var totalLength = totalRecordsList.length ; component.set("v.totalRecordsCount", totalLength); component.set("v.startPage",0); component.set("v.endPage",pageSize-1); var PaginationLst = []; for(var i=0; i < pageSize; i++){ if(component.get("v.listOfAllAccounts").length > i){ PaginationLst.push(oRes[i]); } } component.set('v.PaginationList', PaginationLst); component.set("v.selectedCount" , 0); //use Math.ceil() to Round a number upward to its nearest integer component.set("v.totalPagesCount", Math.ceil(totalLength / pageSize)); }else{ // if there is no records then display message component.set("v.bNoRecordsFound" , true); } } else{ alert('Error...'); } }); $A.enqueueAction(action); }, // navigate to next pagination record set next : function(component,event,sObjectList,end,start,pageSize){ var Paginationlist = []; var counter = 0; for(var i = end + 1; i < end + pageSize + 1; i++){ if(sObjectList.length > i){ if(component.find("selectAllId").get("v.value")){ Paginationlist.push(sObjectList[i]); }else{ Paginationlist.push(sObjectList[i]); } } counter ++ ; } start = start + counter; end = end + counter; component.set("v.startPage",start); component.set("v.endPage",end); component.set('v.PaginationList', Paginationlist); }, // navigate to previous pagination record set previous : function(component,event,sObjectList,end,start,pageSize){ var Paginationlist = []; var counter = 0; for(var i= start-pageSize; i < start ; i++){ if(i > -1){ if(component.find("selectAllId").get("v.value")){ Paginationlist.push(sObjectList[i]); }else{ Paginationlist.push(sObjectList[i]); } counter ++; }else{ start++; } } start = start - counter; end = end - counter; component.set("v.startPage",start); component.set("v.endPage",end); component.set('v.PaginationList', Paginationlist); }, })
- Check code comments.
Test App :
<aura:application extends="force:slds"> <c:tableWithPC/> <!-- here c: is org. default namespace prefix--> </aura:application>
Output :

Other popular Post :
- How To Use jQuery DataTable Plugin In Salesforce Lightning Component -: Sample
- Powerful Lightning Datatable base component – Example Using Fieldset
- Custom Data Table With Inline Editing In Salesforce Lightning Component – Sample
- Add Delete Row Dynamic In Lightning Component : Sample Code
- Add Multiple Child Records to Parent Object With Lightning Component
Like our facebook page for new post updates. & Don’t forget to bookmark this site for your future reference.
Happy Learning 🙂
17 comments
Why would you create a custom table instead of using the delivered lightning:datatable?
it’s depend on you. which one you will prefer.
Hi,
Thanks for the code…. Works… i just have one doubt… i’m using this for a custom object where there are approx 20k records, but the table only displays 882.
Can you let me know how i can display all the records?
Thanks in advance
i have tested it with 20k records as well and it’s working as expected however select all functionality quite slow due to large data-set. might be you have some permission issue ?
Great Component. Thank you very much for sharing it with us 😉
glad to help you
Hey Piyush… Thanks for this. Can we have inline editing of picklist fields for this kind of implementation?
Can we add inline ediying for picklist fields in addition to wrapper n pagination. Can we do using lighting datatable fpr picklist values? Though its a limitation.Pla suggest
hey Piyush…This is great..can we add inline editing of picklist fields to this component?Should we go the parent/child component route or can it be achived with the same component you described here.
Appreciate your help.
Hi Piyush,
Can we edit the existing records(show as an input box) which displayed in table and when checkbox is checked, we create a new records with updated values which is changed by user for checked record? This functionality can be achieved using above code with some changes ?
I Wonder what’s the meaning of this piece of code
if (component.find(“selectAllId”).get(“v.value”)) {
Paginationlist.push(sObjectList[i]);
}else{
Paginationlist.push(sObjectList[i]);
}
Hi piyush,
From server Side List List is filled but in while passsing frim Client Side to server side list is going null .means Object are there in List but all they are null.
I am facing the same issue. How did you remove this issue ?
Can some one tell me apex controller is to fetch data based on check box?
how can load the data faster in jquery datatable, I am using more than 10 accordion section in my component, Each section I am showing more than 500 records, but it takes time to load more than 3 min.
Anyone can help me on this ??
Hi Piyush bro, i wanted a small help with the customLookup component that you’ve created.
The component works wonderfully. Only thing which I cannot find the solution of is that the result of the search thats the ‘Lightning Pill’. Its text is aligned always to the center. i want it in the left. Can you please help!
hello all,can anyone please provide me the LWC code for the same?