When on a view with an ajax pager, and after at least one ajax load, the links to the next pages will include a bunch of extra metadata related to ajax requests. Specifically ajax_page_state and _wrapper_format are included as query parameters. If you try to load this link, Drupal will return a bunch of JSON instead of rendering the page.

Steps to reproduce:

  1. Install latest Drupal 8.2.x
  2. Create 10 dummy articles
  3. Create a view of content with a page display at /test-ajax-pager
  4. Add a full pager, limit to 2 items shown per page
  5. Under advanced, set Use AJAX to Yes
  6. Go to /test-ajax-pager and click the link for page 2
  7. Copy the link for page 3, and open in a new browser window

Here's an example of what the link looks like /test-ajax-pager?ajax_page_state%5Btheme%5D=bartik&ajax_page_state%5Btheme_token%5D=&ajax_page_state%5Blibraries%5D=bartik%2Fglobal-styling%2Cclassy%2Fbase%2Cclassy%2Fmessages%2Ccontextual%2Fdrupal.contextual-links%2Ccontextual%2Fdrupal.contextual-toolbar%2Ccore%2Fdrupal.active-link%2Ccore%2Fhtml5shiv%2Ccore%2Fnormalize%2Cquickedit%2Fquickedit%2Cshortcut%2Fdrupal.shortcut%2Csystem%2Fbase%2Ctoolbar%2Ftoolbar%2Ctoolbar%2Ftoolbar.escapeAdmin%2Ctour%2Ftour%2Cuser%2Fdrupal.user.icons%2Cviews%2Fviews.ajax%2Cviews%2Fviews.module&_wrapper_format=drupal_ajax&page=2 and here's what it should look like /test-ajax-pager?page=2

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

rocketeerbkw created an issue. See original summary.

mondrake’s picture

mondrake’s picture

rocketeerbkw’s picture

I found a similar issue with the destination query. Based on the fix in that issue, I've come up with a fix for this one as well. I think all the code is there, just things happen out of order.

The current order is:

  1. Remove metadata from request and query
  2. Build response/paging from the query
  3. Copy all request data to query
  4. Special code to remove ajax_page_state and _wrapper_format for destination

This patch makes the order:

  1. Copy all request data to query
  2. Remove metadata from query, including ajax_page_state and _wrapper_format
  3. Build response/paging/destination from the query
dawehner’s picture

Do you understand why changing the order fixes the issue exactly? Maybe putting that into a comment in the file would be great for other people to understand WHY something was done in the particular way. We struggled with this exact implementation for a long time.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

LoMo’s picture

The patch would no longer apply, so re-rolled it... looks like the main changes since the patch was written were simple changes to the short-form array syntax.

Status: Needs review » Needs work

The last submitted patch, 7: views_pagers_include-2798947-7.patch, failed testing.

Kristen Pol’s picture

+++ b/core/modules/views/src/Controller/ViewAjaxController.php
@@ -130,9 +130,27 @@ public function ajaxView(Request $request) {
+      // Add all POST data, because AJAX is always a post and many things,

Nitpick: Can "such" move up a line and be in 80 chars?

Otherwise it looks and works great! Thanks!!!

Example:

Before patch:

?sort_bef_combine=delta%20ASC&sort_by=delta&sort_order=ASC&ajax_page_state[theme]=external&ajax_page_state[theme_token]=&ajax_page_state[libraries]=addtoany/addtoany%2Cadmin_toolbar/toolbar.tree%2Cadmin_toolbar_tools/toolbar.icon%2Cadminimal_admin_toolbar/adminimal-admin-toolbar%2Cbetter_exposed_filters/auto_submit%2Cclassy/base%2Ccommerce/toolbar%2Ccore/drupal.active-link%2Ccore/html5shiv%2Ccore/normalize%2Cds/ds_2col%2Centity_ref_tab_formatter/tab_formatter%2Cexternal/font-awesome%2Cexternal/global-styling%2Cexternal/protip-trigger%2Cexternal/typekit%2Cgo_utility/utility.filter_dropdown%2Clayout_discovery/onecol%2Csystem/base%2Ctoolbar/toolbar%2Ctoolbar/toolbar.escapeAdmin%2Cuser/drupal.user.icons%2Cviews/views.ajax%2Cviews/views.module%2Cwebform/webform.element.options&_wrapper_format=drupal_ajax&page=2

After patch:

?sort_bef_combine=delta%20ASC&sort_by=delta&sort_order=ASC&page=2

which is correct because those are the exposed filters. This matches the pager link when AJAX is disabled.

rwam’s picture

FileSize
309.52 KB

The patch #7 works for the url params indeed, but it has critical side effects: after each ajax call the view stylesheet was added and all javascripts (vendor, module, themes) were injected in the page too. And this again and again and again… (see screenshot)

I did a rollback.

error after paging

keithdoyle9’s picture

This also occurs in the admin views associated with Workbench. (subscribing)

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

dawehner’s picture

Here is an idea, I'm not sure how easy/feasible it actually is.

We have a problem of two one state of the request we want to have during rendering the view (without all the additional parameters),
and one afterwards, when we interact with the ajax state, the ajax parameters etc.

Given that I'm suggesting to create a new request object, kind of similar to the existing patch, but a new cloned object, instead of the old one.
$view_request = clone $request.
This object then should be pushed onto the request stack so that everything knows about about this temporary new request:
\Drupal::requestStack()->push($view_request).

Once the rendering is done in

$preview = $this->renderer->executeInRenderContext($context, function() use ($view, $display_id, $args) {
  return $view->preview($display_id, $args);
 });

this request could be dropped again, so that for example the ajax state system could see the original request again.

I don't know whether this idea will actually work and how tricky it would be to actually implement, but it might be worth giving it a try :)

netdreamer’s picture

I reworked the patch #2798947-7: Views pagers include ajax metadata, based on findings by @rwam and I think I've fixed that problem.
I'm using it now on some pages with multiple ajax pagers without any issue, but please test it!

As already stated in the sourcecode, everything is related to #2504709: Prevent _wrapper_format and ajax_form parameters from bleeding through to generated URLs, so I think that this fix will be no more useful when parent issue will be fixed.

netdreamer’s picture

Status: Needs work » Needs review

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Coufu’s picture

Thank you SOOO much netdreamer!! #14 fixed my issues with views ajax and pagers.

Kristen Pol’s picture

I tested per steps in the issue summary and it worked as expected. Note that the link for the first page is:

/test-ajax-pager?page=0

and it would be better if it was just:

/test-ajax-pager

but that is not the focus of this issue. The code is easy to read. It still has the one nitpick I mentioned in #9 (moving "such" up to the previous line) but that shouldn't hold this up. Marking RTBC.

Kristen Pol’s picture

Status: Needs review » Reviewed & tested by the community
Kristen Pol’s picture

Status: Reviewed & tested by the community » Needs work

Actually, this was marked as needs tests so moving back to needs work.

rwam’s picture

Patch #14 works very well. Thank you @netdreamer.

keithdoyle9’s picture

Patch #14 worked for me as well.

loopduplicate’s picture

The last submitted patch, 23: views_pagers_include-2798947-23-test-only.patch, failed testing. View results

Berdir’s picture

Status: Needs review » Reviewed & tested by the community

Confirmed that this helps with our project.

There is a deeper issue for us, but I don't know if that is something specific to our site or possibly a generic issue. Every few clicks on the pager, the ajax action is not triggered and as a result, the browser just normally fetches that URL. Previously, you then get the raw JSON response, with this page, it just loads that page as a normal non-ajax link. Which is not perfect, but so much better than the alternative.

Figuring out what is going on with the ajax action is a separate issue, I'll open an issue if I ever track down what exactly causes it.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/modules/views/src/Controller/ViewAjaxController.php
@@ -130,9 +130,26 @@ public function ajaxView(Request $request) {
+      // Add all POST data, because AJAX is always a post and many things,
+      // such as tablesorts, exposed filters and paging assume GET.
+      $request_all = $request->request->all();
+      $query_all = $request->query->all();
+      $request->query->replace($request_all + $query_all);

This section makes me feel uneasy. Moving everything from POST to GET feels very odd. Also if I remove this section the test still passes so whatever this is here for is not tested.

Lendude’s picture

Status: Needs work » Needs review
FileSize
1.46 KB
2.53 KB

The piece of code pointed out in #26 was originally something that was moved from further down in \Drupal\views\Controller\ViewAjaxController::ajaxView but the deletion got removed in a reroll in #14 so we were left with an addition instead of a move.

But as @alexpott pointed out, this code move isn't needed at all to pass the test so lets remove it.

Berdir’s picture

Status: Needs review » Needs work

Are we sure it's not needed or are we just missing test coverage?

It was moved befere to keep it above the logic

+++ b/core/modules/views/src/Controller/ViewAjaxController.php
@@ -165,6 +176,7 @@ public function ajaxView(Request $request) {
         // @todo Remove this parsing once these are removed from the request in
         //   https://www.drupal.org/node/2504709.
         unset($used_query_parameters[FormBuilderInterface::AJAX_FORM_REQUEST], $used_query_parameters[MainContentViewSubscriber::WRAPPER_FORMAT], $used_query_parameters['ajax_page_state']);
+        $request->query->remove('ajax_page_state');

Right, before it was moved because we also removed the part here.

Keeping it below is OK because above we unset both request and query, so it doesn't make a difference that we merge them together afterwards.

However, the unset here seems bogus then because $used_query_parameters is $request->query and they were already unset above. So possibly we can remove that and move the @todo up?

I guess we are doing the query remove here because it has been merged in from the POST request above. There is no reason for ajax_page_state to ever be present in the query, so what if we move the unset above to unset it on $request_all, so we never merge it into $request->query in the first place?

Lendude’s picture

Status: Needs work » Needs review
FileSize
1.66 KB
2.94 KB

@Berdir looking at the failing test in #23 it covers the steps and result in the IS nicely, so if we are doing things that need extra test coverage, we also need to expand on this in the IS. I think the test coverage is fine, but you are absolutely right, we can reshuffle some more code to make all this much clearer. It seems like we were stripping things from the request in two different places for the same reason, so lets do that in one place.

Had a quick shot at @dawehners proposal in #13 but couldn't get it to work quickly (still sounds like a good idea).

Status: Needs review » Needs work

The last submitted patch, 29: 2798947-29.patch, failed testing. View results

Lendude’s picture

As @Berdir rightly pointed out to me in slack, removing 'ajax_page_state' from the POST data lead to the situation in #10, so we need to make sure it never gets added to GET but not remove it from POST.

Added a new test assertion to test for the double loading of assets as described in #10 and this indeed fails with the patch in #29.

Lets see how this does.

Berdir’s picture

Status: Needs review » Reviewed & tested by the community

Nice, looks good to me.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed and pushed 48726e6908 to 8.6.x and 05eab2037e to 8.5.x. Thanks!

  • alexpott committed 48726e6 on 8.6.x
    Issue #2798947 by Lendude, loopduplicate, rocketeerbkw, netdreamer, LoMo...

  • alexpott committed 05eab20 on 8.5.x
    Issue #2798947 by Lendude, loopduplicate, rocketeerbkw, netdreamer, LoMo...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.