Problem/Motivation
This is a follow up of #2828092: Inline Form Errors not compatible with Quick Edit .
In certain cases (like what Quick Edit does with forms) we want modules to have the possibility to disable Inline Form Errors (IFE) completely.
We shouldn't encourage maintainers to do so because we don't want bad ux and a11y, but in cases those issues are covered they do need the possibility to create their own form interaction UI.
Proposed resolution
add a form property which can be used to disable IFE $form['#disable_inline_form_errors'] = TRUE;
Remaining tasks
Write a CR (https://www.drupal.org/node/2899118)
User interface changes
N/A
API changes
API addition: a new FAPI property to disable inline form errors.
Data model changes
N/A
| Comment | File | Size | Author |
|---|---|---|---|
| #71 | Selection_215.png | 36.91 KB | berdir |
| #65 | 2856950-64.patch | 7.92 KB | dmsmidt |
| #64 | interdiff-2856950-59-64.txt | 8.88 KB | dmsmidt |
Comments
Comment #2
dmsmidtComment #3
dmsmidtComment #4
dmsmidtSince I had no feedback so far I went ahead and created a prototype for the solution that had most of the votes (new $form property).
This patch allows a developer to exclude inline form errors (at form definition time and when using a form_alter).
The test created in #2828092: Inline Form Errors not compatible with Quick Edit already tests if this works. But I guess we may need a standalone unit test for this?
Comment #6
dmsmidtAnd with test fix.
Comment #7
tim.plunkettIn #2828092-58: Inline Form Errors not compatible with Quick Edit catch said
It seems like he also supports this approach. As do I.
opting out *of*
non-inline
Otherwise I think this is RTBC-worthy
Comment #8
dmsmidt@tim Thanks for the review. Those nits seem easy enough to fix.
Comment #9
tameeshb commentedComment #10
andrewmacpherson commentedThe patch in #9 corrects the points from #7. I confirmed the interdiff after downloading both patches form # and #9.
Comment #11
th_tushar commentedLooks good. +1
Comment #12
dmsmidtDid anyone actually test this while altering a form for example?
I love that you guys RTBC this of course, but please leave some testing feedback.
Comment #13
andrewmacpherson commentedIt doesn't work completely yet for forms in general.
I updated @dmsmidt's errorstyle module so that it will optionally use the proposed new
$form[#disable_inline_form_errors']FAPI property.See the GitHub pull request: Support #disable_inline_form_errors FAPI property
I tested the patch from #9 using the errorstyle module, in 3 ways (screenshots attached):
$form[#disable_inline_form_errors']property set.The patch in #9 only affects the error message at the top of the page, via
Drupal\inline_form_errors\FormErrorHandler::Drupal\inline_form_errors(). When Inline Form Errors module is enabled, and$form[#disable_inline_form_errors']is applied to the whole form, we get the normal core error message at the top of the page (i.e. a long list without links), but we still see inline error messages for each individual element.I think we need to test for
$form[#disable_inline_form_errors']during form element preprocessing, in the_inline_form_errors_set_errors()helper function. I poked arounddpm($variables)but couldn't find a quick way to read the parent form property.Comment #14
andrewmacpherson commentedAside: the alternate proposal...
... would make a good contrib module! Just a settings form with a big text area, listing one form ID per line. Then set #disable_inline_form_errors with hook_form_alter().
Comment #15
andrewmacpherson commentedRemoving novice tag, that was for the typos in #7
Comment #16
dmsmidt@andrewmacpherson, thanks for actually testing thoroughly!
Adding 'needs tests' again in order to cover the 'inline messages' part.
Comment #17
dmsmidtAlright here is a new version that should hide all inline error messages.
Still tests needed.
Comment #18
dmsmidtWriting tests
Comment #19
dmsmidtImproved the tests. Now also checks for form elements (next to the summary).
Comment #20
lendude@dmsmidt as promised, the underscore prefixed function moved out of the .module file to a service, with some test coverage.
Comment #21
dmsmidtThanks @lendude, this is indeed much more in line with D8's OOP structure, and makes it much more extendable and better documented.
Comment #22
tedbowLooking good so far!
I am all for moving code out of the module into a service but why not make service in encapsulate more.
You could use a service that does more maybe FormElementProcessor.
Then this hook could just be 1 line.
\Drupal::service('inline_form_errors.form_element_processor')->attachProcessCallback($info);
Then in that method instead of setting #process to again a method in .module I think it would be ok to set the callback to method on the class. If it is ok practice to have a public static method on service. Like so:
$info[$element_type]['#process'][] = [static::class, 'proccesElement'];Should we be explicit the $form must be the top level of the form?
Can just use {@inheritdoc}
Could use a message or comment to tell why this proves it works.
Comment #23
dmsmidtComment #24
sutharsan commentedThis requires the form elements to be instantiated. Using class_implements() will be more performant.
I've tested the below code. It takes (approx.) only 30% of the execution time of the original code.
Comment #25
marcvangendI'm working on this in the a11y sprint @Synetic.
Comment #26
marcvangendLet's start with the low-hanging fruit: #22 points 2, 3 & 4, and one more documentation improvement in \Drupal\Tests\inline_form_errors\Unit\FormElementDeterminerTest::testIsFormElement().
Comment #27
marcvangendHere's another patch which implements @Sutharsan's performance improvement and updates FormElementDeterminerTest to work with the class_implements method.
Comment #28
marcvangendCreated a follow-up: #2891648: Allow hiding of inline form errors on non-form elements when it is disabled for the complete form
Comment #29
marcvangendAs discussed with @Lendude and @dmsmidt at the code sprint, this patch changes the name of FormElementDeterminer to the more meaningful SupportedRenderElementHelper. It also improves some inline docs and cleans up some redundant use statements. Unfortunately interdiffs aren't very readable for changes like this.
Comment #30
marcvangendThis is probably my last patch on this issue for today, related to #22 number 1. The patch moves inline_form_errors_process_form_element() to a static method in a helper class \Drupal\inline_form_errors\FormElementProcessor. The FormElementProcessor class is not a service, because there is no need for it to be instantiated or to be present in the service container.
Comment #31
lendudeAll feedback from #22 has been addressed, this looks really nice now.
Comment #32
dmsmidtReally nice work today, thanks!
Comment #33
lauriiiI did read through the code and it didn't raise questions on my side. I also noticed that we've added test coverage so I removed the tag. I will let one of the other maintainers make the final call on this. Before one of the other maintainers will take a look at this, I think we totally should still create a small change record for this addition.
I've also updated the issue credits.
Thanks everyone!
Comment #34
gábor hojtsyCode example is incorrect.
Is there a use case to alter this? Would swapping the service serve that use case or would multiple sources want to alter it (if there is a use case to alter it in any case)?
Comment #35
dmsmidt@Gábor #34.2: If contrib adds RenderElements that need to show Inline Form Errors while not implementing FormElementInterface they would need to be able to alter this. However that seems like a very small use case.
Let's create a follow up to somehow let those element type be listed in
$this->pseudoFormElements. Maybe we should add an extra property to the annotations.Comment #36
gábor hojtsy@dmsmidt: great, we can definitely expand features later on if this implementation / API does not make further expansion without a BC break impossible / very hard. Not sure how would the expansion work, annotation additions would be backwards compatible I guess. Can you open that followup?
Comment #37
dmsmidt#34.1 Fixed
#34.2 Follow up created #2895882: Allow contrib modules to create Inline Form Errors supported RenderElements that are no Form Elements.
Also note that we already have online documentation in place: https://www.drupal.org/docs/8/core/modules/inline-form-errors/inline-form-errors-module-overview
Comment #38
tim.plunkettMostly nits left:
Constructs a new SuppportedRenderElementHelper.
Determines
Should use ->hasDefinition() and ->getDefinition()
FormElementInterface::class
Tests
Comment #39
dmsmidtComment #40
dmsmidt#38: fixed. Also redid the short descriptions of some docblocks to fit on one line.
Comment #42
marcvangendRe. #34 - #37:
We had already identified that need during the sprint on July 2nd and I created a follow up for it in #28: #2891648: Allow hiding of inline form errors on non-form elements when it is disabled for the complete form.
We should close either that issue or #2895882: Allow contrib modules to create Inline Form Errors supported RenderElements that are no Form Elements as duplicate.
PS re. #34.1 Good catch, can't believe I missed that :-)
Comment #43
andrewmacpherson commentedThis fixes the missing empty line that automated code sniffer found.
I haven't figured out the test failure, but it's to do with the changes for #38.3
Comment #44
dmsmidt@andrew thanks, I discussed this with Tim Plunkett, he is working on fixing the tests.
Comment #45
tim.plunkettUsually "Helper" classes only contain static methods.
Also, services should have interfaces provided
i.e.:
(or don't use i.e.)
These should all be ElementInfoManagerInterface
Ideally these would be using Prophecy
Comment #46
tim.plunkettI don't think that allowing others to add more $pseudoFormElements is in scope for this issue. Plus, only one module per site could swap out the service, so a service isn't an appropriate solution.
This fixes my concerns from above.
The interdiff is bigger than patch, sorry.
Comment #47
tim.plunkettThe change to ElementInfoManagerInterface is a necessary part of this patch, but opened a separate issue in case it is deemed out of scope:
#2899082: ElementInfoManagerInterface must extend DiscoveryInterface
Comment #48
dmsmidtI'm oké with the idea behind the rewrite. I didn't see anything strange on first glance. The patch is less sophisticated (less files and code in the .module file). I'll do a manual test later and check it a bit more in depth.
Comment #50
tim.plunkettComment #51
dmsmidtManual tests confirm everything still works as expected. After thinking a bit more about it I still think not using services is oke, since we don't introduce logic that should be easily reusable by other modules.
Some nits/remarks:
This is no longer correct since this class is now also responsible for adding the element process function and the processing it self.
Maybe use: 'Provides functionality to determine and process supported render elements.'
Since this refers to the module, 'inline form errors' should be with capital letters.
Should we add a description and params for this? Or is it allowed to skip those in tests? I see mixed usage in core.
Comment #52
tim.plunkett@dmsmidt is working on the CR.
Addressed all 3 points, thanks!
Comment #53
dmsmidtThe changes of #52 cover my concerns.
Change record added: https://www.drupal.org/node/2899118.
This has my +1 for RTBC.
Comment #55
tedbowIsn't
$form['#disable_inline_form_errors'] = TRUE;an API shouldn't we have ainline_form_errors.api.phpOtherwise it seems like it would be really hard to figure this out.Comment #56
tedbowI guess we don't have don't have a practice for making *.api.php files for # properties.
It is documented here https://www.drupal.org/docs/8/core/modules/inline-form-errors/inline-for...
Looks great! RTBC 🏅
Comment #57
xjmSome feedback on the change record. It says:
Isn't it more "for a specific form or any form element"? Actually, now that I'm looking at it, the keys are
#disable_inline_form_errorsand#error_no_message. That's a bit confusing.I also agree with @tedbow's first instinct that this needs to be documented in the API documentation. The handbook page is good to have, but it's not API documentation. We've definitely gone through a lot of different best practices for where form elements and properties should be documented, and it's probably not a
*.api.php; I can't remember right now though how we document them normally.Comment #58
tim.plunkettThis was to be the stop-gap solution. The added property ONLY works when used on the top-level $form element.
#2891648: Allow hiding of inline form errors on non-form elements when it is disabled for the complete form is the issue for individual elements. The property to be added in that issue would be documented directly on \Drupal\Core\Render\Element\RenderElement.
The only other properties that exclusive to the top-level $form element I can think of are internal-only: #cache_token, #build_id, #action
They are not documented anywhere.
Comment #59
tim.plunkettRebase since #2899082: ElementInfoManagerInterface must extend DiscoveryInterface went in.
Comment #60
xjmHmm, what is #2891648: Allow hiding of inline form errors on non-form elements when it is disabled for the complete form postponed on? This doesn't seem like it's all that hotfix-y and the other doesn't really propose anything particular yet.
The comment from @catch that spawned this issue is:
(From #2828092-58: Inline Form Errors not compatible with Quick Edit .) So I guess I was imagining this issue's scope to be somewhat wider than it actually is. (For some reason I was thinking the
#error_no_messagething was IFE, but it's just generic FAPI that's also used on a few datetime things.) However, I'm wondering what the value of this is? I could see disabling IFE with a particular module, but an API (a FAPI key, no less) to disable IFE for just a single form seems pretty edgecase to me.I did commit #2899082: ElementInfoManagerInterface must extend DiscoveryInterface separately (thanks for the separate issue; I was about to push back on that change but then the separate issue answered my questions).
In terms of documenting this bit, I guess @tedbow's
inline_form_errors.api.phpsuggestion is as good as any, but I do think we need something more than a handbook page.Comment #61
dmsmidtFirst of all, this issue was indeed spawned from the Quick Edit Quick Fix, the whole idea was just to make disabling IFE possible without specific hacks for certain forms of certain modules from within IFE module. This patch allows to do so for any form with a form_alter or within the form definition directly.
Disabling IFE for a complete module doesn't sound like a solid idea. What if the module adds different kind of forms? Say one form for an advanced use case on the visitor facing part of the site and also some admin forms?
I'm glad this issue is for edge cases, we want to have all forms accessible without a hassle. However we can't ignore that there are edge cases and I don't want to limit experimental UI's. Currently we use this only for Quick Edit in core, but we shouldn't limit contrib and allow it to come up with advanced UI's. Also when #77245: Provide a common API for displaying JavaScript messages lands we can expect more advanced UI's that handle their own error reporting.
A simple use case I could imagine that doesn't need IFE is a form with a single input, like search. Having a summary might be a bit strange when you can just print a singe error near the element.
I renamed #2891648: Allow hiding of inline form errors on non-form elements when it is disabled for the complete form, this follow up is only for non-form elements that are used in a form context (like wrappers), it would only be for contrib and it is postponed on this issue. We can just see if any contrib module needs it at all...
#error_no_messagedoesn't have anything to do with this issue directly, it does not stop displaying the summary, and needs to be set for all elements individually. And yes that we reuse all kinds of properties of Form API becomes messy. Another example is that we now store error messages in a property that was ment to only indicate that the element should be highlighted as having an error, which causes a lot of problems with compound fields and bubbling of errors.I'll think about API documentation, I was cracking my brain on how to do that for form properties as well..
Comment #62
xjmSo, the summary for the other issue is:
That's different from the scope in the changed title, though.
My question is, if this issue was just intended as a "stopgap" that we could land by 8.4.0, and we had a more complete fix in mind, how about we instead just go for the complete fix? What would it look like? As currently titled, #2891648: Allow hiding of inline form errors on non-form elements when it is disabled for the complete form is unrelated to the scope of this issue, but do they have some underlying shared solution?
Comment #63
dmsmidt@xjm, I'll try to clarify. I worked with @marcvangend (creator of the follow up) on this issue during a sprint.
This issue is not at all ment as a "stopgap" it is the pretty solution to the current workaround for Quick Edit. And module developers could already benefit from this even without the follow up. I renamed it again and updated the summary #2891648: Allow hiding of inline form errors on non-form elements when it is disabled for the complete form.
We could potentially just get rid of this list of 'supported render elements', because we don't use it to actually show inline form errors. If the template of an element prints the errors, showing inline form errors is supported out of the box. This list of render elements is actually a list of render elements that can be stopped from showing inline form errors by opting out the complete form in one go (single property on $form).
By getting rid of this list (and the type checks) we would set
#error_no_messageon any render element even if they couldn't display inline errors anyhow. Which doesn't break anything.Comment #64
dmsmidtAlright, this patch is a very simplified version which gets rid of all 'supports' rhetoric, it's confusing.
Using this version of the patch all render elements are handled equally, contrib doesn't need to declare anything for new elements (win!).
Since element processing (
#process) is done in the FormBuilder, processing of elements is only done for render elements in forms, the impact of not discriminating between types is thus not so big.If this approach gets in, sorry guys for the code round trip, your help is much appreciated!
Comment #65
dmsmidtComment #66
gagarine commentedUsability is preferred over UX, D7UX, etc. See https://www.drupal.org/issue-tags/topic
Comment #67
tim.plunkettThis looks great! And there are no remaining tasks.
Comment #68
larowlanShould we document this as an allowed key in https://www.drupal.org/node/2117411
I couldn't find anywhere in the code that we document allowed keys for top-level form and I don't think this fits on FormElement as it only applies to the top level form. Does anyone know if we have that somewhere?
Comment #70
dmsmidtI haven't seen a place where all top level form element properties are listed.
But we could at least add a paragraph about user feedback / error reporting of forms to the documentation page you mention. In there we can mention the new key.
Comment #71
berdirNothing there yet, but \Drupal\Core\Render\Element\Form might be a place to document that?
What about the login and password recovery form and similar forms that only have 1-2 form elements? Possibly disabling just the summary there might make more sense than disabling it completely (#2880011: Add a #disable_inline_form_errors_summary property to disable the Inline Form Errors summary but it is IMHO quite confusing that the message is on the username field when it could just as well be the password being wrong.
Also did a re-test, looked like random fails.
Comment #72
andrewmacpherson commentedI've no objection to disabling IFE on the user login page. The error message is a general one which applies to both fields, and has a call to action ("forgot?") which deserves prominence in the main messages area. It also serves as a good demonstration of the new FAPI property.
Should it be a blocker to this issue, or a follow-up?
Comment #73
skaughtforgot aside:
it's confusing that the link is on the username field when it's not even the password that has an error; in this situation. But, i know, this message is set for it's own purpose..
Comment #74
dmsmidtSetting this to needs work since we need documentation in code.
@Berdir (@andrew), please create a separate issue for individual forms that could use some improvement with IFE on. I don't agree to use to 'old' error messaging since we never know where the login form is in relation to the message. A shared message could also be put on a surrounding fieldset for example. @SKAUGHT it goes without saying that the messages should make sense.
Comment #75
dmsmidtSo, I was thinking about the whole documentation thing mentioned by @larowlan #68 and @Berdir #71.
Adding documentation in code that not belongs to the IFE module seems strange, right?
We would then describe the usage of a key, which can only be used if the module is enabled. Doesn't sound legit. The same logic goes for the generic Form API documentation.
Currently the feature is mentioned on the IFE documentation page, this is IMHO the best spot for now.
https://www.drupal.org/docs/8/core/modules/inline-form-errors/inline-for...
Comment #76
dmsmidtPutting this back to RTBC since no code was changed and tests still pass.
The only issue open for discussion was about documentation.
In code documentation about features added by a module should not be put in code that is not of that module.
There is documentation on DO (see #75), I'll update it when this gets in.
Comment #78
dmsmidtComment #79
larowlanQueued another test run
Comment #80
larowlanUpdating review credits
As discussed on slack, I agree
Comment #82
larowlanCommitted as 128cb26 and pushed to 8.5.x.
As per https://www.drupal.org/core/release-cycle-overview#current-development-c... 8.4 is in 'critical fixes only', so this can't be backported to 8.4
Comment #83
larowlanPublished change record
Comment #84
dmsmidtHooray! I updated the IFE online docs to reflect the change.