KnpLabs / KnpPaginatorBundle

SEO friendly Symfony paginator to sort and paginate
http://knplabs.com/en/blog/knp-paginator-reborn
MIT License
1.76k stars 344 forks source link

Query parameters are lost during filtering #739

Closed sirielua closed 1 year ago

sirielua commented 2 years ago

Bug Report

Q A
BC Break no
Bundle version 5.8.0
Symfony version 6.1.2
PHP version 8.1.8

Summary

Query parameters are lost during filtering

Current behavior

I have a route with pagination like this "/blog?author=208". When I try to filter pagination results, my rendered filter form looks like this (empty hidden input is filter field input, with filterFieldName param set to empty string):

<form method="get" action="/blog?author=208" enctype="application/x-www-form-urlencoded">
        <input type="hidden" name="" value="">
        <input type="search" value="keyword" name="search" placeholder="Searchword...">
        <button>Filter</button>
</form>

After submitting the form, the resulting URI is "/blog?search=keyword" and not "/blog?author=208&search=keyword"

How to reproduce

Render pagination filtering form on the page with additional query parameters in URL

Expected behavior

The filtering form should include hidden fields for query parameters:

<form method="get" action="/blog?author=208&page=1" enctype="application/x-www-form-urlencoded">
        <input type="hidden" name="" value="">
        <input type="hidden" name="author" value="208">
        <input type="hidden" name="page" value="1">
        <input type="search" value="" name="search" placeholder="Searchword...">
        <button>Search</button>
</form>

Possible solution

  1. Pass params variable to the filtering template (Knp\Bundle\PaginatorBundle\Helper\Processor::filter line 202):
/**
     * Create a filter url for the field named $title
     * and identified by $key which consists of
     * alias and field. $options holds all link
     * parameters like "alt, class" and so on.
     *
     * $key example: "article.title"
     *
     * @param SlidingPaginationInterface<mixed> $pagination
     * @param array<string, mixed>              $fields
     * @param array<string, mixed>              $options
     * @param array<string, mixed>              $params
     *
     * @return array<string, mixed>
     */
    public function filter(SlidingPaginationInterface $pagination, array $fields, array $options = [], array $params = []): array
    {
        $options = \array_merge([
            'absolute' => UrlGeneratorInterface::ABSOLUTE_PATH,
            'translationParameters' => [],
            'translationDomain' => null,
            'button' => 'Filter',
        ], $options);

        $params = \array_merge($pagination->getParams(), $params);
        $params[$pagination->getPaginatorOption('pageParameterName')] = 1; // reset to 1 on filter

        $filterFieldName = $pagination->getPaginatorOption('filterFieldParameterName');
        $filterValueName = $pagination->getPaginatorOption('filterValueParameterName');

        $selectedField = $params[$filterFieldName] ?? null;
        $selectedValue = $params[$filterValueName] ?? null;

        $action = $this->router->generate($pagination->getRoute(), $params, $options['absolute']);

        foreach ($fields as $field => $title) {
            $fields[$field] = $this->translator->trans($title, $options['translationParameters'], $options['translationDomain']);
        }
        $options['button'] = $this->translator->trans($options['button'], $options['translationParameters'], $options['translationDomain']);

        unset($options['absolute'], $options['translationDomain'], $options['translationParameters']);

        return \array_merge(
            $pagination->getPaginatorOptions() ?? [],
            $pagination->getCustomParameters() ?? [],
            \compact('fields', 'action', 'filterFieldName', 'filterValueName', 'selectedField', 'selectedValue', 'options', 'params') // Added params to the template variables list
        );
    }
  1. Render hidden form input for every query param (filtration.html.twig). This will also preserve sorting applied by user:
{#
/**
 * @file
 * Bootstrap v5 Filter control implementation.
 *
 * View that can be used with the filter module
 */
#}
<form method="get" action="{{ action }}" enctype="application/x-www-form-urlencoded">

    {% if fields|length > 1 %}
        <select name="{{ filterFieldName }}">
            {% for field, label in fields %}
                <option value="{{ field }}"{% if selectedField == field %} selected="selected"{% endif %}>{{ label }}</option>
            {% endfor %}
        </select>
    {% else %}
        <input type="hidden" name="{{ filterFieldName }}" value="{{ fields|keys|first }}" />
    {% endif %}
    {% if params|length > 0 %}
        {% for param, value in params %}
            <input type="hidden" name="{{ param }}" value="{{ value }}"/>
        {% endfor %}
    {% endif %}
    <input type="text" value="{{ selectedValue }}" name="{{ filterValueName }}" />

    <button>{{ options.button }}</button>

</form>
garak commented 1 year ago

Sorry for the late reply. So, if you found a possible solution, can we close this issue?

sirielua commented 1 year ago

I have opened a new pull request with the proposed fix. https://github.com/KnpLabs/KnpPaginatorBundle/pull/751