Strani rezultatov podatkovne zbirke

Pri razvoju spletnih aplikacij se pogosto srečate z zahtevo po izpisu omejenega števila zapisov na strani.

Iz tega stanja izstopimo, ko izpišemo vse podatke brez listanja. Za izbiro podatkov iz zbirke podatkov imamo razred ArticleRepository, ki vsebuje konstruktor in metodo findPublishedArticles, ki vrne vse objavljene članke, razvrščene v padajočem vrstnem redu glede na datum objave.

namespace App\Model;

use Nette;

class ArticleRepository
{
	public function __construct(
		private Nette\Database\Connection $database,
	) {
	}

	public function findPublishedArticles(): Nette\Database\ResultSet
	{
		return $this->database->query('
			SELECT * FROM articles
			WHERE created_at < ?
			ORDER BY created_at DESC',
			new \DateTime,
		);
	}
}

V Presenter nato injiciramo razred model in v metodi render bomo zahtevali objavljene članke, ki jih posredujemo predlogi:

namespace App\UI\Home;

use Nette;
use App\Model\ArticleRepository;

class HomePresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private ArticleRepository $articleRepository,
	) {
	}

	public function renderDefault(): void
	{
		$this->template->articles = $this->articleRepository->findPublishedArticles();
	}
}

Predloga default.latte bo nato poskrbela za seznam člankov:

{block content}
<h1>Articles</h1>

<div class="articles">
	{foreach $articles as $article}
		<h2>{$article->title}</h2>
		<p>{$article->content}</p>
	{/foreach}
</div>

Na ta način lahko izpišemo vse članke, vendar bo to povzročilo težave, ko bo število člankov naraslo. Takrat bo koristno uvesti mehanizem paginga.

Ta bo zagotovil, da bodo vsi članki razdeljeni na več strani, mi pa bomo prikazali le članke ene trenutne strani. Skupno število strani in razdelitev člankov izračuna Paginator sam, odvisno od tega, koliko člankov imamo skupaj in koliko člankov želimo prikazati na strani.

V prvem koraku bomo spremenili metodo za pridobivanje člankov v razredu repozitorija, da bo vrnila samo članke z ene strani. Dodali bomo tudi novo metodo za pridobitev skupnega števila člankov v zbirki podatkov, ki jo bomo potrebovali za nastavitev Paginatorja:

namespace App\Model;

use Nette;


class ArticleRepository
{
	public function __construct(
		private Nette\Database\Connection $database,
	) {
	}

	public function findPublishedArticles(int $limit, int $offset): Nette\Database\ResultSet
	{
		return $this->database->query('
			SELECT * FROM articles
			WHERE created_at < ?
			ORDER BY created_at DESC
			LIMIT ?
			OFFSET ?',
			new \DateTime, $limit, $offset,
		);
	}

	/**
	 * Returns the total number of published articles
	 */
	public function getPublishedArticlesCount(): int
	{
		return $this->database->fetchField('SELECT COUNT(*) FROM articles WHERE created_at < ?', new \DateTime);
	}
}

Naslednji korak je urejanje predstavnika. Številko trenutno prikazane strani bomo posredovali metodi render. V primeru, da ta številka ni del naslova URL, moramo privzeto vrednost nastaviti na prvo stran.

Metodo upodabljanja razširimo tudi na pridobitev primerka Paginatorja, njegovo nastavitev in izbiro pravilnih člankov za prikaz v predlogi. HomePresenter bo videti takole:

namespace App\UI\Home;

use Nette;
use App\Model\ArticleRepository;

class HomePresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private ArticleRepository $articleRepository,
	) {
	}

	public function renderDefault(int $page = 1): void
	{
		// Poiskali bomo skupno število objavljenih člankov
		$articlesCount = $this->articleRepository->getPublishedArticlesCount();

		// Izdelali bomo primerek Paginatorja in ga nastavili
		$paginator = new Nette\Utils\Paginator;
		$paginator->setItemCount($articlesCount); // skupno število člankov
		$paginator->setItemsPerPage(10); // člankov na stran
		$paginator->setPage($page); // dejansko število strani

		// Na podlagi Paginatorjevih izračunov bomo v zbirki podatkov poiskali omejen nabor člankov
		$articles = $this->articleRepository->findPublishedArticles($paginator->getLength(), $paginator->getOffset());

		// ki ga bomo posredovali predlogi
		$this->template->articles = $articles;
		// in tudi sam Paginator, da prikaže možnosti za prikazovanje strani
		$this->template->paginator = $paginator;
	}
}

Predloga že iterira po člankih na eni strani, dodajte le povezave za paging:

{block content}
<h1>Articles</h1>

<div class="articles">
	{foreach $articles as $article}
		<h2>{$article->title}</h2>
		<p>{$article->content}</p>
	{/foreach}
</div>

<div class="pagination">
	{if !$paginator->isFirst()}
		<a n:href="default, 1">First</a>
		&nbsp;|&nbsp;
		<a n:href="default, $paginator->page-1">Previous</a>
		&nbsp;|&nbsp;
	{/if}

	Page {$paginator->getPage()} of {$paginator->getPageCount()}

	{if !$paginator->isLast()}
		&nbsp;|&nbsp;
		<a n:href="default, $paginator->getPage() + 1">Next</a>
		&nbsp;|&nbsp;
		<a n:href="default, $paginator->getPageCount()">Last</a>
	{/if}
</div>

To je način, kako smo s Paginatorjem dodali paginacijo. Če namesto Nette Database Core kot plast podatkovne baze uporabimo Nette Database Explorer, lahko izvedemo paging tudi brez Paginatorja. Razred Nette\Database\Table\Selection vsebuje metodo page z logiko paginacije, ki je prevzeta iz Paginatorja.

Skladišče bo videti takole:

namespace App\Model;

use Nette;

class ArticleRepository
{
	public function __construct(
		private Nette\Database\Explorer $database,
	) {
	}

	public function findPublishedArticles(): Nette\Database\Table\Selection
	{
		return $this->database->table('articles')
			->where('created_at < ', new \DateTime)
			->order('created_at DESC');
	}
}

Namesto tega bomo uporabili metodo predmeta Selection, ki ga je vrnil repozitorij:

namespace App\UI\Home;

use Nette;
use App\Model\ArticleRepository;

class HomePresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private ArticleRepository $articleRepository,
	) {
	}

	public function renderDefault(int $page = 1): void
	{
		// Poiskali bomo objavljene članke
		$articles = $this->articleRepository->findPublishedArticles();

		// in njihov del, omejen z izračunom metode strani, ki ga bomo posredovali predlogi
		$lastPage = 0;
		$this->template->articles = $articles->page($page, 10, $lastPage);

		// in potrebne podatke za prikaz možnosti za stranski prikaz.
		$this->template->page = $page;
		$this->template->lastPage = $lastPage;
	}
}

Ker ne uporabljamo Paginatorja, moramo urediti razdelek, ki prikazuje povezave za pomikanje:

{block content}
<h1>Articles</h1>

<div class="articles">
	{foreach $articles as $article}
		<h2>{$article->title}</h2>
		<p>{$article->content}</p>
	{/foreach}
</div>

<div class="pagination">
	{if $page > 1}
		<a n:href="default, 1">First</a>
		&nbsp;|&nbsp;
		<a n:href="default, $page - 1">Previous</a>
		&nbsp;|&nbsp;
	{/if}

	Page {$page} of {$lastPage}

	{if $page < $lastPage}
		&nbsp;|&nbsp;
		<a n:href="default, $page + 1">Next</a>
		&nbsp;|&nbsp;
		<a n:href="default, $lastPage">Last</a>
	{/if}
</div>

Na ta način smo izvedli mehanizem za paging brez uporabe Paginatorja.