Problem/Motivation
The \Drupal\views\Plugin\views\display\Block::preBlockBuild() method is responsible for manipulating the view before it is rendered by the corresponding block plugin. The block plugin itself actually calls this method, however blocks cannot show exposed filters without using ajax, and the ajax rebuild for views will not call the preBlockBuild() method.
As a practical example, blocks can override the number of results per page for a view, however if they leverage ajax rebuild, on the first rebuild (which is also used for pagers) the view will be rendered without the block's overrides. If the default view shows 50 per page, and the override shows 5, the first ajax call will result in the view now showing 50 instead of 5.
Proposed resolution
Add new function to view Block.php display plugin (getBlockFromAjaxRequest) that runs when ajax is used in a view block.
Adding new function (preparePreview) to DisplayPluginInterface
Previous solution
The views/ajax controller needs to consult the block when rebuilding block displays. Doing this generically is likely to be a bit convoluted.
Remaining tasks
Identify a solution.
Implement it.
Change record (maybe)
Review
Commit
User interface changes
NA - working ajax for view blocks now.
API changes
NA
Data model changes
NA
| Comment | File | Size | Author |
|---|---|---|---|
| #149 | 2605218-143.diff | 25.6 KB | herved |
| #147 | 2605218-11.2.diff | 19.46 KB | claudiu.cristea |
Issue fork drupal-2605218
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
dawehnerInteresting issue
Comment #4
tim.plunkettDoing a bit of triage: this affects one piece of functionality and is a self-contained bug. While important, it is not major.
Comment #5
tim.plunkettComment #10
marcoscanoMarked #2973751: Views block settings are lost when using AJAX as duplicate of this one.
Comment #11
buenos commentedComment #13
buenos commentedFixed path.
Comment #14
marcoscanoWhat if there is more than one block, with different items per page each?
I believe the hard part of this issue is figuring out the block instance to get the correct contexts, in the first place.
Then, I believe we should call
::preBlockBuild()with the correct contexts loaded, once there may be other settings (overrides) that can be defined by contrib/custom modules (not only items_per_page).Comment #15
marcoscano@buenos, are you still working on this issue? I've unassigned it, but please feel free to assign it back to you if that is the case.
This patch implements a solution that should be generic to all block overrides, once we call ::preBlockBuild() even if previewing the view in an AJAX request. This solution relies on a fix from #2823541-34: Table clicksort is lost when using views exposed filter & Pager exposed '#items', so the ajax post requests persist the query params. I'm attaching then a patch with this fix only, and also a combined patch so we can trigger the testbot.
Comment #18
marcoscanoOh didn't realize the dependency patch has some failing tests... :(
But our test-only fail test passed! :)
Comment #19
marcoscanoOK, not sure if this is the best way to fix the failing tests, but it seems to fix things for me.
Again, the combined patch includes this fix and #2823541-34: Table clicksort is lost when using views exposed filter & Pager exposed '#items'
Comment #21
berdirAlways store the request stack in the property, never the current request. The point of the stack is that the current request can change, this breaks that.
To be nice to subclasses, we could default the new dependencies to NULL and fall back to \Drupal::service()
Not sure about the "instance" thing. Maybe something like block_config_key instead or so?
Not quite sure where things should live.
I was wondering if part of it could be in \Drupal\views\Controller\ViewAjaxController::ajaxView(), some parts of that are duplicated here. Then again, if we move it over there, we have to assume other parts, like check if it is a block display plugin.
Is this a left-over of the earlier approach to call $block->build() which re-created the view? This view should still have it because it is set in that very ViewAjaxController?
Comment #22
marcoscano@Berdir thanks for reviewing!
1- Done, thanks!
2- 👍
3- Me neither. Being the cause of this bug the fact that we bypass
::preBlockBuild(), I tend to see this is more related to the block plugin, so I'd prefer to have some duplication here rather than in the main AJAX controller. But no strong opinions really.4- yes, here the dom_id is the same, so no need for that anymore, thanks!
Comment #24
dawehnerI'm wondering whether you should use
\Drupal\views\ViewExecutable::getRequestinstead here. No injection would be neededWe should document that this is triggered both in the ajax and non ajax case and explain why this is useful :)
As alternative one could set
$view->build_info['fail'] = TRUE;There is a bit nicer Drupal method for that, in case you want to use it:
\Drupal\Component\Utility\UrlHelper::parseComment #25
marcoscanoOoops, wrong patch.. But it was actually only a re-upload of the same test-only patch from above.
Comment #26
marcoscano@dawehner thanks for reviewing!
This should address all points from #24.
I've also realized that the patch I am incorporating from #2823541: Table clicksort is lost when using views exposed filter & Pager exposed '#items' does not fully address that issue, it's only a partial fix. So I guess it's just easier to assume those changes as part of this issue as well, and whatever issue gets in first, the other will need a re-roll.
Comment #28
berdirI think @see is only allowed in a docblock, in an inline comment, you just make it a "See .." and part of a sentence. Also not sure if self:: can actually be resolved by api.drupal.org
Do we have test coverage already for this scenario? Never seen that before and wondering what that exactly does then.
Wouldn't it need an early return then?
Comment #29
marcoscano@Berdir thanks for the feedback!
1- Done, thanks!
2- I didn't know this existed either... I think the early return isn't necessary because it appears to abort the render process entirely: http://cgit.drupalcode.org/drupal/tree/core/modules/views/src/ViewExecut... But I guess it won't hurt either.
About test coverage, not sure the best way to check that... should we manually issue an AJAX request to the views ajax handler with a fake key, and check that the response does not contain the view markup? (any example you know of something similar I could check?)
Thanks!
Comment #31
lendudeI'm not convinced that storing this in key/value is a good idea. At the very least we need to do some sort of manual clean up of everything we store here. Like react to View deletions/updates and block config updates. Alternatively move to key/value-expire, but then you'd have the bug return if a page persist for longer then a week. So again, not ideal.
But otherwise this will bloat key/value and never get cleaned up, which sounds bad.
this should have a delimiter to prevent collisions
Comment #32
berdir1. I was thinking about that too. Entity Autocomplete (where we stole the whole concept from) has the same problem. I thought about using expire too and the long-cache problem. What if we just set the expiration to a very long time (like a year), then it will get deleted *at some point* if not used anymore? (Would also need a refresh logic then, like updating it if it's less than a X or so :-/)
Comment #36
sam152 commentedRan into this today, here is a reroll, unfortunate that KV storage had to get involved to persist the settings, but I couldn't see a better way of doing it when looking into the problem.
I understand the problem with a growing KV table, but it looks like a new entry is only added when the block configuration itself changes? Not ideal, but practically speaking block configuration shouldn't change so frequently that this would really become a huge problem.
I like the idea of setting a long expiry as a solution. What about setting the KV entry every time the block is rendered, so there would be little doubt it would be called again in the span of a year. A loaded page or cached response would then have to survive for longer than a year to trigger any sort of bug.
Comment #38
mrharolda commentedLooks like the failed test is unrelated to this issue? Queued for retesting.
Comment #40
mrharolda commentedHere's a reroll of the patch in 29 against Drupal 8.7 (current stable). It did not apply cleanly, so I had to manually resolve the conflict and rebase it on 8.7.x.
Comment #42
tobiasbComment #44
tobiasbComment #45
tobiasbComment #46
dylan donkersgoed commentedI'm using the patch from comment 40 against 8.7.x and ran into an issue with the layout_builder.entity context. This context causes the patch to fail in Drupal\views\Plugin\views\display\Block::getBlockFromAjaxRequest() due to the context not being in the format it expects. Filtering out the context there avoids the issue and doesn't seem to prevent the context from working.
It seems like this issue should be fixed by https://www.drupal.org/project/drupal/issues/3018782 so this tweak will likely become obsolete in a few Drupal versions. I'm uploading my patch in case anyone is in the same situation as I am, but if you're on 8.8.x and/or not using layout builder you probably want tobiasb's patch above.
Comment #47
tobiasb@Dylan Donkersgoed
We use Layout Builder in 8.8.*. Do you mean that the patch can not work/is wrong then?
Comment #48
dylan donkersgoed commented@tobiasb I wasn't able to apply patch 44 against 8.7.x so my patch is based on patch 40. If you have the same issue on 8.8.x (you might not, I haven't checked) you could add the same change to your patch. You can check by:
Comment #49
dylan donkersgoed commentedApparently that change is still required for 8.8.x. I've attached an updated version of tobiasb's patch against 8.8.x with my change added.
Comment #51
primsi commentedRe-roll for 9.1.x
Comment #53
primsi commentedAddressing fails.
Comment #55
mpotter commentedI ran into this problem when placing a Views block via Layout Builder that was not respecting the number of items set in the block config on subsequent ajax page loads. I applied the patch from #53 and that solved the problem for me here. Marking this RTBC since the tests are passing above on #53 to try and get this moving forwards again after so many years.
Comment #56
berdirThis has coding standard issues that need to be resolved.
Comment #57
anmolgoyal74 commentedComment #58
pyrello commentedApologies if this has already been covered somewhere. I don't understand why the more recent patches abandoned the original approach of making changes to
Drupal\views\Controller\ViewAjaxController::ajaxView(). There have been several comments to the effect that the Key/Value solution has a potential garbage collection issue with proliferating entries. @Sam152 indicated he didn't think that would be a big problem, but it seems to me that with Layout Builder and every block on every LB page having its own configuration that over time this would be quite a big issue. Especially when as site is first under development, there are a lot of changes to block settings going on.Is there not a way to detect the
Drupal\views\Plugin\views\display\Blockinstance being used and run itspreBuildBlock()method? I'm probably missing something, but the current approach seems to be quite a big workaround.Comment #59
pyrello commentedAfter analyzing the code a bit more and doing some tinkering, I can't find any obvious way to do what I was suggesting, so not sure if that is actually possible.
The issue that I have run into and was hoping one of the patches on this thread might solve for is the fact that we need to pass some information between a block instance and the underlying view it is generating. We are attempting to do this via
HOOK_preprocess_block(), but that never gets triggered when the view is refreshed via AJAX since the view sits inside the block container and the block container remains on the page. Also triedHOOK_block_build_alterandHOOK_block_view_alterand neither are triggered by the AJAX refresh of the view, even with the patched code updates.So, I guess this is a little off topic, but if anyone has an idea about how to bridge that gap, I would be very grateful. My best idea at this point for how to proceed is to introduce an event dispatcher that runs during
preBuildBlock.Comment #60
pyrello commentedI have figured out a way to reliably pass some of the settings I need to the view from the block during an AJAX request. This is accomplished by saving any extra data with the block configuration during submission of the block form.
However, the problem that I am running into now is that some view modifications are not "sticking". For example, I am trying, during
preBlockBuild, to hide certain fields based on user the selection (likectools_views):During a non-AJAX load, the code above works just fine, thanks to the fact that my custom views block display classes'
preBlockBuildmethod runs well in advance ofViewExecutable::preExecute(). When the view is generated via AJAX request though, it is generated using theViewExecutable::preview()method, which runsViewExecutable::preExecute()before passing it off to the display handler'spreviewmethod. The patch in https://www.drupal.org/project/drupal/issues/2605218#comment-13784355 allows thepreBlockBuildmethod to run duringpreview, but by then it is too late for the snippet of code to do any good.I don't have a good idea yet of how to adjust the patch to fix this issue, but wanted to get these notes down while they are fresh in my head.
Comment #61
pyrello commentedThe patch I'm attaching introduces a new
preparePreview()method toDrupal\views\Plugin\views\display\DisplayPluginInterface,Drupal\views\Plugin\views\display\DisplayPluginBase, and adds an implementation of it toDrupal\views\Plugin\views\display\Block. It also provides execution of the new display handler method duringDrupal\views\ViewExecutable::preview()prior to running thepreExecute()method. I moved the logic that was added toDrupal\views\Plugin\views\display\Block::preview()in a previous patch to the new method to ensure that thepreBuildBlock()method runs prior toViewExecutable::preExecute(), which allows changes to the view based on block configuration to happen prior to all the initialization of the elements that are impacted by these changes.I'm not sure that 'preparePreview' is the best name for the method, so if anyone has a better suggestion, I'd be happy to hear it. Any other feedback is welcome as well!
Comment #62
pyrello commentedForgot to upload the interdiff.
Comment #64
kim.pepperComment #65
chi commentedComment #68
tobiasbComment #69
tobiasbComment #70
tobiasbComment #71
tobiasbComment #73
tobiasbComment #74
drou7 commentedJust tried patch #73, with core 9.3-beta1, not working.
My view is rendering a block with a views_pre_render hook and setItemsPerPage with 3 items. With AJAX enabled, the first build is taking the value from the view parameters and not the hook.
Comment #76
martijn de witComment #77
ravi.shankar commentedAdded reroll of patch #73 on Drupal 9.4.x.
Comment #78
pyrello commentedQueuing up tests.
Comment #79
pyrello commentedSetting back to "Needs work" based on #74
Comment #81
leanderjccI had this issue and applying the patch from #73 on Drupal 9.3.14 fixed this for me.
Comment #83
skaught+1
am using #73 for 2 projects at this time.
Comment #84
ameymudras commentedPatch #77 wasn't getting applied for D9.5 as well as D10.1.x, I have rerolled the patch for compatibility
Comment #85
kim.pepperThe issue summary needs to be updated with the current solution.
Comment #86
_utsavsharma commentedFixed custom command failed of #84 for 10.1.x.
Comment #88
danielvezaCame across this issue after finding a similar issue happening on a client site.
The patch solves the problem itself, ajax requests respect the items per page, but the block_config_key parameter is duplicated in the URL on each pager click.
It looks like the changes to the ajax.js aren't checking if the key already exists in the URL.
Setting back to needs work for that.
Comment #89
smustgrave commentedAlso got bit by this today.
Comment #90
gauravvvv commentedUpdating attributions
Comment #91
smustgrave commentedSince it doesn’t show changes what was updated?
Comment #92
pyrello commentedI'm adding myself back in for credit on the commit, since versions of this since my patch use the
preparePreview()method I introduced.Comment #93
pyrello commentedHmm... I guess I don't have permission to update credit.
Comment #94
emerham commentedRe-rolled the patch from #84 against 9.5.x
Comment #95
smustgrave commentedSince I've been bit by this before going to try and help get it over the finish line.
Updated issue summary and updated patch for D10 practices.
Comment #97
smustgrave commentedHad to update the test case to do specific checks for view_query vs full array compare.
Comment #98
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".
This does not mean that the patch needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #99
martijn de witDoes the bot still works? l-)
Comment #100
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".
This does not mean that the patch needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #101
nod_still works, patch does not apply anymore. Last test run was last month.
Comment #102
smustgrave commentedRerolled
Comment #104
smustgrave commentedFixed test case.
Comment #106
smustgrave commentedThe tests were broken by #956186: Allow AJAX to use GET requests
I ended up removing
Even without the branch the Hrefs are crazy long. Not sure it's in scope of this issue to fix that part.
Comment #107
lendudeBoth points in #31 still need to be addressed.
Also, the test coverage needs to be expanded, we are making changes to javascript files so we need to have a javascript test I'd think.
Comment #108
carolpettirossi commentedI've tested patch #94 on my project running Drupal 9.5.8 and it worked as expected =)
In my case, I have a view listing articles with views_infinite_scroll to use "Load More" as pages. The load more functionality + AJAX worked fine when the views block was placed on the page using the default settings.
However, when I updated the settings to display 3 blocks per page instead of 12, the "Load more" broke and the "No results" message.
Attaching a video/screencast of my scenario.
Comment #109
danielvezaHaving a look at the patch, I think #88 still needs to be addressed too
Comment #110
carolpettirossi commentedActually, I'm sorry about the early celebration on patch #94.
It seems that issue #88 also happens to me.
The second time I click on "Load more" the block_config_key and page are added to params in the URL. I'm using AJAX view though.
Comment #112
staalex commentedI reworked this a bit on the 10.1.x branch addressing the previous comments:
->getRequest()->request->get()I changed it to->getRequest()->query->get()Comment #114
staalex commentedApplied this to 11.x and fixed a failing test case.
Comment #115
staalex commentedThird time's the charm?
Comment #118
pyrello commentedI think I fixed the error in the test that was failing, but I didn't really understand what I was doing.
Comment #119
pyrello commentedComment #120
smustgrave commentedLeft some comments .
Comment #121
carolpettirossi commentedWhen I try to apply #115 on Drupal 10, it applied successfully. However, I get this
when I run
drush crComment #122
pyrello commentedComment #123
smustgrave commentedRunning the tests again.
Comment #124
smustgrave commentedSeems some failure is happening.
Comment #125
pyrello commentedLearning curve for adding the deprecation notice :)
Comment #126
smustgrave commentedThanks for taking care of that. Lets see if we can move this along and hopefully get into 10.2!
Comment #127
quietone commentedI read the issue summary and comments and I didn't find any unanswered questions. I was concerned that #31 and #88 were missed but there were responded to in #112. And there are no outstanding tags here. I didn't look at the MR (it is too late).
Comment #128
mohit_aghera commentedRe-rolling the #94 for 9.5.x since I need to use it for one project.
Mostly it was minor nit pick related to coding standard.
All 3 test cases are passing on local.
I've hide the patch since 9.5.x support is going to end soon.
Keeping the status as it is.
Comment #129
quietone commentedI cam back and some of the MR. Since I found a few things that should be fixed I am setting this back to needs work.
Comment #130
pyrello commentedComment #131
smustgrave commentedLove this new test-only feature
Remarking as appears feedback @quietone has been resolved, all threads closed, and green.
Comment #132
catchCouple of questions on the MR.
Comment #133
lendudeComment #134
herved commentedHere's a patch version of MR 4399 (latest commit 21efdb1b), if anyone else needs it.
PS: our setup enforces static patches and MR diffs are not allowed (as they can change unexpectedly).
Comment #135
smitghelani commentedRe-roll of patch #112 with branch 10.3.x
Comment #136
gloriaswebtech commentedI hate to be the bearer of bad news on such a long sought after solution for this, but the patches above do not work for 10.3 (I can't verify for Drupal 9 and below). This is also not an ideal nor practical solution. The getBlockFromAjaxRequest() method that was added does not return the expected results. The block gets a duplicate config key and also returns null on the first ajax request via elementPreRender(). The preBlockBuild() function does not get called still because the ajax architecture only returns the view (the block is a wrapper around the view and does not get updated).
My solution for the client that needed this was as follows:
1. Add Drupal JS settings for views blocks: $build['#attached']['drupalSettings']['viewsBlock'][$this->view->dom_id] = $block->getConfiguration();
2. Add a new method to the views block display and views block plugin to add the settings to the build: alterBlockBuild(ViewsBlock $block, &$build)
3. Add an event subscriber that responds to the ViewAjaxResponse response and checks for the block display handler
4. Add an ajax command that responds on ajax and uses the block settings for updating the view
I really think our proposed solution should be changed to something similar to above. This way is much cleaner and follows the ajax workflow mostly with views. Any module that overrides views block settings could then implement their own subscriber and ajax commands with the new viewsBlock drupal settings in the block build.
I spent over 25 hours on this issue, and the only way I could get block settings to update the view, was via drupalSettings on the views block and an ajax command that fires on the response of ViewsAjaxResponse.
Here's a great working example without the viewsBlock drupal settings:
https://thinktandem.io/blog/2020/04/23/altering-views-ajax-in-drupal-8/
Comment #137
joshua.boltz commentedThis is indeed an interesting issue. I just stumbled upon this issue in debugging and researching the issue I am facing.
I'm not really sure if my issue and this issue are the same issue with the same root cause and necessary solution, but it seems somewhat related, so I thought I would chime in with my case.
I have a block and I am loading a View programmatically.
The view has pagination enabled and I am seeing odd behavior on the rendered view when using the pagination.
I believe that the Ajax pagination is losing sight of how the View should be processed for rendering.
I am seeing things like:
* Duplicated results in the view
* Missing results in the view
I can sort of confirm this by enabling XDebug, and when using the Ajax pagers in the view, XDebug does not hit on the lines where I am loading and setting properties in the view for how I need it manipulated and rendered in my block.
Comment #138
mpotter commentedNOTE that in 11.1.x, the line in views.module:
was removed. So the hooks added via this issue patch cause a problem because the autoloader doesn't resolve the class properly. In the hook:
when a `Drupal\block\Entity\Block` entity is passed to the hook, it gives an error:
This happens when you try to do a clean install of any profile that contains config for blocks.
I don't have a real chance to test this patch or update it at the moment, but at least the `use` statement needs to be re-added to the top of views.module file.
Comment #139
mpotter commentedHrm, looks like I still need this patch, so here is a re-roll for D11.1.1 that adds the `use` statement just to get past this error.
In general this patch needs more work to deal with the changes to how core is handling hooks as per #3483599 where the views.module file was refactored.
Comment #141
a.dmitriiev commentedRebased MR on latest 11.x, adjusting the hooks to be placed in classes.
Comment #146
claudiu.cristeaHide patches
Comment #147
claudiu.cristeaPatch to be applied against 11.2
Comment #148
oulalahakabu commented>= 11.3
Comment #149
herved commentedUpdated MR, snapshot applies to 11.3