Problem/Motivation
When an exposed filter is placed twice on the same page it gets the same html identifier.
Every instance of this block / form needs to have their own unique id attribute.
This is needed for some contributed projects, but it's also not allowed to be duplicated according to the w3c rules.
The id attribute specifies its element's unique identifier (ID). The value must be unique amongst all the IDs in the element's home subtree and must contain at least one character. The value must not contain any space characters.
https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#the-id-attribute
Proposed resolution
Use Html::getUniqueId() to generate the ID.
Remaining tasks
Review, fix tests.
User interface changes
None
Release notes snippet
| Comment | File | Size | Author |
|---|---|---|---|
| #147 | 2894747-147.patch | 12.53 KB | fernly |
| #146 | 2894747-145.patch | 13.38 KB | abdelrahman amer |
Issue fork drupal-2894747
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
mvwensen commentedPatch attached, please review and provide feedback if necessary! (Or fix it ;).)
Comment #5
basvanderheijden commentedBy making the ID's unique, the selector (javascript) doesn't work anymore, because it's still referencing the static selector.
In the attached patch, I've resolved the issue by changing the selector from the unique ID (which cannot be derived) to a class name.
Comment #7
mpotter commentedUnfortunately this doesn't seem to work with Ajax enabled-views. It causes a new page to be reloaded as if Ajax was disabled.
Also, the "id" of the input field is still the same. So Ajax-enabled views with exposed filter forms get confused because of the duplicate ids. So it's not just the form-id that needs to be changed.
Comment #8
mpotter commentedThe problem on my site seems to be that this patch adds the class to the wrapping div and not the actual form element. So the JS selector needs to be something more like this:
this.$exposed_form = $('.views-exposed-form-' + settings.view_name.replace(/_/g, '-') + '-' + settings.view_display_id.replace(/_/g, '-') + ' form');
however, even with that change, and changing the input id in my code, having two exposed forms for the same view on the same page using Ajax doesn't seem to ever finish the ajax callback. So at least in my case something more complicated is going on.
Comment #11
gertjanmeire commentedAre there any updates to resolve this issue?
This currently breaks accessibility checks on forms using better exposed filters...
Comment #12
semanthis commentedThis is quite a annoying problem, having to exposed filters of the same view on the same page is not very unusual. Ajax on such pages is important to prevent page-jumping. Why can't the id of the input be constructed by [view-name]-[display]. I'm not so into the ajax-thing of views, so I can't offer a fix, but I would be very happy if someone could take care of that problem...
Comment #13
d.fisher commentedJust came across this when running lighthouse on a new D8 site using views/better exposed filters and the same exposed filter block being used twice on a page. Following for updates.
Comment #14
biigniick commentedI also have a site with this issue. Anyone find a solution?
Comment #15
cilefen commentedThere is a small family of related issues. Some could be duplicates.
Comment #16
bès commentedThe usage of Html::getUniqueId() is a good idea but it will not work until we have the issue #1831560: Remove Html::resetSeenIds() call during form processing fixed.
Comment #17
milindk commentedComment #18
milindk commentedRerolling patch as 8.8.x is released, and solve the test cases issue.
Comment #19
milindk commentedThere was a small mistake while fetching the id, attaching corrected patch.
Comment #20
milindk commentedFix test case issue
Comment #21
milindk commentedComment #22
milindk commentedAttaching patch for 8.7.x as MediaOverviewTest.php not present in 8.7.x and its introduced in 8.8.x
Comment #23
milindk commentedMy bad Attaching patch for 8.7.x above patch fails because of mistakely format-patch with 8.8.x.
Comment #24
super_romeo commentedif in attachment
inherit exposed filters == TRUEthen$view->exposed_widgets == []and:Comment #25
lendudeIf we are refactoring this, wouldn't it make more sense to refactor away from using the HTML ID in the first place? Can't we set an data attribute that just uses the existing 'view_dom_id' setting?
Also, this needs tests.
Comment #26
lendudeHere is a test for this, simply placing the block twice.
The fix in #24 doesn't fix this.
Test only patch is also the interdiff
Comment #27
lendudeSo per #16, this has a hard dependency on #1831560: Remove Html::resetSeenIds() call during form processing which is currently RTBC. That fixes the test-only patch in #26, so this needs additional coverage to show that Views ajax needs a little extra love to get this to work.
Comment #29
lendudeMore tests, full JS test this time.
This new test will fail even with the patch from #1831560: Remove Html::resetSeenIds() call during form processing applied, so when that gets committed we will still have a test only patch to show that Views needs additional fixes.
Expected red/green:
- with #1831560 committed, test only and fix patch in #26 will go green
- with #1831560 committed, test only in #29 will stay red, but fix will be green (well it was locally, so fingers crossed)
Comment #30
lendudeUpdated the title to make it more specific to the bug we are trying to fix once #1831560: Remove Html::resetSeenIds() call during form processing lands and updated the IS a bit.
Comment #34
lendudeHere is a reroll of #29
Comment #36
krzysztof domańskiThe
'test_exposed_form_buttons'view configuration has an exposed filter. An additional exposed filter is added in the$exposed_form->renderExposedForm();. We have two such forms, which is the reason for test failure.Now the second block has the id
'views-exposed-form-test-exposed-form-buttons-default--2'.Comment #37
krzysztof domański1. Needs a re-roll.
2. Helpful problem #3000762: Add assertNoDuplicateIds to a functional test trait
Comment #38
mohrerao commentedRerolled parch in #34 for 9.1.x. Also updated changes suggested in #37.2
Couldnt add an interdiff as there was change in code on
from
this.$exposed_form = $('form#views-exposed-form-' + settings.view_name.replace(/_/g, '-') + '-' + settings.view_display_id.replace(/_/g, '-'));to
this.$exposed_form = $("form#views-exposed-form-".concat(settings.view_name.replace(/_/g, '-'), "-").concat(settings.view_display_id.replace(/_/g, '-')));Comment #39
mohrerao commentedComment #40
krzysztof domański@mohrerao thanks!
Minor, there is no reason to replace
assertCountwithassertEqualhere.Comment #41
mohrerao commented@Krzysztof, Thanks. I believe both would yield same result. However this is from the patch in #34.
Comment #42
krzysztof domańskiThe result is the same, but the
assertCountis more appropriate here.See #3128814: Replace assert* involving count() and an equality operator with assertCount() or #3128573: [meta] Replace assertions with more appropriate ones.
Comment #43
ravi.shankar commentedHere I have tried to address comment #42.
Comment #44
nikro commentedI'm a bit confused by this line. If we rename the ID of the 2nd form and apply the ajax functionality by # - this will only allow 1st form update the view via Ajax, while 2nd makes non-Ajax submission.
I didn't follow the issue from the beginning, however changing this into this (taking in account that we have a class with exactly same id):
Makes both forms submit nicely.
Comment #45
krzysztof domańskiInstead of adding an additional method
assertNoDuplicateIds(), we can use the new assertpageContainsNoDuplicateId(). See The pageContainsNoDuplicateId assert was added to WebAssert.Comment #46
krzysztof domański@Nikro I have not tested manually yet, but I see there is a test coverage for this.
Comment #47
rajandro commentedWorking on this
Comment #48
lendude@Krzysztof Domański Actually, we don't have test coverage for this, only that a submission is done, not that an AJAX submission is done. So we should absolutely add a check that the submissions are done using AJAX and not by page reload. I haven't checked manually either, but we should add it to the automated test either way.
Comment #49
rajandro commentedHi,
Since this is moved to 9.1.x, hence it requires to reroll. I am attaching the rerolled patch for 9.1.x
Though, I have a concern as we have some changes in 9.1.x
Please find the 8.9.x patch difference:
Same changes I have done for 9.1.x
A Javascript expert's opinion will be helpful to determine the use of
concatis required here or not.Thanks!
Comment #50
veronicaseveryn commentedTested patch #43 and had two instances of the same exposed form on the same page (the blocks are placed on the same page in different regions via blocks UI ). I am on Drupal 8.9.4. The view is AJAX enabled. To be 100% sure nothing else affects it, also tested with default Seven theme and without any BEF module. Same result.
FIRST use case: Exposed Form on the AJAX view is set to "Exposed form in block":YES
<form action="/find-a-doctor" method="get" id="views-exposed-form-search-index-doctors-find-doc--3" accept-charset="UTF-8">SECOND use case: Exposed Form on the AJAX view is set to "Exposed form in block":NO
<form class="views-exposed-form views-exposed-form-search-index-doctors-find-doc" data-drupal-selector="views-exposed-form-search-index-doctors-find-doc" action="/find-a-doctor" method="get" id="views-exposed-form-search-index-doctors-find-doc" accept-charset="UTF-8" _lpchecked="1">So, the form didn't even get View Dom_ID identifier as ID or any additional classes.. This happens in the case when . To be clear also, . - maybe I am missing some other fixes?
Did anyone test it manually and was actually successful?
I have looked through the code and had a few minor changes suggested (which were tested and worked in my case - I am not sure how previous patches were tested previously).
Comment #51
veronicaseveryn commentedThis is a patch for Drupal 8.9.4 version.
I modified AJAX JS to get applied to the forms based on this selector:
Also, when I had two AJAX exposed form blocks on the same page, I was getting multiple AJAX progress indicators attached to the document body , and only 1 of them was cleaned up with old code. So, I also added a modification to
ajax.jsas follow, to find all instances and remove on success:I didn't update any TESTS in the patch though..
With this patch I don't get duplicate form IDs any more, AJAX submission works on each form instance, and no hanging throbbers remain.
Comment #53
veronicaseveryn commentedAccidentally committed additional stuff in #51 that shouldn't be there.. cleaning up the patch
Comment #55
lendude@veronicaSeveryn thanks for looking at this, but:
This is very much out of scope for this issue, and when you make changes to a patch please add an interdiff, right now it is hard to see what else was changed, but we can say that it broke some tests.
The exposed form in block case is something we do need to add coverage for (and possibly a fix), and we need to expand the coverage we are adding for the AJAX filtering to make sure they are actually using AJAX and not triggering a page reload.
So tagging with 'Needs tests' again.
Comment #58
krzysztof domańskiWe can use the Views DOM ID
settings.view_dom_idto have unique form IDs.See #2968207: Allow multiple instances of the same exposed filter form on a single page
Comment #59
krzysztof domańskiComment #61
leisurman commentedUsing Drupal 9.2.9. Patch #49 works for me. Thank you
Comment #62
bartlangelaanThis is a simple reroll of #53, which should be compatible with 9.3.x.
Comment #63
abarrioHi all,
patch on #62 fixes the problem about progress icon but not the submit event. I have been working on it and found that in the ajax_views.js there is some point in the creation of ajax_view objects that code search exposed forms with this expression:
this.$exposed_form = $("form[id^=\"views-exposed-form-".concat(settings.view_name.replace(/_/g, '-'), "-").concat(settings.view_display_id.replace(/_/g, '-'), "\"]"));This expression returns all forms so this is why all submit are called at same time.
I changed the load of it to search only in view dom not in complete dom.
this.$view.find("form[id^=\"views-exposed-form-".concat(settings.view_name.replace(/_/g, '-'), "-").concat(settings.view_display_id.replace(/_/g, '-'), "\"]"));I upload diff and interdiff.
Comment #64
ranjith_kumar_k_u commentedComment #66
leisurman commentedUsing Drupal 9.3.0 Patch #49 no longer can be applied
Comment #67
leisurman commentedHas this been rolled into Drupal 9.3? The patch did not apply. I don't have the duplicate exposed form id error anymore. The id of the 2 forms on the page are now different. One has 2 dashes appended to the id name.
Comment #68
leisurman commentedUpdate. The error came back. I still see the duplicate exposed form id error
Comment #71
prpaul commentedThis patch works for me with drupal 9.5.1
Comment #72
_utsavsharma commentedTried to fix CCF for #71.
Please review.
Comment #73
ameymudras commentedCreating a patch for 10.1.x
Comment #74
_utsavsharma commentedTried to fix CCF for #73.
Please review.
Comment #76
super_romeo commentedPatch #72 raise exception on every ajax call
Comment #77
mojiferousThere seemed to be a regression in the regex in patch #72, I have updated it so it applies for 9.5.x and does not break
Comment #78
sahil.goyal commentedupdate patch for the corresponding version D10.1 there. sorry i couldn't able to create interdiff due to some system issue i guess.
Comment #79
cristian100Updated patch for version D9.5.9.
Comment #80
heikkiy commentedWe have been using this patch in our projects for a long time but we also noticed that there were some weird problems with the views exposed filters. In our case it was scrolling the wrong view to focus after the exposed form was submitted.
We debugged the issue and noticed that it was not enough that the view had a unique ID in it but we needed to also make the drupal-data-selector unique.
In our case we basically created an input preprocess and checked if the input is a exposed views filter submit and then changed the drupal-data-selector to be unique:
We have a common function for the unique id but this might be worth checking in this issue also.
Comment #84
codebymikey commentedRerolled for 10.x branch and attached a static patch.
Comment #85
smustgrave commentedFor the CC Failure. Also MR should be updated for 11.x please.
Comment #86
_utsavsharma commentedFixed CC as mentioned in #85.
Comment #87
recrit commentedThe
view_html_idset indrupalSettingsis not used anywhere. Is this still needed? If it is cruft left over from the original approach, then it should be removed.Comment #88
vflirt commentedThere is issue with the patch from #86:
`.${$(this.progress.element).attr('class').replace(/\s/g, '.')},`,this produces selector with extra comma at the end such as :
.ajax-progress.ajax-progress-throbber,and this leads to
Uncaught Error: Syntax error, unrecognized expression: .ajax-progress.ajax-progress-throbber,that breaks every ajax call with progress
Comment #89
recrit commented@vflirt The concatenation has been fixed in the MR. Attached is a static patch of the D10 MR 4386 at commit e07684fd.
Pending Work: open MR for D11.x
Comment #90
recrit commentedhiding old patches to eliminate any confusion
Comment #92
recrit commentedAdded D11 MR 4767
Updated both MRs with a fix for erroneous removals of "++" in
core/modules/views/tests/src/Functional/Plugin/ExposedFormTest.phpfrom original patch.Attached static patches for both D10 and D11.
Comment #93
recrit commentedAttaching static patches for the MRs with passing tests.
Comment #94
recrit commentedComment #95
smustgrave commentedNoticed this was adding additional classes to the block also but IS mentions just using a unique ID.
Example I turned the content view filters into a block placed on the /admin/content page
Before
views-exposed-form block block-views block-views-exposed-filter-blockcontent-page-1
After patch
views-exposed-form views-exposed-form-content-page-1 block block-views block-views-exposed-filter-blockcontent-page-1
Comment #96
recrit commentedThe extra class is being added to the $form in "core/modules/views/src/Form/ViewsExposedForm.php:" (see below). Then views takes all of the $form classes and puts them on the wrapping block DIV instead of the FORM elemtn.
Patched "core/modules/views/src/Form/ViewsExposedForm.php:"
All of the form queries in the patch are using the new "starts with" ID selector so this new class may not be needed.
Comment #97
akalam commentedI have tested some of the patches attached in this issue (#84, #86 and #93) and all of them work with ajax disabled. Having 2 exposed forms with ajax enabled avoid the exposed form block from being replaced (both don't get replaced). Without the patch at least one of the blocks get replaced but with the patch none of them.
Comment #98
matthijsMight be related to my custom code, but I noticed that with #93 the ajax event handler is bound multiple times. This happens because
this.$exposed_formcontains all forms. I fixed this by replacing it with a constant and added the form as argument to theattachExposedFormAjaxfunction.Comment #101
recrit commented@Matthijs
(1) This issue has started with issue branches so please do not submit patches.
(2) If you do submit a patch, you should provide an interdiff.txt showing what you have changed since the previous patch. I attahced an interdiff for reference: interdiff-2894747-98-93.txt.
Comment #102
recrit commented@Matthijs
I applied the intent of your changes to the MR's for 11.x (MR 4767) and 10.1.x (MR 5595).
(1) I kept
this.$exposed_formbeing set. Some other JS or contribute modules may expect it to be set. Example: entity_browser contrib module.(2) Calling
Drupal.views.ajaxView.prototype.attachExposedFormAjaxwith a single element will not work since it is expecting to setup all forms and build the arraythis.exposedFormAjax = [];. I handled this by directly callingthis.attachExposedFormAjax();to setup all forms, and moving theonce()check to the actual submit buttons instead of the exposed form in order to ensure that the actual buttons never have more than one ajax instance.Attached are static patches for any composer builds.
Comment #103
recrit commentedComment #104
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 #105
recrit commentedI rebased the 11.x MR
Comment #106
matthijs@recrit Thanks. It didn't want to change to the MR because I wasn't sure if the issue was caused by my custom code and existed for others as well.
Comment #107
recrit commented@Matthijs Thanks, makes sense. Please try the latest changes to the MR to verify that it fixes your issue as well.
Comment #110
smustgrave commentedRemoving tests tag as they've been added
The fix of using unique ID makes 100% sense just trying to think of a scenario where that could cause a breaking change for some, but ultimately any break would mean a site was broken this whole time.
RTBC+1
Comment #111
veronicaseveryn commentedI have tested MR4767 and it works well on the front-end, but the backend is breaking for Media Library widget.
Once you try to insert a media on a node with Media Library form selection widget and use the text search there to find the media file, it redirects you to "/admin/content/media-widget/image" with 403 status.
If the MR patch is removed, it works well again.
Not sure whether the AJAX is not being re-attached to the form elements, but with the MR patch applied the pager for Media Library form still works well. It's just this search box that is giving hard time so far.
I have looked at the JS changes in ajax_views.js file and tested reverting this single change in https://git.drupalcode.org/project/drupal/-/merge_requests/5595.patch , and it 1) fixed the issue with Media Library view 2) still works for me on the front-end for other views (AJAX is still applied there, even when there're multiple instances of the same form on a single page):
With the MR patch applied looks like
Drupal.views.ajaxView.prototype.attachExposedFormAjax()function was called twice for Media Library view. And while the first time around it did the job to attach AJAX to the form, the second time it was triggered, it didn't go inside theif (once('exposed-form-ajax', this).length) {}condition block, hence,this.exposedFormAjax = [];was reset right above it and remained empty.Comment #112
smustgrave commentedGood catch! Think we will need additional test coverage for this.
Comment #113
recrit commented@veronicaSeveryn Please test the latest MR patches. I updated the "once()" calls so that it should process as expected.
Attached static patches for any composer builds to use.
Pending: New tests for the media library (if needed).
Comment #114
muath khraisat commented#79 patch not solving the issue for me after updating the core
The same patch merged with 2968207-40.patch for Drupal 9.5.11
Comment #115
recrit commentedComment #116
peachez commentedPorted 2894747-10.1.x-MR5595-c96a7b79.diff for Drupal 10.2.3
Comment #117
lobodakyrylo commentedPatch #116 contains JS syntax error so it doesn't work.
Comment #118
recrit commentedComment #119
peachez commentedNot sure about this dude at 116 and his spelling errors ;)
In all seriousness, thank you to @lobodacyril for the heads up.
Here is the corrected patch
Comment #120
peachez commented3rd times the charm?
Comment #121
smustgrave commentedJust fyi would recommend using MRs as they are faster and have quicker review turnaround.
Comment #122
borisson_we're currently working on the new version of facets, in the 3.x branch to make it work only as views exposed filters. This issue blocks us from having feature parity with the 2.x branch.
We plan on using the https://www.drupal.org/project/configurable_views_filter_block module, which would also benefit from this patch.
Adding the contrib project blocker tag to reflect that.
Comment #123
lendudeThe latest patch and the MR patches posted in #113 still break the media library search
Comment #124
recrit commentedhiding all 10.2 patches. The 11.x MR and patch in 113 applies cleanly to 10.2.x.
Comment #125
eugene bocharov commentedThe patch from #113 is not applicable to 10.2, here's the patch #120 with small but important fix
replaced with
otherwise
this.attachExposedFormAjax.bind(this)just bindsthis, but not call the function, so exposed forms don't work by AJAXComment #126
codebymikey commentedFollowing on from the feedback from #55, updated the logic so that only the relevant progress elements were removed as a side-effect.
The previous change inadvertently broke integration with Gutenberg 2.8 when editing Media blocks, because it removed the progress bar that already exists in the React lifecycle before it should've causing it to crash.
Comment #127
niels de ruijter commentedResolved a rejection in core/modules/views/js/ajax_view.js when applying the previous 10.1.x patch to the 10.3.x branch.
Comment #131
sonfdPatch in #127 looks like it includes a lot of additional code, hiding.
Comment #132
sonfdAttaching patch file with changes from MR !10945
Comment #134
wannesdrI added a fix for the failing Unit tests in the merge requests for 10.5.x en 11.x (and also on the 10.1.x for consistency).
Let's wait and see if the tests pass now.
Comment #135
borisson_Updated the issue summary.
Comment #136
borisson_I reviewed the issue together with @wannesdr at drupal mountain camp. I had some remarks, which I shared in person. Wannes is fixing those as we speak. I think this issue looks good and I will rtbc this when the last things are fixed.
Comment #137
wannesdrGood point @borisson_, removed the
tfunction calls.Thanks for the review & feedback!
Comment #138
borisson_I have tried to figure out if we are still missing something, but to me it seems like this is complete.
I was wondering if this would break styling on existing websites, but it won't, because the ID would already change when using ajax.
Comment #139
quietone commentedUpdated credit, always more difficult with an older issue an lots of patches.
Comment #140
nod_Thanks so much for updating the credits, I was worried about it :)
Spent time reviewing this and I'm going to need a few things:
Drupal.views.ajaxView.prototype.attachExposedFormAjaxbecause of bunch of themes overwrite it, let's not make it hard on contrib since it seems doable without changing itComment #142
ressaThanks for working on this! In Facets 3, placing AJAX-supported "Facets exposed filters" anywhere else than above the form in a View (the standard position) is not supported, since you have to use https://www.drupal.org/project/configurable_views_filter_block/ to place filters for example in the sidebar regions -- but #3374162: Make it work with AJAX enabled is waiting on this Drupal core issue ...
This could be holding back many users from upgrading to the new and improved Facets 3, so should the Priority for this issue be raised to "Major", to help give it a nudge?
Comment #143
nod_Seems about right given #3374162-7: Make it work with AJAX enabled
Comment #144
mortona2k commentedWithout patch
When exposed filters are not shown in a block, they are all within a form in the exposed variable, in the views-view template.
Exposed sorts are included here as well.
Printing {{ exposed }} twice in the view template causes the bug in #2876197.
Are there any other ways to print the exposed filters/sort in separate forms, without exposing as a block and using Configurable Views Filter Block?
I have been trying things like manipulating the exposed form id:
This doesn't work though, the first one refreshes the page.
The workaround I have currently is to create the form elements I need manually, then use custom js to copy those values into the exposed form fields, which will trigger the views ajax. In the case of the search input, I also trigger the form submit button. I have the BEF setting enabled to input automatically, except for text fields.
With patch
Print {{ exposed }} twice, or with some elements removed as above.
The ajax is working without issue.
You can also load the form multiple times into variables, which will give unique ids. This improves the html (no duplicate ids), but didn't fix the issue without the patch.
Comment #146
abdelrahman amer commentedReworked 2894747-114.patch to be compatible with Drupal 10.5.x.
Comment #147
fernly commentedProviding patch based on the current MR, working for Drupal 11.3.3.