O tutorial de hoje é uma continuação de um outro tutorial aqui do blog, muito acessado aliás, onde ensino como fazer um sistema de cadastro bem simples em Node.js, usando o web framework ExpressJS e o banco de dados MongoDB. Ou seja, o famoso CRUD.
Nesta segunda parte, parto para um conceitos mais avançados em cima do mesmo projeto: paginação de resultados.
Para conseguir acompanhar todos os códigos, é importante que você tenha realizado a parte anterior ou pelo menos baixe os códigos-fonte que se encontram no formulário ao final do tutorial anterior.
Neste tutorial você vai ver:
- Paginação com Node.js e MongoDB
- Paginação de Resultados em Node.js
- Paginação de Resultados com ExpressJS e EJS
- Melhorando a Usabilidade da Paginação
Vamos lá!
Paginação com Node.js e MongoDB
Nossa aplicação simplesmente lista todos os documentos da nossa coleção no MongoDB, sem qualquer distinção. Nas primeiras duas etapas deste tutorial vamos restringir isso, primeiramente através de uma paginação de resultados e depois através de uma busca.
Conforme trato em detalhes no artigo Tutorial MongoDB para iniciantes em NoSQL: Parte 2, fazemos paginação no MongoDB usando as funções skip e limit de maneira apropriada.
A função skip indica ao MongoDB que deve-se ignorar um número x de resultados da consulta na hora de retornar do servidor de banco. Já a função limit diz ao Mongo que ele deve retornar apenas um número limitado de documentos, independente se a busca retornaria mais elementos normalmente. Ambas funções devem ser usadas após um find, como veremos a seguir.
A lógica para criar paginação é bem simples: determine um tamanho de página, por exemplo 10 elementos, descubra o total de elementos que a consulta retornaria, por exemplo 21 elementos, e depois dividida o total pelo tamanho de página, arredondando sempre pra cima. Pronto, você tem a quantidade de páginas para aquela consulta! Neste exemplo de 21 elementos com uma paginação de tamanho 10, serão 3 páginas, sendo que as duas primeiras terão exatamente 10 elementos e a última apenas 1.
Entendeu?
Para determinar o tamanho de página ideal para seu sistema você tem de levar em consideração a performance da sua aplicação e a experiência do seu usuário. Muitas páginas com poucos elementos é fácil do banco retornar, mas ruim pro usuário ficar navegando. Poucas páginas com muitos elementos é o oposto e o segredo está no equilíbrio. Neste exemplo, ficaremos com tamanho 10 mesmo, para fins de teste.
Paginação de Resultados em Node.js
Então nosso primeiro passo será modificar uma function já existente no módulo db.js da nossa aplicação para que ela retorne os resultados de maneira paginada, como abaixo.
1 2 3 4 5 6 7 8 9 10 11 |
async function findAll(pagina) { const tamanhoSkip = TAMANHO_PAGINA * (pagina - 1); const db = await connect() return db.collection(COLLECTION) .find() .skip(tamanhoSkip) .limit(TAMANHO_PAGINA) .toArray(); } |
Note que comecei definindo uma constante com o tamanho das páginas sendo 5. Depois, adicionei um novo parâmetro na função findAll que espera a página que a aplicação deseja apresentar. Este parâmetro eu uso para calcular o skip, ou seja, quantos elementos da consulta eu devo ignorar. Se a página for a primeira, o skip será zero e serão mostrados os primeiros 10 elementos. Se a página for a segunda, o skip será 10 pela fórmula e serão mostrados os elementos das posições 11 a 20 (ordinal, ou 10 a 19 considerando um array zero-based).
Agora vamos modificar onde esta função findAll é chamada: na pasta routes, módulo index.js, que vamos modificar a rota GET padrão em dois pontos.
Primeiro, apenas para adicionar o parâmetro página nela. Note que coloquei o parâmetro no path como sendo opcional (?) e se ele não tiver sido passado na URL, será atribuído como um. Esse truque do || para atribuir um valor default eu ensinei no artigo 15 Dicas de JavaScript, lembra?
1 2 3 4 5 6 7 8 9 10 11 12 |
router.get('/:pagina?', async (req, res, next) => { const pagina = parseInt(req.params.pagina || "1"); try { const docs = await db.findAll(pagina); res.render('index', { title: 'Lista de Clientes', docs }); } catch (err) { next(err); } }) |
Certifique-se de que esta rota seja a última do arquivo index.js, logo antes do module.exports!
Como as rotas são testadas da primeira até a última para ver qual que processará a requisição deixaremos esta pro final para não interferir nas demais avaliações.
Agora execute a aplicação e teste a index passando a página na URL, como abaixo. Note que tive de adicionar muitos customers no banco de dados para podermos ter alguma paginação.
Mas obviamente não faremos nosso usuário ter de mudar parâmetros na URL para acessar as páginas, certo?
Está curtindo o post? Para uma formação ainda mais completa como programador web recomendo meu livro sobre programação web com Node.js clicando no banner abaixo!
Paginação de resultados com ExpressJs e EJS
Para que o usuário saiba quantas páginas existem e para que consiga acessar as mesmas, vamos ter de criar uma lógica para construir o HTML de paginação no frontend e uma lógica para retornar algumas informações importantes no backend.
Vamos começar pelo backend que é mais a minha praia. XD
Abra seu arquivo db.js novamente e vamos criar uma function countAll que retorna a quantidade de documentos na coleção customers. Note que atualizei o module.exports com a nova função e também exportando a constante com o tamanho de página.
1 2 3 4 5 6 7 8 |
async function countAll(){ const db = await connect(); return db.collection(COLLECTION).countDocuments(); } module.exports = { findAll, insert, findOne, update, deleteOne, countAll, TAMANHO_PAGINA } |
Agora na routes/index.js, mais especificamente no nosso GET default (a última rota do arquivo), vamos ajustar as chamadas para construir o model com as informações que precisaremos no frontend EJS:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
router.get('/:pagina?', async (req, res, next) => { const pagina = parseInt(req.params.pagina || "1"); try { const docs = await db.findAll(pagina); const count = await db.countAll(); const qtdPaginas = Math.ceil(count / db.TAMANHO_PAGINA); res.render('index', { title: 'Lista de Clientes', docs, count, qtdPaginas }); } catch (err) { next(err); } }) |
Note que recebo a quantidade de clientes na base através de um await novamente (pois a função countAll é assíncrona, tenho de usar await para esperar seu retorno). A cereja do bolo fica para o cálculo de quantidade de páginas que fizemos ali, dividindo o total de documentos pelo tamanho da página, usando a constante existente no módulo db.js. Ah sim, você não esqueceu de expor esta constante no module.exports, certo?
Agora que nosso model está completo, vamos mexer na index.ejs, nossa view inicial da aplicação para renderizar a quantidade total de elementos e o HTML de paginação para que o usuário possa navegar entre as páginas:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<hr /> <p><%= count %> clientes encontrados!</p> <p> <% for(let i=1; i <= qtdPaginas; i++) {%> <a href="/<%= i %>"><%= i %></a> | <%}%> </p> <hr /> <a href="/new">Cadastrar novo cliente</a> </body> </html> |
Esse código eu coloquei bem no final do arquivo EJS, onde antes ficava apenas o botão de Cadastrar novo cliente. Se você fez tudo certo até aqui, quando testar novamente você deve ver o seguinte resultado na interface da listagem:
Melhorando usabilidade da paginação
Para encerrar este tutorial, vamos adicionar uma perfumaria para melhorar ainda mais a usabilidade desta página: a página atual não deve ter link, apenas as demais páginas, assim, o usuário saberá em que página ele se encontra atualmente.
Vamos iniciar ajustando o model retornado no nosso index.js para informar também a página atual solicitada pelo usuário:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
router.get('/:pagina?', async (req, res, next) => { const pagina = parseInt(req.params.pagina || "1"); try { const docs = await db.findAll(pagina); const count = await db.countAll(); const qtdPaginas = Math.ceil(count / db.TAMANHO_PAGINA); res.render('index', { title: 'Lista de Clientes', docs, count, qtdPaginas, pagina }); } catch (err) { next(err); } }) |
Note que a mudança foi bem sutil, apenas uma nova propriedade no JSON ‘pagina’.
Para fazer a lógica necessário para que o HTML ora tenha link, ora não tenha, basta adicionar um if na lógica de construção do nosso EJS usando a informação da página oriunda do model:
1 2 3 4 5 6 7 8 9 10 11 12 |
<p> <% for(let i=1; i <= qtdPaginas; i++) { if(i !== pagina){ %> <a href="/<%= i %>"><%= i %></a> | <%} else {%> <%= i %> | <% } }%> </p> |
Ou seja, se a variável de iteração ‘i’ for diferente da página atual, escreve um link HTML na tela, caso contrário, escreve apenas o número da página. Visualmente falando temos a imagem abaixo como referência:
Note como ficou evidente que estamos na página 2, pois não tem link pra ela!
Outras melhorias poderiam incluir legendas quando se passa o mouse sobre as páginas, links de próxima página e página anterior, lógica para quando tivermos muitas páginas (e se tiver 100 páginas, como ficará a tela?) e até mesmo informação textual de que você está vendo os itens x a y da página z.
Mas…isso fica para você pensar meu amigo ou minha amiga! 🙂
No próximo post desta série, você confere como modularizar melhor as suas páginas EJS e como estilizar tudo com Bootstrap. Confira neste link!
Gostou do tutorial? Quer aprender ainda mais sobre Node.js, ExpressJS, EJS e MongoDB comigo? Conheça meu curso de Nodejs e MongoDB clicando no banner abaixo!
Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.