Paginação
Feita para o Paginator do Django: você passa o número da página e o total, e ela monta o resto. Os
itens são links com href de verdade, que preservam filtro, busca e ordem da URL — então abrir em outra aba, o
voltar do navegador e o buscador continuam funcionando.
Exemplos
Nesta página os cliques não navegam: cada exemplo troca a página atual no lugar, para dar para experimentar.
Poucas páginas
Sem reticência, e a ponta inativa sai do caminho.
Vizinhança maior
data-around="2" mostra duas páginas de cada lado da atual.
Mais páginas nas pontas
data-edges="2" mantém as duas primeiras e as duas últimas.
Outros textos e parâmetro
data-prev-text, data-next-text e data-param="p" na URL.
Centralizada
class="tuc-pagination is-center" junto do data-tuc-pagination.
À direita
is-end, para ficar sob a coluna de valores de uma tabela.
Como usar
Com os números que o Paginator já tem. O elemento é trocado pela navegação no carregamento e a cada
htmx:afterSwap; com uma página só, nada é renderizado — não há para onde ir.
No template do Django
{% for contract in page_obj %}
…
{% endfor %}
<div data-tuc-pagination
data-page="{{ page_obj.number }}"
data-pages="{{ page_obj.paginator.num_pages }}"></div>from django.core.paginator import Paginator
def contracts(request):
qs = Contract.objects.filter(customer__name__icontains=request.GET.get("q", "")).order_by("-created_at")
page_obj = Paginator(qs, 25).get_page(request.GET.get("page"))
return render(request, "contracts/list.html", {"page_obj": page_obj})Numa página em /contracts/?q=padaria&sort=amount, o link da página 3 sai como
/contracts/?q=padaria&sort=amount&page=3: só o parâmetro da página muda. É isso que deixa a busca e a ordem
de pé ao virar a página, sem o template ter de remontar a query string.
| Atributo | Padrão | Para quê |
|---|---|---|
data-page | 1 | A página atual |
data-pages | 1 | O total de páginas |
data-param | page | Nome do parâmetro na query string |
data-around | 1 | Páginas visíveis de cada lado da atual |
data-edges | 1 | Páginas visíveis em cada ponta |
data-prev-text | Anterior | Texto da ponta de voltar |
data-next-text | Próxima | Texto da ponta de avançar |
Com HTMX
Como os itens já são links, o hx-boost num contêiner acima basta para a troca de página virar requisição
parcial — e a paginação que volta no trecho novo é inicializada sozinha. Não é preciso onChange.
Em JavaScript
Para quem monta a lista no cliente. O atalho Tucano.pagination() devolve o elemento pronto para inserir; a classe
devolve a instância, com o elemento em node.
const pag = new Tucano.Pagination({
page: 1,
pages: 12,
label: 'Paginação dos contratos', // aria-label da navegação
onChange: (page, p) => { // com onChange, o clique não navega
load(page);
p.setPage(page); // redesenha com a nova atual
},
});
document.querySelector('#footer').append(pag.node);
pag.href(3); // '/contracts/?q=padaria&page=3' — o link de uma página
pag.opts.pages = 15;
pag.render(); // redesenha depois de mudar as opções
pag.destroy(); // remove o elemento
// Só o elemento, sem guardar a instância
document.querySelector('#footer').append(Tucano.pagination({ page: 2, pages: 5 }));O alinhamento é classe no .tuc-pagination: pelo JavaScript, pag.node.classList.add('is-center'), ou
is-end. No template o elemento marcado recebe a navegação dentro dele, então escreva a classe junto do atributo:
<div class="tuc-pagination is-center" data-tuc-pagination
data-page="{{ page_obj.number }}"
data-pages="{{ page_obj.paginator.num_pages }}"></div>Quais números aparecem
Com muitas páginas não cabe tudo. Ficam as pontas, a atual e a vizinhança dela; o resto vira reticência. O cálculo é
exportado como Tucano.pageWindow(), e devolve null onde entra a reticência:
Tucano.pageWindow(5, 20); // [1, null, 4, 5, 6, null, 20]
Tucano.pageWindow(3, 7); // [1, 2, 3, 4, null, 7]
Tucano.pageWindow(10, 30, { around: 2 }); // [1, null, 8, 9, 10, 11, 12, null, 30]Um buraco de uma página só nunca vira reticência: mostrar o número ocupa o mesmo espaço e dá um destino a mais para
clicar. Por isso, com página 3 de 7, o 2 aparece em vez de ….
Por que links, e não botões
Numa lista paginada pelo servidor — o caso de todo CRUD Django — o número da página mora na URL. Trocar os links
por onclick quebraria de uma vez o botão do meio, o voltar do navegador e a indexação.
Os itens são o botão do sistema
Cada item é um .tuc-btn is-ghost, e a página atual é is-outline. Altura, raio, foco e toque já estão resolvidos no botão, e um segundo desenho de botão na mesma tela é o que denuncia biblioteca remendada. A atual ganha contorno e não preenchimento: numa barra com dez alvos, dez botões sólidos brigariam entre si.
A ponta desativada é <span>, não <a> sem href
Um link que não leva a lugar nenhum continua no caminho do Tab e é anunciado como link. O <span> com aria-disabled simplesmente sai do caminho, e o .tuc-btn desenha o estado desativado sem uma regra a mais.
No celular (abaixo de 40rem) as palavras das pontas saem e ficam as setas: o espaço vale mais para os números.
Antes de o script montar, o elemento vazio já reserva a altura de um controle, para o conteúdo abaixo não subir.
Teclado e acessibilidade
São links numa <nav>, e o teclado é o do navegador.
| Tecla | Ação |
|---|---|
Tab Shift+Tab | Anda entre as páginas; a ponta desativada e a reticência ficam fora do caminho |
Enter | Vai para a página focada |
A navegação é role="navigation" com aria-label — "Paginação" por padrão, trocável pela opção
label quando houver duas na mesma tela. A página atual leva aria-current="page", e a reticência,
aria-hidden.
API
Gerada do código a cada build — se algo não está aqui, não existe.
[data-tuc-pagination]new Tucano.Pagination(opcoes) Tucano.pageWindow() Tucano.pagination()data-around data-edges data-next-text data-page data-pages data-param data-prev-texthref render setPage destroyOpções
| Opção | Padrão | Para quê |
|---|---|---|
page | 1 | |
pages | 1 | |
param | 'page' | |
around | 1 | paginas visiveis de cada lado da atual |
edges | 1 | paginas visiveis nas pontas |
prevText | undefined | default: setTexts ("Anterior") |
nextText | undefined | default: setTexts ("Próxima") |
label | undefined | default: setTexts ("Paginação") |
onChange | null |