Feature #81814

Allow additional arguments for Fluid's widget.paginate view helper

Added by Tobias Schmidt about 2 years ago. Updated over 1 year ago.

Status:
New
Priority:
Should have
Assignee:
-
Category:
Fluid
Target version:
-
Start date:
2017-07-06
Due date:
% Done:

0%

PHP Version:
Tags:
Fluid, ViewHelper, Pagination, Page Browser
Complexity:
Sprint Focus:

Description

The current version of Fluid's widget.paginate ViewHelper does not allow adding additional arguments to page links. This prevents the use of the widget in combination with a search filter for example. Filter values send with method GET or POST are not added to the query string of page links and are lost when browsing result pages.

One could set addQueryStringMethod: GET,POST in paginate's configuration array to add all field values of the current request to the query string. But this includes the hidden __referrer fields of Extbase for example which should not be part of the query string of page links.

There should be a way to tell the paginate widget precisely which arguments to add to the query string of page links. This requires changes not only in the paginate widget but also in the link widget which is used by the paginate widget to create page links.

Here's my suggestion:

Usage of paginate widget

<f:widget.paginate objects="{myObjects}" as="myPaginatedObjects" additionalArgumentsPrefix="tx_myext_pi1[filter]" additionalArguments="{searchTerm: filter.searchTerm, category: filter.category}">

Changes in Classes/ViewHelpers/Widget/PaginateViewHelper.php

Register arguments in method initializeArguments:

$this->registerArgument('AdditionalArgumentsPrefix', 'string', 'Prefix of additional arguments', false);
$this->registerArgument('AdditionalArguments', 'array', 'Additional arguments', false);

Change in Classes/ViewHelpers/Widget/Controller/PaginateController.php

Add class variables:

/**
 * @var string
 */
protected $additionalArgumentsPrefix;

/**
 * @var array
 */
protected $additionalArguments;

Add default values to $configuration:

/**
 * @var array
 */
protected $configuration = [
    'itemsPerPage' => 10,
    'insertAbove' => false,
    'insertBelow' => true,
    'maximumNumberOfLinks' => 99,
    'addQueryStringMethod' => '',
    'section' => '',
    'additionalArgumentsPrefix' => '',
    'additionalArguments' => '',
];

Initialize arguments in method initializeAction:

$this->additionalArgumentsPrefix = $this->widgetConfiguration['additionalArgumentsPrefix'];
$this->additionalArguments = $this->widgetConfiguration['additionalArguments'];

Assign arguments to view in method indexAction:

$this->view->assign('additionalArgumentsPrefix', $this->additionalArgumentsPrefix);
$this->view->assign('additionalArguments', $this->additionalArguments);

Changes in Resources/Private/Templates/ViewHelpers/Widget/Paginate/Index.html

Add arguments to render ViewHelpers of insertAbove and insertBelow:

<f:render section="paginator" arguments="{pagination: pagination, additionalArgumentsPrefix: additionalArgumentsPrefix, additionalArguments: additionalArguments, configuration:configuration}" />

Add arguments to all usages of the widget link ViewHelper:

<f:widget.link arguments="{currentPage: page.number}" addQueryStringMethod="{configuration.addQueryStringMethod}" additionalArgumentsPrefix="{additionalArgumentsPrefix}" additionalArguments="{additionalArguments}"  section="{configuration.section}">{page.number}</f:widget.link>

Changes to Classes/ViewHelpers/Widget/LinkViewHelper.php

Add arguments to method render:

/**
 * Render the link.
 *
 * @param string $action Target action
 * @param array $arguments Arguments
 * @param string $section The anchor to be added to the URI
 * @param string $format The requested format, e.g. ".html
 * @param bool $ajax TRUE if the URI should be to an AJAX widget, FALSE otherwise.
 * @param string $additionalArgumentsPrefix
 * @param array $additionalArguments
 * @return string The rendered link
 * @api
 */
public function render($action = null, $arguments = [], $section = '', $format = '', $ajax = false, $additionalArgumentsPrefix = '', $additionalArguments = [])
{

Add arguments in method getAjaxUri:

$additionalArgumentsPrefix = $this->arguments['additionalArgumentsPrefix'];
$additionalArguments = $this->arguments['additionalArguments'];
$arguments[$additionalArgumentsPrefix] = $additionalArguments;

Add arguments in method getWidgetUri:

$additionalArgumentsPrefix = $this->hasArgument('additionalArgumentsPrefix') ? $this->arguments['additionalArgumentsPrefix'] : '';
$additionalArguments = $this->hasArgument('additionalArguments') ? $this->arguments['additionalArguments'] : [];

Add arguments to UriBuilder call in method getWidgetUri:

return $uriBuilder->reset()
    ->setArguments([$argumentPrefix => $arguments, $additionalArgumentsPrefix => $additionalArguments])
    ->setSection($this->arguments['section'])
    ->setAddQueryString(true)
    ->setAddQueryStringMethod($this->arguments['addQueryStringMethod'])
    ->setArgumentsToBeExcludedFromQueryString([$argumentPrefix, 'cHash'])
    ->setFormat($this->arguments['format'])
    ->build();

That's it. I see at least two problems with this solution. First there could be a conflict between addQueryStringMethod and additional arguments. Second there ist only one prefix possible. So this is not supposed to be a perfect but a somehow working solution. What do you think of it?

History

#1 Updated by Claus Due about 2 years ago

I don't agree that this is necessary or desirable. I'll state my reasons:

  • The ViewHelper side of the Widget is designed as a "overall API" where you pass a limited set of instructions and then, should you need additional changes to the output, replace the template file.
  • The Controller side of the Widget is already scoped to a certain plugin and by design simply generates the current link + whichever arguments change. This is tied to UriBuilder via https://github.com/TYPO3/TYPO3.CMS/blob/master/typo3/sysext/fluid/Classes/ViewHelpers/Widget/UriViewHelper.php#L105 and as you can see, resolving of the argument prefix already takes place. If your use case does not apply this adding of query string with the proper prefix, you are probably looking at an implementation problem rather than a missing feature.
  • By extending this Widget's API but not others, it introduces a logical requirement to also change other Widgets in the same way, therefore a generic solution would be preferable. Such a solution could be the ability to pass arbitrary template variables to any Widget and then require that you manipulate the template to add these variables to links etc. as you see fit.
  • Your suggested code itself directly includes the solution to this specific use case: build the appropriate arguments array before you pass it to the widget. If you do that, you may not even have to override the template. Such argument array building could much appropriately take place in the controller (or you could create a VH that does it, or VHS may include one or more VH that can get the job done - if you use VHS that is).
  • But in general: Widgets are fairly costly to use as they cannot be compiled, when they are in AJAX mode they must serialize all context (including variables you pass) and you can usually get the exact same result by adding one or more controller arguments (it can be an arbitrary array if you like) then applying sorting/filtering in the controller and paginate that result directly. This frees you completely to decide exactly how you want to paginate and is particularly useful in your exact use case (and when you don't need AJAX mode, which is a bit harder to implement this way).

Technically about the chosen solution:

  • Rather than specify a prefix like this, you should be assuming that such a prefix is: a) included in the array of additional arguments, or b) automatically determined from context by analysing plugin name and extension name.
  • Render method arguments are deprecated and a feature that adds more will be rejected; the only currently correct way to register arguments for new features is via `registerArgument` (hint: always attempt to patch dev-master of TYPO3 if you suggest new features - they will have to be applied there and won't be backported, so they must always use that as a starting point)

What could come out of this instead:

  • Possibly, if the benefits are determined to outweigh the drawbacks, allowing to pass arbitrary arrays of template variables to any Widget; with the implied risk that passing any non-serializable value causes a fatal PHP error (this risk btw also exists currently because the WidgetContext must be serialized and it too can contain improper values already as it is now).

Personal recommendation:

  • Don't use Widgets. They're bad for performance and the use cases they cover, with the exception of AJAX mode, are very easily reproduced with a controller and a tiny bit of PHP code - and this also allows you to do things like validate the inputs.

#2 Updated by Moritz Ahl over 1 year ago

I agree that one can argue if the whole pagionation widget approach is a good idea at all. However, as long as it's there, people will be using it. So we should either remove it, create a new approach or make it fit the needs.

Currently the f:widget.paginate / f:widget.uri viewhelper passes around arbitrary URL parameters, for example parameters from search or filter. That is a nice feature, however it creates a huge SEO problem. This is because when there is a URL somewhere on the net which accesses a page with a pagination on it, an in that URL are useless parameters (for example for tracking), then these parameters find their way into the pagination links. The google bot follows those links and therefore thousands of duplicate content pages are generated / appearing.

So I'm in favor to have a whitelist ("additional arguments") of parameters to pass around instead of passing around arbitrary parameters.

Also available in: Atom PDF