Paginación
Hecha para el Paginator de Django: pasas el número de página y el total, y ella arma el resto. Los
elementos son enlaces con href de verdad, que conservan filtro, búsqueda y orden de la URL — así que abrir en otra pestaña, el
botón atrás del navegador y el buscador siguen funcionando.
Ejemplos
En esta página los clics no navegan: cada ejemplo cambia la página actual en el sitio, para que puedas probar.
Pocas páginas
Sin puntos suspensivos, y el extremo inactivo se aparta del camino.
Vecindad mayor
data-around="2" muestra dos páginas a cada lado de la actual.
Más páginas en los extremos
data-edges="2" mantiene las dos primeras y las dos últimas.
Otros textos y parámetro
data-prev-text, data-next-text y data-param="p" en la URL.
Centrada
class="tuc-pagination is-center" junto al data-tuc-pagination.
A la derecha
is-end, para quedar bajo la columna de importes de una tabla.
Cómo usar
Con los números que el Paginator ya tiene. El elemento se reemplaza por la navegación al cargar y en cada
htmx:afterSwap; con una sola página no se renderiza nada — no hay adónde ir.
En la plantilla de 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})En una página en /contracts/?q=padaria&sort=amount, el enlace a la página 3 sale como
/contracts/?q=padaria&sort=amount&page=3: solo cambia el parámetro de la página. Eso es lo que mantiene la búsqueda y el orden
en pie al pasar de página, sin que la plantilla tenga que reconstruir la query string.
| Atributo | Por defecto | Para qué |
|---|---|---|
data-page | 1 | La página actual |
data-pages | 1 | El total de páginas |
data-param | page | Nombre del parámetro en la query string |
data-around | 1 | Páginas visibles a cada lado de la actual |
data-edges | 1 | Páginas visibles en cada extremo |
data-prev-text | Anterior | Texto del extremo para retroceder |
data-next-text | Próxima | Texto del extremo para avanzar |
Con HTMX
Como los elementos ya son enlaces, el hx-boost en un contenedor superior basta para que el cambio de página sea una petición
parcial — y la paginación que vuelve en el fragmento nuevo se inicializa sola. No hace falta onChange.
En JavaScript
Para quien arma la lista en el cliente. El atajo Tucano.pagination() devuelve el elemento listo para insertar; la clase
devuelve la instancia, con el elemento en node.
const pag = new Tucano.Pagination({
page: 1,
pages: 12,
label: 'Paginación de los contratos', // aria-label de la navegación
onChange: (page, p) => { // con onChange, el clic no navega
load(page);
p.setPage(page); // redibuja con la nueva actual
},
});
document.querySelector('#footer').append(pag.node);
pag.href(3); // '/contracts/?q=padaria&page=3' — el enlace de una página
pag.opts.pages = 15;
pag.render(); // redibuja después de cambiar las opciones
pag.destroy(); // quita el elemento
// Solo el elemento, sin guardar la instancia
document.querySelector('#footer').append(Tucano.pagination({ page: 2, pages: 5 }));La alineación es una clase en .tuc-pagination: desde JavaScript, pag.node.classList.add('is-center'), o
is-end. En la plantilla el elemento marcado recibe la navegación dentro, así que escribe la clase junto al atributo:
<div class="tuc-pagination is-center" data-tuc-pagination
data-page="{{ page_obj.number }}"
data-pages="{{ page_obj.paginator.num_pages }}"></div>Qué números aparecen
Con muchas páginas no cabe todo. Quedan los extremos, la actual y su vecindad; el resto se vuelve puntos suspensivos. El cálculo se
exporta como Tucano.pageWindow(), y devuelve null donde van los puntos suspensivos:
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]Un hueco de una sola página nunca se vuelve puntos suspensivos: mostrar el número ocupa el mismo espacio y da un destino más para
hacer clic. Por eso, con la página 3 de 7, aparece el 2 en lugar de ….
Por qué enlaces, y no botones
En una lista paginada por el servidor — el caso de todo CRUD de Django — el número de página vive en la URL. Cambiar los enlaces
por onclick rompería de una vez el clic con el botón central, el botón atrás del navegador y la indexación.
Los elementos son el botón del sistema
Cada elemento es un .tuc-btn is-ghost, y la página actual es is-outline. Altura, radio, foco y toque ya están resueltos en el botón, y un segundo diseño de botón en la misma pantalla es lo que delata una biblioteca remendada. La actual recibe contorno y no relleno: en una barra con diez objetivos, diez botones sólidos pelearían entre sí.
El extremo desactivado es <span>, no <a> sin href
Un enlace que no lleva a ninguna parte sigue en el recorrido del Tab y se anuncia como enlace. El <span> con aria-disabled simplemente sale del camino, y el .tuc-btn dibuja el estado desactivado sin una regla más.
En el móvil (por debajo de 40rem) las palabras de los extremos desaparecen y quedan las flechas: el espacio vale más para los números.
Antes de que el script la arme, el elemento vacío ya reserva la altura de un control, para que el contenido de abajo no suba.
Teclado y accesibilidad
Son enlaces en un <nav>, y el teclado es el del navegador.
| Tecla | Acción |
|---|---|
Tab Shift+Tab | Recorre las páginas; el extremo desactivado y los puntos suspensivos quedan fuera del recorrido |
Enter | Va a la página enfocada |
La navegación es role="navigation" con aria-label — "Paginação" por defecto, que se cambia con la opción
label cuando haya dos en la misma pantalla. La página actual lleva aria-current="page", y los puntos suspensivos,
aria-hidden.
API
Generada a partir del código en cada build — si algo no está aquí, no 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 destroyOpciones
Las notas de esta tabla salen de los comentarios del código fuente, que están escritos en portugués.
| Opción | Por defecto | 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 |