Showing posts with label Datatable. Show all posts
Showing posts with label Datatable. Show all posts

Monday, November 9, 2020

Build Configurable Dynamic Table from Field Set using Lightning Web Component

 

Motivation behind this


I have been looking for building configurable Related list component using Field Set and Lightning Web Component. Earlier I have written blog on  Describe Objects and Retrieve Records using Salesforce Lightning Web Components flavored with Dynamic Datatable.

Now, I want to move it one step further using field set. This approach will help many developers to quickly develop and leverage this concepts.

Here is a justification why it is needed to build.

Use Case


Business has requirement to view the Record detail page as well as its Related list. User has edit and delete permissions to the related list's objects. But business wants that user will be not see any options to edit or delete records from the related list.

For example, Case related list is available under Account or Contact record. User has a permission to edit or delete on Case object but those records only be available for view purpose. User will not able to see Edit/Delete option.

Only option is to built this component as read-only viewing purpose.

This requirement is applicable for similar use cases like this, so it will be better to create a configurable component using Field Set.

Fields from the Field Set will be displayed as columns and records based on those fields will be displayed as rows.

Possible End Result


After building the use case, it will look like this:



Solution Approach


Refer this video for three step solutions:


For an example purpose, I have tried to build Case object as Related List.


First Step

Create a Field Set on Case Object and add necessary fields on the layout. For example name is CaseRelatedListFS


Second Step

Build a LWC Component which will take following configurable attributes:
  • Related Object API Name - Here it is Case
  • Field Set Name - The name of field set defined on the Object whose fields will be displayed as table columns. Here it is CaseRelatedListFS
  • Reference API Name - It is the field based on which query to be fired. Here, it is ContactId, as Case related list will be placed on Contact Record Page.
  • First Column As RecordHyperlink - It will take Yes/No. For example, if we define CaseNumber as first column and if we choose Yes option then Case Number will be shown as hyperlink and clicking on that link, it will be navigated to Case Detail page. If we choose No then, it will be same for other columns.
Let's see this lwcFieldSetComponent.js-meta.xml where those parameters can be configured as targetConfig property.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>50.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__RecordPage</target>
        <target>lightning__AppPage</target>
        <target>lightning__HomePage</target>
    </targets>
    <targetConfigs>
        <targetConfig targets="lightning__RecordPage,lightning__AppPage,lightning__HomePage">
            <property name="SFDCobjectApiName" label="Related Object API Name" type="String" default=""/>
            <property name="fieldSetName" label="Field Set Name" type="String" default=""/>
            <property name="criteriaFieldAPIName" label="Reference FieldAPIName" type="String" default=""
                            description="The field on which query to be performed. 
                            e.g. it can be AccountId from which Case records will be fetched."/>
            <property name="firstColumnAsRecordHyperLink" label="First Column As RecordHyperLink" 
                            type="String" datasource="Yes,No" default="Yes"/>                        
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>

Approach has been taken following way:
  • Create a LWC component adding a lightning-datatable which will be populated on load.
  • In the connectedCallback method of js it will fetch fields and records from the database
  • Finally display those in the datatable.
lwcFieldSetComponent.html


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<template>    
    <div class="c-container">
        <div class="slds-card">
            <div class="slds-media__body">
                <h2 class="slds-card__header-title">
                    <span>{lblobjectName} Records ({recordCount})</span>
                </h2>
            </div>
            <div class="slds-card__body">
                <lightning-datatable 
                    key-field="Id"
                    data={tableData}
                    columns={columns}
                    min-column-width=200>
                </lightning-datatable> 
            </div>                           
        </div> 
    </div>    
</template>

lwcFieldSetComponent.js

Few important points to refer here:


  • Never define an attribute as objectApiName as it is reserved, that's why I have used SFDCobjectApiName, otherwise, it will always take current object.

  • When we pull entries from a Map which has been stored in Apex class, the index is inversed. For example, I have first stored FIELD_LIST and then RECORD_LIST in the Map. Now, the index of keys will be 1 for FIELD_LIST and 0 from RECORD_LIST.

  • Extra coding to handle hyperlink record navigation in the first column.


Rest of the comments in the code is self-explanatory.


  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
/*
 *   Author: Santanu Boral
*/
import { LightningElement, api, track } from 'lwc';
import getFieldsAndRecords from '@salesforce/apex/FieldSetHelper.getFieldsAndRecords';

export default class LwcFieldSetComponent extends LightningElement {
    
    @api recordId;  // record id from record detail page e.g. ''0012v00002WCUdxAAH'
    @api SFDCobjectApiName; //kind of related list object API Name e.g. 'Case'
    @api fieldSetName; // FieldSet which is defined on that above object e.g. 'CaseRelatedListFS'
    @api criteriaFieldAPIName; // This field will be used in WHERE condition e.g.'AccountId'
    @api firstColumnAsRecordHyperLink; //if the first column can be displayed as hyperlink

    @track columns;   //columns for List of fields datatable
    @track tableData;   //data for list of fields datatable
    
    recordCount; //this displays record count inside the ()
    lblobjectName; //this displays the Object Name whose records are getting displayed

    connectedCallback(){
        let firstTimeEntry = false;
        let firstFieldAPI;

        //make an implicit call to fetch records from database
        getFieldsAndRecords({ strObjectApiName: this.SFDCobjectApiName,
                                strfieldSetName: this.fieldSetName,
                                criteriaField: this.criteriaFieldAPIName,
                                criteriaFieldValue: this.recordId})
        .then(data=>{        
            //get the entire map
            let objStr = JSON.parse(data);   
            
            /* retrieve listOfFields from the map,
             here order is reverse of the way it has been inserted in the map */
            let listOfFields= JSON.parse(Object.values(objStr)[1]);
            
            //retrieve listOfRecords from the map
            let listOfRecords = JSON.parse(Object.values(objStr)[0]);

            let items = []; //local array to prepare columns

            /*if user wants to display first column has hyperlink and clicking on the link it will
                naviagte to record detail page. Below code prepare the first column with type = url
            */
            listOfFields.map(element=>{
                //it will enter this if-block just once
                if(this.firstColumnAsRecordHyperLink !=null && this.firstColumnAsRecordHyperLink=='Yes'
                                                        && firstTimeEntry==false){
                    firstFieldAPI  = element.fieldPath; 
                    //perpare first column as hyperlink                                     
                    items = [...items ,
                                    {
                                        label: element.label, 
                                        fieldName: 'URLField',
                                        fixedWidth: 150,
                                        type: 'url', 
                                        typeAttributes: { 
                                            label: {
                                                fieldName: element.fieldPath
                                            },
                                            target: '_blank'
                                        },
                                        sortable: true 
                                    }
                    ];
                    firstTimeEntry = true;
                } else {
                    items = [...items ,{label: element.label, 
                        fieldName: element.fieldPath}];
                }   
            });
            //finally assigns item array to columns
            this.columns = items; 
            this.tableData = listOfRecords;

            console.log('listOfRecords',listOfRecords);
            /*if user wants to display first column has hyperlink and clicking on the link it will
                naviagte to record detail page. Below code prepare the field value of first column
            */
            if(this.firstColumnAsRecordHyperLink !=null && this.firstColumnAsRecordHyperLink=='Yes'){
                let URLField;
                //retrieve Id, create URL with Id and push it into the array
                this.tableData = listOfRecords.map(item=>{
                    URLField = '/lightning/r/' + this.SFDCobjectApiName + '/' + item.Id + '/view';
                    return {...item,URLField};                     
                });
                
                //now create final array excluding firstFieldAPI
                this.tableData = this.tableData.filter(item => item.fieldPath  != firstFieldAPI);
            }

            //assign values to display Object Name and Record Count on the screen
            this.lblobjectName = this.SFDCobjectApiName;
            this.recordCount = this.tableData.length;
            this.error = undefined;   
        })
        .catch(error =>{
            this.error = error;
            console.log('error',error);
            this.tableData = undefined;
            this.lblobjectName = this.SFDCobjectApiName;
        })        
    }
}

FieldSetHelper.cls

Few notable points on this getFieldsAndRecords method:

  • Getting the instance of SObject based on strObjectApiName, the reflection has been used which is way more faster that globalDescribe.
  • Use of Schema.FieldSetMember to get the fields from Field Set. Refer Documentation
  • getFieldPath() of FieldSetMember gives the fieldAPI which has been used to build SOQL query
Finally, both field List and record List are getting stored in the Map and returned to js.


 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
public with sharing class FieldSetHelper {
    @AuraEnabled (cacheable=true)
    public static String getFieldsAndRecords(String strObjectApiName, String strfieldSetName,
                                             String criteriaField, String criteriaFieldValue){
        Map<String, String> returnMap = new Map<String,String>();
        if(!String.isEmpty(strObjectApiName) && !String.isEmpty(strfieldSetName)){
            //get fields from FieldSet
            SObject sObj = (SObject)(Type.forName('Schema.'+ strObjectApiName).newInstance());
            List<Schema.FieldSetMember> lstFSMember = 
                sObj.getSObjectType().getDescribe().fieldSets.getMap().get(strfieldSetName).getFields();

	    //prepare SOQL query based on fieldAPIs	
	    String query = 'SELECT ';
	    for(Schema.FieldSetMember f : lstFSMember) {
	        query += f.getFieldPath() + ', ';
            }
            query += 'Id FROM ' + strObjectApiName ;

            //Just in case criteria field not specified then it will return all records
            if(!(String.isEmpty(criteriaField) && String.isEmpty(criteriaFieldValue))){
                query += ' WHERE ' + criteriaField + '=\'' + criteriaFieldValue + '\'';
            }
                        
	    //execute query
             List<SObject> lstRecords = Database.query(query);
            
             //prepare a map which will hold fieldList and recordList and return it
	     returnMap.put('FIELD_LIST', JSON.serialize(lstFSMember));
	     returnMap.put('RECORD_LIST', JSON.serialize(lstRecords));
	     return JSON.serialize(returnMap);
        }
        return null;
    }
}

Final Step

After building the component, place it on Record Page through App Builder and define attribute values.



You can see that component is displaying fields and records.

This concept can be leveraged easily at any project. The component can be improvised more like, handling events, based on Salesforce field type Datatable column types can be defined, it could be good to incorporate paginations etc, but it is a good start.

We are done and thanks for reading.



Further Reading


Tuesday, April 28, 2020

Developing Suggested Cases Component using Lightning Web Components firing SOSL query

Motivation behind this


Today, I have received a requirement to build a suggested cases or similar cases functionality which will help Business to refer during resolving a case. Since this functionality is not by-default available as part of Case Management so tried to build from my own.

There are few approaches I have followed which might be helpful to learn and explore to build this simple component. For example, why should I use SOSL query instead of SOQL.

Let's get started.

Use Case


Business wants a have a functionality to show suggested cases or similar cases in the case detail page. This will help to refer previously closed cases and to resolve current cases quickly. 

It will have following functionality:
  • Based on current case subject, system will pull all the occurrences of matching keywords from any fields from Case object.
  • It will display case records which has been created recently and closed.
  • It might filter based on other criteria like, Case Types
  • In the datatable, clicking on the Case Number (URL), it will open the case record.

Possible End Result


The component will look like as highlighted.


Solution Approach


Based on the above diagram, need to create component which will show data in datatable.

suggestedCases.html


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<template>
    <lightning-card>      
        <p class="slds-p-horizontal_large">Suggested Cases</p>        
        <div style="height: 200px;">
            <lightning-datatable 
                key-field="Id"
                data={records}
                columns={columns}>
            </lightning-datatable>
        </div>
    </lightning-card>
</template>

suggestedCases.js

This js controller is most important for implementing this functionality and some of the key points have been highlighted below:


  • Defining a column as Case Number which will act as hyperlink. Refer type and typeAttributes
{
        label: 'Case Number', 
        fieldName: 'URLField',
        fixedWidth: 120,
        type: 'url', 
        typeAttributes: { 
            label: {
                fieldName: 'CaseNumber'
            },
            target: '_blank'
        },
        sortable: true 
}

  • Use recordId with @api to capture record Id of the Case Detail Record.
  • Since the Case Number column to be created and not readily available like that formatted way so elements to be created on the fly and to be pushed into the records array.
Entire code is as follows:

 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
import { LightningElement, track, wire, api} from 'lwc';
import getSuggestedCases from '@salesforce/apex/CaseController.getSuggestedCases';

//define datatable columns with customized Case Number URL column
const columns = [
    {
        label: 'Case Number', 
        fieldName: 'URLField',
        fixedWidth: 120,
        type: 'url', 
        typeAttributes: { 
            label: {
                fieldName: 'CaseNumber'
            },
            target: '_blank'
        },
        sortable: true 
    },
    { label: 'Subject', fieldName: 'Subject' }       
];
export default class SuggestedCases extends LightningElement {

    @api recordId; //it will be passed from the screen
    @track records; //datatable records
    @track columns; //datatable columns

    //retrieve suggested cases based on case recordId
    @wire(getSuggestedCases,{caseId: '$recordId'})
    wiredCases({ error, data }) {
        if (data) {
            let URLField;
            //retrieve Id, create URL with Id and push it into the array
            this.records = data.map(item=>{
                URLField = '/lightning/r/Case/' + item.Id + '/view';
                return {...item,URLField};                
            });
            this.columns = columns;
            this.error = undefined;
        } else if (error) {
            this.error = error;
            this.records = undefined;
        }
    }
    
}

CaseController.cls

In the class, as keyword occurrences need to be search on multiple fields, so SOSL (Salesforce Object Search Language) query has been used.

Since, it needs to be searched on Case Description field which is LongTextArea so SOQL search is not possible, it cannot be used in WHERE condition.

As it should include all the keywords for search taking from Case's subject, so all the keywords to be separated by OR operator (this is tricky).

Rest of the code is simple.


 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
public with sharing class CaseController {
    @AuraEnabled (cacheable=true)
    public static List<Case> getSuggestedCases(String caseId){
        List<Case> lstCase = new List<Case>(); 
        //retrieve case subject of existing case       
        Case caseObj = [SELECT Subject, Type FROM Case WHERE Id=:caseId];

        //since all the keywords need to searched so, need to put 'OR' condition between the keywords
        List<String> strList = caseObj.Subject.split(' ');
        String strSearch = String.join(strList, '\' OR \'') + '*';
        
        System.debug('strSearch=' + strSearch);
        //strSearch=Seeking' OR 'guidance' OR 'on' OR 'electrical' OR 'wiring' OR 'installation' OR 'for' OR 'GC5060*                                 

        //retrieve cases which are already closed, created recently and eliminating current case
        List<List<SObject>> searchList = [FIND :strSearch IN ALL FIELDS 
                                         RETURNING Case(Id,CaseNumber, Subject 
                                         WHERE Id!=:caseId
                                         AND Status = 'Closed'
                                         AND Type =:caseObj.Type
                                         ORDER BY CreatedDate DESC
                                         LIMIT 20) 
                                        ];

        if(searchList.size()>0){
            return searchList[0];
        }
        return lstCase;
    }
}

suggestedCases.js-meta.xml

Define where this component to be exposed.


<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>48.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__RecordPage</target>
        <target>lightning__AppPage</target>
        <target>lightning__HomePage</target>
    </targets>
</LightningComponentBundle>

After developing this component, expose it to Case Detail Page as shown in the picture.

Overall, it is a small piece of work but good to explore new things in Javascript and LWC. Thanks for reading!


References



Further Reading

Wednesday, September 25, 2019

Pagination using Salesforce Lightning Web Components with array slicing

Motivation behind this


Currently I am exploring Lightning Web Components (LWC) and trying to build pagination functionality using LWC. I was searching on google on the same, but didn't find this functionality on LWC, though there are tons of materials available on the same either using Lightning Aura Components or by Visualforce StandardSetController.

I have seen couple of solutions where developers either used offset to fetch page by page data every time directly from controller's SOQL query or taken entire dataset on the client side controller but every time looping through the array in the for loop to get paginated data.

Both of the solutions are not favorable to me, which motivates me to build a proof-of-concept and sharing a knowledge to Salesforce community members.

I have gone through Create and Dispatch Events documentation, there is some light on pagination but that topic is used to understand on how events are generated and dispatched.

Let's get started.

Use Case


Business has a requirement to view the tabular data retrieving from the database in a paginated way. 

Developer wants to build the same using Lightning Web Components.


Expected Behavior




In the above demo, I am trying to show Account data is displaying page by page.

Before going to solution approach it is recommended to read Salesforce Lightning Web Components Cheat Sheet to have an understanding on different type of variables, dispatching events and calling apex classes' method from js controller.



Solution Approach


There are two components have been used.

1. Paginator component - which contains previous and next button as specified in Create and Dispatch Events documentation. For the sake of simplicity, take all the code as specified there.

2. displayPaginatedRecords component - which contains lightning datatable and Paginator component to implement entire functionality.

Let's first discuss about displayPaginatedRecords component.

displayPaginatedRecords.html

This html contains datatable and Paginator component using c-paginator which is listening events using onprevious and onnext event listeners.



<template>
    <lightning-card title="List of Accounts" icon-name="custom:custom9">
        <div class="slds-m-around_medium">
            <p class="slds-p-horizontal_medium">Display records in paginated way </p>
            <br></br>
            <div style="height: 180px;">
                <lightning-datatable 
                    key-field="id"
                    data={data}
                    columns={columns}>
                </lightning-datatable>
            </div>
        </div>
        <div class="slds-m-around_medium">
            <p class="slds-m-vertical_medium content">
                     Displaying {startingRecord} to {endingRecord} of {totalRecountCount} records.
                     Page {page} of {totalPage}. </p>
            <c-paginator onprevious={previousHandler} onnext={nextHandler}></c-paginator>
        </div>
    </lightning-card>
</template>

PaginationController.cls

This class has retrieveAccounts method which retrieves the data from Account object. For simplicity, WHERE clause and filter criteria have not given.


public with sharing class PaginationController {
    @AuraEnabled (cacheable=true)
    public static List<Account> retrieveAccounts(){
        return [SELECT Id, Name, Type, BillingCountry
                FROM Account
                LIMIT 1000];
    }
}

displayPaginatedRecords.js

Let's talk main important points about this js controller.


  • All the respective @track variables have been declared which have been used in the page, like page, startingRecord, endingRecord, pageSize, totalRecountCount, totalPage, data, columns. All are self-explanatory but written their usage as comments for each.
  • default pageSize = 5, so every page will display 5 records.
  • Datatable columns have been defined with label and fieldName attributes.
  • @wire to function i.e retrieveAccounts which performs following:

  1. retrieves the data from Apex controller and assigns all data in items array
  2. calculates totalRecountCount from data.length and calculates totalPage to be displayed.
  3. Now, main part is: use of Array.slice() which returns selected elements from the array

this.data = this.items.slice(this.startingRecord, this.endingRecord);

The above principle has been applied in displayRecordPerPage method and everything is written as comments for better understanding.

For example, on 2nd page, label will shown as => "Displaying 6 to 10 of 23 records. Page 2 of 5"
        page = 2; pageSize = 5; startingRecord = 5, endingRecord = 10
        so, slice(5,10) will give 5th to 9th records.
        
  • previousHandler and nextHandler methods changes the page and calls displayRecordPerPage method
If we remove the comments from the method bodies then code will be compact. I have mentioned them for easy understanding.



/* eslint-disable no-console */
import { LightningElement, track, wire } from 'lwc';

import retrieveAccounts from '@salesforce/apex/PaginationController.retrieveAccounts';
//define columns of the datatable
const columns = [ { label: 'Id', fieldName: 'Id' }, { label: 'Name', fieldName: 'Name' }, { label: 'Type', fieldName: 'Type' }, { label: 'BillingCountry', fieldName: 'BillingCountry' }, ]; let i=0; export default class DisplayPaginatedRecords extends LightningElement { @track page = 1; //this will initialize 1st page @track items = []; //it contains all the records. @track data = []; //data to be displayed in the table @track columns; //holds column info. @track startingRecord = 1; //start record position per page @track endingRecord = 0; //end record position per page @track pageSize = 5; //default value we are assigning @track totalRecountCount = 0; //total record count received from all retrieved records @track totalPage = 0; //total number of page is needed to display all records @wire(retrieveAccounts) wiredAccounts({ error, data }) { if (data) { //if you want to perform data transformation then following code will be used, //so that individual values to be assigned into each columns /* for(i=0; i<data.length; i++) { this.items = [...this.items, {Id:data[i].Id, Name:data[i].Name, Type:data[i].Type, BillingCountry:data[i].BillingCountry}]; } */ this.items = data; this.totalRecountCount = data.length; //here it is 23 this.totalPage = Math.ceil(this.totalRecountCount / this.pageSize); //here it is 5 //initial data to be displayed -----------> //slice will take 0th element and ends with 5, but it doesn't include 5th element //so 0 to 4th rows will be displayed in the table this.data = this.items.slice(0,this.pageSize); this.endingRecord = this.pageSize; this.columns = columns; this.error = undefined; } else if (error) { this.error = error; this.data = undefined; } } //clicking on previous button this method will be called previousHandler() { if (this.page > 1) { this.page = this.page - 1; //decrease page by 1 this.displayRecordPerPage(this.page); } } //clicking on next button this method will be called nextHandler() { if((this.page<this.totalPage) && this.page !== this.totalPage){ this.page = this.page + 1; //increase page by 1 this.displayRecordPerPage(this.page); } } //this method displays records page by page displayRecordPerPage(page){ /*let's say for 2nd page, it will be => "Displaying 6 to 10 of 23 records. Page 2 of 5" page = 2; pageSize = 5; startingRecord = 5, endingRecord = 10 so, slice(5,10) will give 5th to 9th records. */ this.startingRecord = ((page -1) * this.pageSize) ; this.endingRecord = (this.pageSize * page); this.endingRecord = (this.endingRecord > this.totalRecountCount) ? this.totalRecountCount : this.endingRecord; this.data = this.items.slice(this.startingRecord, this.endingRecord); //increment by 1 to display the startingRecord count, //so for 2nd page, it will show "Displaying 6 to 10 of 23 records. Page 2 of 5" this.startingRecord = this.startingRecord + 1; } }


Let's take paginator component.


paginator.html


This html contains two button Previous and Next for firing events.


<template>
    <lightning-layout>
        <lightning-layout-item>
            <lightning-button label="Previous" icon-name="utility:chevronleft" onclick={previousHandler}></lightning-button>
        </lightning-layout-item>
        <lightning-layout-item flexibility="grow"></lightning-layout-item>
        <lightning-layout-item>
            <lightning-button label="Next" icon-name="utility:chevronright" icon-position="right" onclick={nextHandler}></lightning-button>
        </lightning-layout-item>
    </lightning-layout>
</template>

paginator.js

This js controller dispatches two events from event handlers.


// paginator.js
import { LightningElement } from 'lwc';

export default class Paginator extends LightningElement {
    previousHandler() {
        this.dispatchEvent(new CustomEvent('previous'));
    }

    nextHandler() {
        this.dispatchEvent(new CustomEvent('next'));
    }
}


displayPaginatedRecords.js-meta.xml

This meta xml contains the information as to where this component will be exposed.


<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="displayPaginatedRecords">
    <apiVersion>46.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__RecordPage</target>
        <target>lightning__AppPage</target>
        <target>lightning__HomePage</target>
    </targets>
</LightningComponentBundle>

Now, from Lightning App Builder page, drag and drop the displayPaginatedRecords component and open the page.


Final Outcome



Finally, we are done and thanks for reading.

Further Reading