Autocomplete: ARIA live region as extension, adding a messages option #657

Closed
wants to merge 7 commits into
from

Projects

None yet

3 participants

@jzaefferer
Member

Replacing #652

Starting this pull request to coordinate work between Everett, Hans, Scott and me (could also close this and put it on the wiki, though we should have more code soon). I've just spend a good amount of time reviewing the yahoo.com autocomplete, which is actually pretty similar to our approach. We figured that announcing only "autocomplete popup" is not useful. To make the announcements useful, it should be close to what yahoo.com is doing: "No search results available" or "x search results available, use up and down cursors to navigate". They don't set aria-activedescendent, so navigation gets announced properly as the input's value changes.

Here's a suggestion what the message format could look like:

var defaults = {
    messages: {
        noresults: "No search results",
        xresults: "$1 results available"
        xresults: function(amount) {
            return amount + " result" + (amount > 1 ? "s" : "") + " available, use up/down cursor...";
        }
    }
}

var de = {
    messages: {
        noresults: "Nichts gefunden",
        xresults: function(amount) {
            return amount + " Ergebnisse...";
        }
    }
}

xresults.replace("$1", amount)

If its a string, interpolate, if its a function, call it with all variables. That way we can allow simple interpolation where that's enough, and make it flexible enough to deal with more complex cases. Once Globalize adds support for this (globalizejs/globalize#100), we can rip out the little custom code we'd require here and use Globalize instead.

The other change we should make is to extend menu to allow control over aria attributes. For autocomplete, we don't actually want any of them (see the yahoo.com example), just an unordered list. For Selectmenu, we want listbox and role option on the items.

This would make the autocomplete work in the current generation of screenreaders, following the more pragmatic approach that yahoo.com did on their homepage (not in YUI though). At the same time, we should get screenreader implementations to do more with the aria attributes for that. Currently aria-autocomplete and aria-owns are pretty much completely ignored. If that changes, we could get rid of the live region hack.

@jzaefferer jzaefferer referenced this pull request May 15, 2012
Closed

Autocomplete aria #652

@scottgonzalez scottgonzalez commented on an outdated diff May 15, 2012
ui/jquery.ui.autocomplete.js
@@ -532,4 +532,37 @@ $.extend( $.ui.autocomplete, {
}
});
+
+// live region extension, adding a `messages` option
+$.widget( "ui.autocomplete", $.ui.autocomplete, {
+ options: {
+ messages: {
+ noResults: "No search results.",
+ results: function(amount) {
+ return amount + " result" + ( amount > 1 ? "s" : "" ) + " are available, use up and down arrow keys to navigate between them.";
@scottgonzalez
scottgonzalez May 15, 2012 jQuery member

This pluralization is wrong. You have to switch between "are" and "is" as well. We also need better wording for the navigation because you cannot navigate between one item.

@scottgonzalez scottgonzalez commented on an outdated diff May 15, 2012
ui/jquery.ui.autocomplete.js
@@ -532,4 +532,37 @@ $.extend( $.ui.autocomplete, {
}
});
+
+// live region extension, adding a `messages` option
+$.widget( "ui.autocomplete", $.ui.autocomplete, {
+ options: {
+ messages: {
+ noResults: "No search results.",
+ results: function(amount) {
+ return amount + " result" + ( amount > 1 ? "s" : "" ) + " are available, use up and down arrow keys to navigate between them.";
+ }
+ }
+ },
+ _create: function() {
+ this._superApply( arguments );
@scottgonzalez
scottgonzalez May 15, 2012 jQuery member

_create() doesn't accept any parameters, this should just be this._super().

@scottgonzalez scottgonzalez commented on an outdated diff May 15, 2012
ui/jquery.ui.autocomplete.js
@@ -532,4 +532,37 @@ $.extend( $.ui.autocomplete, {
}
});
+
+// live region extension, adding a `messages` option
+$.widget( "ui.autocomplete", $.ui.autocomplete, {
+ options: {
+ messages: {
+ noResults: "No search results.",
+ results: function(amount) {
+ return amount + " result" + ( amount > 1 ? "s" : "" ) + " are available, use up and down arrow keys to navigate between them.";
+ }
+ }
+ },
+ _create: function() {
+ this._superApply( arguments );
+ this.liveRegion = $( "<span role='status' class='ui-helper-hidden-accessible' aria-live='polite'></span>" ).insertAfter( this.element );
@scottgonzalez
scottgonzalez May 15, 2012 jQuery member

This should follow our normal creation syntax: $( "<span>", attrs ) or $( "<span>" ).attr( ... ).addClass( ... ).

@scottgonzalez scottgonzalez and 2 others commented on an outdated diff May 15, 2012
ui/jquery.ui.autocomplete.js
+ options: {
+ messages: {
+ noResults: "No search results.",
+ results: function(amount) {
+ return amount + " result" + ( amount > 1 ? "s" : "" ) + " are available, use up and down arrow keys to navigate between them.";
+ }
+ }
+ },
+ _create: function() {
+ this._superApply( arguments );
+ this.liveRegion = $( "<span role='status' class='ui-helper-hidden-accessible' aria-live='polite'></span>" ).insertAfter( this.element );
+ this.element.removeAttr( "aria-autocomplete aria-haspopup" );
+ this.menu.element.removeAttr( "role" );
+ this._bind( this.menu.element, {
+ menufocus: function() {
+ this.menu.element.removeAttr( "aria-activedescendant" );
@scottgonzalez
scottgonzalez May 15, 2012 jQuery member

Wow, that's annoying that we have to deal with this even after removing the role.

@jzaefferer
jzaefferer May 15, 2012 jQuery member

It doesn't stop IE from moving focus to the menu, so we actually need to update menu. Could also address the selectmenu issue, which currently has to override various roles.

@jzaefferer
jzaefferer May 15, 2012 jQuery member

How about...

// to disable (for autocomplete)
aria: false 
// for selectmenu
aria: { role: "listbox", items: "option" }
// as default
aria: { role: "menu", items: "menuitem" }
@scottgonzalez
scottgonzalez May 15, 2012 jQuery member

Could we just have a role option and infer the item roles from that? There have been requests for a role option on dialog as well, so that the type of dialog can be defined.

@hanshillen
hanshillen May 15, 2012

I kind of like the aria option, since it provides a dedicated object that can be used for any numer of aria related options without cluttering the option set itself. For example, for some complex widgets there may be various sub parts that each need independent configurable roles, names, descriptions, relationships etc. The aria option could be used for other things as well, e.g. whether to use activedescendants or a roving tabindex, specific strings for live regions, aria-controls values, and so on. It's something that can be useful future widgets. Having said that, in this particular case a role option would suffice: leaving it empty would mean no activedescendant is used.

@scottgonzalez
scottgonzalez May 15, 2012 jQuery member

Do you have examples of complex widgets where this would be necessary? I don't like the idea of putting ARIA related strings under and aria option and other strings under a messages option. When would you use a role on the menu, but not use activedescendant? Why would you disable roving tabindex?

@hanshillen
hanshillen May 15, 2012

I wasn't thinking about specific widgets at this point, but more as a common way to configure widgets with common ARIA attributes (specifically relationship attributes such as aria-labelledby, describedby, controls, etc.). For example, a slider may have multiple handles that all need to reference the same external ID as part of their aria-labelledby or aria-controls value. Or if dialog were to get more toolbar buttons. In some cases you can add such attributes on the original element, but in other cases (e.g. DatePicker) the relevant elements are created from scratch.

I just mentioned roving tab index in case we or someone else wants to have a widget where the developer can choose whether activedescendant or a roving tabindex should be used to focus sub elements (e.g. because a roving tabindex is more backwards compatible.

I agree on not spreading strings between options, if there is already a messages option then aria strings should go in there as well. Again, in this particular case a role option would suffice. So that means the menu widget could be configured with a role of menu (implies menuitem as inner role), listbox (implies option as inner role), or null (implies no inner role, no activedescendant).

@jzaefferer
jzaefferer May 15, 2012 jQuery member

Seems like we don't really have enough data at this point to justify a container option like aria, so I'll implement the simple role on menu. We can handle that as experimental, just as we do with the autocomplete changes itself.

@scottgonzalez scottgonzalez commented on an outdated diff May 15, 2012
ui/jquery.ui.autocomplete.js
+ _create: function() {
+ this._superApply( arguments );
+ this.liveRegion = $( "<span role='status' class='ui-helper-hidden-accessible' aria-live='polite'></span>" ).insertAfter( this.element );
+ this.element.removeAttr( "aria-autocomplete aria-haspopup" );
+ this.menu.element.removeAttr( "role" );
+ this._bind( this.menu.element, {
+ menufocus: function() {
+ this.menu.element.removeAttr( "aria-activedescendant" );
+ }
+ });
+ },
+ _suggest: function( content ) {
+ this.liveRegion.text( this.options.messages.results( content.length ) );
+ this._superApply( arguments );
+ },
+ _close: function() {
@scottgonzalez
scottgonzalez May 15, 2012 jQuery member

This is wrong. We don't want to update the live region on close. The user shouldn't hear that there are no results immediately after they pick an item or tab out of the field. You should probably proxy __response() instead of _suggest() and _close().

jzaefferer added some commits May 15, 2012
@jzaefferer jzaefferer Autocomplete: Fix pluralization, DOM element creation, overwrite __re…
…sponse to not update live region when tabbing out of the input
0622b25
@jzaefferer jzaefferer Autocomplete/Menu: Add role option to menu
Allows control over role attribute on menu and menu items.
Set to null for autocomplete to disable those attributes and
aria-activedescdenent. Selectmenu can set it to 'listbox' to get
'option' as item roles.
88a19bc
@scottgonzalez scottgonzalez commented on an outdated diff May 15, 2012
ui/jquery.ui.autocomplete.js
@@ -532,4 +534,38 @@ $.extend( $.ui.autocomplete, {
}
});
+
+// live region extension, adding a `messages` option
+$.widget( "ui.autocomplete", $.ui.autocomplete, {
+ options: {
+ messages: {
+ noResults: "No search results.",
+ results: function(amount) {
+ return amount + " result" + ( amount > 1 ? "s" : "" ) + " " + ( amount > 1 ? "are" : "is" ) + " available, use up and down arrow keys to navigate.";
@scottgonzalez
scottgonzalez May 15, 2012 jQuery member

Might as well merge these conditionals together. It'd be easier to read as:
return amount + ( amount > 1 ? " results are" : " result is" ) + " available...";

@scottgonzalez scottgonzalez commented on an outdated diff May 15, 2012
ui/jquery.ui.autocomplete.js
@@ -532,4 +534,38 @@ $.extend( $.ui.autocomplete, {
}
});
+
+// live region extension, adding a `messages` option
+$.widget( "ui.autocomplete", $.ui.autocomplete, {
+ options: {
+ messages: {
+ noResults: "No search results.",
+ results: function(amount) {
+ return amount + " result" + ( amount > 1 ? "s" : "" ) + " " + ( amount > 1 ? "are" : "is" ) + " available, use up and down arrow keys to navigate.";
+ }
+ }
+ },
+ _create: function() {
+ this._super();
+ this.liveRegion = $( "<span>", {
+ role: "status",
@scottgonzalez
scottgonzalez May 15, 2012 jQuery member

double indent here so that the indentation is correct on line 553.

@scottgonzalez scottgonzalez commented on an outdated diff May 15, 2012
ui/jquery.ui.autocomplete.js
+ messages: {
+ noResults: "No search results.",
+ results: function(amount) {
+ return amount + " result" + ( amount > 1 ? "s" : "" ) + " " + ( amount > 1 ? "are" : "is" ) + " available, use up and down arrow keys to navigate.";
+ }
+ }
+ },
+ _create: function() {
+ this._super();
+ this.liveRegion = $( "<span>", {
+ role: "status",
+ "aria-live": "polite"
+ })
+ .addClass( "ui-helper-hidden-accessible" )
+ .insertAfter( this.element );
+ this.element.removeAttr( "aria-autocomplete aria-haspopup" );
@scottgonzalez
scottgonzalez May 15, 2012 jQuery member

Only one attribute per call; we still support 1.6.

@scottgonzalez scottgonzalez commented on an outdated diff May 15, 2012
ui/jquery.ui.autocomplete.js
+ }
+ }
+ },
+ _create: function() {
+ this._super();
+ this.liveRegion = $( "<span>", {
+ role: "status",
+ "aria-live": "polite"
+ })
+ .addClass( "ui-helper-hidden-accessible" )
+ .insertAfter( this.element );
+ this.element.removeAttr( "aria-autocomplete aria-haspopup" );
+ },
+ __response: function( content ) {
+ var message;
+ if ( !this.options.disabled && content && content.length && !this.cancelSearch ) {
@scottgonzalez
scottgonzalez May 15, 2012 jQuery member

I think this actually needs to be smarter than the existing implementation. If the widget is disabled or cancelSearch is set, then we shouldn't announce anything.

@scottgonzalez scottgonzalez commented on an outdated diff May 15, 2012
ui/jquery.ui.menu.js
focus: function( event, item ) {
- var nested, borderTop, paddingTop, offset, scroll, elementHeight, itemHeight;
+ var nested, borderTop, paddingTop, offset, scroll, elementHeight, itemHeight, focussed;
@scottgonzalez
scottgonzalez May 15, 2012 jQuery member

American preferred spelling is focused.

@jzaefferer
Member

Rebased into f4b2d7a and landed in master.

@jzaefferer jzaefferer closed this May 16, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment