For coders TYPO3 Tech Corner

Pagebrowser ViewHelper in TYPO3 11

Pagebrowser ViewHelper in TYPO3 11

We think the decision to remove TYPO3 widgets (i.e. a ViewHelper with its own controller) is great. Having your own controller for a ViewHelper has always felt a bit strange. Still, we like the idea of a PageBrowser-ViewHelper in most cases. This keeps the controller slim (concept of a slim controller in Extbase).

In the following we will show you how you can build your own Paginate-ViewHelper for arrays or query results (result from an Extbase repository) in TYPO3 11 (and already in 10).

In the example, the new ViewHelper is located under Classes/ViewHelpers/Pagination/PaginateViewHelper.php:

<?php declare(strict_types = 1); namespace In2code\Lux\ViewHelpers\Pagination; use Closure; use In2code\Lux\Exception\NotPaginatableException; use TYPO3\CMS\Core\Pagination\ArrayPaginator; use TYPO3\CMS\Core\Pagination\PaginationInterface; use TYPO3\CMS\Core\Pagination\PaginatorInterface; use TYPO3\CMS\Core\Pagination\SimplePagination; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Pagination\QueryResultPaginator; use TYPO3\CMS\Extbase\Persistence\QueryResultInterface; use TYPO3\CMS\Extbase\Service\ExtensionService; use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; /** * PaginateViewHelper */ class PaginateViewHelper extends AbstractViewHelper { /** * @var bool */ protected $escapeOutput = false; /** * @return void */ public function initializeArguments() { parent::initializeArguments(); $this->registerArgument('objects', 'mixed', 'array or queryresult', true); $this->registerArgument('as', 'string', 'new variable name', true); $this->registerArgument('itemsPerPage', 'int', 'items per page', false, 10); $this->registerArgument('name', 'string', 'unique identification - will take "as" as fallback', false, ''); } /** * @param array $arguments * @param Closure $renderChildrenClosure * @param RenderingContextInterface $renderingContext * @return string * @throws NotPaginatableException */ public static function renderStatic( array $arguments, Closure $renderChildrenClosure, RenderingContextInterface $renderingContext ) { if ($arguments['objects'] === null) { return $renderChildrenClosure(); } $templateVariableContainer = $renderingContext->getVariableProvider(); $templateVariableContainer->add($arguments['as'], [ 'pagination' => self::getPagination($arguments, $renderingContext), 'paginator' => self::getPaginator($arguments, $renderingContext), 'name' => self::getName($arguments) ]); $output = $renderChildrenClosure(); $templateVariableContainer->remove($arguments['as']); return $output; } /** * @param array $arguments * @param RenderingContextInterface $renderingContext * @return PaginationInterface * @throws NotPaginatableException */ protected static function getPagination( array $arguments, RenderingContextInterface $renderingContext ): PaginationInterface { $paginator = self::getPaginator($arguments, $renderingContext); return GeneralUtility::makeInstance(SimplePagination::class, $paginator); } /** * @param array $arguments * @param RenderingContextInterface $renderingContext * @return PaginatorInterface * @throws NotPaginatableException */ protected static function getPaginator( array $arguments, RenderingContextInterface $renderingContext ): PaginatorInterface { if (is_array($arguments['objects'])) { $paginatorClass = ArrayPaginator::class; } elseif (is_a($arguments['objects'], QueryResultInterface::class)) { $paginatorClass = QueryResultPaginator::class; } else { throw new NotPaginatableException('Given object is not supported for pagination', 1634132847); } return GeneralUtility::makeInstance( $paginatorClass, $arguments['objects'], self::getPageNumber($arguments, $renderingContext), $arguments['itemsPerPage'] ); } /** * @param array $arguments * @param RenderingContextInterface $renderingContext * @return int */ protected static function getPageNumber(array $arguments, RenderingContextInterface $renderingContext): int { $extensionName = $renderingContext->getControllerContext()->getRequest()->getControllerExtensionName(); $pluginName = $renderingContext->getControllerContext()->getRequest()->getPluginName(); $extensionService = GeneralUtility::makeInstance(ExtensionService::class); $pluginNamespace = $extensionService->getPluginNamespace($extensionName, $pluginName); $variables = GeneralUtility::_GP($pluginNamespace); if ($variables !== null) { if (!empty($variables[self::getName($arguments)]['currentPage'])) { return (int)$variables[self::getName($arguments)]['currentPage']; } } return 1; } /** * @param array $arguments * @return string */ protected static function getName(array $arguments): string { return $arguments['name'] ?: $arguments['as']; } }

To build the links in the fluid, we use a second ViewHelper Classes / ViewHelpers / Pagination / UriViewHelper.php:

<?php declare(strict_types = 1); namespace In2code\Lux\ViewHelpers\Pagination; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Service\ExtensionService; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper; /** * UriViewHelper */ class UriViewHelper extends AbstractTagBasedViewHelper { /** * Initialize arguments */ public function initializeArguments() { parent::initializeArguments(); $this->registerArgument('name', 'string', 'identifier important if more widgets on same page', false, 'widget'); $this->registerArgument('arguments', 'array', 'Arguments', false, []); } /** * Build an uri to current action with &tx_ext_plugin[currentPage]=2 * * @return string The rendered uri */ public function render(): string { $uriBuilder = $this->renderingContext->getControllerContext()->getUriBuilder(); $extensionName = $this->renderingContext->getControllerContext()->getRequest()->getControllerExtensionName(); $pluginName = $this->renderingContext->getControllerContext()->getRequest()->getPluginName(); $extensionService = GeneralUtility::makeInstance(ExtensionService::class); $pluginNamespace = $extensionService->getPluginNamespace($extensionName, $pluginName); $argumentPrefix = $pluginNamespace . '[' . $this->arguments['name'] . ']'; $arguments = $this->hasArgument('arguments') ? $this->arguments['arguments'] : []; if ($this->hasArgument('action')) { $arguments['action'] = $this->arguments['action']; } if ($this->hasArgument('format') && $this->arguments['format'] !== '') { $arguments['format'] = $this->arguments['format']; } $uriBuilder->reset() ->setArguments([$argumentPrefix => $arguments]) ->setAddQueryString(true) ->setArgumentsToBeExcludedFromQueryString([$argumentPrefix, 'cHash']); $addQueryStringMethod = $this->arguments['addQueryStringMethod'] ?? null; if (is_string($addQueryStringMethod)) { $uriBuilder->setAddQueryStringMethod($addQueryStringMethod); } return $uriBuilder->build(); } }

So you can use it almost as usual in the fluid:

<lux:pagination.paginate objects="{downloads}" as="downloadsPaginator" itemsPerPage="15"> <f:for each="{downloadsPaginator.paginator.paginatedItems}" as="download"> <p>{download.title}</p> </f:for> <f:alias map="{pagination:downloadsPaginator.pagination, paginator:downloadsPaginator.paginator, name:downloadsPaginator.name}"> <f:render partial="Miscellaneous/Pagination" arguments="{_all}" /> </f:alias> </lux:pagination.paginate>

The partial with the actual page browser could then look like this or something similar:

<f:if condition="{paginator.numberOfPages} > 1"> <nav aria-label="pagebrowser"> <ul class="f3-widget-paginator pagination"> <f:if condition="{pagination.previousPageNumber} && {pagination.previousPageNumber} >= {pagination.firstPageNumber}"> <li class="previous"> <a href="{lux:pagination.uri(arguments:'{currentPage:pagination.previousPageNumber}',name:name)}" title="previous" class="page-link"> &lt; </a> </li> </f:if> <f:if condition="{pagination.hasLessPages}"> <li class="page-item"></li> </f:if> <f:for each="{pagination.allPageNumbers}" as="page"> <f:if condition="{page} == {paginator.currentPageNumber}"> <f:then> <li class="page-item current active"> <span class="page-link">{page}</span> </li> </f:then> <f:else> <li class="page-item"> <a href="{lux:pagination.uri(arguments:'{currentPage:page}',name:name)}" class="page-link">{page}</a> </li> </f:else> </f:if> </f:for> <f:if condition="{pagination.hasMorePages}"> <li class="page-item"></li> </f:if> <f:if condition="{pagination.nextPageNumber} && {pagination.nextPageNumber} <= {pagination.lastPageNumber}"> <li class="next"> <a href="{lux:pagination.uri(arguments:'{currentPage:pagination.nextPageNumber}',name:name)}" title="next" class="page-link"> &gt; </a> </li> </f:if> </ul> </nav> </f:if>

Tip: All information about the new paginator and pagination classes in TYPO3 can be found in the documentation

Back

"Code faster, look at the time" - does this sound familiar to you?

How about time and respect for code quality? Working in a team? Automated tests?

Join us