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.
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.
Related Issues
#1806308: Review Views JavaScript + generic modals for accessibility
Comment | File | Size | Author |
---|---|---|---|
#21 | interdiff-2124397-9-21.txt | 1.01 KB | alexdmccabe |
#21 | drupal-ajax-keyboard-focus-2124397-21.patch | 600 bytes | alexdmccabe |
#19 | 2124397-drupal-file-upload-19-do-not-test.patch | 1.82 KB | hefox |
#9 | 2124397-9-ajax-keyboard-focus.patch | 600 bytes | Sam152 |
#4 | 2124397-4-ajax-keyboard-focus.patch | 868 bytes | Stuart Miller |
Comments
Comment #1
mgiffordThis is going to be a major usability fail for keyboard-only users.
Comment #2
Stuart Miller CreditAttribution: Stuart Miller commentedComment #3
mgiffordAny progress @Stuart?
Comment #4
Stuart Miller CreditAttribution: Stuart Miller commentedI 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.
Comment #5
mgiffordThanks 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.
Comment #6
Stuart Miller CreditAttribution: Stuart Miller commentedGlad 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.
Comment #7
Stuart Miller CreditAttribution: Stuart Miller commentedSetting to needs review to get some community feedback on what the behaviour should be if:
Comment #8
mgiffordIn 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.
Comment #9
Sam152 CreditAttribution: Sam152 commentedThe 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.
Comment #10
mgiffordThis 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?
Comment #11
Stuart Miller CreditAttribution: Stuart Miller commentedThis 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.
Comment #12
mgiffordHopefully we can get this marked RTBC before hand. However, if not, hopefully we can look at it at Twin Cities Drupal Camp.
Comment #13
mgiffordHave 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.
Comment #14
Stuart Miller CreditAttribution: Stuart Miller commentedBrilliant, 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/
Comment #15
mgiffordI think this is good to go. Just tested the functionality again with just the keyboard.
Comment #16
droplet CreditAttribution: droplet commentedDoesn't it will be dead loop on AJax's Autocomplete ? (blur event fired -> back to input -> blur even fired -> back to input )
Comment #17
hefox CreditAttribution: hefox commentedOne 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.
Comment #18
hefox CreditAttribution: hefox commentedForgot --relative :(
Comment #19
hefox CreditAttribution: hefox commentedOOps, needed to change this.$form to this.form for d7
Comment #20
mgiffordDoesn't seem to apply anymore.
Comment #21
alexdmccabeIt still applied for me, but with an offset error. Rerolling to fix it.
Comment #22
mckinzie25 CreditAttribution: mckinzie25 as a volunteer commentedThe 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.
Comment #23
mgiffordGiven 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.
Comment #24
mckinzie25 CreditAttribution: mckinzie25 as a volunteer commentedAs 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?
Comment #25
hrezaei CreditAttribution: hrezaei commentedTBH, 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.
Comment #26
mgiffordThanks @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.
Comment #27
mckinzie25 CreditAttribution: mckinzie25 as a volunteer commentedDrupal.announce() looks pretty nice. That might be the way to do it that is most consistent with the rest of the Drupal interface.
Comment #28
mgiffordOk, 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.phpI don't know where we would insert this into FileWidgetAjaxController.php though. It's JS code.
Comment #29
mckinzie25 CreditAttribution: mckinzie25 as a volunteer commentedInserting 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.
Comment #30
mgiffordI don't know that it can be inserted from PHP code directly at this point.
Comment #31
jessebeach CreditAttribution: jessebeach as a volunteer commentedWhat about using the messages API instead of Drupal.announce. I think page messages can be triggered from AJAX response payloads.
Comment #32
mgiffordYou 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.
Comment #33
nod_Bit more complete solution over at #1824636: Do not move the cursor to the top of the page on ajax calls. closing this.