Pagination
WireDrupal offers the ability to paginate results within a component.
Unfortunately, Drupal's existing pagination implementation is not flexible enough to be extended but a helper trait \Drupal\wire\WithPagination
is available to help with the implementation.
Assuming you have a articles
component, but you want to limit the results to 10 articles per page.
Here is an example of a simple prev/next pager implementation.
use Drupal\wire\View;
use Drupal\wire\WireComponent;
use Drupal\wire\WithPagination;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
/**
* Implementation for Articles Wire Component.
*
* @WireComponent(
* id = "articles",
* label = @Translation("Articles"),
* )
*/
class Articles extends WireComponent {
use WithPagination;
const ITEMS_PER_PAGE = 10;
public string $search = '';
protected string $pagerId = 'page';
protected array $queryString = [
'search' => ['except' => '', 'as' => 's'],
'page' => ['except' => 0, 'as' => 'p'],
];
protected ?EntityTypeManagerInterface $entityTypeManager;
public static function create(
ContainerInterface $container,
array $configuration,
$plugin_id,
$plugin_definition
) {
$instance = new static($configuration, $plugin_id, $plugin_definition);
$instance->entityTypeManager = $container->get('entity_type.manager');
return $instance;
}
public function render(): ?View {
$nodeStorage = $this->entityTypeManager->getStorage('node');
$nodeQuery = $nodeStorage->getQuery()->accessCheck(TRUE);
$getBaseQuery = function () use ($nodeQuery) {
$thisQuery = clone $nodeQuery;
$query = $thisQuery
->condition('type', 'article')
->condition('status', 1);
if (!empty($this->search)) {
$query->condition('title', '%' . addcslashes($this->search, '\%_') . '%', 'LIKE');
}
return $query;
};
$articleEntities = $nodeStorage->loadMultiple(
$getBaseQuery()
->range(floor($this->page * self::ITEMS_PER_PAGE), self::ITEMS_PER_PAGE)
->execute()
);
$totalPages = ceil($getBaseQuery()->count()->execute() / self::ITEMS_PER_PAGE);
return View::fromTpl('articles', [
'items' => array_map(function ($article) {
return $article->label();
}, $articleEntities),
'paginator' => $this->getPaginationData($totalPages),
]);
}
private function getPaginationData($totalPages): ?array {
$items = [];
if ($this->page > 0) {
$items['previous']['hasItems'] = TRUE;
}
if ($this->page < ($totalPages - 1)) {
$items['next']['hasItems'] = TRUE;
}
return ['items' => $items];
}
}
<div>
<input
wire:model="search"
type="search"
placeholder="Search article by title..."
>
<h1>Articles</h1>
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
</ul>
{% if paginator.items %}
<nav role="navigation" aria-labelledby="pager-articles">
{% if paginator.items.previous.hasItems %}
<button
wire:click="previousPage()"
wire:loading.attr="disabled"
type="button"
title="{{ 'Go to previous page'|t }}"
>
{{ 'Prev'|t }}
</button>
{% endif %}
{% if paginator.items.next.hasItems %}
<button
wire:click="nextPage()"
wire:loading.attr="disabled"
type="button"
title="{{ 'Go to next page'|t }}"
>
{{ 'Next'|t }}
</button>
{% endif %}
</nav>
{% endif %}
</div>
* Note that the $pagerId
property must be defined in order to identify the pager in case there are multiple ones on the same page.
* The previousPage()
and nextPage()
methods are provided by WithPagination
trait which calls setPage()
method. You should call this method if you're implementing a Full pagination to go to a specific page.
You might need to reset pagination to it's first page when performing any filtering of data as it would return a different set of results.
E.g: If a user is on the page "2" and types into a search field to narrow the results you might want to reset the page as first one to make sense of any pagination if any.
The WithPagination
trait exposes a resetPage()
method to accomplish this.
This method can be used in combination with the updating/updated
lifecycle hooks to reset the page when certain component data is updated.
public function updatingSearch(): void {
$this->resetPage();
}
If you implemented the search when a specific action, add $this->resetPage();
in the action method accordingly.