Add Pagination In HTML Table in Salesforce Lightning Web Component (LWC)

Add Pagination In HTML Table in Salesforce Lightning Web Component (LWC) using JavaScript

In this post, we will discuss how to implement pagination in a HTML Table. This will be done in Salesforce Lightning Web Component (LWC) using JavaScript. Pagination is a common feature in web development. It allows users to navigate through large amounts of data by breaking it up into smaller, more manageable chunks.

Scenario :

Imagine, we have a requirement to show account data in a table. We must use pagination to display multiple account records. We will create the LWC component for it. We will use an HTML table and JavaScript to show data on the UI.

Code Explanation:
  • To add pagination in a HTML Table, first, we imported the necessary classes and methods from the LWC module. This includes the LightningElement class, the track decorator for data binding, and the wire decorator. The wire decorator connects to an Apex method that retrieves the data we want to paginate. We also imported the getAccounts method from the AccountController Apex class and the Images resource.
  • Next, we declared the Pagenationex class as the default export, and defined the variables used for data binding. These variables include the accounts data array. It stores the records returned from the Apex controller. The displayAccounts array holds the records to be displayed on the current page. The pageNumbers array holds the page numbers for the pagination component.
  • We declared variables for the current page number and the total number of pages. We included the number of records per page and the total number of records. We also had flags indicating whether the current page was the first or last page. We had flags for disabling the previous and next buttons. Furthermore, we have included a variable that specifies the field for sorting the data. There is also a flag that indicates if the data should be sorted in ascending order.
  • Using the wire decorator, we connected to the getAccounts Apex method, which returned the data to be paginated. In the callback function, we checked if data was returned. If data was returned, we assigned it to the accounts property. We calculated the total number of pages and called the setPages method. The data was passed into the setPages method. We also called the navigateToFirstPage method to navigate to the first page.
  • The setPages method created an array of page numbers. It used the length of the data and the page size. This array was then assigned to the pageNumbers property.
  • The getPagesList method calculated the middle of the page size. It returned a slice of page numbers starting from the current page minus the middle value. It continued to the current page plus the middle value minus one.
  • The navigateToFirstPage method assigned the current page to the first page. It set the flag for the first page to true. The flag for the last page was set to false. The previous button was disabled, and the next button was enabled. It also assigned the accounts to be displayed on the first page.
  • Other methods in the component included navigateToLastPage, navigateToPreviousPage, and navigateToNextPage. These methods handled navigation to the last page, the previous page, and the next page, respectively.
  • We also had sortData method. It handled sorting of data on a specified field. The handlePageClick method facilitated navigation to a specific page.

Demo :
Sample Code :
/**
* AccountPaginationController
* —————————-
* This Apex controller is used for retrieving a paginated list of Account records.
*
* Key Features:
* – Retrieves specific fields from the Account object.
* – Enforces field-level security using WITH SECURITY_ENFORCED.
* – Orders the Account records by the Name field for consistent display.
* – Designed for use with Aura or Lightning Web Components (LWC) via @AuraEnabled.
*/
public with sharing class AccountPaginationController {
/**
* Fetches a list of Account records with specific fields.
*
* Key Points:
* – Uses @AuraEnabled(cacheable=true) to support Lightning Data Service (LDS) caching.
* – Applies field-level security with WITH SECURITY_ENFORCED to ensure compliance with sharing and visibility rules.
* – Returns Account records ordered alphabetically by Name.
*
* @return List<Account> – List of Account records with selected fields.
*/
@AuraEnabled(cacheable=true)
public static List<Account> getAccounts() {
// Query to retrieve Account records with specific fields
return [
SELECT Id, Name, Phone, Industry, CreatedDate, LastModifiedDate
FROM Account
WITH SECURITY_ENFORCED
ORDER BY Name
];
}
}
table {
table-layout: fixed;
border-collapse: collapse;
width: 100%;
}
th,
td {
text-align: left;
padding: 8px;
width: 100px;
word-wrap: break-word;
overflow-wrap: break-word;
}
<!– sldsValidatorIgnore –>
<template>
<template if:true={showSpinner}>
<div id="spinnerDiv" style="height: 10rem">
<div class="slds-spinner_container">
<div role="status" class="slds-spinner slds-spinner_medium">
<span class="slds-assistive-text">Loading</span>
<div class="slds-spinner__dot-a"></div>
<div class="slds-spinner__dot-b"></div>
</div>
</div>
</div>
</template>
<!– A lightning-card component to display the content –>
<lightning-card>
<!– A table to display the accounts data –>
<table class="slds-table slds-table_bordered slds-table_cell-buffer">
<!– The header of the table –>
<thead>
<!– A row to contain the header cells –>
<tr class="slds-line-height_reset">
<!– A header cell for the 'Name' column –>
<th
data-field-name="Name"
class="slds-text-title_caps"
scope="col"
onclick={handleSort}
>
Name
<span data-field-id="Name" lwc:dom="manual"></span>
</th>
<!– A header cell for the 'Phone' column –>
<th
data-field-name="Phone"
class="slds-text-title_caps"
scope="col"
onclick={handleSort}
>
Phone
<span data-field-id="Phone" lwc:dom="manual"></span>
</th>
<!– A header cell for the 'Industry' column –>
<th
data-field-name="Industry"
class="slds-text-title_caps"
scope="col"
onclick={handleSort}
>
Industry
<span data-field-id="Industry" lwc:dom="manual"></span>
</th>
<!– A header cell for the 'CreatedDate' column –>
<th
data-field-name="CreatedDate"
class="slds-text-title_caps"
scope="col"
onclick={handleSort}
>
CreatedDate
<span data-field-id="CreatedDate" lwc:dom="manual"></span>
</th>
<!– A header cell for the 'LastModifiedDate' column –>
<th
data-field-name="LastModifiedDate"
class="slds-text-title_caps"
scope="col"
onclick={handleSort}
>
LastModifiedDate
<span data-field-id="LastModifiedDate" lwc:dom="manual"></span>
</th>
</tr>
</thead>
<!– The body of the table –>
<tbody>
<!– A conditional template to display the accounts data if there are any –>
<template if:true={accounts.length}>
<!– A for:each template to iterate through the displayAccounts array –>
<template for:each={displayAccounts} for:item="account">
<!– A row to display a single account data –>
<tr key={account.Id}>
<!– A cell for the 'Name' column –>
<td>{account.Name}</td>
<!– A cell for the 'Phone' column –>
<td>
<!– A lightning-formatted-phone component to format the phone number –>
<lightning-formatted-phone
value={account.Phone}
></lightning-formatted-phone>
</td>
<!– A cell for the 'Industry' column –>
<td>{account.Industry}</td>
<!– A cell for the 'CreatedDate' column –>
<td>
<!– A lightning-formatted-date-time component to format the date and time –>
<lightning-formatted-date-time
value={account.CreatedDate}
year="numeric"
month="numeric"
day="numeric"
hour="2-digit"
minute="2-digit"
>
</lightning-formatted-date-time>
</td>
<!– A cell for the 'LastModifiedDate' column –>
<td>
<!– A lightning-formatted-date-time component to format the date and time –>
<lightning-formatted-date-time
value={account.LastModifiedDate}
year="numeric"
month="numeric"
day="numeric"
hour="2-digit"
minute="2-digit"
>
</lightning-formatted-date-time>
</td>
</tr>
</template>
</template>
<!– A conditional template to display a message if there are no accounts data –>
<template if:false={accounts.length}>
<tr>
<td colspan="5">No data to display</td>
</tr>
</template>
</tbody>
</table>
<!– A lightning-layout component to organize the content into multiple rows –>
<lightning-layout multiple-rows="true">
<!– A lightning-layout-item component to specify the size of the content –>
<lightning-layout-item size="12">
<!– A div with slds-align_absolute-center class to align the content to the center –>
<div class="slds-align_absolute-center">
<!– A ul with slds-button-group-row class to display the buttons in a row –>
<ul class="slds-button-group-row">
<!– A li with slds-button-group-item class to display the first button in the group –>
<li class="slds-button-group-item">
<!– A lightning-button component to represent the first page button –>
<lightning-button
label="First"
onclick={navigateToFirstPage}
disabled={isFirstPage}
>
</lightning-button>
</li>
<!– A li with slds-button-group-item class to display the previous button in the group –>
<li class="slds-button-group-item">
<!– A lightning-button component to represent the previous page button –>
<lightning-button
label="Previous"
onclick={navigateToPreviousPage}
disabled={isPreviousDisabled}
>
</lightning-button>
</li>
<template lwc:if={showPageButtons}>
<!– A template with if:true condition to check if the pageNumbers array has length –>
<template if:true={pageNumbers.length}>
<!– A template with for:each loop to iterate through the pageNumbers array –>
<template for:each={pageNumbers} for:item="pageNumber">
<!– A li with slds-button-group-item class to display each page number button in the group –>
<li class="slds-button-group-item" key={pageNumber}>
<!– A button with data-id attribute and slds-button_neutral class to represent each page number –>
<button
data-id={pageNumber}
class="slds-button slds-button_neutral"
onclick={navigateToPage}
>
{pageNumber}
</button>
</li>
</template>
</template>
</template>
<template lwc:else>
<li class="slds-button-group-item" style="padding: 5px">
Showing Page {currentPage} of {totalPages}
</li>
</template>
<!– A li with slds-button-group-item class to display the next button in the group –>
<li class="slds-button-group-item">
<!– A lightning-button component to represent the next page button –>
<lightning-button
label="Next"
onclick={navigateToNextPage}
disabled={isNextDisabled}
>
</lightning-button>
</li>
<!– A li with slds-button-group-item class to display the last button in the group –>
<li class="slds-button-group-item">
<!– A lightning-button component to represent the last page button –>
<lightning-button
label="Last"
onclick={navigateToLastPage}
disabled={isLastPage}
>
</lightning-button>
</li>
</ul>
</div>
</lightning-layout-item>
</lightning-layout>
</lightning-card>
</template>
/* eslint-disable no-else-return */
/* eslint-disable default-case */
// Import the LightningElement class and the track decorator from the lwc module
import { LightningElement, track, wire } from "lwc";
// Import the getAccounts method from the AccountPagenationController Apex class
import getAccounts from "@salesforce/apex/AccountPagenationController.getAccounts";
// Import the Images resource for sort up and down icon from static resources
import Images from "@salesforce/resourceUrl/Images";
import './pagenationex.css';
// Declare the Pagenationex class as the default export
export default class Pagenationex extends LightningElement {
// Declare variables for data binding
// Accounts data array to store account records returned from apex controller
@track accounts = [];
// Accounts data array to be displayed on current page
@track displayAccounts = [];
// Array of page numbers
@track pageNumbers = [];
// Current page number
@track currentPage = 1;
// Total number of pages
@track totalPages = 0;
// Number of records per page
@track pageSize = 10;
// Total number of records
@track totalRecords;
// Flag to indicate if current page is first page
@track isFirstPage = true;
// Flag to indicate if current page is last page
@track isLastPage = false;
// Flag to disable previous button
@track isPreviousDisabled = true;
// Flag to disable next button
@track isNextDisabled = false;
// Field on which data is to be sorted
@track sortField;
// Flag to indicate if data is to be sorted in ascending order
@track sortAscending = true;
// Flag to show/hide spinner
@track showSpinner = true;
// Flag to show/hide paginationbuttons
@track showPageButtons = true;
//using the @wire decorator to connect to the getAccounts Apex method
@wire(getAccounts)
/**
* wiredAccounts method is used to handle the data returned from the Apex method.
* It assigns the data to the accounts property and sets the total number of records and total number of pages.
* It also calls the setPages and navigateToFirstPage methods to set the pagination and navigate to the first page.
* @param {Object} data – data returned from the Apex method
* @param {Object} error – error returned from the Apex method
*/
wiredAccounts({ data, error }) {
// If data is returned from the Apex method
if (data) {
// Assign the data to the accounts property
this.accounts = data;
// Assign the total number of records to the totalRecords property
this.totalRecords = data.length;
// Calculate the total number of pages based on the page size and the total number of records
this.totalPages = Math.ceil(this.accounts.length / this.pageSize);
// Call the setPages method, passing in the data
this.setPages(data);
// Call the navigateToFirstPage method to navigate to the first page
this.navigateToFirstPage();
this.showSpinner = false;
} else if (error) {
// If an error is returned, handle it
this.showSpinner = false;
}
}
/**
* setPages method is used to set the page numbers for the pagination component.
* It creates an array of page numbers based on the length of the data and the page size.
* The created array is assigned to this.pageNumbers so that it can be used in the pagination component.
* @param {Object} data – data used to calculate the number of pages
*/
setPages(data) {
// Create an array of page numbers based on the length of the data and the page size.
// this.pageNumbers is assigned the array so that it can be used in the pagination component.
this.pageNumbers = Array.from(
// Using the Array.from method with the length of Math.ceil(data.length / this.pageSize)
{ length: Math.ceil(data.length / this.pageSize) },
// _ is a placeholder for the value of the array and i is the index of the array, and it starts with 1.
(_, i) => i + 1
);
}
/**
* getPagesList method is used to return the list of page numbers to be displayed in the pagination component.
* It calculates the middle of the page size and checks if the total number of pages is greater than the middle of the page size.
* If so, it returns a slice of page numbers from the current page – middle to the current page + middle – 1.
* If the total number of pages is less than or equal to the middle of the page size,
it returns a slice of page numbers from the start to the page size
*/
getPagesList() {
//Calculates the middle of the page size
let mid = Math.floor(this.pageSize / 2) + 1;
//Checks if the total number of pages is greater than the middle of the page size
if (this.pageNumbers > mid) {
//Returns a slice of page numbers from the current page – middle to the current page + middle – 1
return this.pageNumbers.slice(
this.currentPage – mid,
this.currentPage + mid – 1
);
}
//If the total number of pages is less than or equal to the middle of the page size,
//returns a slice of page numbers from the start to the page size
return this.pageNumbers.slice(0, this.pageSize);
}
/**
* navigateToFirstPage method is used to navigate to the first page of the pagination component.
* It assigns the current page to the first page, sets the flags for the first page, last page,
previous button, and next button, and assigns the accounts to be displayed on the first page.
*/
navigateToFirstPage() {
// Assign the current page to the first page
this.currentPage = 1;
// Assign the flag for first page to true
this.isFirstPage = true;
// Setting the flag for last page to false
this.isLastPage = false;
// Assign the flag for previous button to be disabled
this.isPreviousDisabled = true;
// Assign the flag for next button to be enabled
this.isNextDisabled = false;
// Assign the accounts to be displayed on the first page
this.displayAccounts = this.accounts.slice(0, this.pageSize);
}
/**
* navigateToLastPage method is used to navigate to the last page of the pagination.
* It assigns the current page variable to the total number of pages, updates the isFirstPage and isLastPage variables,
and sets the isPreviousDisabled and isNextDisabled variables.
* It also assigns the displayAccounts variable to the slice of the accounts array that corresponds to the current page
*/
navigateToLastPage() {
// Assign the current page variable to the total number of pages
this.currentPage = this.totalPages;
// Assign the isFirstPage variable to false, indicating that the current page is not the first page
this.isFirstPage = false;
// Assign the isLastPage variable to true, indicating that the current page is the last page
this.isLastPage = true;
// This line sets the isPreviousDisabled variable to false, indicating that the "previous" button should be enabled
this.isPreviousDisabled = false;
// Assign the isNextDisabled variable to true, indicating that the "next" button should be disabled
this.isNextDisabled = true;
// Assign the displayAccounts variable to the slice of the accounts array that corresponds to the current page, using the currentPage, pageSize and the accounts array
this.displayAccounts = this.accounts.slice(
(this.currentPage – 1) * this.pageSize,
this.currentPage * this.pageSize
);
}
/**
* navigateToPage method is used to navigate to a specific page.
* It sets the currentPage variable to the page number that was clicked and checks if the current page
is the first page, last page, and if the previous and next buttons should be disabled.
* It also calls the displayAccounts property and slice the accounts array to display the accounts for the current page.
* @param {Object} event – event object passed when a page number is clicked.
*/
navigateToPage(event) {
//Sets the currentPage variable to the page number that was clicked
this.currentPage = parseInt(event.target.textContent, 10);
//Checks if the current page is the first page
this.isFirstPage = this.currentPage === 1;
//Checks if the current page is the last page
this.isLastPage = this.currentPage === this.totalPages;
//Checks if the previous button should be disabled
this.isPreviousDisabled = this.currentPage === 1;
//Checks if the next button should be disabled
this.isNextDisabled = this.currentPage === this.totalPages;
//Displays the accounts for the current page
this.displayAccounts = this.accounts.slice(
//Calculates the start index for the current page
(this.currentPage – 1) * this.pageSize,
//Calculates the end index for the current page
this.currentPage * this.pageSize
);
}
/**
* navigateToPreviousPage method is used to navigate to the previous page in the pagination.
* It updates the currentPage, isFirstPage, isLastPage, isPreviousDisabled and isNextDisabled properties
and also updates the accounts to be displayed on the current page
*/
navigateToPreviousPage() {
// Assign the current page variable to the current page minus 1
this.currentPage = this.currentPage – 1;
// Assign the isLastPage variable to false, indicating that the current page is not the last page
this.isLastPage = false;
// If the current page is equal to 1
if (this.currentPage === 1) {
// Assign the isFirstPage variable to true, indicating that the current page is the first page
this.isFirstPage = true;
// Assign the isPreviousDisabled variable to true, indicating that the "previous" button should be disabled
this.isPreviousDisabled = true;
}
// Assign the isNextDisabled variable to false, indicating that the "next" button should be enabled
this.isNextDisabled = false;
// Assign the accounts to be displayed on the previous page
this.displayAccounts = this.accounts.slice(
(this.currentPage – 1) * this.pageSize,
this.currentPage * this.pageSize
);
}
/**
* navigateToNextPage method is used to navigate to the next page of accounts.
* It checks if the current page is less than the total number of pages.
* If true, it increments the current page by 1, updates the isFirstPage, isLastPage, isPreviousDisabled and isNextDisabled properties.
* It also updates the displayAccounts array to show the accounts for the current page
*/
navigateToNextPage() {
//Checks if the current page is less than the total number of pages
if (this.currentPage < this.totalPages) {
//Increments the current page by 1
this.currentPage++;
//Checks if the current page is equal to the total number of pages
if (this.currentPage === this.totalPages) {
//Sets isFirstPage and isPreviousDisabled to false, isLastPage and isNextDisabled to true
this.isFirstPage = false;
this.isLastPage = true;
this.isPreviousDisabled = false;
this.isNextDisabled = true;
} else {
//Sets isFirstPage, isLastPage, isPreviousDisabled, and isNextDisabled to false
this.isFirstPage = false;
this.isLastPage = false;
this.isPreviousDisabled = false;
this.isNextDisabled = false;
}
//Updates the displayAccounts array to show the accounts for the current page
this.displayAccounts = this.accounts.slice(
(this.currentPage – 1) * this.pageSize,
this.currentPage * this.pageSize
);
}
}
/**
* handleSort method is used to handle the sorting of data when user clicks on the table header.
* It gets the field name of the clicked table header and checks if the current sort field is the same as the field name of the clicked table header.
* If the same, it toggles the sort order. If not, it updates the sort field and sets the sort order as ascending.
* It also calls the sortData method to sort the data.
* @param {Event} event – event object of the table header click
*/
handleSort(event) {
// get the field name of the clicked table header
let fieldName = event.target.dataset.fieldName;
// check if the current sort field is the same as the field name of the clicked table header
if (this.sortField === fieldName) {
// if the same, toggle the sort order
this.sortAscending = !this.sortAscending;
} else {
// if not the same, update the sort field and set sort order as ascending
this.sortField = fieldName;
this.sortAscending = true;
}
// call the sortData method to sort the data
this.sortData(this.sortField, this.sortAscending);
}
/**
* sortData method is used to sort the data based on the given field name and sort order.
* It creates a copy of the accounts array, sorts it using the provided field name and sort order, and assigns the sorted data back to the accounts array.
* It also assigns a slice of the sorted data to the displayAccounts array to only show a certain number of records per page,
and creates and adds a sort icon to indicate the current sort order.
* @param {String} sortField – the field name by which to sort the data.
* @param {Boolean} sortAscending – the sort order, true for ascending and false for descending
*/
sortData(sortField, sortAscending) {
this.accounts = […this.accounts].sort((a, b) => {
let valueA;
let valueB;
// Check if the field name exists in the object, if not set value to null
if (a[sortField] === undefined) {
valueA = null;
} else {
valueA = a[sortField];
}
if (b[sortField] === undefined) {
valueB = null;
} else {
valueB = b[sortField];
}
if (valueA === null) {
// return 1 for ascending and -1 for descending if valueA is null
return sortAscending ? 1 : -1;
}
if (valueB === null) {
// return -1 for ascending and 1 for descending if valueB is null
return sortAscending ? -1 : 1;
}
if (typeof valueA === "string") {
// convert valueA to lowercase if it is a string
valueA = valueA.toLowerCase();
}
if (typeof valueB === "string") {
// convert valueB to lowercase if it is a string
valueB = valueB.toLowerCase();
valueB = valueB.toLowerCase();
}
if (sortAscending) {
return valueA > valueB ? 1 : -1;
} else {
return valueA < valueB ? 1 : -1;
}
});
/* Assign a slice of the sorted data to the displayAccounts
* array to only show a certain number of records per page
*/
this.displayAccounts = this.accounts.slice(0, this.pageSize);
// Select any existing sort icon on the page
let existingIcon = this.template.querySelectorAll('img[id="sorticon"]');
// If an existing sort icon is found, remove it
if (existingIcon[0]) {
existingIcon[0].parentNode.removeChild(existingIcon[0]);
}
// Create a new sort icon element
let icon = document.createElement("img");
/* If sortAscending is true, set the sort icon's
*source to the ascending arrow image
*/
if (sortAscending) {
icon.setAttribute("src", Images + "/Images/arrowup.png");
}
/* If sortAscending is false, set the sort icon's
* source to the descending arrow image
*/
if (!sortAscending) {
icon.setAttribute("src", Images + "/Images/arrowdown.png");
}
// Set the sort icon's id attribute to "sorticon"
icon.setAttribute("id", "sorticon");
// Set the sort icon's height and width
icon.style.height = "15px";
icon.style.width = "15px";
icon.style.paddingBottom = "2px";
// Select the table header for the sortField passed in
let nodes = this.template.querySelectorAll(
'span[data-field-id="' + sortField + '"]'
);
// Append the sort icon to the selected table header
nodes.forEach((input) => {
input.appendChild(icon);
});
this.navigateToFirstPage();
}
}
view raw pagenationex.js hosted with ❤ by GitHub

For more helpful articles please visit – https://thesalesforcedev.in

One response to “Add Pagination In HTML Table in Salesforce Lightning Web Component (LWC) using JavaScript”

  1. […] everyone, with continuation to our previous blog post- Add Pagination In HTML Table in Salesforce Lightning Web Component (LWC) using JavaScript, this blog post will explore how to add Filtering & Sorting In HTML Table With Pagination. This […]

    Like

Leave a comment