Σελίδωση αποτελεσμάτων βάσης δεδομένων
Κατά τη δημιουργία web εφαρμογών, πολύ συχνά θα συναντήσετε την απαίτηση για περιορισμό του αριθμού των εμφανιζόμενων στοιχείων ανά σελίδα.
Θα ξεκινήσουμε από την κατάσταση όπου εμφανίζουμε όλα τα δεδομένα
χωρίς σελίδωση. Για την επιλογή δεδομένων από τη βάση δεδομένων έχουμε
την κλάση ArticleRepository, η οποία εκτός από τον constructor περιέχει τη μέθοδο
findPublishedArticles
, η οποία επιστρέφει όλα τα δημοσιευμένα άρθρα
ταξινομημένα φθίνοντα κατά ημερομηνία δημοσίευσης.
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,
);
}
}
Στον presenter, στη συνέχεια, κάνουμε inject την κλάση του μοντέλου και στη μέθοδο render ζητάμε τα δημοσιευμένα άρθρα, τα οποία περνάμε στο template:
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();
}
}
Στο template default.latte
φροντίζουμε στη συνέχεια για την εμφάνιση των
άρθρων:
{block content}
<h1>Άρθρα</h1>
<div class="articles">
{foreach $articles as $article}
<h2>{$article->title}</h2>
<p>{$article->content}</p>
{/foreach}
</div>
Με αυτόν τον τρόπο μπορούμε να εμφανίσουμε όλα τα άρθρα, πράγμα που όμως αρχίζει να δημιουργεί προβλήματα τη στιγμή που ο αριθμός των άρθρων αυξάνεται. Σε εκείνη τη στιγμή, έρχεται βολική η υλοποίηση ενός μηχανισμού σελίδωσης.
Αυτός εξασφαλίζει ότι όλα τα άρθρα θα χωριστούν σε αρκετές σελίδες και εμείς θα εμφανίσουμε μόνο τα άρθρα μιας τρέχουσας σελίδας. Τον συνολικό αριθμό σελίδων και τη διαίρεση των άρθρων θα τον υπολογίσει ο Paginator μόνος του ανάλογα με το πόσα άρθρα έχουμε συνολικά και πόσα άρθρα ανά σελίδα θέλουμε να εμφανίσουμε.
Στο πρώτο βήμα, θα τροποποιήσουμε τη μέθοδο για την απόκτηση άρθρων στην κλάση του repository έτσι ώστε να μπορεί να μας επιστρέφει μόνο άρθρα για μία σελίδα. Θα προσθέσουμε επίσης μια μέθοδο για τη διαπίστωση του συνολικού αριθμού άρθρων στη βάση δεδομένων, την οποία θα χρειαστούμε για τη ρύθμιση του Paginator:
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,
);
}
/**
* Επιστρέφει τον συνολικό αριθμό δημοσιευμένων άρθρων
*/
public function getPublishedArticlesCount(): int
{
return $this->database->fetchField('SELECT COUNT(*) FROM articles WHERE created_at < ?', new \DateTime);
}
}
Στη συνέχεια, θα προχωρήσουμε στις τροποποιήσεις του presenter. Στη μέθοδο render θα περνάμε τον αριθμό της τρέχουσας εμφανιζόμενης σελίδας. Για την περίπτωση που αυτός ο αριθμός δεν θα είναι μέρος του URL, θα ορίσουμε την προεπιλεγμένη τιμή της πρώτης σελίδας.
Επίσης, θα επεκτείνουμε τη μέθοδο render με την απόκτηση της παρουσίας του Paginator, τη ρύθμισή του και την επιλογή των σωστών άρθρων για εμφάνιση στο template. Ο HomePresenter μετά τις τροποποιήσεις θα μοιάζει ως εξής:
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
{
// Θα διαπιστώσουμε τον συνολικό αριθμό δημοσιευμένων άρθρων
$articlesCount = $this->articleRepository->getPublishedArticlesCount();
// Θα δημιουργήσουμε μια παρουσία του Paginator και θα τον ρυθμίσουμε
$paginator = new Nette\Utils\Paginator;
$paginator->setItemCount($articlesCount); // συνολικός αριθμός άρθρων
$paginator->setItemsPerPage(10); // αριθμός στοιχείων ανά σελίδα
$paginator->setPage($page); // αριθμός τρέχουσας σελίδας
// Από τη βάση δεδομένων θα τραβήξουμε ένα περιορισμένο σύνολο άρθρων σύμφωνα με τον υπολογισμό του Paginator
$articles = $this->articleRepository->findPublishedArticles($paginator->getLength(), $paginator->getOffset());
// το οποίο θα περάσουμε στο template
$this->template->articles = $articles;
// και επίσης τον ίδιο τον Paginator για την εμφάνιση των επιλογών σελίδωσης
$this->template->paginator = $paginator;
}
}
Το template μας ήδη τώρα επαναλαμβάνεται μόνο πάνω στα άρθρα μιας σελίδας, αρκεί να προσθέσουμε τους συνδέσμους σελίδωσης:
{block content}
<h1>Άρθρα</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">Πρώτη</a>
|
<a n:href="default, $paginator->page-1">Προηγούμενη</a>
|
{/if}
Σελίδα {$paginator->getPage()} από {$paginator->getPageCount()}
{if !$paginator->isLast()}
|
<a n:href="default, $paginator->getPage() + 1">Επόμενη</a>
|
<a n:href="default, $paginator->getPageCount()">Τελευταία</a>
{/if}
</div>
Έτσι συμπληρώσαμε τη σελίδα με τη δυνατότητα σελίδωσης
χρησιμοποιώντας τον Paginator. Στην περίπτωση που αντί του Nette Database Core ως επίπεδο βάσης δεδομένων
χρησιμοποιήσουμε το Nette Database Explorer,
είμαστε σε θέση να υλοποιήσουμε τη σελίδωση και χωρίς τη χρήση του
Paginator. Η κλάση Nette\Database\Table\Selection
περιέχει τη μέθοδο page με τη λογική
σελίδωσης που έχει ληφθεί από τον Paginator.
Το repository με αυτόν τον τρόπο υλοποίησης θα μοιάζει ως εξής:
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');
}
}
Στον presenter δεν χρειάζεται να δημιουργήσουμε Paginator, θα
χρησιμοποιήσουμε αντί γι' αυτόν τη μέθοδο της κλάσης Selection
, την
οποία μας επιστρέφει το repository:
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
{
// Θα τραβήξουμε τα δημοσιευμένα άρθρα
$articles = $this->articleRepository->findPublishedArticles();
// και στο template θα στείλουμε μόνο το μέρος τους που περιορίζεται σύμφωνα με τον υπολογισμό της μεθόδου page
$lastPage = 0;
$this->template->articles = $articles->page($page, 10, $lastPage);
// και επίσης τα απαραίτητα δεδομένα για την εμφάνιση των επιλογών σελίδωσης
$this->template->page = $page;
$this->template->lastPage = $lastPage;
}
}
Επειδή στο template τώρα δεν στέλνουμε τον Paginator, θα τροποποιήσουμε το μέρος που εμφανίζει τους συνδέσμους σελίδωσης:
{block content}
<h1>Άρθρα</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">Πρώτη</a>
|
<a n:href="default, $page - 1">Προηγούμενη</a>
|
{/if}
Σελίδα {$page} από {$lastPage}
{if $page < $lastPage}
|
<a n:href="default, $page + 1">Επόμενη</a>
|
<a n:href="default, $lastPage">Τελευταία</a>
{/if}
</div>
Με αυτόν τον τρόπο υλοποιήσαμε τον μηχανισμό σελίδωσης χωρίς τη χρήση του Paginator.