Stranskanje rezultatov podatkovne baze
Pri ustvarjanju spletnih aplikacij se zelo pogosto srečate z zahtevo po omejitvi števila izpisanih postavk na strani.
Izhajali bomo iz stanja, ko izpisujemo vse podatke brez stranskanja. Za izbiro podatkov iz podatkovne baze imamo razred
ArticleRepository, ki poleg konstruktorja vsebuje metodo findPublishedArticles
, ki vrača vse objavljene članke,
razvrščene padajoče po datumu 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 presenterju si nato injiciramo modelni razred in v render metodi zahtevamo objavljene članke, ki jih posredujemo v predlogo:
namespace App\Presentation\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();
}
}
V predlogi default.latte
se nato poskrbimo za izpis člankov:
{block content}
<h1>Články</h1>
<div class="articles">
{foreach $articles as $article}
<h2>{$article->title}</h2>
<p>{$article->content}</p>
{/foreach}
</div>
Na ta način znamo izpisati vse članke, kar pa začne povzročati težave v trenutku, ko število člankov naraste. V tem trenutku pride prav implementacija mehanizma za stranskanje.
Ta zagotovi, da se vsi članki razdelijo na več strani in mi prikažemo samo članke ene trenutne strani. Skupno število strani in razdelitev člankov si izračuna Paginator sam glede na to, koliko člankov skupaj imamo in koliko člankov na stran želimo prikazati.
V prvem koraku si prilagodimo metodo za pridobivanje člankov v razredu repozitorija tako, da nam zna vračati samo članke za eno stran. Dodamo tudi metodo za ugotavljanje skupnega števila člankov v podatkovni bazi, 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,
);
}
/**
* Vrača skupno število objavljenih člankov
*/
public function getPublishedArticlesCount(): int
{
return $this->database->fetchField('SELECT COUNT(*) FROM articles WHERE created_at < ?', new \DateTime);
}
}
Nato se lotimo prilagoditev presenterja. V render metodo bomo posredovali številko trenutno prikazane strani. Za primer, ko ta številka ne bo del URL-ja, nastavimo privzeto vrednost prve strani.
Nadalje render metodo razširimo še s pridobivanjem instance Paginatorja, njegovo nastavitvijo in izbiro pravilnih člankov za prikaz v predlogi. HomePresenter bo po prilagoditvah izgledal takole:
namespace App\Presentation\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
{
// Ugotovimo skupno število objavljenih člankov
$articlesCount = $this->articleRepository->getPublishedArticlesCount();
// Izdelamo instanco Paginatorja in jo nastavimo
$paginator = new Nette\Utils\Paginator;
$paginator->setItemCount($articlesCount); // skupno število člankov
$paginator->setItemsPerPage(10); // število postavk na stran
$paginator->setPage($page); // številka trenutne strani
// Iz podatkovne baze izvlečemo omejeno množico člankov glede na izračun Paginatorja
$articles = $this->articleRepository->findPublishedArticles($paginator->getLength(), $paginator->getOffset());
// ki jo posredujemo v predlogo
$this->template->articles = $articles;
// in tudi sam Paginator za prikaz možnosti stranskanja
$this->template->paginator = $paginator;
}
}
Predloga nam že zdaj iterira samo nad članki ene strani, dodati moramo le še povezave za stranskanje:
{block content}
<h1>Články</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">Prva</a>
|
<a n:href="default, $paginator->page-1">Prejšnja</a>
|
{/if}
Stran {$paginator->getPage()} od {$paginator->getPageCount()}
{if !$paginator->isLast()}
|
<a n:href="default, $paginator->getPage() + 1">Naslednja</a>
|
<a n:href="default, $paginator->getPageCount()">Zadnja</a>
{/if}
</div>
Tako smo stran dopolnili z možnostjo stranskanja s pomočjo Paginatorja. V primeru, ko namesto Nette Database Core kot podatkovno plast uporabimo Nette Database Explorer, smo sposobni implementirati stranskanje tudi brez
uporabe Paginatorja. Razred Nette\Database\Table\Selection
namreč vsebuje metodo page z logiko stranskanja, prevzeto iz
Paginatorja.
Repozitorij bo pri tem načinu implementacije izgledal 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');
}
}
V presenterju nam ni treba ustvarjati Paginatorja, namesto njega uporabimo metodo razreda Selection
, ki nam jo
vrača repozitorij:
namespace App\Presentation\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
{
// Izvlečemo objavljene članke
$articles = $this->articleRepository->findPublishedArticles();
// in v predlogo pošljemo samo njihov del, omejen glede na izračun metode page
$lastPage = 0;
$this->template->articles = $articles->page($page, 10, $lastPage);
// in tudi potrebne podatke za prikaz možnosti stranskanja
$this->template->page = $page;
$this->template->lastPage = $lastPage;
}
}
Ker v predlogo zdaj ne pošiljamo Paginatorja, prilagodimo del, ki prikazuje povezave za stranskanje:
{block content}
<h1>Články</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">Prva</a>
|
<a n:href="default, $page - 1">Prejšnja</a>
|
{/if}
Stran {$page} od {$lastPage}
{if $page < $lastPage}
|
<a n:href="default, $page + 1">Naslednja</a>
|
<a n:href="default, $lastPage">Zadnja</a>
{/if}
</div>
Na ta način smo implementirali mehanizem za stranskanje brez uporabe Paginatorja.