Search code examples
phptwigshopwareshopware6

Shopware 6: how to make a simple listing pagnation in storefront


I have a custom plugin that has an entity certificate and it lists all the certificates from that entity in the storefront.

The problem: I managed to create the view for listing, and the pagination, but when clicking on the pagination buttons, they don't work or go anywhere.

Maybe I'm doing something wrong here.

- CertificateController.php:

<?php declare(strict_types=1);

namespace Certificate\Storefront\Controller;

use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting;
use Shopware\Storefront\Controller\StorefrontController;
use Certificate\Service\ReadingData;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

/**
 * @Route(defaults={"_routeScope"={"storefront"}})
 */
class CertificateController extends StorefrontController
{
    private ReadingData $readingData;
    private EntityRepository $certificateReposiitory;

    public function __construct(ReadingData $readingData, EntityRepository $certificateReposiitory)
    {
        $this->readingData = $readingData;
        $this->certificateReposiitory = $certificateReposiitory;
    }

    /**
     * @Route("/certificate", name="frontend.certificate.certificate", methods={"GET"})
     */
    public function index(Request $request, Context $context): Response
    {
        $searchQuery = $request->query->get('search', '');
        $results = [];

        $criteria = new Criteria();
        $criteria->setOffset(1);
        $criteria->setLimit(25);
        $criteria->addSorting(new FieldSorting('name', FieldSorting::ASCENDING));

        $result = $this->certificateReposiitory->search($criteria, $context);
        $total = $this->certificateReposiitory->search(new Criteria(), $context)->getTotal();
        $pages = ceil($total / $criteria->getLimit());

        if (!empty($searchQuery)) {
            $results = $this->readingData->readData($searchQuery, $context);
        } else {
            $results = $result->getEntities();
        }

        return $this->renderStorefront('@Certificate/storefront/page/certificate.html.twig', [
            'searchQuery' => $searchQuery,
            'results' => $results,
            'pages' => $pages,
            'currentPage' => $request->query->get('page', 1),
            'limit' => $criteria->getLimit(),
            'criteria' => $criteria,
            'total' => $total
        ]);
    }
}

- src/Resources/views/storefront/page/certificate.html.twig:

{% sw_extends '@Storefront/storefront/base.html.twig' %}

{% block base_content %}
    <form action="{{ path('frontend.certificate.certificate') }}" method="GET">
        <div class="form-group md-4">
            <label for="search">Search:</label>
            <input type="text" class="form-control" id="search" name="search" placeholder="Enter your search query"
                   value="{{ searchQuery|default('') }}">
        </div>
        <button type="submit" class="btn btn-primary">Search</button>
    </form>
    
    {% if searchQuery is not empty or results %}
        {% if results %}
            <h2>Certificates</h2>
        {% else %}
            <h2>Search Results:</h2>
        {% endif %}
        {% if results|length > 0 %}
            <div class="row">
                {% for certificate in results %}
                    <div class="col-md-4 mb-4">
                        <div class="card">
                            <div class="card-body">
                                <h5 class="card-title">{{ certificate.name }}</h5>
                                {% if certificate.media is not null %}
                                <a href="{{ certificate.media.url }}" target="_blank" class="btn btn-secondary">Download</a>
                                {% else %}
                                    <a href="{{ certificate.fileName }}" target="_blank" class="btn btn-secondary">Download</a>
                                {% endif %}
                            </div>
                        </div>
                    </div>
                {% endfor %}
            </div>
                {% block page_actions_pagination %}
                    <div class="cms-element-product-listing-actions row justify-content-between">
                        <div class="col-md-auto">
                            {% sw_include '@Storefront/storefront/component/pagination.html.twig' with {
                                prev_link: currentPage > 1 ? path('frontend.certificate.certificate', {page: currentPage - 1, search: searchQuery}) : null,
                                next_link: currentPage < pages ? path('frontend.certificate.certificate', {page: currentPage + 1, search: searchQuery}) : null,
                                'currentPage': currentPage,
                                'total': total,
                                'limit': limit,
                                'pages': pages,
                                'criteria': criteria,
                                'searchRoute': 'frontend.certificate.index',
                                'searchQuery': searchQuery
                            } %}

                        </div>
                    </div>
                {% endblock %}
        {% else %}
            <p>No results found for "{{ searchQuery }}".</p>
        {% endif %}
    {% endif %}
{% endblock %}

Solution

  • The pagination needs to be included inside a form element. You can then use the FormAutoSubmit plugin to make it submit on changes by adding the data-form-auto-submit attribute.

    <form action="{{ path('frontend.certificate.certificate') }}"
          method="get"
          data-form-auto-submit="true">
        {% sw_include '@Storefront/storefront/component/pagination.html.twig' with {
            prev_link: currentPage > 1 ? path('frontend.certificate.certificate', {page: currentPage - 1, search: searchQuery}) : null,
            next_link: currentPage < pages ? path('frontend.certificate.certificate', {page: currentPage + 1, search: searchQuery}) : null,
            'currentPage': currentPage,
            'total': total,
            'limit': limit,
            'pages': pages,
            'criteria': criteria,
            'searchRoute': 'frontend.certificate.index',
            'searchQuery': searchQuery
        } %}
    </form>
    

    In your controller you have to use the p parameter to calculate offset:

    $currentPage = (int) $request->query->get('p', 1);
    $limit = 25;
    $criteria->setOffset(($currentPage - 1) * $limit);
    $criteria->setLimit($limit);
    

    You can also avoid a second search to find the total number of record by setting the total count mode of the criteria, which will cause the total to ignore limit and offset.

    $criteria->setTotalCountMode(Criteria::TOTAL_COUNT_MODE_EXACT);
    $result = $this->certificateReposiitory->search($criteria, $context);
    $total = $result->getTotal();