Comment implémenter la pagination dans Lightning Web Component – SFDCPanther10 minutes de lecture
Chers #Trailblazers, #Ohana,
Dans cet article, je vais vous montrer comment créer un composant Web Pagination Lightning réutilisable que vous pouvez utiliser pour n’importe quel composant pour afficher les données sous forme de tableau ou sous toute autre forme que vous souhaitez afficher.
Fonctionnalités disponibles dans le composant
- Affichez les enregistrements dans un tableau ou dans un autre format.
- Edition en ligne pour la table de données LWC
- Tri dans la table de données LWC
- Prend en charge l’action au niveau de la ligne
- Affiche les détails de la page et le nombre d’enregistrements
Avant, nous commençons à travailler sur les implémentations, parlons du concept de Pagination.
La pagination est l’une de ces fonctionnalités ennuyeuses qui ne sont pas amusantes à implémenter dans aucune langue, mais qui est à peu près essentielle pour une bonne interface utilisateur.
Lorsque nous parlons de pagination, nous avons généralement une liste d’enregistrements et nous voulions ensuite afficher ces enregistrements sur de nombreuses pages.
Donc, si nous disons que nous récupérons des enregistrements de la liste (nous pouvons également dire découper les enregistrements comme nous découpons le gâteau) et les afficher sur la page. Vous trouverez ci-dessous la mise en œuvre étape par étape de la pagination à l’aide de JavaScript.
1. Quelques variables utiles
var recordList; // The List of Complete Records
var pageList; // The record List which needs to be displayed in a page
var currentPage = 1;
// by default will always be 1
var recordPerPage = 10;
// The no of records needs to be displayed in a single page
var totalPages = 1; // calculates the total number of pages
2. Calculer le nombre de pages
this.totalPages = Math.ceil(recordList.length / recordPerPage );
3. Créez des boutons de navigation et leurs méthodes.
Dans Pagination, nous recherchons généralement 4 boutons de navigation principaux.
- Première
- précédent
- Prochain
- Dernier
handleNext() {
this.pageNo += 1;
this.preparePaginationList();
}
handlePrevious() {
this.pageNo -= 1;
this.preparePaginationList();
}
handleFirst() {
this.pageNo = 1;
this.preparePaginationList();
}
handleLast() {
this.pageNo = this.totalPages;
this.preparePaginationList();
}
4. Préparez les enregistrements de pagination
let begin = (this.pageNo - 1) * parseInt(this.recordsperpage);
let end = parseInt(begin) + parseInt(this.recordsperpage);
this.recordsToDisplay = this.records.slice(begin, end);
Dans le code ci-dessus, nous utilisons la méthode slice de JavaScript pour obtenir l’enregistrement exact basé sur le numéro de page afin que nous puissions afficher les enregistrements corrects sur la page.
Créer un composant doPagination
Code HTML
Displaying Page No:
{pageNo}/{totalPages} and displaying records
from {endRecord}/{totalRecords}
from ({startRecord}-{endRecord})/{totalRecords}
Code JsvaScript
import { LightningElement, api, track } from "lwc";
import { updateRecord } from 'lightning/uiRecordApi';
import { refreshApex } from '@salesforce/apex';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
const DELAY = 300;
export default class DoPaginaton extends LightningElement {
@api showTable = false;
@api records;
@api recordsperpage;
@api columns;
@track draftValues = [];
@track recordsToDisplay;
totalRecords;
pageNo;
totalPages;
startRecord;
endRecord;
end = false;
pagelinks = [];
isLoading = false;
defaultSortDirection = 'asc';
sortDirection = 'asc';
ortedBy;
connectedCallback() {
this.isLoading = true;
this.setRecordsToDisplay();
}
setRecordsToDisplay() {
this.totalRecords = this.records.length;
this.pageNo = 1;
this.totalPages = Math.ceil(this.totalRecords / this.recordsperpage);
this.preparePaginationList();
for (let i = 1; i this.totalRecords ? this.totalRecords : end;
this.end = end > this.totalRecords ? true : false;
const event = new CustomEvent('pagination', {
detail: {
records : this.recordsToDisplay
}
});
this.dispatchEvent(event);
window.clearTimeout(this.delayTimeout);
this.delayTimeout = setTimeout(() => {
this.disableEnableActions();
}, DELAY);
this.isLoading = false;
}
disableEnableActions() {
let buttons = this.template.querySelectorAll("lightning-button");
buttons.forEach(bun => {
if (bun.label === this.pageNo) {
bun.disabled = true;
} else {
bun.disabled = false;
}
if (bun.label === "First") {
bun.disabled = this.pageNo === 1 ? true : false;
} else if (bun.label === "Previous") {
bun.disabled = this.pageNo === 1 ? true : false;
} else if (bun.label === "Next") {
bun.disabled = this.pageNo === this.totalPages ? true : false;
} else if (bun.label === "Last") {
bun.disabled = this.pageNo === this.totalPages ? true : false;
}
});
}
handleRowAction(event) {
const actionName = event.detail.action.name;
const row = event.detail.row;
const rowAction = new CustomEvent('actions', {
detail: {
actionName : actionName,
data : row
}
});
this.dispatchEvent(rowAction);
}
handlePage(button) {
this.pageNo = button.target.label;
this.preparePaginationList();
}
onHandleSort(event) {
const { fieldName: sortedBy, sortDirection } = event.detail;
const cloneData = [...this.recordsToDisplay];
cloneData.sort(this.sortBy(sortedBy, sortDirection === 'asc' ? 1 : -1));
this.recordsToDisplay = cloneData;
this.sortDirection = sortDirection;
this.sortedBy = sortedBy;
}
sortBy( field, reverse, primer ) {
const key = primer
? function( x ) {
return primer(x[field]);
}
: function( x ) {
return x[field];
};
return function( a, b ) {
a = key(a);
b = key(b);
return reverse * ( ( a > b ) - ( b > a ) );
};
}
handleSave(event) {
this.isLoading = true;
const recordInputs = event.detail.draftValues.slice().map(draft => {
const fields = Object.assign({}, draft);
return { fields };
});
const promises = recordInputs.map(recordInput => updateRecord(recordInput));
window.console.log(' Updating Records.... ');
Promise.all(promises).then(record => {
this.dispatchEvent(
new ShowToastEvent({
title: 'Success',
message: 'All Records updated',
variant: 'success'
})
);
this.draftValues = [];
eval("$A.get('e.force:refreshView').fire();");
return refreshApex(this.recordsToDisplay);
}).catch(error => {
window.console.error(' error **** n '+error);
})
.finally(()=>{
this.isLoading = false;
})
}
}
Code CSS
.customSelect select {
padding-right: 1.25rem;
min-height: inherit;
line-height: normal;
height: 1.4rem;
}
.customSelect label {
margin-top: 0.1rem;
}
.customSelect .slds-select_container::before {
border-bottom: 0;
}
.customInput {
width: 3rem;
height: 1.4rem;
text-align: center;
border: 1px solid #dddbda;
border-radius: 3px;
background-color: #fff;
}
Propriétés publiques dans le composant
Ce composant possède les propriétés suivantes qui doivent être transmises par le parent où vous souhaitez utiliser la table de données.
- showTable – Accepte vrai / faux. Si vrai, il affichera la table de données.
- records – La liste complète des enregistrements
- enregistrements par page – N’accepte aucune valeur et définit le nombre d’enregistrements devant être affichés sur une seule page.
- Colonnes – si la valeur de la propriété showTable est true, elle nécessite la colonne de données valide. Pour plus de détails, visitez ici.
Événements dans le composant
Ce composant contient
- pagination – Cet événement envoie la liste de tous les enregistrements qui sont affichés sur la page actuelle et à utiliser si vous ne souhaitez pas utiliser Lightning Data Table pour afficher les enregistrements. Par exemple, vous vouliez utiliser l’accordéon Lightning pour la pagination au lieu de la table de données.
- Actions – Cet événement envoie les données sur la ligne actuelle et l’action au niveau de la ligne. Ainsi, si vous avez des actions au niveau de la ligne, vous pouvez gérer cet événement dans votre composant parent.
Testez le composant de pagination.
Pour tester cela, vous devez créer une classe Apex qui renverra une liste d’enregistrements. J’ai utilisé la requête ci-dessous.
public with sharing class ContactController {
@AuraEnabled
public static List getContacts() {
List contactList = [
SELECT
Id,
Name,
AccountId,
Account.Name,
Title,
Phone,
Email,
OwnerId,
Owner.Name,
Owner.Email
FROM CONTACT
WITH SECURITY_ENFORCED
];
return contactList;
}
}
Créez le composant Lightning et appelez le composant doPagination à l’intérieur de ce composant.
Si vous avez remarqué cela, nous transmettons la valeur de la propriété requise et gérons les événements requis dont nous avons discuté ci-dessus.
Voici le code complet du composant de test
HTML
Code JS
/**
* @File Name : contactDataTable.js
* @Description :
* @Author : A Singh
* @Group :
* @Last Modified By : Amit Singh
* @Last Modified On : 12-06-2020
* @Modification Log :
* Ver Date Author Modification
* 1.0 6/5/2020 A Singh Initial Version
**/
import { LightningElement, track } from 'lwc';
import getContacts from '@salesforce/apex/ContactController.getContacts';
import sharedjs from 'c/sharedjs';
const columns = [
{ label: 'Name', fieldName: 'Name', wrapText: 'true', sortable: true, editable: true },
{ label: 'Email', fieldName: 'Email', type: 'email', sortable: true, editable: true },
{ label: 'Phone', fieldName: 'Phone', type: 'phone', sortable: true, editable: true },
{ label: 'Title', fieldName: 'Title', sortable: true, editable: true },
{
label: 'Account',
fieldName: 'ACC_NAME',
wrapText: 'true',
cellAttributes: {
iconName: { fieldName: 'accIconName' },
iconPosition: 'left'
},
sortable: true
},
{
label: 'Owner',
fieldName: 'OWNER',
cellAttributes: {
iconName: { fieldName: 'iconName' },
iconPosition: 'left'
},
sortable: true
},
{
label: 'View',
fieldName: 'URL',
type: 'url',
wrapText: 'true',
typeAttributes: {
tooltip: { fieldName: 'Name' },
label: {
fieldName: 'Name'
},
target: '_blank'
}
},
{ label: 'View', type: 'button', typeAttributes: {
label: 'View', name: 'View', variant: 'brand-outline',
iconName: 'utility:preview', iconPosition: 'right'
}
},
];
export default class ContactDataTable extends LightningElement {
@track records;
@track errors;
columns = columns;
connectedCallback() {
this.handleDoInit();
}
handleDoInit() {
sharedjs._servercall(
getContacts,
undefined,
this.handleSuccess.bind(this),
this.handleError.bind(this)
);
}
handleSuccess(result) {
result.forEach(element => {
if (element.OwnerId) {
element.OWNER = element.Owner.Name;
element.iconName = 'standard:user';
}
if (element.AccountId) {
element.ACC_NAME = element.Account.Name;
element.accIconName = 'standard:account';
}
element.URL = 'https://' + window.location.host + '/' + element.Id;
});
this.records = result;
this.errors = undefined;
}
handleError(error) {
this.errors = error;
this.records = undefined;
}
handleRowActions(event){
window.console.log(' Row Level Action Handled ', event.detail.actionName);
window.console.log(' Row Level Action Handled ', JSON.stringify(event.detail.data));
}
handlePagination(event){
//window.console.log('Pagination Action Handled ', JSON.stringify(event.detail.records));
}
}
Fichier .XML
50.0
true
Contact Data Table
lightning__RecordPage
lightning__AppPage
lightning__HomePage
lightningCommunity__Page
lightningCommunity__Default
Créer un composant SharedJs
Le composant de test ci-dessus a la dépendance d’un nouveau composant Web Lightning et ci-dessous se trouve le code JS pour le même composant.
/* eslint-disable no-else-return */
/**
* @File Name : sharedjs.js
* @Description :
* @Author : [email protected]
* @Group :
* @Last Modified By : Amit Singh
* @Last Modified On : 12-06-2020
* @Modification Log :
* Ver Date Author Modification
* 1.0 5/17/2020 [email protected] Initial Version
**/
import { ShowToastEvent } from "lightning/platformShowToastEvent";
/*
! To store all the JS functions for the various LWC
* This JavaScript file is used to provide many reusability functionality like pubsub
* Reusable Apex Calls to Server, Preparing Dynamic Toasts
Todo : PubSub JS file of LWC & Aura Components
? V2
*/
var callbacks = {};
/**
* Registers a callback for an event
* @param {string} eventName - Name of the event to listen for.
* @param {function} callback - Function to invoke when said event is fired.
*/
const subscribe = (eventName, callback) => {
if (!callbacks[eventName]) {
callbacks[eventName] = new Set();
}
callbacks[eventName].add(callback);
};
/**
* Unregisters a callback for an event
* @param {string} eventName - Name of the event to unregister from.
* @param {function} callback - Function to unregister.
*/
const unregister = (eventName, callback) => {
if (callbacks[eventName]) {
callbacks[eventName].delete(callback);
// ! delete the callback from callbacks variable
}
};
const unregisterAll = () => {
callbacks = {};
};
/**
* Fires an event to listeners.
* @param {string} eventName - Name of the event to fire.
* @param {*} payload - Payload of the event to fire.
*/
const publish = (eventName, payload) => {
if (callbacks[eventName]) {
callbacks[eventName].forEach(callback => {
try {
callback(payload);
} catch (error) {
// fail silently
}
});
}
};
/**
* Todo: Calls an Apex Class method and send the response to call back methods.
* @param {*} _serveraction - Name of the apex class action needs to execute.
* @param {*} _params - the list of parameters in JSON format
* @param {*} _onsuccess - Name of the method which will execute in success response
* @param {*} _onerror - Name of the method which will execute in error response
*/
const _servercall = (_serveraction, _params, _onsuccess, _onerror) => {
if (!_params) {
_params = {};
}
_serveraction(_params)
.then(_result => {
if (_result && _onsuccess) {
_onsuccess(_result);
}
})
.catch(_error => {
if (_error && _onerror) {
_onerror(_error);
}
});
};
/**
* Todo: Prepare the toast object and return back to the calling JS class
* @param {String} _title - title of of the toast message
* @param {String} _message - message to display to the user
* @param {String} _variant - toast type either success/error/warning or info
* @param {String} _mode - defines either toast should auto disappear or it should stick.
*/
const _toastcall = (_title, _message, _variant, _mode) => {
const _showToast = new ShowToastEvent({
title: _title,
message: _message,
mode: _mode,
variant: _variant
});
return _showToast;
};
/**
* Todo: Parse the Error message and returns the parsed response to calling JS method.
* @param {Array} errors - Error Information
*/
const _reduceErrors = errors => {
if (!Array.isArray(errors)) {
errors = [errors];
}
return errors
.filter(error => !!error)
.map(error => {
if (Array.isArray(error.body)) {
return error.body.map(e => e.message);
} else if (error.body && typeof error.body.message === "string") {
return error.body.message;
} else if (typeof error.message === "string") {
return error.message;
}
return error.statusText;
})
.reduce((prev, curr) => prev.concat(curr), [])
.filter(message => !!message);
};
/*
Todo: Export all the functions so that these are accisible from the other JS Classes
*/
export default {
subscribe,
unregister,
publish,
unregisterAll,
_servercall,
_toastcall,
_reduceErrors
};
Production
Ressources
- https://www.thatsoftwaredude.com/content/6125/how-to-paginate-through-a-collection-in-javascript
- https://jasonwatmore.com/post/2018/08/07/javascript-pure-pagination-logic-in-vanilla-js-typescript
- https://developer.salesforce.com/docs/component-library/bundle/lightning-datatable/example
Merci d’avoir lu 🙂
En cas de doute, n’hésitez pas à me contacter.
#Salesforce #DeveloperGeeks #SFDCPanther
Cliquez pour noter cet article!
[Total:[Total:1 Moyenne: 5]