Pagination

The TYPO3 Core provides an interface to implement the native pagination of lists like arrays or query results of Extbase.

The foundation of that new interface \TYPO3\CMS\Core\Pagination\PaginatorInterface is that it's type agnostic. It means, that it doesn't define the type of paginatable objects. It's up to the concrete implementations to enable pagination for specific types. The interface only forces you to reduce the incoming list of items to an iterable sub set of items.

Along with that interface, an abstract paginator class \TYPO3\CMS\Core\Pagination\AbstractPaginator is available that implements the base pagination logic for any kind of Countable set of items while it leaves the processing of items to the concrete paginator class.

Two concrete paginators are available:

Code example for the ArrayPaginator in an Extbase controller:

EXT:my_extension/Controller/ExampleController.php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Controller;

use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Core\Pagination\ArrayPaginator;
use TYPO3\CMS\Core\Pagination\SimplePagination;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

final class ExampleController extends ActionController
{
    public function myAction(): ResponseInterface
    {
        // For better demonstration we create fixed items, in real
        // world usage a list of models is used instead.
        $itemsToBePaginated = ['apple', 'banana', 'strawberry', 'raspberry', 'pineapple'];
        $itemsPerPage = 2;
        $currentPageNumber = 3;

        $paginator = new ArrayPaginator($itemsToBePaginated, $currentPageNumber, $itemsPerPage);
        $paginator->getNumberOfPages(); // returns 3
        $paginator->getCurrentPageNumber(); // returns 3, basically just returns the input value
        $paginator->getKeyOfFirstPaginatedItem(); // returns 4
        $paginator->getKeyOfLastPaginatedItem(); // returns 4

        $pagination = new SimplePagination($paginator);
        $pagination->getAllPageNumbers(); // returns [1, 2, 3]
        $pagination->getPreviousPageNumber(); // returns 2
        $pagination->getNextPageNumber(); // returns null

        // ... more logic ...

        $this->view->assignMultiple(
            [
                'pagination' => $pagination,
                'paginator' => $paginator,
            ],
        );

        return $this->htmlResponse();
    }
}
Copied!

And the corresponding Fluid template:

EXT:my_extension/Resources/Private/Templates/ExamplePagination.html
<ul class="pagination">
    <f:for each="{pagination.allPageNumbers}" as="page">
        <li class="page-item">
            <f:link.action
                arguments="{currentPageNumber:page}"
                class="page-link {f:if(condition:'{currentPageNumber}=={page}',then:'active')}"
            >
                {page}
            </f:link.action>
        </li>
    </f:for>
</ul>

<f:for each="{paginator.paginatedItems}" as="item">
    {item}
</f:for>
Copied!

Sliding window pagination

New in version 12.0

The sliding window pagination can be used to paginate array items or query results from Extbase. The main advantage is that it reduces the amount of pages shown.

Example: Imagine 1000 records and 20 items per page which would lead to 50 links. Using the SlidingWindowPagination, you will get something like this < prev ... 21 22 23 24 ... next > or < 1 ... 21 22 23 24 ... 50 > or simple < 21 22 23 24 >. Customise the template to suit your needs.

Usage

Replace the usage of SimplePagination with \TYPO3\CMS\Core\Pagination\SlidingWindowPagination and you are done. Set the 2nd argument to the maximum number of links which should be rendered.

EXT:my_extension/Controller/ExampleController.php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Controller;

use MyVendor\MyExtension\Domain\Repository\ExampleRepository;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Core\Pagination\SlidingWindowPagination;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use TYPO3\CMS\Extbase\Pagination\QueryResultPaginator;

final class ExampleController extends ActionController
{
    public function __construct(
        private readonly ExampleRepository $exampleRepository,
    ) {}

    public function myAction(): ResponseInterface
    {
        $allItems = $this->exampleRepository->findAll();

        $currentPage = $this->request->hasArgument('currentPageNumber')
            ? (int)$this->request->getArgument('currentPageNumber')
            : 1;
        $itemsPerPage = 10;
        $maximumLinks = 15;

        $paginator = new QueryResultPaginator(
            $allItems,
            $currentPage,
            $itemsPerPage,
        );
        $pagination = new SlidingWindowPagination(
            $paginator,
            $maximumLinks,
        );

        // ... more logic ...

        $this->view->assign(
            'pagination',
            [
                'pagination' => $pagination,
                'paginator' => $paginator,
            ],
        );

        return $this->htmlResponse();
    }
}
Copied!