Paginação é uma técnica usada para dividir grandes conjuntos de dados ou conteúdo em pedaços menores e mais gerenciáveis chamados páginas. É uma estratégia comum empregada em interfaces de usuário para melhorar a experiência do usuário ao lidar com grandes quantidades de informação.
Mas ao lidar com grandes conjuntos de dados que excedem os limites do governador do Salesforce, como o limite de consulta de 2.000 registros, você precisa implementar uma estratégia de paginação mais sofisticada, conhecida como ” Paginação de conjunto de chaves”. Nessa abordagem, você usa o valor de uma coluna específica (geralmente um atributo classificável, exclusivo e indexado, como um registro de data e hora ou ID de registro) da última linha retornada na página anterior de resultados como um ponto de referência para buscar a próxima página.
Recursos da paginação aprimorada:
- Técnica de paginação do lado do servidor , como paginação de conjunto de chaves, para buscar dados em blocos gerenciáveis sem atingir o limite de OFFSET.
- Controles de navegação para permitir que os usuários se movam entre as páginas, incluindo os botões “Primeira”, “Última”, “Anterior” e “Próxima”.
- Acompanhe o número da página atual e o número total de páginas para gerenciar a navegação com precisão
- Permita que os usuários pesquisem registros usando um campo de entrada de pesquisa que aciona uma consulta de pesquisa do lado do servidor com base na entrada do usuário.
- Lógica de classificação do lado do servidor para buscar dados classificados de acordo com a preferência do usuário.
Aqui está o processo para implementar o Datatable com Paginação para mostrar o grande conjunto de dados de Fatura de Conta .
- Crie um objeto personalizado chamado “Fatura” com os campos Nome(AutoNumeração), Conta, Data_da_fatura, Data_de_vencimento, Status(Lista_de_seleção) e Total e carregue os dados de 10 mil registros.
- Crie o componente LWC customDataTable da seguinte forma
Tabela de Dados customizada.html
< template >
< lightning-card title = "Faturas" icon-name = "standard:drafts" >
< div >
< div class = "slds-p-bottom_small slds-grid slds-grid_align-spread" >
< p style = "margin: auto 0 auto 0;" > < b > Total de registros: </ b > {totalRecords} </ p >
< div class = "slds-grid" >
< lightning-input type = "search" label = "Pesquisar" onchange = {handleSearchChange} > </ lightning-input >
< lightning-button label = "Pesquisar" variant = "brand" onclick = {handleSearch} class = "slds-p-left_small" style = "margin-top:auto;" > </ lightning-button >
</ div >
</ div >
< div style = "height:380px;" >
< lightning-datatable
campo-chave = "Id"
dados = {registros}
colunas = {colunas}
ocultar-caixa-de-seleção-coluna
mostrar-número-da-linha-coluna = "verdadeiro"
ordenado-por = {sortByUI}
direção-ordenada = {sortDirection}
onsort = {handleSort}
deslocamento-do-número-da-linha = {rowNumberOffset} >
</ lightning-datatable >
</ div >
< modelo if:true = {showBar} >
< div estilo = "fundo: rgb(243, 243, 243);altura:50px;margem-superior:5px;" >
< div classe = "grade-slds" estilo = "altura:4em;flutuante:certo;" >
< classe de botão = "slds-button slds-button_icon icon_button icon_button_disabled" título = "Primeiro" role = "primeiro" onclick = {handleFirst} desabilitado = {primeiro} >
< svg viewBox = "0 0 1024 1024" class = "ícone" >
< g > < path d = "M1024 1024 256 512 1024 0Z" /> < path d = "M0 128l256 0 0 768-256 0 0-768Z" /> </ g >
</ svg >
</ button >
< button class = "slds-button slds-button_icon icon_button icon_button_disabled" title = "Anterior" role = "anterior" onclick = {handlePrevious} desabilitado = {primeiro} >
< svg viewBox = "0 0 1024 1024" class = "ícone" >
< g > < path d = "M1024 1024 256 512 1024 0Z" /> </ g >
</ svg >
</ button >
< span class = "vcenter currentpage" > {currentPage} </ span >
< span class = "vcenter" style = "font-weight:500;" > / {totalPages} </ span >
< button class = "slds-button slds-button_icon icon_button" title = "Próximo" role = "próximo" onclick = {handleNext} disabled = {último} >
< svg viewBox = "0 0 1024 1024" class = "ícone" >
< g > < path d = "M0 0 768 512 0 1024Z" /> </ g >
</ svg >
</ button >
< button class = "slds-button slds-button_icon icon_button" título = "Último" função = "último" onclick = {handleLast} desabilitado = {último} >
< svg viewBox = "0 0 1024 1024" classe = "ícone" >
< g > < caminho d = "M0 0 768 512 0 1024Z" /> < caminho d = "M768 128l256 0 0 768-256 0 0-768Z" /> </ g >
</ svg >
</ botão >
</ div >
</ div >
</ modelo >
</ div >
< modelo if:true = {isLoading} >
< relâmpago-spinner texto-alternativo = "Carregando" > </ relâmpago-spinner >
</ modelo >
</ relâmpago-cartão >
</ modelo >
customDataTable.js
importar { LightningElement, wire, track, api } de 'lwc' ;
importar getRecords de '@salesforce/apex/InvoiceController.getRecords' ;
importar getCount de '@salesforce/apex/InvoiceController.getCount' ;
const colunas = [
{
rótulo: "Nome" , fieldName: "Link" , tipo: "url" , classificável: "true" , typeAttributes: {
rótulo: { fieldName: 'Nome' }, alvo: '_self'
}
},
{ rótulo: "Data da fatura" , fieldName: "Data_da_fatura__c" , tipo: "Data" , classificável: "true" },
{ rótulo: "Data de vencimento" , fieldName: "Data_de_vencimento__c" , tipo: "Data" , classificável: "true" },
{ rótulo: "Status" , fieldName: "Status__c" , tipo: "Texto" , classificável: "true" },
{ rótulo: "Total" , fieldName: "Total__c" , tipo: "Número" , classificável: "true" }
];
const pageSize = 100 ;
export default class CustomDataTable extends LightningElement {
@track records = [];
@api recordId;
currentPage = 1 ;
totalRecords;
totalPages;
columns = columns;
searchText = '' ;
searchKey = '' ;
sortByUI = 'Link' ;
sortBy = 'Name' ;
sortByType = 'Text' ;
sortDirection = 'asc' ;
//usado para carregar a primeira página.
first = true ;
//usado para carregar a próxima página. Ele contém o valor do campo classificado do último registro da página atual
after = '' ;
//usado para carregar a próxima página se vários registros tiverem o mesmo valor de campo classificado.
Ele contém o Id do último registro da página atual lastId = '' ;
//usado para carregar a Página Anterior. Ele contém o valor do campo classificado do primeiro registro da página atual
before = '' ;
//usado para carregar a Página Anterior se vários registros tiverem o mesmo valor de campo classificado. Ele contém o Id do primeiro registro da página atual
firstId = '' ;
//usado para Carregar a Última Página.
last = false ;
lastPageSize = 0 ;
isLoading = true ;
@wire(getRecords, {
accountId: " $recordId " ,
searchKey: " $searchKey " ,
sortBy: " $sortBy " ,
sortOrder: " $sortDirection " ,
pageSize,
primeiro: " $first " ,
depois: " $after " ,
lastId: " $lastId " ,
antes: " $before " ,
firstId: " $firstId " ,
último: " $last " ,
lastPageSize: " $lastPageSize " ,
sortByType: " $sortByType "
})
getInvoiceRecords({ dados , erros }) {
this .isLoading = false ;
if ( dados ) {
this .records = dados .map((rec) => {
return {...rec, "Link" : '/' +rec.Id }
});
}
}
@wire(getCount, {
accountId: " $recordId " ,
searchKey: " $searchKey "
})
getCount({ dados , erros }) {
if ( dados ) {
this .totalRecords = dados ;
this .totalPages = Math.ceil( dados /pageSize);
}
}
handleSearchChange(evento) {
this .searchText = evento.target.valor;
}
handleSearch() {
este .searchKey = este.searchText;
esta .currentPage = 1 ;
esta .resetFields();
esta .refreshButtons();
}
handleSort(evento) {
esta .currentPage = 1 ;
esta .resetFields();
esta .refreshButtons();
esta .sortByUI = event.detail.fieldName;
esta .sortBy = esta .sortByUI == 'Link' ? 'Nome' : esta .sortByUI;
esta .sortByType = esta .sortByUI == 'Link' ? 'Texto' : esta .columns.find(ele => ele.fieldName == esta .sortByUI).type;
esta .sortDirection = event.detail.sortDirection;
}
obter showBar() {
retornar esta .totalPages > 1 ;
}
manipularPrimeiro() {
esta .currentPage = 1 ;
este .resetFields();
este .refreshButtons();
}
handleNext() {
este .currentPage++;
este .resetFields();
var lastRecord = este .records[ este .records.length- 1 ];
este .after = lastRecord[ este .sortBy] ? lastRecord[ este .sortBy] : 'NULL' ;
este .lastId = lastRecord[ 'Id' ];
este .first = false ;
este .last = ( este .currentPage == este .totalPages);
este .refreshButtons();
}
handlePrevious() {
este .currentPage--;
este .resetFields();
var firstRecord = este .records[ 0 ];
este .before = firstRecord[ este .sortBy] ? firstRecord[ este .sortBy] : 'NULL' ;
este .firstId = firstRecord[ 'Id' ];
este .first = ( este .currentPage == 1 );
este .refreshButtons();
}
handleLast() {
este .currentPage = este .totalPages;
este .resetFields();
este .first = false ;
este .last = true ;
este .lastPageSize = este .totalRecords % pageSize;
este .refreshButtons();
}
resetFields() {
este .isLoading = true ;
este .before = '' ;
este .firstId = '' ;
este .after = '' ;
este .lastId = '' ;
este .first = true ;
este .last = false ;
este .lastPageSize = 0 ;
}
refreshButtons() {
este .template.querySelectorAll( '.icon_button' ).forEach(button => {
button.classList.remove( 'icon_button_disabled' );
});
se ( este .último) {
este .template.querySelector( '[função=próximo]' ).classList.add( 'ícone_button_desabilitado' );
este .template.querySelector( '[função=último]' ).classList.add( 'ícone_button_desabilitado' );
}
se ( este .primeiro) {
este .template.querySelector( '[função=primeiro]' ).classList.add( 'ícone_button_desabilitado' );
este .template.querySelector( '[função=anterior]' ).classList.add( 'ícone_button_desabilitado' );
}
}
obter rowNumberOffset() {
retornar ( este .currentPage- 1 )*pageSize;
}
}
customDataTable.js-meta.xml
<?xml version= "1.0" encoding= "UTF-8" ?>
< LightningComponentBundle xmlns = "http://soap.sforce.com/2006/04/metadata" >
< apiVersion > 60.0 </ apiVersion >
< isExposed > true </ isExposed >
< targets >
< target > lightning__RecordPage </ target >
</ targets >
</ LightningComponentBundle >
Tabela de Dados customizada.css
.icon {
largura : 1em ;
altura : 1em ;
preenchimento: rgb ( 116 , 116 , 116 );
}
.icon_button {
largura : 2em ;
altura : 2em ;
margem :auto 5px auto 5px ;
}
.vcenter {
margem :auto 5px auto 5px ;
}
.currentpage {
peso-da-fonte : 500 ;
largura : 30px ;
borda : rgb ( 116 , 116 , 116 ) 1px sólido;
alinhamento-do-texto : centralizado;
}
.icon_button_disabled {
cursor :auto;
}
.icon_button_disabled .icon {
preenchimento: #e5e5e5 ;
}
3. Crie a classe Apex InvoiceController
public class InvoiceController {
@AuraEnabled(cacheable=true)
public static List<Invoice__c> getRecords (Id accountId, String searchKey, String sortBy, String sortByType, String sortOrder, Integer pageSize,
String after, String lastId, String before, String firstId, boolean first, boolean last, Integer lastPageSize) {
List<Invoice__c> records = null ;
String query = 'SELECT Id, Nome, Data_da_fatura__c, Data_de_vencimento__c, Status__c, Total__c FROM Fatura__c Onde Conta__c=\'' + accountId+ '\' ' ;
if (searchKey != '' ) {
String textFilter = '%' + searchKey+ '%' ;
query = buildSearchTerm(query, searchKey);
}
if ((after != '' && !last) || (before != '' && !first)) {
String param = ':textParam' ;
String textParam = after != '' && after != 'NULL' ? after : (before != '' && before != 'NULL' ? before : null );
if (sortByType == 'Number' && textParam != null ) {
param = ':numberParam' ;
Integer numberParam = Integer.valueOf(textParam);
} else if (sortByType == 'Date' && textParam != null ) {
param = ':dateParam' ;
Date dateParam = Date.valueOf(textParam);
}
String field = sortBy;
if (after != '' && !last) {
String operator = sortOrder == 'asc' ? '>' :'<' ;
if (after == 'NULL' ) {
consulta += 'e (' + campo + ' != NULL OU (' + campo+ '= NULL E Id' + operador + '\''+lastId+ '\')) ' ;
} else if (sortOrder == 'desc' ) {
query += 'e (' + campo + operador + parâmetro + ' OU ' +campo+ ' = NULO OU (' +campo+ '=' +parâmetro+ ' E Id' + operador + '\'' +lastId+ '\')) ' ;
} else {
query += 'e (' + campo + operador + parâmetro + ' OU (' +campo+ '=' +parâmetro+ ' E Id' + operador + '\'' +lastId+ '\')) ' ;
}
} else if (antes != '' && !primeiro) {
String operador = sortOrder == 'asc' ? '<' : '>' ;
se (antes == 'NULO' ) {
consulta += 'e (' + campo + ' != NULO OU (' + campo+ '= NULO E Id' + operador + '\'' +firstId+ '\')) ' ;
} senão se (ordemDeClassificação == 'asc' ) {
consulta += 'e (' + campo + operador + parâmetro + ' OU ' + campo+ ' = NULO OU (' + campo+ '=' + parâmetro + ' E Id' + operador + '\'' +firstId+ '\')) ' ;
} senão {
consulta += 'e (' + campo + operador + parâmetro + ' OU (' + campo+ '=' + parâmetro + ' E Id' + operador + '\'' +firstId+ '\')) ' ;
}
}
}
String orderQuery = '' ;
se (último || (antes != '' && !primeiro)) {
orderQuery = 'Order BY ' + sortBy + ' ' + (sortOrder == 'asc' ? 'desc NULLS LAST ' : 'asc NULLS FIRST ' ) + ', Id ' + (sortOrder =='asc' ? 'desc' : 'asc' );
} else {
orderQuery = 'Order BY ' + sortBy + ' ' + (sortOrder == 'asc' ? 'asc NULOS PRIMEIRO ' : 'desc NULOS ÚLTIMO ' ) + ', Id ' + sortOrder;
}
query += orderQuery + ' LIMITE ' + (último && lastPageSize > 0 ? lastPageSize : pageSize);
System.debug(query);
records = Database.query(query);
if ((before != '' && !first) || last) {
List<Invoice__c> tmp = new List <Invoice__c>();
for (Integer i=records.size()- 1 ; i>= 0 ; i--) {
tmp.add(records.get(i));
}
records = tmp;
}
retornar registros;
}
@AuraEnabled(cacheable=true)
public static Integer getCount (Id accountId, String searchKey) {
String query = 'SELECT Count() FROM Invoice__c Where Account__c=:accountId ' ;
if (searchKey != '' ) {
String textFilter = '%' +searchKey+ '%' ;
query = buildSearchTerm(query, searchKey);
}
return Database.countQuery(query);
}
private static String buildSearchTerm (String query, String searchKey) {
query += 'and ( Name Like :textFilter or Status__c Like :textFilter ' ;
if (searchKey.isNumeric()) {
consulta += ' ou CALENDAR_YEAR(Data_Fatura__c)=' +searchKey+ ' ou CALENDAR_MONTH(Data_Fatura__c)=' +searchKey+ ' ou DAY_IN_MONTH(Data_Fatura__c)=' +searchKey;
consulta += ' ou CALENDAR_YEAR(Data_Vencimento__c)=' +searchKey+ ' ou CALENDAR_MONTH(Data_Vencimento__c)=' +searchKey+ ' ou DAY_IN_MONTH(Data_Vencimento__c)=' +searchKey;
consulta += ' ou Total__c=' +searchKey+ ' ) ' ;
} senão {
Padrão p = Pattern.compile( '(\\d{4})-(\\d{1,2})(-(\\d{1,2}))?' );
Correspondente pm = p.matcher(searchKey);
if (pm.matches()) {
consulta += ' ou (ANO_CALENDÁRIO(Data_Fatura__c)=' +pm.group( 1 )+ ' e MÊS_CALENDÁRIO(Data_Fatura__c)=' +pm.group( 2 )+ ' ' +
(pm.group( 3 ) != nulo ? ' e DIA_DO_MÊS(Data_Fatura__c)=' +pm.group( 3 ).replace( '-' , '' )+ ' ' : '' )+ ') ' ;
consulta += ' ou (ANO_CALENDÁRIO(Data_de_Vencimento__c)=' +pm.group( 1 )+ ' e MÊS_CALENDÁRIO(Data_de_Vencimento__c)=' +pm.group( 2 )+ ' ' +
(pm.group( 3 ) != null ? ' e DIA_DO_MÊS(Data_de_Vencimento__c)=' +pm.group( 3 ).replace( '-' , '' )+ ' ' : '' ) + ' ) ' ; } consulta de retorno ; } }
4. Vá para Account LightningRecordPage, crie uma nova aba chamada “Invoices” ao lado da aba Details, adicione este componente e salve.
Primeira página (classificada por data de vencimento):
Próxima página (classificada por data de vencimento):
5. Você pode usar o snippet abaixo para gerar os registros da fatura
Lista < Fatura __c> invList = new Lista < Fatura __c>();
para ( Inteiro i= 1 ;i<= 10000 ;i++) {
Inteiro randm = Inteiro . valor de (( Math . aleatório () * 700 ));
Fatura __c inv = new Fatura __c( Conta __c= '{ID}' );
inv. Data_da_fatura__c = Sistema . Hoje ()-randm;
inv. Data_de_vencimento__c = inv. Data_da_fatura__c + Inteiro . valor de (( Math . aleatório () * 30 ));
inv. Total__c =( Inteiro . valor de (( Math . aleatório () * 10000 )))+ 10000 ;
Inteiro ran = Inteiro . valueof (( Math . random () * 5 ));
inv. Status__c = ran == 1 ? 'Rascunho' : ran == 2 ? 'Pendente' : ran == 3 ? 'Em andamento' : 'Pago' ;
invList. add (inv);
}
insert invList;
Obrigado por reservar um tempo para ler meu artigo! Espero que você tenha achado ele informativo e envolvente. Seu feedback é inestimável para mim, e eu adoraria ouvir suas ideias.
Recursos:
Fonte: https://medium.com/@sfdcpulse