Pagination is a pretty cool bundle of logic, UI, and Apex to start your assignments with. Server-side Pagination is useful when you have to fetch records each time from the server (salesforce). LWC server-side pagination is faster than normal aura components.
LWC uses client JavaScript which your browser understands natively. This makes LWC super fast to work. With API version 45.0, the Lightning components can be build using 2 Salesforce programming models: –
- Lightning Web Components model
- Aura Components model
Client-Side Pagination component features:
- Generic component
- Dynamic Object Selection
- Dynamic Fields Selection
- Super Fast to navigate between records
- Accordion to switch between your data table and object/field selection.
- Set the number of records to display at once i.e., 10, 25, 50, 100
Drawback:
- This component is using offset in SOQL to query records whose limit is 2000 only.
Demo GIF:
You can Dynamically select any object and fields of that object.
Step-1: Create an Apex controller
Our apex controller will fetch the object list, fields list, and records on processing. Here we are using a wrapper class which will return data to our LWC.
Create ServerSidePaginationController.cls Apex class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
/* Code by CafeForce || www.cafeforce.com || support@cafeforce.com || Mandatory Header */ public with sharing class ServerSidePaginationController { @AuraEnabled public static List<PicklistOptions> fetchObjectList() { List<PicklistOptions> objectList = new List<PicklistOptions>(); for(Schema.SObjectType objTyp : Schema.getGlobalDescribe().Values()) { Schema.DescribeSObjectResult describeSObjectResultObj = objTyp.getDescribe(); if(describeSObjectResultObj.isQueryable() && describeSObjectResultObj.isUpdateable() && describeSObjectResultObj.isAccessible() && describeSObjectResultObj.isCreateable()) { String name = objTyp.getDescribe().getName(); if(!name.containsignorecase('history') && !name.containsignorecase('tag')&& !name.containsignorecase('share') && !name.containsignorecase('feed')) { objectList.add( new PicklistOptions(describeSObjectResultObj.getLabel(), describeSObjectResultObj.getName(), '') ); } } } objectList.sort(); return objectList; } @AuraEnabled public static List<PicklistOptions> fetchFieldsList(String objectName) { List<PicklistOptions> fieldsList = new List<PicklistOptions>(); if(String.isNotBlank(objectName)){ Map<String, Schema.SObjectField> fieldMap = Schema.getGlobalDescribe().get(objectName).getDescribe().fields.getMap(); for (String fieldName: fieldMap.keySet()) { Schema.DescribeFieldResult field = fieldMap.get(fieldName).getDescribe(); if (field.isAccessible() && field.isCreateable()) { fieldsList.add(new PicklistOptions(field.getLabel(), field.getName(), apexTypetoJSType.get(String.valueof(field.getType())) )); } } } fieldsList.sort(); return fieldsList; } @AuraEnabled public static ReturnWrapper fetchRecords(String objectName, List<String> fieldsList, Integer pageNumber, Integer recordSize) { try { ReturnWrapper ret = new ReturnWrapper(); if(String.isNotBlank(objectName) && fieldsList != null){ Integer offset = (pageNumber - 1) * recordSize; String query = 'SELECT id, ' + String.join(fieldsList, ',') + ' FROM '+objectName + ' LIMIT '+recordSize+' OFFSET '+offset; ret.records = Database.query(query); String countQuery = 'Select count() FROM '+ objectName; ret.totalRecords = Database.countQuery(countQuery); } return ret; } catch(Exception ex) { throw new AuraHandledException(ex.getMessage()); } } public class PicklistOptions implements Comparable { @AuraEnabled public String label; @AuraEnabled public String value; @AuraEnabled public String datatype; public PicklistOptions(String label, String value, String datatype) { this.label = label; this.value = value; this.datatype = datatype; } public Integer compareTo(Object ObjToCompare) { return label.CompareTo(((PicklistOptions)ObjToCompare).label); } } public class ReturnWrapper { @AuraEnabled public List<sObject> records; @AuraEnabled public Integer totalRecords; } public static Map<String, String> apexTypetoJSType = new Map<String, String> { 'ADDRESS' => 'text', 'ID' => 'text', 'TEXTAREA' => 'text', 'STRING' => 'text', 'REFERENCE' => 'text', 'PICKLIST' => 'text', 'MULTIPICKLIST' => 'text', 'DATE' => 'date', 'DOUBLE' => 'number', 'PERCENT' => 'percent', 'PHONE' => 'phone', 'URL' => 'url', 'INTEGER' => 'number', 'CURRENCY' => 'currency', 'DATETIME' => 'date', 'BOOLEAN' => 'boolean', 'EMAIL' => 'email' }; } /* Code by CafeForce Website: http://www.cafeforce.com DO NOT REMOVE THIS HEADER/FOOTER FOR FREE CODE USAGE */ |
Step-2: Now we will create the Server-Side Pagination Lightning Web Component.
Create a new web component serverSidePagination.
Now open the HTML file serverSidePagination.html and paste the below code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
<!-- Code by CafeForce || www.cafeforce.com || support@cafeforce.com || Mandatory Header --> <template> <div style="background-color: #fff; position: relative;" class="slds-p-around_medium"> <div if:true={showSpinner}> <lightning-spinner alternative-text="Loading" size="small"></lightning-spinner> </div> <lightning-accordion class="pagination-accordion" active-section-name="A"> <lightning-accordion-section name="A" label="Object and Fields Selection"> <lightning-combobox name="object" label="Status" value={selectedbject} placeholder="Select Object" options={objectList} onchange={handleObjectChange} ></lightning-combobox> <lightning-dual-listbox name="languages" label="Select Fields" class="slds-m-top_medium" source-label="Available" selected-label="Selected" field-level-help="Select Fields" options={fieldsList} value={values} onchange={handleFieldsChange}></lightning-dual-listbox> <br /> <lightning-button variant="brand" label="Fetch Records" disabled={disableProcessBtn} onclick={fetchRecords} ></lightning-button> </lightning-accordion-section> <lightning-accordion-section name="B" label="Records Listview"> <div style="display:flow-root;"> <div style="float:left"> <lightning-combobox name="object" variant="label-hidden" disabled={disableCombobox} value={recordSize} placeholder="Record Size" options={getRecordSizeList} onchange={handleRecordSizeChange} ></lightning-combobox> </div> <div style="float:right"> <lightning-button variant="brand" label="First" disabled={disablePreviousButtons} class="slds-m-top_medium" onclick={handleNavigation} ></lightning-button> <lightning-button variant="brand" label="Previous" disabled={disablePreviousButtons} class="slds-m-top_medium" onclick={handleNavigation} ></lightning-button> <lightning-button variant="brand" label="Next" disabled={disableNextButtons} class="slds-m-top_medium" onclick={handleNavigation} ></lightning-button> <lightning-button variant="brand" label="Last" disabled={disableNextButtons} class="slds-m-top_medium" onclick={handleNavigation} ></lightning-button> </div> </div> <br /> <div> <lightning-datatable key-field="id" hide-checkbox-column=true data={records} columns={columns}> </lightning-datatable> </div> <br /> <div style="text-align:right;"> {recordViewMessage} </div> </lightning-accordion-section> </lightning-accordion> </div> </template> <!-- Code by CafeForce Website: http://www.cafeforce.com DO NOT REMOVE THIS HEADER/FOOTER FOR FREE CODE USAGE --> |
Now open the JS file serverSidePagination.js and paste the below code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
/* Code by CafeForce || www.cafeforce.com || support@cafeforce.com || Mandatory Header */ import { LightningElement, track } from 'lwc'; import fetchObjectList from '@salesforce/apex/ServerSidePaginationController.fetchObjectList'; import fetchFieldsList from '@salesforce/apex/ServerSidePaginationController.fetchFieldsList'; import fetchRecords from '@salesforce/apex/ServerSidePaginationController.fetchRecords'; import { ShowToastEvent } from 'lightning/platformShowToastEvent'; export default class ServerSidePagination extends LightningElement { @track objectList; @track selectedObject; @track fieldsList; @track selectedFields; @track pageNumber = 1; @track recordSize = '10'; @track records; @track totalRecords; @track totalPages; @track columns; @track values; @track showSpinner; constructor() { super(); this.showSpinner = true; fetchObjectList() .then(result => { this.objectList = result; this.showSpinner = false; }).catch(error => { console.log(error); this.showSpinner = false; }) } handleObjectChange(event) { this.selectedFields = []; this.values = []; this.showSpinner = true; this.selectedObject = event.detail.value; fetchFieldsList({ objectName : event.detail.value }) .then(result => { this.fieldsList = result; this.showSpinner = false; }).catch(error => { console.log(error); this.showSpinner = false; }) } handleFieldsChange(event) { this.selectedFields = event.detail.value; } get disablePreviousButtons() { if(this.selectedFields == undefined || this.selectedFields.length == 0 || this.pageNumber == 1) return true; } get disableNextButtons() { if(this.selectedFields == undefined || this.selectedFields.length == 0 || this.pageNumber == this.totalPages) return true; } get getRecordSizeList() { let recordSizeList = []; recordSizeList.push({'label':'10', 'value':'10'}); recordSizeList.push({'label':'25', 'value':'25'}); recordSizeList.push({'label':'50', 'value':'50'}); recordSizeList.push({'label':'100', 'value':'100'}); return recordSizeList; } get disableCombobox() { if(!this.records || this.records.length == 0) return true; } get recordViewMessage() { return 'Total Records - ' + this.totalRecords + ' | Current Page - ' + this.pageNumber + '/' + this.totalPages; } handleNavigation(event){ let buttonName = event.target.label; if(buttonName == 'First') { this.pageNumber = 1; } else if(buttonName == 'Next') { this.pageNumber = this.pageNumber >= this.totalPages ? this.totalPages : this.pageNumber + 1; } else if(buttonName == 'Previous') { this.pageNumber = this.pageNumber > 1 ? this.pageNumber - 1 : 1; } else if(buttonName == 'Last') { this.pageNumber = this.totalPages; } this.fetchRecords(); } handleRecordSizeChange(event) { this.recordSize = event.detail.value; this.pageNumber = 1; this.fetchRecords(); } fetchRecords(event) { this.showSpinner = true; fetchRecords({ objectName : this.selectedObject, fieldsList : this.selectedFields, pageNumber : this.pageNumber, recordSize : Number(this.recordSize) }) .then(result => { if(result != null && result != undefined) { this.records = result.records; this.totalRecords = result.totalRecords; this.totalPages = Math.ceil(result.totalRecords / Number(this.recordSize)); var fieldsColumn = []; for(var i = 0; i < this.fieldsList.length; i++) { for(var j = 0; j < this.selectedFields.length; j++) { if(this.fieldsList[i].value == this.selectedFields[j]) { fieldsColumn.push(this.fieldsList[i]); } } } var columnList = []; for(var j = 0; j < fieldsColumn.length; j++) { columnList.push({'label': fieldsColumn[j].label, 'fieldName': fieldsColumn[j].value, 'type': fieldsColumn[j].datatype}); } this.columns = columnList; } const accordion = this.template.querySelector('.pagination-accordion'); accordion.activeSectionName = 'B'; this.showSpinner = false; }).catch(error => { console.log(error); if(error && error.body && error.body.message) this.showNotification(error.body.message, 'error'); this.showSpinner = false; }) } showNotification(message, variant) { const evt = new ShowToastEvent({ 'message': message, 'variant': variant }); this.dispatchEvent(evt); } } /* Code by CafeForce Website: http://www.cafeforce.com DO NOT REMOVE THIS HEADER/FOOTER FOR FREE CODE USAGE */ |
Open the meta file serverSidePagination.js-meta.xml and paste the below code.
1 2 3 4 5 6 7 8 9 10 11 12 |
<?xml version="1.0" encoding="UTF-8"?> <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata"> <apiVersion>49.0</apiVersion> <isExposed>true</isExposed> <targets> <target>lightning__AppPage</target> <target>lightning__RecordPage</target> <target>lightning__HomePage</target> </targets> </LightningComponentBundle> |
Step-3: Now we will call the component.
Create a test aura application test.app and paste the below code in it.
1 2 3 4 5 6 |
<!-- Code by CafeForce || www.cafeforce.com || support@cafeforce.com || Mandatory Header --> <aura:application extends="force:slds"> <c:serverSidePagination></c:serverSidePagination> </aura:application> |
To see the output of the component, go to record page > click on setting gear on top and click on Edit Page. Now drop your component on the screen. Save and Activate the Page to Org Default. Now go back to your record. Your component is visible now.
You can also place this component on App Page and Home Page.
Also, check – Client-Side Pagination in LWC
Your component is now ready to use. Cheers …. Happy Coding … 🙂