The root of the problem
It seems quite puzzling that screen readers would continue to read the old contents of a page after an asynchronous update. All the more curious that synchronous interactions do trigger an update of their buffer. Why haven't assistive technology providers fixed this bug after the steady rise of Ajax technologies we've seen these last years?
The sad answer is that they couldn't fix it easily: before Internet Explorer 8, the browser wouldn't raise any event that screen readers could handle when the DOM is modified. This explains why synchronous updates that are triggered by user interaction such as a pressed button or a navigated link can and do cause an update of their buffer, because they are triggering events that screen readers can handle. But if the update to the DOM happens asynchronously, detached from the user interaction that caused it, no event is raised and the screen reader has no clue that something changed. The latest state of the page it knows about is the one at the moment of the interaction, not the one after the actual update was performed. This is true of XmlHttpRequest (or XHR) requests as well as of timed updates.
Note: The JAWS 9 screen reader does work around that problem by polling and monitoring changes on the DOM, but other popular screen readers such as Window Eyes don't.
A technique that was invented by George Young from Microsoft involves simulating browser navigation, which is another case where screen readers update their buffer. What we need to do is to trigger navigation from script without causing our page to actually navigate away, and also without creating an entry in browser history. The trick to achieve that is to navigate a hidden iframe using
location.replace, which does exactly what we want, i.e. navigate without creating history points.
To encapsulate that solution, which would otherwise remain relatively complex to set-up, I've built a convenient control that handles it all automatically. Just drop it on your page and it will take care of rendering the iframe to the page and navigate it every time an asynchronous postback comes back from the server. Here's the markup that you need on the page:
And that's it.
Well, almost. One thing that I didn't mention yet about the iframe navigating trick is that every time the navigation happens, Internet Explorer emits a "click" sound. While it's hardly noticeable when it happens due to an explicit user action, it can become an issue when it happens long after the interaction or without a user interaction altogether (like can be the case if you're updating contents based on a timer). It becomes even worse if you're doing several partial updates in rapid succession. That's why most applications that use this trick make it opt-in. The way you would do that with this control would be by making it invisible initially (set
Visible to false) and enable the user to turn it on by the click of a button. You can even do that in a partial update as this code demonstrates:
Listing 2: Putting the accessibility helper in an update panel
What about “pure Ajax”?
If you're not using UpdatePanel, or if you're using it in conjunction with "pure Ajax" asynchronous calls that do client-side DOM manipulations in response to a web service call, you still face the problem of updating the screen reader's buffer. To handle this scenario, the AccessibilityHelper control provides a client-side method that you can call every time you need to notify the screen reader of a change in the DOM:
updateBuffer just navigates the hidden iframe, but this time you need to do it explicitly because the control can't guess when it makes sense like it could in the case of UpdatePanel.
A few more things to know about screen reader accessibility
There are still a few more things to know about screen reader accessibility even when using this control. The control takes care of telling the screen reader to refresh its buffer, but it doesn't do anything to give hints to the user about what exactly on the page was updated. Vision impaired users typically access the page as a linear entity whereas others view it more as a two-dimensional graphical entity. The first time the page is read to them, they will form a mental image of the structure of the page and of its different sections. That's why it's important to structure pages with easily identifiable sections (bonus points for providing anchors and links to skip the navigation sections). In particular, using
H6 tags to identify section heads provides easier navigation as most screen readers provide shortcuts to quickly navigate the kind of table of contents this provides to the page. When an asynchronous update happened and the iframe has navigated, the user will need to know what part of the page they need to re-read. The best way of doing that is probably to make interactive UI elements such as buttons and links very explicit about what exactly they will cause to happen and what part of the page will be updated as a result of clicking them. Of course, making the text of the button or link very explicit may make the UI heavier and more verbose than you'd like, so I recommend putting that additional information in the
ToolTip attribute if it's a server control, or in the
title attribute if it's a client tag.
Finally, a note about focus. Screen reader users will expect the focus to remain stable after any interaction. You should refrain from setting focus to the part of the page that you just updated. As much as possible, focus should remain on the element that the user manipulated last. The user should be in control of moving the focus to the updated part of the page, which makes the previous recommendation about informing him about what exactly happened all the more important.
Wrapping it all together
Here's a simple page that applies all the recommendations in this article to some simple client-side and server-side Ajax updates:
Listing 4: Wrapping it all together: AccessibleUpdatePanel.aspx
In this article, we've learned about the reasons behind accessibility difficulties that Ajax creates. We've learned how to work around them using a simple server control as well as a few best practices to make a page as comfortable as possible for screen reader users while maintaining the Ajax functionality intact.
Bertrand Le Roy is a program manager in the ASP.NET team.
This author has published 3 articles on DotNetSlackers. View other articles or the complete profile here.
You might also be interested in the following related blog posts
Building Interactive User Interfaces with Microsoft ASP.NET AJAX: Triggering Full Page Postbacks From An UpdatePanel
Mixing Silverlight and MS ASP.NET AJAX 3.5 in the same web application.
VSTS 2008, Web Tests and using Fiddler
Multiple UpdatePanels - Who caused the update ?
Impressive author lineup over at DotNetSlackers
AJAX UpdatePanel - "Statefull" Control Update Trigger
April 11th Links: ASP.NET, ASP.NET AJAX, ASP.NET MVC, Visual Studio, Silverlight
How to make UpdatePanel accessible
A New AJAX Approach - Client-side Controls Invoking Web Services
Results for Ajax usage among .NET developers
Please login to rate or to leave a comment.