terça-feira, dezembro 2, 2025
HomeSalesforceLWCSalesforce LWC: Datatable com paginação

Salesforce LWC: Datatable com paginação

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 .

  1. 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.
  2. 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:

  1. https://developer.salesforce.com/docs/component-library/bundle/lightning-datatable/documentation

Fonte: https://medium.com/@sfdcpulse

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Mais acessados