[RESOLVIDO] Renderizar dataTable com milhares de linhas - Node.JS, JavaScript e MySQL

Galera, seguinte, acredito que muitos possuem este mesmo problema e assim como eu já não aguenta mais procurar uma solução completa e padronizada na WEB, tudo pela metade ou soluções vagas, por favor nos ajudem a resolver ok.

Problema: Aplicação WEB desenvolvida em Node.JS, onde utilizamos a dataTable do bootstrap para mostrar na tela todos os registros da tabela materiais, esta tabela possui mais de 12 mil registros cadastrados, ao entrar na tela demora cerca de 8 minutos para carregar a dataTable no front end, a consulta em si é rápido, cerca de 8 segundos (olhando no prompt), porém no front end pra popular a dataTable demora pra caramba, e sim, preciso carregar tudo na tela pois é cadastro de materiais e o usuário poderá filtrar qualquer item da lista de 12 mil linhas.

Requisitos:

  • 1-Deve seguir a estrutura do código existente, não vamos deixar mais complexa não blz;
  • 2-Não deve ser sugerido nada com PHP, post exclusivo para Javascript e Node.JS;
  • 3-Não deve ser sugerido o server-side, não se aplica a esta aplicação;
  • 4-Não deve ser sugerido nada sobre API, RestFull, etc… ok. Usar a lógica da estrutura existente.

Vamos ao código, estrutura simples e de fácil entendimento, básico para iniciantes, sem complexidade, por favo, vamos tentar manter assim:

A estrutura do meu código é:
app.js > database > route > dao > view

Aqui está minha rota /material


Ao acessar este endereço entramos no arquivo materiais.js que está na pasta dao e que contém a renderização das páginas .ejs , veja abaixo que ao acessar /material entramos na rota pageMaterial, nesta rota tenho uma função que recebe a consulta da tabela materiais e grava na variável materiais e depois passo essa variável materiais para o render da view como DTMaterial:

Ai lá na página .ejs que é meu font end recebo o DTMaterial e passo em um forEach populando assim a dataTable, row por row:

E lá no meu tails eu inicializo minha dataTable:

E esta é minha função que faz a consulta e retorna para a variável que vai pra rota:

Está tudo ok, funcionando perfeitamente, porém demora 300 anos pra carregar a dataTable, galera, por favor, vamos montar juntos a solução? já tentei de tudo, a meses pesquisando e nada.

Esta é minha dataTable depois de carregada 100%:

Se queres carregar a lista toda para memória e trabalhar com ela em memória, sofres as consequências, que são o fraco desempenho.

BDs existem para fazer esse trabalho por ti. Se não sabes ou não queres aplicar um where no teu select e limitar o máximo de registos que apresentas, dúvido que consigas algo performático.

Então @pmlm, eu realmente estou convencido disto viu… eu só não sei como implementar isto no meu código, tipo, como implementar esta lógica usando a mesma estrutura deste projeto, ou seja, faço a consulta, armazeno numa variável e passo esta variável ao render da página ejs, outro ponto é disponibilizar todos os registros da tabela para utilização dos filtros da datatable do bootstrap.

O intuito deste post é conseguirmos implementar esta solução, acredito que será útil para dezenas de milhares de usuários, consegue me ajudar @pmlm?

No Postgres eu faço assim:

SELECT 
    *
FROM
    tabela
ORDER BY id
LIMIT 10 OFFSET 0;

Pense que o OFFSET seria o número página atual e vc quer ir passando de 10 em 10. Agora basta ir trocando o valor de OFFSET pra 0,10,20,30,40,50,60,70,80,100....

LEMBRE-SE QUE COMEÇA SEMPRE NO ZERO

Acho que no MySQL tem isso tbm.


E pra mostrar isso no front?

Vc pode pegar a qtd total de linhas vindo do seu banco de dados e fazer o seguinte cálculo:

//qtd de itens no seu array divido pela qtd de itens que
//vc quer mostrar por pagina
console.log(Math.ceil(items.length / per_page_items));

Aí só ir navegando de página por página, suponhamos que deu 36 páginas… Agora só preciso navegar da 1 até a 36.

Ok, to na pagina 2, e agora?

SELECT 
    *
FROM
    tabela
ORDER BY id
LIMIT 10 OFFSET 10; 
#Aqui está mostrando a segunda pagina.
#Quer ir pra terceira? basta mudar o valor que está ali para 20.

Agora uma solução inteiramente do frontend (js)

//página atual: page 1, 2, 3, etc...
let current_page = 1;

//itens que vc quer por página
const items_per_page = 10;

//suponhamos que vc já tem os valores que quer mostrar, 
//pode ser um array de obj também, por isso fiz a mistura 
const items_db = [{id: 1}, {id: 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, 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, 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];

Crie uma função para faciitar a sua vida, trazendo dados como: prox pagina, pagina anterior, os dados recebidos e etc…

function paginar_array(items, current_page, per_page_items) {
    let page = current_page || 1;
    let per_page = per_page_items || 10;
    let offset = (page - 1) * per_page;
    let paginatedItems = items.slice(offset).slice(0, per_page_items);
    let total_pages = Math.ceil(items.length / per_page);

    return {
        page: page,
        per_page: per_page,
        pre_page: page - 1 ? page - 1 : null,
        next_page: (total_pages > page) ? page + 1 : null,
        total_itens: items.length,
        total_pages: total_pages,
        data: paginatedItems
    };
}

Retorno disso (Exemplo),:

{
  page: 1,
  per_page: 20,
  pre_page: null,
  next_page: 2,
  total_itens: 352,
  total_pages: 18,
  data: [
    { id: 1 }, { id: 2 }, 3,
    4,         5,         6,
    7,         8,         9,
    10,        11,        12,
    13,        14,        15,
    16,        17,        18,
    19,        20
  ]
}
1 curtida

@rodriguesabner, @pmlm , muito obrigado pelas respostas, me ajudou a juntar as peças e montar o quebra cabeça, não ficou com a usabilidade ideal como eu queria, mas de imediato resolve minha vida e futuramente vou deixando da forma ideal, vamos lá, segue a solução:

Como ficou o front end, veja que ficou 2 tipos de paginação:

A paginação superior:
Representa a qtde de páginas baseadas na qtde total de registros da minha tabela, no meu caso tenho 12.099 registros, se cada página tem limite de 1000 registros, então tenho 13 páginas de 1.000.

A paginação inferior:
É a paginação original da “dataTable” do bootstrap, porém navegável somente dentro da página selecionada, ou seja, 1000 registros, desta forma, se seleciono a página 2 vou ter os registros 10001 ao 2000, a paginação inferior navegará somente entre esses 1000 registros, no caso: de 10 em 10 ou 25 em 25 ou 50 em 50 ou 75 em 75 ou 100 em 100.

O filtro original do dataTable:
Também ficou limitado a página selecionada, ou seja, se eu seleciono na paginação superior a página 5 terei 1000 registros que vão do 4001 ao 5000, o filtro da dataTable só localizará itens pertencentes a este range (4001 -> 5000) de registros, vou precisar isso melhorar futuramente.

Nota: Ficou um pouco estranho 2 tipos de paginação e filtro somente dentro do range da página selecionada, mas de inicio resolve e acaba até que acostumando, porém futuramente seria interessante pensar em algo como somente 1 paginação e o filtro olhar todos os registros e não somente ao range da página selecionada, mas fica para outro fórum.

Vamos ao código

No arquivo “route.js” adicionei uma rota que receberá um número de página como parâmetro:

Ao entrar na rota (endereço) acessa a classe DAO que contem o render para a página, aqui é que está a implementação back end da paginação:


Veja que tenho uma função que faz uma consulta completa na tabela sem usar nenhum limite ou where, gravo o resultado completo da consulta na variável materiais.

Depois passo a variável materiais para o array itens e depois passo a lista de itens para a variável itensList porém desta vez olhando a paginação currentPage

E por fim passo tudo isso para o render da página ejs:

  • A lista de itens relacionado a página (DTMaterial: itensList,)
  • O tamanho da página (pageSize: pageSize,), eu defini 1.000 por página
  • O total de linhas da tabela (totalItens: totalItens,)
  • A qtde de páginas (pageCount: pageCount,) que é qtde de registros dividido por 1.000
  • A página corrente selecionada (currentPage: currentPage,) que é o parâmetro page

Feito isso vamos para o front end, meu arquivo .ejs



E por fim, a inicialização da minha dataTable no meu arquivo tails.ejs

E aqui o css da paginação superior:

.pagination {
    display: inline-block;
    padding-left: 0;
    margin: 20px 0;
    border-radius: 4px
}
.pagination>li {
    display: inline
}
.pagination>li>a, .pagination>li>span {
    position: relative;
    float: left;
    padding: 6px 12px;
    margin-left: -1px;
    line-height: 1.42857143;
    color: #428bca;
    text-decoration: none;
    background-color: #fff;
    border: 1px solid #ddd
}
.pagination>li:first-child>a, .pagination>li:first-child>span {
    margin-left: 0;
    border-top-left-radius: 4px;
    border-bottom-left-radius: 4px
}
.pagination>li:last-child>a, .pagination>li:last-child>span {
    border-top-right-radius: 4px;
    border-bottom-right-radius: 4px
}
.pagination>li>a:hover, .pagination>li>span:hover, .pagination>li>a:focus, .pagination>li>span:focus {
    color: #2a6496;
    background-color: #eee;
    border-color: #ddd
}
.pagination>.active>a, .pagination>.active>span, .pagination>.active>a:hover, .pagination>.active>span:hover, .pagination>.active>a:focus, .pagination>.active>span:focus {
    z-index: 2;
    color: #fff;
    cursor: default;
    background-color: #428bca;
    border-color: #428bca
}
.pagination>.disabled>span, .pagination>.disabled>span:hover, .pagination>.disabled>span:focus, .pagination>.disabled>a, .pagination>.disabled>a:hover, .pagination>.disabled>a:focus {
    color: #777;
    cursor: not-allowed;
    background-color: #fff;
    border-color: #ddd
}
.pagination-lg>li>a, .pagination-lg>li>span {
    padding: 10px 16px;
    font-size: 18px
}
.pagination-lg>li:first-child>a, .pagination-lg>li:first-child>span {
    border-top-left-radius: 6px;
    border-bottom-left-radius: 6px
}
.pagination-lg>li:last-child>a, .pagination-lg>li:last-child>span {
    border-top-right-radius: 6px;
    border-bottom-right-radius: 6px
}
.pagination-sm>li>a, .pagination-sm>li>span {
    padding: 5px 10px;
    font-size: 12px
}
.pagination-sm>li:first-child>a, .pagination-sm>li:first-child>span {
    border-top-left-radius: 3px;
    border-bottom-left-radius: 3px
}
.pagination-sm>li:last-child>a, .pagination-sm>li:last-child>span {
    border-top-right-radius: 3px;
    border-bottom-right-radius: 3px
}
.pager {
    padding-left: 0;
    margin: 20px 0;
    text-align: center;
    list-style: none
}
.pager li {
    display: inline
}
.pager li>a, .pager li>span {
    display: inline-block;
    padding: 5px 14px;
    background-color: #fff;
    border: 1px solid #ddd;
    border-radius: 15px
}
.pager li>a:hover, .pager li>a:focus {
    text-decoration: none;
    background-color: #eee
}
.pager .next>a, .pager .next>span {
    float: right
}
.pager .previous>a, .pager .previous>span {
    float: left
}
.pager .disabled>a, .pager .disabled>a:hover, .pager .disabled>a:focus, .pager .disabled>span {
    color: #777;
    cursor: not-allowed;
    background-color: #fff
}

Se estiverem a fim de criticar, sugerir alguma alteração/melhoria, fiquem a vontade, quanto mais simples e com melhor usabilidade melhor.

1 curtida

Isso aí, obrigado por postar o resultado. Ficou muito bom.

Não consegui esperar… acabei de finalizar… paginação e filtragem 100% funcionando como queria, página carrega em 1 segundo agora, independente da qtde de registros do BD:

Todos os registros:

Filtros avançados

2 curtidas