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:
animated gif showing AJAX bug

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.

CommentFileSizeAuthor
#76 interdiff.txt486 byteslauriii
#76 2627788-76.patch8.86 KBlauriii
#73 interdiff.txt3.48 KBlauriii
#73 2627788-73.patch8.86 KBlauriii
#64 interdiff.txt1.52 KBlauriii
#64 2627788-64.patch8.88 KBlauriii
#62 interdiff.txt515 byteslauriii
#62 2627788-62.patch8.78 KBlauriii
#60 interdiff.txt730 byteslauriii
#60 2627788-60.patch8.78 KBlauriii
#60 2627788-60-test-only.patch4.36 KBlauriii
#59 interdiff.txt657 byteslauriii
#59 2627788-59.patch8.78 KBlauriii
#59 2627788-59-test-only.patch4.36 KBlauriii
#58 interdiff.txt5.88 KBlauriii
#58 2627788-57.patch9.02 KBlauriii
#58 2627788-57-test-only.patch4.6 KBlauriii
#56 interdiff.txt1.8 KBlauriii
#56 2627788-56.patch3.81 KBlauriii
#55 interdiff.txt572 byteslauriii
#55 2627788-55.patch3.82 KBlauriii
#54 2627788-54.patch3.82 KBlauriii
#51 2627788-51.patch1.8 KB_utsavsharma
#51 interdiff_d10.txt1.8 KB_utsavsharma
#49 2627788-nr-bot.txt146 bytesneeds-review-queue-bot
#44 interdiff_42-44.txt500 bytesvsujeetkumar
#44 2627788-44.patch1.79 KBvsujeetkumar
#42 2627788-42.patch2.13 KBKapilV
#31 2627788_28-31_interdiff.txt1.41 KBPancho
#31 disable_auto_refocus_on_blur-2627788-31.patch1.79 KBPancho
#29 ajax-blur-norefocus- textfield.gif275.89 KBPancho
#29 ajax-blur-refocus- textfield.gif621.91 KBPancho
#29 ajax-change-norefocus- textfield.gif298.65 KBPancho
#29 ajax-change-refocus- textfield.gif554.33 KBPancho
#28 disable_auto_refocus_on_blur-2627788-28.patch1.4 KBDuaelFr
#21 disable_auto_refocus_on_blur-2627788-21.patch1.39 KBfacine
#5 disable_auto_refocus_on_blur-2627788-5.patch1.39 KBDuaelFr
bNKH3ijhFy.gif132.18 KBDanny_Joris
ajax_test.zip2.14 KBDanny_Joris
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Danny_Joris created an issue. See original summary.

droplet’s picture

Version: 8.1.x-dev » 8.0.x-dev
droplet’s picture

Issue tags: +JavaScript
Danny_Joris’s picture

Issue summary: View changes
DuaelFr’s picture

Status: Active » Needs review
FileSize
1.39 KB

Here 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.

juampynr’s picture

By looking at ajax.js I found the following logic which does the focus() call:

    // If the focus hasn't be changed by the ajax commands, try to refocus the
    // triggering element or one of its parents if that element does not exist
    // anymore.
    if (!focusChanged && this.element && !$(this.element).data('disable-refocus')) {
      var target = false;

      for (var n = elementParents.length - 1; !target && n > 0; n--) {
        target = document.querySelector('[data-drupal-selector="' + elementParents[n].getAttribute('data-drupal-selector') + '"]');
      }

      if (target) {
        $(target).trigger('focus');
      }
    }

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?

DuaelFr’s picture

You 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.

juampynr’s picture

Status: Needs review » Needs work

Thanks 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.

juampynr’s picture

Okay, 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.

DuaelFr’s picture

I 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.

juampynr’s picture

Status: Needs work » Needs review

Agree, your patch looks fine to me.

seppelM’s picture

There 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.

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

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

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

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

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

jmccormick’s picture

I'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.

droplet’s picture

Status: Needs review » Reviewed & tested by the community

I have a thought and I think it's prime time to take this action now. RBTC. #12, we need another approach for that unfortunately

DuaelFr’s picture

Issue tags: +Usability, +Ajax, +fapi ajax

Yeah #12 is totally another problem related to the replacement of some parts of the form by the AjaX callbacks.
Thanks for the review @droplet

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs tests

Now we have javascript tests this is testable - so we don;t ever break it again.

sumanthkumarc’s picture

Adding 'disable-refocus' => true to my '#ajax' array fixed the problem.

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.

facine’s picture

Hi, here is a patch Drupal 8.3.x.

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.

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.

Grimreaper’s picture

Hello,

Thanks all for the work done here.

I didn't know about the 'disable-refocus' attributes. It solved my problem.

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

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

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

W01F’s picture

Just 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.

matthieuscarset’s picture

Just 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.

DuaelFr’s picture

Version: 8.6.x-dev » 8.7.x-dev
Status: Needs work » Needs review
FileSize
1.4 KB

Just 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.

Pancho’s picture

I 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):

Blur: Change:
Blur Blur

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;

Pancho’s picture

Version: 8.7.x-dev » 8.6.x-dev
Priority: Normal » Major

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;

Sorry 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.

Pancho’s picture

Here's the patch. Assigning to myself for the tests.

Pancho’s picture

Tests are a bit tricky, so I probably need the weekend. in the meantime, please feel free to review patch #31.

andrewmacpherson’s picture

Issue tags: +accessibility

Putting this on my review list. I'm still trying to grok our AJAX system, finding some of the abstractions a bit hard to follow.

tim.plunkett’s picture

Issue tags: -Ajax, -fapi ajax, -accessibility (duplicate tag) +Accessibility

Fixing tags

akalata’s picture

Is 8.6.x the correct target version, now that 8.7.0 has been released? (Patch is working on 8.7 for me, fwiw).

dpi’s picture

Version: 8.6.x-dev » 8.8.x-dev
Assigned: Pancho » Unassigned
Status: Needs review » Needs work

Still needs tests.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

solideogloria’s picture

Issue tags: -JavaScript +JavaScript

Does anyone know how to write a test for something like this? It would be great to have this fixed.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

mxh’s picture

Issue tags: +Needs documentation

Thank 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.

KapilV’s picture

Status: Needs work » Needs review
FileSize
2.13 KB

Reroll 9.2.x.

Status: Needs review » Needs work

The last submitted patch, 42: 2627788-42.patch, failed testing. View results

vsujeetkumar’s picture

Status: Needs work » Needs review
FileSize
1.79 KB
500 bytes

Fixing tests.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
FileSize
146 bytes

The 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.

brad.bulger’s picture

I 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.

_utsavsharma’s picture

Patch for 10.1.x.

mgifford’s picture

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

lauriii’s picture

Status: Needs work » Needs review
FileSize
3.82 KB

Let's see what the testbot thinks

lauriii’s picture

lauriii’s picture

smustgrave’s picture

Status: Needs review » Needs work

Woo all green! Moving to NW for the tests though.

lauriii’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests
FileSize
4.6 KB
9.02 KB
5.88 KB

Here's tests for this.

lauriii’s picture

lauriii’s picture

The last submitted patch, 60: 2627788-60-test-only.patch, failed testing. View results

lauriii’s picture

Indeed, it is hard to pass all linters 🤦‍♂️

tim.plunkett’s picture

  1. +++ b/core/lib/Drupal/Core/Render/Element/RenderElement.php
    @@ -327,6 +333,16 @@ public static function preRenderAjaxForm($element) {
    +      $text_types = ['password', 'textfield', 'number', 'tel', 'textarea'];
    ...
    +        if ($element['#ajax']['event'] === 'blur' || $element['#ajax']['event'] === 'change' && in_array($element['#type'], $text_types)) {
    

    These 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.

  2. +++ b/core/misc/ajax.js
    @@ -1083,19 +1094,30 @@
    +                for (
    +                  let n = elementParents.length - 1;
    +                  !target && n >= 0;
    +                  n--
    +                ) {
    

    Is this how JS wants us to format for loops?

    Also, any reason to not use the more standard i instead of n for the counter?

lauriii’s picture

#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.

Status: Needs review » Needs work

The last submitted patch, 64: 2627788-64.patch, failed testing. View results

tim.plunkett’s picture

Status: Needs work » Reviewed & tested by the community

The 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.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 64: 2627788-64.patch, failed testing. View results

lauriii’s picture

Status: Needs work » Reviewed & tested by the community

Looks 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.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs change record, +Needs issue summary update

I think we need a change record for the new refocus-previous setting. Plus the issue summary needs updating for the current solution.

lauriii’s picture

Issue summary: View changes
Status: Needs work » Reviewed & tested by the community
Issue tags: -Needs change record, -Needs issue summary update

Opened a CR and updated the issue summary.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/lib/Drupal/Core/Render/Element/RenderElement.php
@@ -273,6 +273,12 @@ public static function preRenderAjaxForm($element) {
+    // Add a data attribute to attempt to focus element that was focused before
+    // executing ajax commands.
+    if ($element['#ajax']['refocus-previous'] ?? FALSE) {
+      $element['#attributes']['data-refocus-previous'] = "true";
+    }

Discussed 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.

alexpott’s picture

Also we should open an issue to document advanced AJAX settings somewhere - maybe in a section following the AJAX docs in core.api.php

lauriii’s picture

Status: Needs work » Needs review
FileSize
8.86 KB
3.48 KB

Addressed #71.

lauriii’s picture

Issue tags: -Needs documentation

Status: Needs review » Needs work

The last submitted patch, 73: 2627788-73.patch, failed testing. View results

lauriii’s picture

Status: Needs work » Needs review
FileSize
8.86 KB
486 bytes

Helps if I change the attribute name in the JavaScript code too 😇

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Seems point in #71 has been addressed.

alexpott’s picture

Crediting re-rollers and commenters.

alexpott’s picture

Version: 11.x-dev » 10.2.x-dev
Status: Reviewed & tested by the community » Fixed

Committed and pushed 27d76911e88 to 11.x and df73d39e0ec to 10.2.x. Thanks!

  • alexpott committed 27d76911 on 11.x
    Issue #2627788 by lauriii, DuaelFr, Pancho, vsujeetkumar, _utsavsharma,...

  • alexpott committed df73d39e on 10.2.x
    Issue #2627788 by lauriii, DuaelFr, Pancho, vsujeetkumar, _utsavsharma,...

Status: Fixed » Closed (fixed)

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