16. Pagination
One day you go on Amazon intending to buy socks. When you hit the Enter button it opens a page with all the socks in the store, roughly 23,000 of them. Without any consideration it shows them all to you, one on top of another. The browser scrollbar is almost so thin it's barely clickable with the mouse. Can you imagine how uncomfortable this scenario is, being able to see all the results at once? That's exactly why paginators were created: buttons to move between pages of results. Mechanisms to advance through blocks of items with a well-defined number.
To build this system, which you can apply in any programming language, we'll need a bit of ingenuity sprinkled with a little basic math.
Showing only one page
We'll start with the first 10 characters, sorted alphabetically, from Game of Thrones as our "database".
$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)'
];
Our goal will be to show only 3 results per page, and we'll gradually add more functions.
We start with the first page. We define the result limit.
// Constant with the number of results per page: 3
define('LIMITE_RESULTADOS', 3);
We'll start with page number 1, like any book.
$paginaActual = 1;
Now we need to shrink the array down to the first 3.
$personajesPagina = array_slice($personajes, ($paginaActual - 1) * LIMITE_RESULTADOS, LIMITE_RESULTADOS);
Then we print each character in HTML with a foreach.
<html>
<body>
<?php foreach ($personajesPagina as $personaje): ?>
<div>
<h1><?= $personaje ?></h1>
<hr>
</div>
<?php endforeach; ?>
</body>
</html>
All together it gives us the following example as a starting point.
<?php
// Constant with the number of results per page: 3
define('LIMITE_RESULTADOS', 3);
// Page we're on. If there's a GET named 'pagina' it's saved, otherwise it will be 1.
$paginaActual = 1;
// Create a new array with the characters to show on the page
$personajesPagina = array_slice($personajes, ($paginaActual - 1) * LIMITE_RESULTADOS, LIMITE_RESULTADOS);
?>
<html>
<body>
<?php foreach ($personajesPagina as $personaje): ?>
<div>
<h1><?= $personaje ?></h1>
<hr>
</div>
<?php endforeach; ?>
</body>
</html>
It generates what we need, the first 3 names.
:quality(85)/https://andros.dev/static/img/courses/php/paginador1.png)
<html>
<body>
<div>
<h1>Abelar Hightower</h1>
<hr>
</div>
<div>
<h1>Addam Frey</h1>
<hr>
</div>
<div>
<h1>Addam</h1>
<hr>
</div>
</body>
</html>
We've broken the characters array into a smaller one, with the help of array_slice.
array_slice([array], [starting position], [slice length]);
Our array_slice looks like this:
array_slice($personajes, ($paginaActual - 1) * LIMITE_RESULTADOS, LIMITE_RESULTADOS);
Next button
When we want to move to another page, still talking about the example, we'll have to pass the page we want to move to. To do that we save the parameter.
// Page we're on. If there's a GET named 'pagina' it's saved, otherwise it will be 1.
$paginaActual = isset($_REQUEST['pagina']) ? (int) $_REQUEST['pagina'] : 1;
Now we just have to include in the HTML the button where we indicate that the parameter, the one we capture and that determines the page we're on, is paginaActual + 1.
<a href="paginador.php?pagina=<?= $paginaActual + 1 ?>">Next</a>
At this point we can already move forward.
//======================================================================
// 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)'
];
// Constant with the number of results per page: 3
define('LIMITE_RESULTADOS', 3);
// Page we're on. If there's a GET named 'pagina' it's saved, otherwise it will be 1.
$paginaActual = isset($_REQUEST['pagina']) ? $_REQUEST['pagina'] : 1;
// Create a new array with the characters to show on the page
$personajesPagina = array_slice($personajes, ($paginaActual - 1) * LIMITE_RESULTADOS, LIMITE_RESULTADOS);
//======================================================================
// HTML
//======================================================================
?>
<html>
<body>
<!-- Loop that draws all the characters -->
<?php foreach ( $personajesPagina as $personaje): ?>
<div>
<h1><?= $personaje ?></h1>
<hr>
</div>
<?php endforeach; ?>
<!-- Button to move forward -->
<a href="paginador.php?pagina=<?= $paginaActual + 1 ?>">Next</a>
</body>
</html>
If you've run it you'll find a problem: we reach the last page and it still lets us click Next again.
First we must find out whether we're at the end.
$esUltima = ceil(count($personajes) / LIMITE_RESULTADOS) == $paginaActual;
Now we only show the button if it's false.
<!-- Button to move forward -->
<?php if (!$esUltima): ?>
<a href="paginador.php?pagina=<?= $paginaActual + 1 ?>">Next</a>
<?php endif; ?>
Ending up like this.
<?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)'
];
// Constant with the number of results per page: 3
define('LIMITE_RESULTADOS', 3);
// Page we're on. If there's a GET named 'pagina' it's saved, otherwise it will be 1.
$paginaActual = isset($_REQUEST['pagina']) ? (int) $_REQUEST['pagina'] : 1;
// Create a new array with the characters to show on the page
$personajesPagina = array_slice($personajes, ($paginaActual - 1) * LIMITE_RESULTADOS, LIMITE_RESULTADOS);
// Store True or False for whether we're on the last page: is the current page the last one?
$esUltima = ceil(count($personajes) / LIMITE_RESULTADOS) == $paginaActual;
//======================================================================
// HTML
//======================================================================
?>
<html>
<body>
<!-- Loop that draws all the characters -->
<?php foreach ( $personajesPagina as $personaje): ?>
<div>
<h1><?= $personaje ?></h1>
<hr>
</div>
<?php endforeach; ?>
<!-- Button to move forward -->
<?php if (!$esUltima): ?>
<a href="paginador.php?pagina=<?= $paginaActual + 1 ?>">Next</a>
<?php endif; ?>
</body>
</html>
Previous button
We repeat the strategy, first we find out whether it should be visible: are we on paginaActual 1?
// Store True or False for whether we're on the first page: is the current page the first one?
$esPrimera = $paginaActual == 1;
Next we generate a button subtracting 1 from paginaActual.
<!-- Button to go back -->
<?php if (!$esPrimera): ?>
<a href="paginador.php?pagina=<?= $paginaActual - 1 ?>">Previous</a>
<?php endif; ?>
Final result
<?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)'
];
// Constant with the number of results per page: 3
define('LIMITE_RESULTADOS', 3);
// Page we're on. If there's a GET named 'pagina' it's saved, otherwise it will be 1.
$paginaActual = isset($_REQUEST['pagina']) ? $_REQUEST['pagina'] : 1;
// Create a new array with the characters to show on the page
$personajesPagina = array_slice($personajes, ($paginaActual - 1) * LIMITE_RESULTADOS, LIMITE_RESULTADOS);
// Store True or False for whether we're on the first page: is the current page the first one?
$esPrimera = $paginaActual == 1;
// Store True or False for whether we're on the last page: is the current page the last one?
$esUltima = ceil(count($personajes) / LIMITE_RESULTADOS) == $paginaActual;
//======================================================================
// HTML
//======================================================================
?>
<html>
<body>
<!-- Loop that draws all the characters -->
<?php foreach ( $personajesPagina as $personaje): ?>
<div>
<h1><?= $personaje ?></h1>
<hr>
</div>
<?php endforeach; ?>
<!-- Button to go back -->
<?php if (!$esPrimera): ?>
<a href="paginador.php?pagina=<?= $paginaActual - 1 ?>">Previous</a>
<?php endif; ?>
<!-- Button to move forward -->
<?php if (!$esUltima): ?>
<a href="paginador.php?pagina=<?= $paginaActual + 1 ?>">Next</a>
<?php endif; ?>
</body>
</html>
Querying a database
This time we're going to build an optimized paginator hitting a real database with SQLite and Chinook. We download it and, in the same folder, create a file called paginador_SQL.php with the following content.
<?php
//======================================================================
// Variables
//======================================================================
define('LIMITE_RESULTADOS', 10);
$hostDB = 'Chinook_Sqlite.sqlite';
// Connect to the database
$hostPDO = "sqlite:$hostDB";
$miPDO = new PDO($hostPDO);
// Disable the database adding single quotes in the LIMIT
// Mandatory when using MySQL/MariaDB
$miPDO->setAttribute( PDO::ATTR_EMULATE_PREPARES, FALSE);
$resultados = [];
$paginaActual = isset($_REQUEST['pagina']) ? (int) $_REQUEST['pagina'] : 1;
//======================================================================
// Get results from the database
//======================================================================
// Prepare the SELECT
$miConsulta = $miPDO->prepare('SELECT Name, (SELECT COUNT(*) FROM Artist) as cantidad FROM Artist ORDER BY Name ASC LIMIT :pagina, :limite;');
// Run
$miConsulta->execute([
'pagina' => ($paginaActual - 1) * LIMITE_RESULTADOS,
'limite' => LIMITE_RESULTADOS
]);
// Store all the results
$resultados = $miConsulta->fetchAll();
// Get the value of my 'cantidad' column, which tells me the number of rows in the Artist table
$cantidad = $resultados[0]['cantidad'];
// Store True or False for whether we're on the first page: is the current page the first one?
$esPrimera = $paginaActual === 1;
// Store True or False for whether we're on the last page: is the current page the last one?
$esUltima = (int) ceil($cantidad / LIMITE_RESULTADOS) === $paginaActual;
//======================================================================
// HTML
//======================================================================
?>
<html>
<body>
<!-- Loop that draws all the characters -->
<?php foreach ($resultados as $columna): ?>
<div>
<h1><?= $columna['Name'] ?></h1>
<hr>
</div>
<?php endforeach; ?>
<!-- Button to go back -->
<?php if (!$esPrimera): ?>
<a href="paginador_SQL.php?pagina=<?= $paginaActual - 1 ?>">Previous</a>
<?php endif; ?>
<!-- Button to move forward -->
<?php if (!$esUltima): ?>
<a href="paginador_SQL.php?pagina=<?= $paginaActual + 1 ?>">Next</a>
<?php endif; ?>
</body>
</html>
Activity 1
- Download the SQL file from the following link.
- Migrate the information into a database using a MySQL interface like PHPMyAdmin or Adminer.
- List all the columns in an HTML table.
- Show only 25 results.
- Add a button to move to the next page.
- Insert another button to go back.
Pro:
- If it's endangered, show the name in red.
- Create a form to add new plants.
- Place a button in each row to delete.
- Place a button in each row to modify.
Activity 2
Using the Chinook database, print all the artists in a table.
- Paginate the results 10 at a time.
- Add a button to go to the next page.
- Add a button to go back to the previous page.
PRO:
Create a preview of the next pages with 5 upcoming ones.
For example, if you're on page 1.
1 - 2 - 3 - 4 - 5 | Next
If you're on page 12.
Previous | 12 - 13 - 14 - 15 - 16 | Next
This work is under a Attribution-NonCommercial-NoDerivatives 4.0 International license.
Desafíos de programación atemporales y multiparadigmáticos
Te encuentras ante un librillo de actividades, divididas en 2 niveles de dificultad. Te enfrentarás a los casos más comunes que te puedes encontrar en pruebas técnicas o aprender conceptos elementales de programación.
Buy the bookWill you buy me a coffee?
This is how I keep writing without ads or paywalls.
Sure, it's on me!
Comments
There are no comments yet.