Problem/Motivation
#1824636: Do not move the cursor to the top of the page on ajax calls solved a form focus state issue where by default the focus state comes back to the form element after rebuilding part of the form after an AJAX call. This was fixed against a select element in the views module. The problem is that it works fine on select elements and checkboxes, but not on text fields and textareas. The default AJAX event for these text fields is 'blur', meaning AJAX triggers when unfocusing, for example by clicking anywhere else or pressing tab. The problem is that that by doing so, the focus comes back on these fields by default now, making it impossible to select any other form element in the form.
I don't know if this happens anywhere in Drupal out of the box, but I included a small module to reproduce the issue. Enable ajax_test and navigate to /admin/config/development/ajax-test
to see the form.
A quick example of this behaviour is shown here. The top field has an AJAX callback (the sample module has it on both, resulting in a never ending back and forth focussing between the two fields). The focus always comes back to that field:
Proposed resolution
Return focus to the element that was focused prior to the execution of AJAX commands for event listeners attached to the blur
event. This is needed because when the event listener is attached to blur
event, user may have moved the focus to another element and then consequently triggering the blur event.
There could be some advanced use cases where the default behavior is not sufficient. For this reason, there's an API to explicitly override this if needed. For example, a custom form element could explicitly enable this behavior for itself.
Comment | File | Size | Author |
---|---|---|---|
#76 | interdiff.txt | 486 bytes | lauriii |
#76 | 2627788-76.patch | 8.86 KB | lauriii |
#73 | interdiff.txt | 3.48 KB | lauriii |
#73 | 2627788-73.patch | 8.86 KB | lauriii |
#64 | interdiff.txt | 1.52 KB | lauriii |
Comments
Comment #2
droplet CreditAttribution: droplet commentedI was mentioned it in old issue:
https://www.drupal.org/node/2124397#comment-9159611
Comment #3
droplet CreditAttribution: droplet commentedComment #4
Danny_Joris CreditAttribution: Danny_Joris at Lullabot commentedComment #5
DuaelFrHere is a patch that disable the automatic refocus when the defined event is "blur".
I had to move the code a bit to benefit from the default event defined according to the element type.
Comment #6
juampynr CreditAttribution: juampynr at Lullabot commentedBy looking at ajax.js I found the following logic which does the focus() call:
This seems to be a non-documented feature: if you add a data attribute
disable-refocus
to your element, then this issue won't happen any more. I added the following to the affected form element and the issue stopped happening:'#attributes' => array('data-disable-refocus' => 'true'),
.I feel that more people will encounter this bug and waste time trying to figure it out. Why are we setting focus in the first place?
Comment #7
DuaelFrYou are right, I didn't document this feature when I developed it.
The best way to disable the auto-refocus is to use
$element['#ajax']['disable-refocus'] = TRUE;
, though.I don't know how the FAPI documentation is generated but I suppose we should explain this a bit more there.
I still think we need to fix that particular use case because it's generating an infinite focus loop.
Comment #8
juampynr CreditAttribution: juampynr at Lullabot commentedThanks for the feedback @DuaelFr. Do you why do we have that trigger logic? I still don't understand what it is supposed to do. Sounds too opinionated to me that core focuses on the element's wrapper.
Comment #9
juampynr CreditAttribution: juampynr at Lullabot commentedOkay, I just finished reading the summary of #1824636: Do not move the cursor to the top of the page on ajax calls and now I get it. To be honest, I don't know what would be best to do to fix this.
Comment #10
DuaelFrI think my patch fixes the issue. The blur event is really special so we should just avoid it. I suppose we should do the same with the focus event as it also could lead to an infinite loop in some cases.
Comment #11
juampynr CreditAttribution: juampynr at Lullabot commentedAgree, your patch looks fine to me.
Comment #12
seppelM CreditAttribution: seppelM commentedThere is another problem with TabIndex in Internet Explorer (not covered/solved by the patch in #5) and
"disable-refocus"-Inputs. (Tested in IE11 Win7 & Win10 IE13)
If you leave the field with TAB while the Ajax process is running, the focus/cursor is completely removed.
So one can't finish the form that way.
Comment #15
jmccormick CreditAttribution: jmccormick commentedI've been banging my head on this issue for a few weeks now. Adding
'disable-refocus' => true
to my'#ajax'
array in the field generation array fixed the problem.Comment #16
droplet CreditAttribution: droplet commentedI have a thought and I think it's prime time to take this action now. RBTC. #12, we need another approach for that unfortunately
Comment #17
DuaelFrYeah #12 is totally another problem related to the replacement of some parts of the form by the AjaX callbacks.
Thanks for the review @droplet
Comment #18
alexpottNow we have javascript tests this is testable - so we don;t ever break it again.
Comment #19
sumanthkumarc CreditAttribution: sumanthkumarc at Azri Solutions commentedAdding 'disable-refocus' => true to my '#ajax' array fixed the problem.
Comment #21
facine CreditAttribution: facine as a volunteer and at Cambrico commentedHi, here is a patch Drupal 8.3.x.
Comment #24
GrimreaperHello,
Thanks all for the work done here.
I didn't know about the 'disable-refocus' attributes. It solved my problem.
Comment #26
W01F CreditAttribution: W01F commentedJust tested this patch on 8.6.2 and it doesn't seem to be working - live example www.kobejet.com - the ultimenu dropdowns have ajax textbox filters.
Comment #27
matthieuscarset CreditAttribution: matthieuscarset as a volunteer and commentedJust had this issue with the Drupal Commerce Quantity field (e.g a number input).
Tested to apply the patch from #12.
Patch applies correctly but the refocus issue is not fixed.
Tested to simply add
$element['#ajax']['disable-refocus'] = TRUE
in my form.This works :) Tested on Drupal 8.6.3 and 8.6.4.
Comment #28
DuaelFrJust reapplied the patch from #21 on 8.7.x HEAD and tried it with the little module linked in the Issue Summary.
Without the patch it is not possible to leave the form fields as the ajax call refocus the blured field.
With the patch, the ajax call does not refocus the blured field anymore.
Comment #29
PanchoI tested both 'blur' and 'change' with and without 'disable-refocus'.
While on 'change' the refocus is just a bit annoying, on 'blur' it creates an endless loop, as shown above.
And yes, you want 'change' on a textfield if you don't want AJAX to be triggered unless something was actually changed.
See these two screencasts for 'blur' and 'change' (without patch applied):
I don't have to embed the ones with the patch (resp. 'disable-refocus' enabled) as they are flawless. I'm appending them though.
So think this patch clearly is a first, important stop-gap, as we definitely need to avoid an endless loop.
However, refocussing back to the triggering element is a very opinionated default setting. And not refocussing at all is not always a useful alternative, for example if the target element gets removed.
Furthermore, there are valid usecases - even for 'blur' - where AJAX field validation should indeed force the cursor back to the triggering element. We should provide a framework that works for all these cases, though in a followup.
As this is just a stop-gap, we don't want to remove functionality possibly used somewhere in contrib. I wonder if we shouldn't just default to 'disable-refocus' on blur, while at least allowing to override it with a
'disable-refocus' => FALSE;
Comment #30
PanchoSorry for citing myself, but I really think we shouldn't go as far to completely prohibit refocussing after 'blur', so I'm preparing a patch that only changes the default, if nothing is specified.
Also think this is a major usability bug, even though this is "only" the slightly neglected Ajax API everybody thinks is only for "pros" who know what they're doing. A default that locks the cursor in, basically rendering the whole form unusable, clearly needs to be fixed and backported.
Everything else may go to the followup #3033151: Add ['#ajax']['refocus'] property to FormAPI elements.
Comment #31
PanchoHere's the patch. Assigning to myself for the tests.
Comment #32
PanchoTests are a bit tricky, so I probably need the weekend. in the meantime, please feel free to review patch #31.
Comment #33
andrewmacpherson CreditAttribution: andrewmacpherson as a volunteer and at Annertech commentedPutting this on my review list. I'm still trying to grok our AJAX system, finding some of the abstractions a bit hard to follow.
Comment #34
tim.plunkettFixing tags
Comment #35
akalata CreditAttribution: akalata at Tag1 Consulting commentedIs 8.6.x the correct target version, now that 8.7.0 has been released? (Patch is working on 8.7 for me, fwiw).
Comment #36
dpiStill needs tests.
Comment #39
solideogloria CreditAttribution: solideogloria commentedDoes anyone know how to write a test for something like this? It would be great to have this fixed.
Comment #41
mxhThank you very much for pointing out the 'disable-refocus' option, it saved my day. That option seems to miss in the documentations about AJax in Drupal forms. Will write a documentation part once I find time for it.
Comment #42
KapilV CreditAttribution: KapilV as a volunteer and at Innoraft for Drupal Care, Drupal Association commentedReroll 9.2.x.
Comment #44
vsujeetkumar CreditAttribution: vsujeetkumar at Srijan | A Material+ Company for Drupal India Association commentedFixing tests.
Comment #49
needs-review-queue-bot CreditAttribution: needs-review-queue-bot as a volunteer commentedThe Needs Review Queue Bot tested this issue. It either no longer applies to Drupal core, or fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #50
brad.bulger CreditAttribution: brad.bulger commentedI have a multivalue field (a list of emails with an Add another button) with a change event ajax trigger on each item of the field (eg field_email[0][value]). The problem I am having is that if I disable refocus, and then end up having to replace the entire field, the focus ends up at the top of the form.
Would this fix cover that problem? Or maybe one of the related issues? The best thing would be if the cursor ended up where it was going to go before ajax intervened, I don't know if that's possible.
Comment #51
_utsavsharma CreditAttribution: _utsavsharma at OpenSense Labs for DrupalFit commentedPatch for 10.1.x.
Comment #52
mgiffordadding sc https://www.w3.org/WAI/WCAG21/Understanding/no-keyboard-trap
Comment #54
lauriiiLet's see what the testbot thinks
Comment #55
lauriiiComment #56
lauriiiComment #57
smustgrave CreditAttribution: smustgrave at Mobomo commentedWoo all green! Moving to NW for the tests though.
Comment #58
lauriiiHere's tests for this.
Comment #59
lauriiiComment #60
lauriiiPassing all linters is hard 😅
Comment #62
lauriiiIndeed, it is hard to pass all linters 🤦♂️
Comment #63
tim.plunkettThese 5 types all have their #ajax][event type set to blur unless it's manually set to something else... Not sure if that's what this is protecting against or not.
Additionally, the mixing of || and && without parentheses makes this extremely hard to parse.
Is this how JS wants us to format
for
loops?Also, any reason to not use the more standard
i
instead ofn
for the counter?Comment #64
lauriii#63.1 I tried to clarify the comments and added parentheses to help with the grouping.
#63.2 This is how prettier wants to format it.. Not much I can do about it. Note that that part is pre-existing code, but it was reformatted because there's an additional if now.
Comment #66
tim.plunkettThe prettier reformatting was so jarring I didn't even notice that it was existing code, lol sorry for being distracted.
The comment change and added () make that better, thanks.
Comment #68
lauriiiLooks like unrelated random fails. #65 was #3386682: [random test failure] MenuUiTest::testMenuAdministration and I can't see how #67 could be caused by this given that the error occurred before any AJAX events had been triggered.
Comment #69
alexpottI think we need a change record for the new
refocus-previous
setting. Plus the issue summary needs updating for the current solution.Comment #70
lauriiiOpened a CR and updated the issue summary.
Comment #71
alexpottDiscussed the meaning of "previous" with @lauriii and we decided that it is really hard to know what it means in this context. So we're going to rename it and improve the documentation.
Comment #72
alexpottAlso we should open an issue to document advanced AJAX settings somewhere - maybe in a section following the AJAX docs in core.api.php
Comment #73
lauriiiAddressed #71.
Comment #74
lauriiiOpened follow-up for the advanced settings: #3393894: Improve ajax system documentation to include advanced settings.
Comment #76
lauriiiHelps if I change the attribute name in the JavaScript code too 😇
Comment #77
smustgrave CreditAttribution: smustgrave at Mobomo commentedSeems point in #71 has been addressed.
Comment #78
alexpottCrediting re-rollers and commenters.
Comment #79
alexpottCommitted and pushed 27d76911e88 to 11.x and df73d39e0ec to 10.2.x. Thanks!