Lección 16: Paginador

Un día entras en Amazon con la intención de comprar calcetines. Al apretar el botón de Enter te abre una página donde están todos los calcetines de la tienda, aproximadamente 23.000. Sin miramientos te enseña todos, uno encima del otro. El scroll del navegador casi es tan fino que apenas es clicable con el ratón. ¿Te imaginas lo incómodo de este escenario, poder ver todos los resultados? Por ello mismo se crearon los paginadores: botones para pasar entre páginas de resultados. Mecanismos para avanzar por bloques de elementos con un número bien definido.

Para construir este sistema, que puedes aplicar en cualquier lenguaje de programación, necesitaremos un poco de ingenio espolvoreado con un poco de matemáticas básicas.

Ver solo una página

Tomamos de partida los 10 primeros personajes, ordenados alfabéticamente, de juegos de tronos como “base de datos”.

$personajes = [
  'Abelar Hightower',
  'Addam Frey',
  'Addam',
  'Addam Osgrey',
  'Addam Marbrand',
  'Addison Hill',
  'Aegon Blackfyre',
  'Addam Velaryon',
  'Aegon Frey (son of Aenys)',
  'Aegon Frey (son of Stevron)'
];

Nuestro objetivo será mostrar únicamente 3 resultados por página, a continuación iremos completando con más funciones.

Iniciamos con la primera página. Definimos el Limite de resultados.

// Constante con el número de resultados por página: 3
define('LIMITE_RESULTADOS', 3);

Empezaremos por la página número 1, como cualquier libro.

$paginaActual = 1; 

Ahora debemos reducir el array con los 3 primeros.

$personajesPagina = array_splice($personajes, ($paginaActual - 1) * LIMITE_RESULTADOS, LIMITE_RESULTADOS);

Luego imprimimos con foreach cada personaje en HTML.

<html>
    <body>
        <?php foreach ($personajesPagina as $personaje): ?>
        <div>
            <h1><?= $personaje ?></h1>
            <hr>
        </div>
        <?php endforeach; ?>
    </body>
</html>

Todo junto nos dará el siguiente ejemplo como punto de partida.

<?php
// Constante con el número de resultados por página: 3
define('LIMITE_RESULTADOS', 3);
// Página donde nos encontramos. Si existe un GET con el nombre 'página' se guarda sino será 1.
$paginaActual = 1; 
// Crea un nuevo array con los personajes a mostrar en la página
$personajesPagina = array_splice($personajes, ($paginaActual - 1) * LIMITE_RESULTADOS, LIMITE_RESULTADOS);
?>
<html>
    <body>
        <?php foreach ($personajesPagina as $personaje): ?>
        <div>
            <h1><?= $personaje ?></h1>
            <hr>
        </div>
        <?php endforeach; ?>
    </body>
</html>

Genera lo que necesitamos, los 3 primeros nombres.

resultados paginador

<html>
    <body>
        <div>
            <h1>Abelar Hightower</h1>
            <hr>
        </div>
        <div>
            <h1>Addam Frey</h1>
            <hr>
        </div>
        <div>
            <h1>Addam</h1>
            <hr>
        </div>
    </body>
</html>

Hemos roto el array de personajes en otro más pequeño, con ayuda de array_splice.

array_splice([array], [posicion donde empezar], [longitud del corte]);

Nuestro array_splice luce de la siguiente manera:

array_splice($personajes, ($paginaActual - 1) * LIMITE_RESULTADOS, LIMITE_RESULTADOS);

Botón avanzar

Cuando queramos pasar de página, sigo hablando del ejemplo, tendremos que pasar la página donde queremos movernos. Para ello guardamos el parámetro.

// Página donde nos encontramos. Si existe un GET con el nombre 'página' se guarda sino será 1.
$paginaActual = isset($_REQUEST['pagina']) ? $_REQUEST['pagina'] : 1; 

Ahora solo tendremos que incluir en el HTML el botón donde indicamos que el parámetro, el que recogemos y determina la página donde nos encontramos, es la paginaActual + 1.

<a href="paginador.php?pagina=<?= $paginaActual + 1 ?>">Siguiente</a>

En estos momentos ya podremos avanzar.

//======================================================================
// Variables
//======================================================================
$personajes = [
  'Abelar Hightower',
  'Addam Frey',
  'Addam',
  'Addam Osgrey',
  'Addam Marbrand',
  'Addison Hill',
  'Aegon Blackfyre',
  'Addam Velaryon',
  'Aegon Frey (son of Aenys)',
  'Aegon Frey (son of Stevron)'
];
// Constante con el número de resultados por página: 3
define('LIMITE_RESULTADOS', 3);
// Página donde nos encontramos. Si existe un GET con el nombre 'página' se guarda sino será 1.
$paginaActual = isset($_REQUEST['pagina']) ? $_REQUEST['pagina'] : 1; 
// Crea un nuevo array con los personajes a mostrar en la página
$personajesPagina = array_splice($personajes, ($paginaActual - 1) * LIMITE_RESULTADOS, LIMITE_RESULTADOS);

//======================================================================
// HTML
//======================================================================
?>
<html>
    <body>
        <!-- Bucle que dibuja todos los personajes -->
        <?php foreach ( $personajesPagina as $personaje): ?>
        <div>
            <h1><?= $personaje ?></h1>
            <hr>
        </div>
        <?php endforeach; ?>
        <!-- Botón para avanzar -->
        <a href="paginador.php?pagina=<?= $paginaActual + 1 ?>">Siguiente</a>
    </body>
</html>

Si lo has ejecutado encontrarás un problema: llegamos a la última página y nos permite volver a pulsar en continuar.

Primero debemos averiguar si nos encontramos en el final.

$esUltima = ceil(count($personajes) / LIMITE_RESULTADOS) == $paginaActual;

Ahora solo mostramos el botón si es negativo.

<!-- Botón para avanzar -->
<?php if (!$esUltima): ?>
<a href="paginador.php?pagina=<?= $paginaActual + 1 ?>">Siguiente</a>
<?php endif; ?>

Quedando de la siguiente manera.

<?php
//======================================================================
// Variables
//======================================================================
$personajes = [
  'Abelar Hightower',
  'Addam Frey',
  'Addam',
  'Addam Osgrey',
  'Addam Marbrand',
  'Addison Hill',
  'Aegon Blackfyre',
  'Addam Velaryon',
  'Aegon Frey (son of Aenys)',
  'Aegon Frey (son of Stevron)'
];
// Constante con el número de resultados por página: 3
define('LIMITE_RESULTADOS', 3);
// Página donde nos encontramos. Si existe un GET con el nombre 'página' se guarda sino será 1.
$paginaActual = isset($_REQUEST['pagina']) ? $_REQUEST['pagina'] : 1; 
// Crea un nuevo array con los personajes a mostrar en la página
$personajesPagina = array_splice($personajes, ($paginaActual - 1) * LIMITE_RESULTADOS, LIMITE_RESULTADOS);
// Guardo un True o False si nos encontramos en la última página: ¿La página actual es la última?
$esUltima = ceil(count($personajes) / LIMITE_RESULTADOS) == $paginaActual;

//======================================================================
// HTML
//======================================================================
?>
<html>
    <body>
        <!-- Bucle que dibuja todos los personajes -->
        <?php foreach ( $personajesPagina as $personaje): ?>
        <div>
            <h1><?= $personaje ?></h1>
            <hr>
        </div>
        <?php endforeach; ?>
        <!-- Botón para avanzar -->
        <?php if (!$esUltima): ?>
        <a href="paginador.php?pagina=<?= $paginaActual + 1 ?>">Siguiente</a>
        <?php endif; ?>
    </body>
</html>

Botón retroceder

Repetimos estrategia, primero averiguamos si debe ser visible: ¿Estamos en la paginaActual 1?

// Guardo un True o False si nos encontramos en la primera página: ¿La página actual es la primera?
$esPrimera = $paginaActual == 1;

A continuación generamos un botón restando 1 la paginaActual.

<!-- Botón para retroceder -->
<?php if (!$esPrimera): ?>
<a href="paginador.php?pagina=<?= $paginaActual - 1 ?>">Anterior</a>
<?php endif; ?>

Resultado final

<?php
//======================================================================
// Variables
//======================================================================
$personajes = [
  'Abelar Hightower',
  'Addam Frey',
  'Addam',
  'Addam Osgrey',
  'Addam Marbrand',
  'Addison Hill',
  'Aegon Blackfyre',
  'Addam Velaryon',
  'Aegon Frey (son of Aenys)',
  'Aegon Frey (son of Stevron)'
];
// Constante con el número de resultados por página: 3
define('LIMITE_RESULTADOS', 3);
// Página donde nos encontramos. Si existe un GET con el nombre 'página' se guarda sino será 1.
$paginaActual = isset($_REQUEST['pagina']) ? $_REQUEST['pagina'] : 1; 
// Crea un nuevo array con los personajes a mostrar en la página
$personajesPagina = array_splice($personajes, ($paginaActual - 1) * LIMITE_RESULTADOS, LIMITE_RESULTADOS);
// Guardo un True o False si nos encontramos en la primera página: ¿La página actual es la primera?
$esPrimera = $paginaActual == 1;
// Guardo un True o False si nos encontramos en la última página: ¿La página actual es la última?
$esUltima = ceil(count($personajes) / LIMITE_RESULTADOS) == $paginaActual;

//======================================================================
// HTML
//======================================================================
?>
<html>
    <body>
        <!-- Bucle que dibuja todos los personajes -->
        <?php foreach ( $personajesPagina as $personaje): ?>
        <div>
            <h1><?= $personaje ?></h1>
            <hr>
        </div>
        <?php endforeach; ?>
        <!-- Botón para retroceder -->
        <?php if (!$esPrimera): ?>
        <a href="paginador.php?pagina=<?= $paginaActual - 1 ?>">Anterior</a>
        <?php endif; ?>
        <!-- Botón para avanzar -->
        <?php if (!$esUltima): ?>
        <a href="paginador.php?pagina=<?= $paginaActual + 1 ?>">Siguiente</a>
        <?php endif; ?>
    </body>
</html>

16-1

Consultado una base de datos

En esta ocasión vamos a realizar un paginador optimizado atacando una base real con SQLite y Chinook. Lo descargamos y en la misma carpeta creamos un archivo llamado paginador_SQL.php con el siguiente contenido.

<?php
//======================================================================
// Variables
//======================================================================
define('LIMITE_RESULTADOS', 10);
$hostDB = 'Chinook_Sqlite.sqlite';
// Conecta con base de datos
$hostPDO = "sqlite:$hostDB";
$miPDO = new PDO($hostPDO);
$resultados = [];
$paginaActual = isset($_REQUEST['pagina']) ? (int) $_REQUEST['pagina'] : 1; 

//======================================================================
// Obtener resultados de la base de datos
//======================================================================

// Prepara SELECT
$miConsulta = $miPDO->prepare('SELECT Name, (SELECT COUNT(*) FROM Artist) as cantidad FROM Artist ORDER BY Name ASC LIMIT :pagina, :limite;');
// Ejecuta
$miConsulta->execute([
    'pagina' => ($paginaActual - 1) * LIMITE_RESULTADOS,
    'limite' => LIMITE_RESULTADOS
]);

// Guardo todos los resultados
$resultados = $miConsulta->fetchAll();
// Obtengo el dato de mi columna 'count', que me indica la cantidad de filas que tiene la tabla de Artist
$cantidad = $resultados[0]['cantidad'];

// Guardo un True o False si nos encontramos en la primera página: ¿La página actual es la primera?
$esPrimera = $paginaActual === 1;
// Guardo un True o False si nos encontramos en la última página: ¿La página actual es la última?
$esUltima = (int) ceil($cantidad / LIMITE_RESULTADOS) === $paginaActual;

//======================================================================
// HTML
//======================================================================
?>
<html>
    <body>
        <!-- Bucle que dibuja todos los personajes -->
        <?php foreach ($resultados as $columna): ?>
        <div>
            <h1><?= $columna['Name'] ?></h1>
            <hr>
        </div>
        <?php endforeach; ?>
        <!-- Botón para retroceder -->
        <?php if (!$esPrimera): ?>
        <a href="paginador_SQL.php?pagina=<?= $paginaActual - 1 ?>">Anterior</a>
        <?php endif; ?>
        <!-- Botón para avanzar -->
        <?php if (!$esUltima): ?>
        <a href="paginador_SQL.php?pagina=<?= $paginaActual + 1 ?>">Siguiente</a>
        <?php endif; ?>
    </body>
</html>