Pagination is a crucial UI pattern for handling large datasets in web applications. In Salesforce, implementing efficient pagination using Lightning Web Components (LWC) requires careful consideration of performance, user experience, and Salesforce best practices. In this blog post, I'll walk through a complete pagination solution for Opportunity records.
Overview
The solution consists of three main components:
- Apex Controller - Handles data retrieval and pagination logic
- LWC JavaScript - Manages component state and user interactions
- LWC Template - Renders the UI with Salesforce Lightning Design System
Apex Controller: The Backend Logic
KK_PaginationController.cls
public with sharing class KK_PaginationController {
public KK_PaginationController() {
// Constructor can be used for initialization if needed
}
@AuraEnabled(Cacheable=true)
public static List<Opportunity> fetchOpportunity(Integer pageSize, Integer pageNumber) {
// Validate input parameters
if (pageSize == null || pageSize <= 0) {
throw new IllegalArgumentException('Page size must be greater than 0');
}
if (pageNumber == null || pageNumber <= 0) {
throw new IllegalArgumentException('Page number must be greater than 0');
}
Integer offsetSize = (pageNumber - 1) * pageSize;
try {
List<Opportunity> lstopp = [
SELECT Id, Name, StageName, CloseDate, Amount, AccountId, CreatedDate
FROM Opportunity
ORDER BY CreatedDate DESC
LIMIT :pageSize
OFFSET :offsetSize
];
return lstopp;
} catch (QueryException e) {
System.debug('Query exception: ' + e.getMessage());
return new List<Opportunity>();
} catch (Exception e) {
System.debug('Unexpected error: ' + e.getMessage());
return new List<Opportunity>();
}
}
@AuraEnabled(Cacheable=true)
public static Integer totalRecords() {
try {
return [SELECT COUNT() FROM Opportunity];
} catch (Exception e) {
System.debug('Error counting opportunities: ' + e.getMessage());
return 0;
}
}
// Additional method to get total pages for better pagination control
@AuraEnabled(Cacheable=true)
public static Integer totalPages(Integer pageSize) {
if (pageSize == null || pageSize <= 0) {
return 0;
}
Integer totalRecs = totalRecords();
return (Integer)Math.ceil((Double)totalRecs / pageSize);
}
}
Key Features of the Apex Controller:
- Parameter Validation - Ensures pageSize and pageNumber are valid positive integers
- SOQL with OFFSET - Uses Salesforce's native pagination capability
- Error Handling - Comprehensive try-catch blocks with proper exception handling
- Cacheable Methods - Uses `@AuraEnabled(Cacheable=true)` for better performance
- Total Records Calculation - Separate method to count total records efficiently
LWC JavaScript: Component Logic
kk_pagination.js
import { LightningElement,track,wire} from 'lwc';
import getTotalRecordCount from '@salesforce/apex/KK_PaginationController.totalRecords';
import fetchopportunityData from '@salesforce/apex/KK_PaginationController.fetchOpportunity';
export default class Kk_Pagination extends LightningElement {
@track opprec;
pageSize = 10;
pageNumber = 1;
totalRecords;
totalPages;
connectedCallback() {
this.loadData();
this.loadTotalRecords();
}
loadData() {
fetchopportunityData({ pageSize: this.pageSize, pageNumber: this.pageNumber })
.then(result => {
this.opprec = result;
})
.catch(error => {
console.error(error);
});
}
loadTotalRecords() {
getTotalRecordCount()
.then(result => {
this.totalRecords = result;
this.totalPages = Math.ceil(this.totalRecords / this.pageSize);
})
.catch(error => {
console.error(error);
});
}
handleNext() {
if (this.pageNumber < this.totalPages) {
this.pageNumber++;
this.loadData();
}
}
handlePrev() {
if (this.pageNumber > 1) {
this.pageNumber--;
this.loadData();
}
}
get isFirstPage() {
return this.pageNumber === 1;
}
get isLastPage() {
return this.pageNumber === this.totalPages;
}
}
JavaScript Highlights:
- Reactive Properties - Uses `@track` decorator for reactive data binding
- Lifecycle Hooks - `connectedCallback()` initializes data when component loads
- Getter Methods - Computed properties for page state management
- Error Handling - Proper promise handling with `.catch()`
LWC HTML Template: User Interface
kk_pagination.html
<template>
<lightning-card title="💼 Opportunity List (Pagination)" icon-name="standard:opportunity">
<div class="slds-p-around_medium">
<!-- Loader -->
<template if:true={isLoading}>
<lightning-spinner alternative-text="Loading Opportunities..." size="medium"></lightning-spinner>
</template>
<!-- Table Display -->
<template if:true={opprec}>
<div class="slds-box slds-theme_default custom-table-container">
<table class="slds-table slds-table_bordered slds-table_cell-buffer slds-table_striped">
<thead class="custom-header">
<tr>
<th scope="col"><div class="slds-truncate" title="Name">Name</div></th>
<th scope="col"><div class="slds-truncate" title="Stage">Stage</div></th>
<th scope="col"><div class="slds-truncate" title="Amount">Amount</div></th>
</tr>
</thead>
<tbody>
<template for:each={opprec} for:item="opp">
<tr key={opp.Id} class="custom-row">
<td data-label="Name">
<lightning-icon icon-name="standard:opportunity" size="x-small" class="slds-m-right_x-small icon-color"></lightning-icon>
<span class="slds-text-title_bold">{opp.Name}</span>
</td>
<td data-label="Stage">{opp.StageName}</td>
<td data-label="Amount">₹ {opp.Amount}</td>
</tr>
</template>
</tbody>
</table>
</div>
<!-- Pagination Controls -->
<div class="pagination-controls slds-align_absolute-center slds-m-top_medium">
<lightning-button-icon icon-name="utility:chevronleft" alternative-text="Previous"
onclick={handlePrev} disabled={isFirstPage} class="nav-button">
</lightning-button-icon>
<span class="slds-m-horizontal_medium slds-text-title_bold">
Page {pageNumber} of {totalPages}
</span>
<lightning-button-icon icon-name="utility:chevronright" alternative-text="Next"
onclick={handleNext} disabled={isLastPage} class="nav-button">
</lightning-button-icon>
</div>
</template>
</div>
</lightning-card>
CSS Styling
kk_pagination.css
.custom-table-container {
border-radius: 0.8rem;
background-color: #ffffff;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
}
.custom-header {
background-color: #f3f6fb;
}
.custom-row:hover {
background-color: #f0f8ff;
transform: scale(1.01);
transition: all 0.2s ease-in-out;
}
.icon-color {
color: #0176d3;
}
.pagination-controls {
display: flex;
align-items: center;
justify-content: center;
}
.nav-button {
background-color: #f3f6fb;
border-radius: 50%;
transition: background-color 0.2s ease;
}
.nav-button:hover {
background-color: #dce6f1;
}
lightning-card {
border-radius: 1rem;
}
lightning-spinner {
display: flex;
justify-content: center;
margin-top: 1rem;
}
Best Practices Implemented
1. Performance Optimization
- Uses `@AuraEnabled(Cacheable=true)` for efficient data caching
- Implements SOQL OFFSET for server-side pagination
- Limits data transfer by fetching only required records
2. Error Handling
- Comprehensive try-catch blocks in Apex
- Promise rejection handling in JavaScript
- Parameter validation to prevent invalid queries
3. User Experience
- Loading spinner during data fetch
- Disabled states for navigation buttons
- Hover effects and smooth transitions
- Responsive design with SLDS
4. Maintainability
- Clear separation of concerns
- Reusable methods
- Proper code documentation
- Consistent naming conventions
Potential Enhancements
- Search and Filtering - Add search functionality to filter opportunities
- Dynamic Page Size - Allow users to change records per page
- Jump to Page - Add input field for direct page navigation
- Bulk Actions - Implement selection and bulk operations
- Sorting - Add column-based sorting capabilities
Conclusion
This pagination solution demonstrates a robust, production-ready implementation for Salesforce LWC. It follows Salesforce best practices, provides excellent user experience, and maintains good performance even with large datasets. The modular approach makes it easy to extend and adapt for different objects and use cases.
The combination of efficient Apex queries, reactive JavaScript components, and SLDS-compliant UI creates a seamless pagination experience that users will appreciate when working with large lists of Opportunities.
Remember to always test with your organization's data volume and consider governor limits when deploying pagination solutions in Salesforce.
Post a Comment