Na internetu můžete nalézt hodně řešení, která se zabývají právě stránkováním. Tady na Programujte.com je jedno řešení, ne příliš kvalitní a 3 roky staré, proto jsem se rozhodl vám touto cestou stránkování oživit.
Začnu nejprve tím, co to vlastně stránkování je. Už jste se určitě setkali s weby, kde ať jsou novinky, aktuality, soubory či cokoliv jiného, musíte nechutně dlouho scrollovat (posouvat) dolů, abyste se dostali na konec stránky.
Je to způsobeno tím, že v databázi (popř. v souboru či jiném datové úložišti, zde budu mluvit o databázi, jelikož je to nejčastější způsob ukládání dat), konkrétně v tabulce/tabulkách, je uloženo velké množství záznamů, které vypisujeme (nejčastěji pod sebe). A pokud je těchto záznamů například přes 1000, udělá to právě tento nežádaný efekt.
Proto se používá právě stránkování, díky kterému si těchto 1000 záznamů přehledně budete vypisovat po deseti.
Jednoduché rozvržení: << < 4 5 6 7 8 9 10 > >>
Trochu teorie máme za sebou, nyní se pustíme do samotného programování. Předpokládejme, že máme tabulku clanky, dejme tomu s takovouto strukturou.
CREATE TABLE `clanky` (
`id` int(11) NOT NULL auto_increment,
`clanek` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Tuto tabulku naplníme daty...
INSERT INTO `clanky` (`id`, `clanek`) VALUES
(1, 'Mass Effect 2'),
(2, 'FIFA 10'),
(3, 'Dragon Age: Origins'),
(4, 'G.I. JOE: The Rise of Cobra'),
(5, 'NFS: Shift'),
(6, 'MySims Agents'),
(7, 'Dead Space Extraction'),
(8, 'Spore Hero'),
(9, 'Spore Hero Arena'),
(10, 'Brutal Legend'),
(11, 'LITTLEST PET SHOP FRIENDS '),
(12, 'Hasbro Family Game Night '),
(13, 'The Sims 3 World Adventures'),
(14, 'NFS: Nitro'),
(15, 'Army of Two: The 40th Day'),
(16, 'Dante s Inferno'),
(17, 'All Points Bulletin'),
(18, 'Monopoly'),
(19, 'Testuju'),
(20, 'Testuju2'),
(21, 'asd'),
(22, 'ddd'),
(23, 'haaaf'),
(24, 'asd'),
(25, 'test18.20');
Máme celkem 25 záznamů (jsou použity záznamy z jedné již existující databáze, proto ty názvy), což nás vede k tomu, že bychom mohli záznamy stránkovat po pěti na hezkých 5 stránek.
Nejdříve, jelikož se předpokládá, že se při ostrém provozu budou data v tabulce měnit, ať už přidávat nebo odebírat, musíme zjistit, kolik máme v tabulce celkem záznamů.
To učiníme následovně:
$countQ = $mysqli->query("SELECT COUNT(*) as max FROM clanky");
// pokud nepoužíváte ke spojení s databází třídu mysqli,
// zde je ukázka pro řešení procedurální
$countQ = mysql_query("SELECT COUNT(*) as max FROM clanky");
Stále ještě ale nevíme, kolik máme záznamů v databázi, takže musíme provést ještě dalších pár kroků:
// může se stát, že také záznamů bude 0, což by vedlo k chybové hlášce,
// tak musíme ověřit, že počet záznamů v tabulce je kladný
if($countQ->num_rows > 0) {
$count = $countQ->fetch_object(); // načtu si výsledek do objektu
$max = $count->max; // a náš počet COUNT(*) uložím do proměnné $max
}
// řešení v mysql
if(mysql_num_rows($countQ) > 0) {
$count = mysql_fetch_object($countQ);
$max = $count->max;
}
Nyní mám v proměnné $max uložený počet záznamů v tabulce. V tomto případě, pokud byste $max vyechovali, dostanete toto:
echo $max; // 25
Potřebujeme ale také nastavit, kolik že vlastně chceme, aby se zobrazovalo záznamů na stránce. Proto si nadefinujeme konstantu. Konvence říká, že konstanta by měla být psána velkými písmeny.
define ("ON_PAGE", 5);
Nyní následuje odchycení stránky. Detekce stránky bude probíhat pomocí pole $_GET, konkrétněji $_GET["page"], jehož hodnotu si uložíme do proměnné $page. Tato proměnná nám bude uchovávat informaci, na které že stránce se právě nacházíme.
Proměnnou si budeme předávat v URL jako parametr. Pokud uživatel na naši stránku přijde poprvé, dejme tomu, že máme stránku clanky.php, v URL uvidí právě tuto adresu (tzn. www.stranka.cz/clanky.php - pozn. o mod_rewrite jindy). Ale kde máme informaci, na které stránce se nacházíme? To jsme na "žádné" stránce? Proto se o toto musíme postarat. Také se musíme postarat o bezpečnost kódu. Jelikož $page odchytáváme pomocí $_GETu z URL adresy, pak musíme kód zabezpečit proti XSS (Cross Site Scripting). Z tohoto důvodů si napíšeme funkci, která se postará o to, že v proměnné $page bude číslo a ne nekalý skript.
function right_int($arg) {
$arg = (int) $arg;
if (is_numeric($arg)) {
return $arg;
}
// v případě, že $arg neprojde kontrolou, bude naše $page 1 => začátek
return 1;
}
if(!isset($_GET["page"]))
$page = 1;
else
$page = right_int($_GET["page"]);
Co to znamená? Pokud není inicializována (není nastavena, nemá žádnou hodnotu), řekneme, ať proměnná $page má hodnotu 1, čímž myslíme, že se uživatel nachází na první stránce. Pokud ale tato proměnná v URL je (už si uživatel stránkuje), proměnná $page bude mít právě tuto hodnotu.
Takže nyní máme následující kousek kódu:
$countQ = $mysqli->query("SELECT COUNT(*) as max FROM clanky");
if($countQ->num_rows > 0) {
$count = $countQ->fetch_object();
$max = $count->max;
}
define ("ON_PAGE", 5);
function right_int($arg) {
$arg = (int) $arg;
if (is_numeric($arg)) {
return $arg;
}
// v případě, že $arg neprojde kontrolou, bude naše $page 1 => začátek
return 1;
}
if(!isset($_GET["page"]))
$page = 1;
else
$page = right_int($_GET["page"]);
Kdo zná pole, ví, kdo nezná, určitě se je naučí. Zde potřebuje vědět jednu věc, a to, že 1. záznam v tabulce má index 0. Druhý záznam v tabulce je schovaný pod indexem 1 atd.
Nyní trochu předběhnu a řeknu vám o konstrukci, díky které zajistíme v SQL dotazu stránkování. Jedná se o klauzuli LIMIT a OFFSET. Nebudu zde zabíhat do detailů, ale představte si to jako "kolik" a "odkud".
"Kolik" už víme. To máme uložené v konstantě ON_PAGE. Ale jak zjistit "odkud"? Proto jsem výše vysvětloval pozici jednotlivých záznamů. To znamená, že pokud chci vybrat první záznam, musím začít od nuly. Pokud chci druhý, tak od jedničky.
A jak to spočítáme? Tak, že konstantu ON_PAGE vynásobíme aktuální stránkou - 1, jelikož, jak jsem již řekl, začínáme od nuly. Takže do našeho kódu přidáme:
$by = ($on_page * ($page - 1));
Nyní už máme všechny potřebné proměnné, takže můžeme přejít k SQL dotazu, kterým si vytáhneme záznamy z databáze, již s přihlédnutím na stránkování.
$articles = $mysqli->query("SELECT clanek FROM clanky ORDER BY clanek DESC LIMIT ".ON_PAGE." OFFSET " . $by);
// popř.
$articles = mysql_query("SELECT clanek FROM clanky ORDER BY clanek DESC LIMIT ".ON_PAGE." OFFSET " . $by);
A články vypíšeme..
if($articles->num_rows > 0) {
while($article = $articles->fetch_object()) {
echo $article->clanek . "<br />";
}
}
// mysql..
if(mysql_num_rows($articles) > 0) {
while($article = mysql_fetch_object($articles)){
echo $article->clanek . "<br />";
}
}
Nyní jsme vypsali záznam, ale nechybí tu něco? No jo... jak přecházet mezi stránkami?
Nejdříve opět zjistíme, jestli se nám neobjevuje zase ta zlá nula. V tomto případě se jedná o to, jestli máme vůbec co stránkovat. Protože pokud máme třeba dva záznamy a ON_PAGE nastavenou na 5, pak není přeci co stránkovat. Musíme tedy zajistit i to, že nebude stránkovat, když nemá.
// pouze pokud je co stránkovat
if(ON_PAGE < $max){ // nebo $max > ON_PAGE. Je to naprosto to samé, ale z vlastní zkušenosti vím, že někomu se líbí i ta druhá varianta... Že to s ní prý lépe chápe.
Nyní se vrhneme na samotné zobrazení odkazů na stránky.
// NA ZAČÁTEK
// pokud nejsme na začátku, udělat odkaz na začátek...
if($page > 1) {
echo "<a href='clanky.php/?page=1'><<</a>";
}
// PŘEDCHOZÍ
// pokud nejsme na začátku, zobrazit odkaz na předchozí a cyklus na předchozí
if($page > 1) {
echo "<a href='clanky.php/?page=".($page-1)."'><</a>";
// PŘEDCHOZÍ - CYKLUS
// vypíše 3 předchozí stránky
for($i = 4; $i > 0; $i--) {
if(($page - $i) >= 1){
echo "<a href='clanky.php/?page=".($page-$i)."'>".($page-$i)."</a>";
}
}
}
Tady bych se trochu zastavil. Chci vypsat tři předchozí stránky. Což v praxi znamená, pokud jsem na stránce č. 5, chci mít také odkaz na stránku č. 4, 3 a 2.
for($i = 4; $i > 0; $i--)
// Zajistí, že mi cyklus proběhne celkem 3×. Což chci.
// Musí zde probíhat dekrementace ($i--), jelikož budu čísla (odkazy na stránku) zmenšovat.
Dále musí pokaždé proběhnout kontrola, abych se nedostal na nulovou nebo zápornou stránku. O to se stará podmínka:
if(($page - $i) >= 1)
Dále následuje echo odkazů.
Jelikož jsme u středu, zobrazíme také aktuální stránku. To provedeme naprosto jednoduše.
echo $page;
Jdeme dál..
// DALŠÍ
// když nejsme na konci, tzn. je aktuální menší než maximální počet / na stránce.
// Např. pokud bude max 10 a na stránce bude po 2, tak pokud je aktuální ($page) menší než 5.
if($page < ($max / ON_PAGE)) {
// DALŠÍ - CYKLUS
// vypíše 3 následující stránky
for($i = 1; $i < 4; $i++) {
if(($page + $i) <= ceil($max / ON_PAGE)) {
echo "<a href='clanky.php/?page=".($page+$i)."'>".($page+$i)."</a>";
}
}
// další
echo "<a href='clanky.php/?page=".($page+1)."'>></a>";
}
Opět se zde trochu pozastavíme. Jak zajistit, aby nám to nestránkovalo dopředu (další), máte vysvětleno v komentáři. Zaměřím se na cyklus. Chci vypsat opět tři, ale nyní dopředu (další). Co znamená tento řádek?
if(($page + $i) <= ceil($max / ON_PAGE))
Vemte si příklad, kdy $max bude 13 a ON_PAGE = 4 (tedy 13 / 4 = 3,25). V podmínce se ptám, zda ta následující stránka ($page + $i) je menší nebo rovna poslední stránce. Kdybych se neptal, tak v případě, že bych měl stránek 10 a byl na deváté, vypsal by se odkaz na 10., 11. a 12. stránku, což je samozřejmě nesmysl.
A proč ceil? Funkce ceil zaokrouhluje nahoru (pozor: round() zaokrouhluje od 4 dolů, od 5 nahoru. Ceil zaokrouhlí 2.1 na 3). Přeci nemůžeme mít 3,25 stránku. Proto zaokrouhlíme nahoru a ejhle. Ona je to vlastně 4. stránka. Ale nemá plný počet záznamů, které mohou být na stránce. V tomto případě, kde je $max 13 se na stránku číslo 4 vypíše jeden článek.
A zbývá nám ještě udělat odkaz na konec.
// KONEC
// pokud nejsme na konci, udělat odkaz na konec
// opět nemůže být 3,25. stránka
if($page < ceil($max / $on_page)) {
echo "<a href='clanky.php/?page=".ceil($max / $on_page)."'>>></a>";
}
Tak jste se přelouskali ke konci. Doufám, že vám tento článek alespoň trochu v něčem pomohl nebo jste se díky němu něco nového naučili. Nezaobíral jsem se tu vzhledem. Já při své práci obaluji odkazy CSS, takže to pak vypadá lépe než klasický text. Toto už jsem nechal na vás. Přeci jenom je to článek o PHP, a ne o HTML či CSS.
Pokud byste měli zájem, můžu vám toto řešení předělat do OOP, které by bylo prakticky více opakovaně použitelné.