Ajaxové stránkování: včetně funkční aplikace

Mon, 6 Feb 2023 20:06:35 +1100

V prvním dílu jsem přiblížil, jak nalepit Ajaxové stránkování na hotový výpis. S druhým dílem jsem se o dva roky zapomněl. Postup se za tu dobu ovšem nezměnil, tak doufám, že i po takové pauze přinese článek někomu potřebné odpovědi.

V něm si napíšeme takovou mini-aplikaci, kde tohle všechno spojíme dohromady. Vysvětlování bude méně, jelikož je vše popsáno v předchozích článcích. A na závěr dostaneme funkční řešení.

Miniaplikaci jsem se snažil napsat tak, aby tam bylo co nejméně kódu i souborů. Proto je na hulváta všechno v indexu, proto ani neparsuji jedinou GET proměnnou, byť ve všech tutoriálech o mod_rewrite to doporučuji. Cílem je podchytit 3 stavy (výpis článku, filtr podle kategorie, detail článku) s fallbackem do 404ky, tak, aby kódu bylo co nejméně a já vám necpal nějakou strukturu aplikace, kterou vůbec nechcete. Zařazení článku do kategorie PHP: Základy má proto své opodstatnění; článek je určený začínajícím kodérům.

Kód #1: SQL

Tabulky mají nějakou základní strukturu a sloupce, které u kterých se očekává, že je mít budou. Do těla článku je zbytečně vkládat nebudu, můžete si je prohlédnout zde.

Kód #2: PHP

.htaccess na ukázku není potřeba, takže můžeme přejít rovnou ke skriptům. V configu nastavíme přístupové údaje k databázi a root url, více nastavení zatím nepotřebujeme. Obojí si navíc mohu podmínit pro localhost a ostrý server.

Výchozí stránkování bude po čtyřech, abych nemusel do tabulek vkládat kýble testovacích dat.

config.php

$config['paging_limit'] = 4;

$config['root'] = 'http://localhost/ajaxarticles2/';

$config['db_host'] = 'localhost';
$config['db_user'] = 'root';
$config['db_pass'] = '';
$config['db_name'] = 'evil';

index.php - kostra

Jak jsem zmínil výše, cílem miniaplikace je co nejmenší množství kódu i souborů, takže vše naplácáme do indexu a opustíme ho až při načtení šablony. To je základní minimum, které je dobré vždy oddělit. Tady už je čistě na vás, jak si vše poskládáte. Já doporučuji alespoň inicializaci nějaké základní třídy, která se o zbytek postará. Pokud tedy nevyužíváte hotový framework.

To jen pro úplnost, ať nemusím každý blok kódu uvádět a popisovat. Proto se nyní pustíme do samotného načítání obsahu.

<?php

// #0 zapnu zobrazeni chyb, pro jistotu
// #1 includy
// #2 funkce
// #3 pripojeni k databazi
// #4 inicializace promenne $tpl, sem budu predavat sablonova data
// #5 nacteni sdilenych komponent (menu), pouze pres standardni request
// #6 sekvecne podminek na filtraci dle kategorie, vypis a detail clanku
// #7 nacteni templaty

?>

Víceméně každý z bloků by měl mít nějaký vlastní layer v systému, já mám oddělenou pouze funkční část od templatů.

Sdílené komponenty

A protože je ukázka opravdu jednoduchá, více věcí, které chci načítat na každé stránce, tu nemám. Do následujícího bloku ale přijde vše, co se přímo netýká výpisu článků: vše, co chceme z Ajaxového requestu vyjmout. V uvedeném příkladu tedy pouze hlavní menu.

Z následující ukázky je také jasné, co se asi dělo ve skriptu předtím - aniž byste se hned museli dívat do kódu.

if (!isAjax()) {
	$cats_query = "
		SELECT *
		FROM `article_categories`
		WHERE `active` = '1'
		ORDER BY `sort_order` ASC
	";

	$cats = $sqli->query($cats_query);
	$tpl['categories'] = array();
	while ($item = $cats->fetch_assoc()) {
		$item['href'] = $config['root'] . '?cat=' . $item['seourl'];
		$tpl['categories'][] = $item;
	}
}

Výpis článků

Ukázce předchází filtrace dle kategorie, ale na tu se můžete podívat až ve výsledném souboru. Je to pouze funkce navíc, abych mohl ukázat, že Ajaxové stránkování jde nalepit na více modulů zaráz. Proměnná $category_condition bude nabývat jedné z následujících hodnot:

$category_condition = ''; // OR
$category_condition = "AND `category_id` = '{$category['id']}'";

A nyní živá ukázka stránkovací knihovny. Odpálíme SQL dotaz na počet záznamů, vytvoříme instanci Pagingu a hurá na SQL dotaz pro výběr objektů. Zde jenom připomenu informaci z minulého článku, že ošetření vstupu si třída ohlídá sama. Nemusíme řešit.

$total_query = "
	SELECT COUNT(`id`)
	FROM `articles`
	WHERE `active` = '1' {$category_condition}
";

$total_row = $sqli->query($total_query)->fetch_row();
$total = $total_row[0]; // kvuli php5
$paging_limit = 4;

$paging = new Paging($total, $paging_limit, 'page');
$paging->set_around(1)
       ->set_drop_vars(array('ajax'))
       ->set_paging();
$paging_start = $paging->get_start();

$tpl['paging'] = array(
	'desktop' => $paging->export_paging(),
	'limit' => $paging_limit,
);

$articles_query = "
	SELECT * FROM `articles`
	WHERE `active` = '1' {$category_condition}
	ORDER BY `timestamp` DESC
	LIMIT {$paging_start}, {$paging_limit}
";

$articles = $sqli->query($articles_query);
$tpl['articles'] = array();
while ($item = $articles->fetch_assoc()) {
	$item['href'] = $config['root'] . '?art=' . $item['seourl'];
	$tpl['articles'][] = $item;
}

A to je z PHP vše.

Co mi tu jednoznačně chybí, je podmínka na výběr článků pouze z aktivní kategorie, nemáme-li zúžený filtr. Pro úplnost uvedu, ale ukázku tím komplikovat nechci. Musel bych totiž ještě řešit výjimku z podmínky, kde články z neaktivní kategorie vybrat chci, jsem-li v jejím detailu. Ale to bychom se příliš zamotali v blogové funkcionalitě, když chci jenom ukázat Ajaxové stránkování. Každopádně pro požadované chování stačí zjoinovat tabulky v obou SQL dotazech. Opět na to jdeme nejjednodušší možnou cestou.

SELECT a.* // COUNT(a.`id`)
FROM `articles` a
LEFT JOIN `article_categories` c ON c.`id` = a.`category_id`
WHERE a.`active` = '1' AND c.`active` = '1'

A to je vlastně úplně vše. Frontend zde znovu rozebírat nemusím, tomu se věnoval předchozí článek. Article detail v aplikaci mám, nicméně to není důležitá funkce k tématu. Tu si můžet prohlédnout v náhledu kompletního zdrojáku.

Bug fix

Při testování jsem narazil na jeden zajímavý bug, kterým je ukládání do cache na straně prohlížeče. Uživateli se pak zobrazí pouze tělo stránky bez načteného headeru a footeru.

A právě to je důvod těch pár nenápadných drobností v kódu, kterých si možná na první pohled nevšimnete; Ajax Request posílám na jinou URL, než na které obsah zobrazuji při stisku F5. Ono by to tak i správně mělo být (unikátní URL pro unikátní response), ale člověk často na podobné věci kašle... Proto parametr ?ajax=1, který bug vyřeší.

Ukázka: index.php

Kód: index.php
Kód: templates/index.php
Kód: tables.sql
ZIP: archív ke stažení zde