Pagination

Paginations are good to become master of many data. A simple AFX based pagination can be found here. Be sure you have at least Neos 4.3 installed for this type of pagination.


Example (unstyled)


Installation

For every file you have to change the Vendor.Packagename informations (don't change Arsors.Prototypes)

You need Paginated.fusion
You need PaginationArrayImplementation.php
You need Routes.yaml
Configure Settings.yaml


Attribute Explanation

<Arsors.Neos:Paginated
    collection              = (FlowQuery-Object) | Required | Holds the Nodes wich should get a pagination
    itemsPerPage            = (string) | Default: 24 | How many items per page do you want?
    paginationMaxAmount     = (string) | Default: 15 | What is the maximum amount of numbers you want to see in the pagination?
    paginationClass         = (string) | Optional | Additional Class for the pagination
    paginationId            = (string) | Optional | Additional ID for the pagination
    paginationTop           = (boolean) | Default: false | Show/Hide the top pagination
    paginationBottom        = (boolean) | Default: true | Show/Hide the bottom pagination
    enableFirst             = (boolean) | Default: false | Show/Hide go to first page
    enableLast              = (boolean) | Default: false | Show/Hide go to last page
    enableArrows            = (boolean) | Default: true | Show/Hide next and prev arrows
>


Look what's behind it:

Just copy this file and adjust this line to your vendor and package name: Arsors\\Neos\\Fusion\\PaginationArrayImplementation

/*

AFX-PAGINATION
Written by Marvin Schieler https://neos.arsors.de (c) 2019
Inpsired by https://github.com/Flowpack/Flowpack.Listable

*/


prototype(Arsors.Prototypes:Paginated) < prototype(Neos.Fusion:Component) {
# Pagination variables
collection = 'must-be-set'
currentPage = ${request.arguments.currentPage || 1}
itemsTotalAmount = ${this.collection.count()}
itemsPerPage = 24
paginationMaxAmount = 15
needPagination = ${ itemsTotalAmount > itemsPerPage ? true : false }

# Optional pagination variables
paginationClass = null
paginationId = null
paginationTop = false
paginationBottom = true
enableFirst = false
enableLast = false
enableArrows = true

# Set paginatedItems as Array
paginatedItems = ${ this.collection.slice( (this.currentPage - 1) * this.itemsPerPage, this.currentPage * this.itemsPerPage ) }
@context.paginatedItems = ${this.paginatedItems}

# Set pagination as AFX-Template
@context {
itemsTotalAmount = ${this.itemsTotalAmount}
itemsPerPage = ${this.itemsPerPage}
paginationMaxAmount = ${this.paginationMaxAmount}
paginationClass = ${this.paginationClass}
paginationId = ${this.paginationId}
enableFirst = ${this.enableFirst}
enableLast = ${this.enableLast}
enableArrows = ${this.enableArrows}
}
pagination = Arsors.Prototypes:Pagination {
currentPage = ${request.arguments.currentPage || 1}
itemsTotalAmount = ${itemsTotalAmount}
itemsPerPage = ${itemsPerPage}
paginationMaxAmount = ${paginationMaxAmount}
enableFirst = ${enableFirst}
enableLast = ${enableLast}
enableArrows = ${enableArrows}
}

# Output
renderer = afx`
<ul @if.show={props.paginationTop && props.needPagination} class={['pagination', 'paginationTop', props.paginationClass]} id={props.paginationId}>{props.pagination}</ul>
{props.content}
<ul @if.show={props.paginationBottom && props.needPagination} class={['pagination', 'paginationBottom', props.paginationClass]} id={props.paginationId}>{props.pagination}</ul>
`

@cache {
mode = 'cached'
entryIdentifier {
node = ${node}
}
entryDiscriminator = ${request.arguments.currentPage}
context {
1 = 'node'
2 = 'documentNode'
3 = 'site'
}
}
}

prototype(Arsors.Prototypes:PaginationArray) {
@class = 'Arsors\\Neos\\Fusion\\PaginationArrayImplementation'
itemsTotalAmount = ''
currentPage = ''
itemsPerPage = ''
paginationMaxAmount = ''
enableFirst = false
enableLast = false
enableArrows = true
}

prototype(Arsors.Prototypes:Pagination) < prototype(Neos.Fusion:Component) {
@context {
currentPage = ${this.currentPage}
itemsTotalAmount = ${this.itemsTotalAmount}
itemsPerPage = ${this.itemsPerPage}
paginationMaxAmount = ${this.paginationMaxAmount}
enableFirst = ${this.enableFirst}
enableLast = ${this.enableLast}
enableArrows = ${this.enableArrows}
}

# pagination numbers
paginationArray = Arsors.Prototypes:PaginationArray {
itemsTotalAmount = ${itemsTotalAmount}
currentPage = ${currentPage}
itemsPerPage = ${itemsPerPage}
paginationMaxAmount = ${paginationMaxAmount}
enableFirst = ${enableFirst}
enableLast = ${enableLast}
enableArrows = ${enableArrows}
}

renderer = afx`
<li @if.showFirst={props.paginationArray.first}>
<Neos.Neos:NodeLink node={documentNode} additionalParams={{'currentPage':props.paginationArray.first}} content="first" />
</li>

<li @if.showPrevious={props.paginationArray.previous}>
<Neos.Neos:NodeLink node={documentNode} additionalParams={{'currentPage':props.paginationArray.previous}} content="prev" />
</li>

<Neos.Fusion:Loop items={props.paginationArray.pagination} item="item">
<li class={item == props.paginationArray.raw.currentPage ? 'current' : 'normal'}>
<Neos.Neos:NodeLink @if.link={item!='...'} node={documentNode} additionalParams={{'currentPage':item}} content={item} />
<span @if.separator={item=='...'}>{item}</span>
</li>
</Neos.Fusion:Loop>

<li @if.showFirst={props.paginationArray.next}>
<Neos.Neos:NodeLink node={documentNode} additionalParams={{'currentPage':props.paginationArray.next}} content="next" />
</li>

<li @if.showLast={props.paginationArray.last}>
<Neos.Neos:NodeLink node={documentNode} additionalParams={{'currentPage':props.paginationArray.last}} content="last" />
</li>
`
}

prototype(Neos.Fusion:GlobalCacheIdentifiers) {
pagination = ${request.arguments.currentPage}
}
root.@cache.entryIdentifier.pagination = ${request.arguments.currentPage}
prototype(Neos.Neos:Page) {
@cache.entryIdentifier.pagination = ${request.arguments.currentPage}
}
prototype(Neos.Neos:PrimaryContent).default {
renderer.@cache.entryIdentifier.pagination = ${request.arguments.currentPage}
}

Just adjust the namespace to your vendor and package name.

<?php
namespace Arsors\Neos\Fusion;

use Neos\Flow\Annotations as Flow;
use Neos\Fusion\FusionObjects\AbstractFusionObject;

class PaginationArrayImplementation extends AbstractFusionObject {
/**
* @return array
*/
public function evaluate() {
/*
* This snippet comes from https://github.com/Flowpack/Flowpack.Listable
* Extended by Marvin Schieler https://neos.arsors.de
* */
$maximumNumberOfLinks = intval($this->fusionValue('paginationMaxAmount')) - 2;
$paginationMaxAmount = intval($this->fusionValue('paginationMaxAmount'));
$itemsPerPage = intval($this->fusionValue('itemsPerPage'));
$totalCount = intval($this->fusionValue('itemsTotalAmount'));
$currentPage = intval($this->fusionValue('currentPage'));
if ($totalCount > 0 !== true) {
return [];
}
$numberOfPages = ceil($totalCount / $itemsPerPage);
if ($maximumNumberOfLinks > $numberOfPages) {
$maximumNumberOfLinks = $numberOfPages;
}
$delta = floor($maximumNumberOfLinks / 2);
$displayRangeStart = $currentPage - $delta;
$displayRangeEnd = $currentPage + $delta + ($maximumNumberOfLinks % 2 === 0 ? 1 : 0);
if ($displayRangeStart < 1) {
$displayRangeEnd -= $displayRangeStart - 1;
}
if ($displayRangeEnd > $numberOfPages) {
$displayRangeStart -= ($displayRangeEnd - $numberOfPages);
}
$displayRangeStart = (integer)max($displayRangeStart, 1);
$displayRangeEnd = (integer)min($displayRangeEnd, $numberOfPages);
$links = \range($displayRangeStart, $displayRangeEnd);
if ($displayRangeStart >= 2) {
if ($displayRangeStart > 2) array_unshift($links, "...");
if ($displayRangeStart == 2) {
array_pop($links);
}
array_unshift($links, 1);
}
if ($displayRangeEnd + 1 <= $numberOfPages) {
if($displayRangeEnd + 1 < $numberOfPages) $links[] = "...";
if($displayRangeEnd + 1 == $numberOfPages) unset($links[2]);
$links[] = $numberOfPages;
}


$enableFirst = $this->fusionValue('enableFirst');
$enableLast = $this->fusionValue('enableLast');
$enableArrows = $this->fusionValue('enableArrows');

$showFirst = false;
$showLast = false;
$showArrows = [false,false];

if ($enableFirst && $currentPage != 1) $showFirst = 1;
if ($enableLast && $currentPage != $numberOfPages) $showLast = $numberOfPages;
if ($enableArrows) {
if ($currentPage != 1) $showArrows[0] = $currentPage-1;
if ($currentPage != $numberOfPages) $showArrows[1] = $currentPage+1;
}

return [
'raw'=> [
'itemsTotalAmount' => $totalCount,
'currentPage' => $currentPage,
'itemsPerPage' => $itemsPerPage,
'paginationMaxAmount' => $paginationMaxAmount,
],
'first' => $showFirst,
'previous'=>$showArrows[0],
'pagination'=>$links,
'next'=>$showArrows[1],
'last'=>$showLast
];
}
}

Make sure you got the right routing rules for your Settings.yaml

Neos:
  Flow:
    mvc:
      routes:
        'Arsors.Neos':
          position: 'before Neos.Neos'

Set this route rule, so neos can serve you a link like: xyz.com/en/page/2

-
  name: 'pagination page'
  uriPattern: '{node}/page/{currentPage}'
  defaults:
    '@package': 'Neos.Neos'
    '@controller': 'Frontend\Node'
    '@format': 'html'
    '@action': 'show'
  routeParts:
    node:
      handler: Neos\Neos\Routing\FrontendNodeRoutePartHandlerInterface
  appendExceedingArguments: TRUE

If you insert all the scripts above to your project you got all what you need.
So last but not least you can call the pagination where ever you want like below.
Note: In paginatedItems you get all the nodes for the current page. You can't change this variable name unless you change the source file.

prototype(Arsors.Neos:Pagination) < prototype(Neos.Neos:ContentComponent) {

collection = ${q(site).find('[instanceof Arsors.Neos:Page]')}

renderer = afx`
<Arsors.Prototypes:Paginated collection={props.collection} itemsPerPage="4">
<ul>
<Neos.Fusion:Loop items={paginatedItems} item="item">
<li><Neos.Neos:NodeLink node={item} /></li>
</Neos.Fusion:Loop>
</ul>
</Arsors.Prototypes:Paginated>
`

}
Last cache from 2021-05-14 at 01:21:01 (Example Eel Helper)