Problem/Motivation

I noticed this issue while testing #1806308: Review Views JavaScript + generic modals for accessibility.

After interacting with a form element that triggers an AJAX update of the page, the page focus is reset to the document. This means that a user must then re-tab back to the form element that triggered the AJAX update in order to continue filling out the form.

Screen of a view creation page. A caption in the image explains that the select box that had had focus has lost of the focus after interacting with it. The body element now has focus

Proposed resolution

The form element that triggers an AJAX request should get focus after the request is completed.

Remaining tasks

Propose a fix.

User interface changes

Focus will remain on an AJAX request triggering form element.

API changes

None.

#1806308: Review Views JavaScript + generic modals for accessibility

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

mgifford’s picture

Priority: Normal » Major
Issue summary: View changes

This is going to be a major usability fail for keyboard-only users.

Stuart Miller’s picture

Assigned: Unassigned » Stuart Miller
mgifford’s picture

Any progress @Stuart?

Stuart Miller’s picture

Status: Active » Needs work
FileSize
868 bytes

I have made a start and some progress, patch attached.

Currently I am trying to create a solution for what should happen if the triggering element is no longer on the page and an element with the same name is not returned in the updated part of the page. A good solution I think would be that the first element in the page before the updated section that can receive focus would become selected but I am having trouble achieving this without convoluted and verbose code.

mgifford’s picture

Thanks Stuart.

Let me know how I can help. Appreciate seeing the first patch. Did very basic testing by going to admin/structure/views/add#main-content and without a mouse working to create a view.

I was happy to see that on update I didn't have to begin at the start of the page.

We might want to set this up for a new issue, but when editing a view such as admin/structure/views/view/content and just updating content there. I would expect after updating "Content: Published status (Status)" I should be on that link after updating the modal.

I can't help you with the triggering element problem, but I can ask around and see if someone can help. Let me know if that's useful.

Stuart Miller’s picture

Glad to hear that initially seems to be working okay.

Yes I think that perhaps a separate issue for the behaviour you described would be good as that is different to the issue here.

I will have another look at the triggering element problem when it or an element with the same name no longer exists in the page markup.
If you anyone has any input on what the desired behaviour should be in that situation that would be good.

Thanks.

Stuart Miller’s picture

Status: Needs work » Needs review

Setting to needs review to get some community feedback on what the behaviour should be if:

  • The form element is no longer present on the page after AJAX call.
  • An element with the same name="" attribute is not returned in the AJAX response markup.
mgifford’s picture

In that situation shouldn't the "first element in the page before the updated section that can receive focus"?

That's right from you, but it made sense to me.

Sam152’s picture

The first element before the replaced markup also make sense to me, however Is there anywhere in core where this actually exists? It seems like it would be a bit of an anti-pattern, changing an element's value then hides the element? Wouldn't this mean the user has triggered an irreversible action in the UI?

Given the improvement this patch makes and the rarity (and possibly the relative complexity) of the edge case being discussed, I think it would be worth a follow-up in another issue if this ever comes up as a real problem.

I have attached a patch which I think is a little more concise and would easily allow an else condition to be added if we ever wanted to handle the "no longer exists" case.

mgifford’s picture

This is a much better pattern. In my tests of adding a new view admin/structure/views/add it worked.

This structure is a definite improvement for keyboard only users.

Do we need any Javascript review before marking it RTBC?

Stuart Miller’s picture

This looks good to me,

My reasoning for the more verbose solution from what I remember:

The #id of the triggering element changes if that element is part of the replaced HTML. So an object with that #id no longer exists in the page. Name however as you have used does not.

My thinking for preferring #id was that it may be the case that multiple form elements have the same name but are part of different forms. Again could perhaps be a bit of an edge case but given that nature of Drupal can see a case for example where an Entity Form is created and then placed in a page more than once in different areas, perhaps in a slightly different form. Or completely different forms may easily have items with similar names given the nature of information often collected.

It seemed to be the more standard compliant approach to use the #id as this must be unique within a page.

mgifford’s picture

Issue tags: +TwinCities

Hopefully we can get this marked RTBC before hand. However, if not, hopefully we can look at it at Twin Cities Drupal Camp.

mgifford’s picture

Issue tags: -TwinCities +TCDrupal 2014

Have a test environment up here - http://sc5e22a027672051.s2.simplytest.me/

I'll see if I can get confirmation from someone today on what else would need to be done to get this RTBC.

Stuart Miller’s picture

Brilliant, I am in the UK so it might be the middle of the night if you do get some information today but if there is anything that needs doing I can try my best to jump on it.

Still thinking about using the elements name to set the focus as perhaps different forms with the same name won't be an issue if it is only searching within the form that submitted the request I also wondered about the behaviour of form elements where multiple DOM elements share the same name. I put together an experiment to see how this might behave and looks like it will always be set to the last element in the set: http://jsfiddle.net/t2krzdor/

mgifford’s picture

Status: Needs review » Reviewed & tested by the community

I think this is good to go. Just tested the functionality again with just the keyboard.

droplet’s picture

Status: Reviewed & tested by the community » Needs review

Doesn't it will be dead loop on AJax's Autocomplete ? (blur event fired -> back to input -> blur even fired -> back to input )

hefox’s picture

One of the major examples of "element no longer on page" is file fields, as a single file field the element is completily changed.

Here's a patch that just fixes it for fiel fields by adding a span and focusing on it via command replace. Against 7.x . Figure break it off into own issue after this gets in assuming this doesn't inspire any changes to the current approach that covers this and the current way.

hefox’s picture

FileSize
1.77 KB

Forgot --relative :(

hefox’s picture

FileSize
1.82 KB

OOps, needed to change this.$form to this.form for d7

mgifford’s picture

Status: Needs review » Needs work

Doesn't seem to apply anymore.

alexdmccabe’s picture

It still applied for me, but with an offset error. Rerolling to fix it.

mckinzie25’s picture

The Drupal 8 patch works well for me on text fields with unlimited potential responses after I click enter on "Add another item". However, I notice that when I upload an image or file, the focus still gets lost after the AJAX callback is complete.

mgifford’s picture

Status: Needs work » Needs review

Given the loss of focus mentioned in #22, this probably should go back to needs work. But it wasn't set to needs review so that patch in #21 never got tested by the box.

mckinzie25’s picture

As Hefox mentioned, perhaps the most significant case of an "element no longer on the page" is for file fields, both for single file uploads and unlimited file uploads. But I think for file and image fields, this is a bigger question than just where should the focus be set. When you upload a file or remove a file, no message is given to the user that the file has been uploaded or removed. Would there be any value in creating a div with that message in the same spot on the page, and setting focus to that message? It would be more helpful to someone who can't see that the file is there or not.

I've had some success with this in a Drupal 7 sandbox module, but is this something that should be added to core? Or should this be a separate issue?

hrezaei’s picture

TBH, having a message telling you that the operation is done would save time for screen reader users, It saves us having to go and confirm that the file is there or not.

mgifford’s picture

Status: Needs review » Needs work

Thanks @hrezaei for giving input on this. Really appreciated.

So there would be value in creating a a message in a <div> with that message in the same spot on the page, and setting focus to that message as it would tell someone who can't see that the file is that there is a message.

I do think we could also just leverage drupal.announce() https://www.drupal.org/node/2014521 to convey this message appropriately to the screen reader user.

mckinzie25’s picture

Drupal.announce() looks pretty nice. That might be the way to do it that is most consistent with the rest of the Drupal interface.

mgifford’s picture

Ok, so we should make the change to core/misc/ajax.js to see that the focus is reset to the proper place.

I think we'll still need the $new_content_id = $form['#id'] . '-ajax-new-content'; to point it to in core/modules/file/src/Controller/FileWidgetAjaxController.php

I don't know where we would insert this into FileWidgetAjaxController.php though. It's JS code.

Drupal.announce(
  Drupal.t('The file has been successfully uploaded.')
);
Drupal.announce(
  Drupal.t('The file has been successfully removed.')
);
mckinzie25’s picture

Inserting the JS command Drupal.announce in FileWidgetAjaxController.php looks kind of tricky. InvokeCommand can be used to invoke basic jQuery methods, but I don't think it can be used to invoke a Drupal JavaScript function. I'll try to look into to see what the options are.

mgifford’s picture

Issue tags: +keyboard focus

I don't know that it can be inserted from PHP code directly at this point.

jessebeach’s picture

What about using the messages API instead of Drupal.announce. I think page messages can be triggered from AJAX response payloads.

mgifford’s picture

You don't just mean adding this to the FileWidgetAjaxController.php
drupal_set_message(t('The file has been successfully uploaded.'), 'status');

I don't know how to set up page messages to be triggered from AJAX response payloads... Any good examples you can think of in Core to emulate.

nod_’s picture

Status: Needs work » Closed (duplicate)
Related issues: +#1824636: Do not move the cursor to the top of the page on ajax calls

Bit more complete solution over at #1824636: Do not move the cursor to the top of the page on ajax calls. closing this.