Tucano v0.37.2

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.

AtributoPor defectoPara qué
data-page1La página actual
data-pages1El total de páginas
data-parampageNombre del parámetro en la query string
data-around1Páginas visibles a cada lado de la actual
data-edges1Páginas visibles en cada extremo
data-prev-textAnteriorTexto del extremo para retroceder
data-next-textPróximaTexto 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 ….

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.

TeclaAcción
Tab Shift+TabRecorre las páginas; el extremo desactivado y los puntos suspensivos quedan fuera del recorrido
EnterVa 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.

Marcado
[data-tuc-pagination]
En JS
new Tucano.Pagination(opcoes) Tucano.pageWindow() Tucano.pagination()
Atributos
data-around data-edges data-next-text data-page data-pages data-param data-prev-text
Métodos
href render setPage destroy

Opciones

Las notas de esta tabla salen de los comentarios del código fuente, que están escritos en portugués.

OpciónPor defectoPara qué
page1
pages1
param'page'
around1paginas visiveis de cada lado da atual
edges1paginas visiveis nas pontas
prevTextundefineddefault: setTexts ("Anterior")
nextTextundefineddefault: setTexts ("Próxima")
labelundefineddefault: setTexts ("Paginação")
onChangenull