Permalink
Browse files

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

…. Fixes #7840 - Autocomplete: popup results not read by screen-readers
  • Loading branch information...
1 parent c0f6b0c commit f4b2d7a4115814b64ff291e3518fe15f2dfbe390 @jzaefferer jzaefferer committed May 15, 2012
@@ -47,7 +47,8 @@
}
},
focus: function() {
- // prevent value inserted on focus
+ // prevent value inserted on focus, update liveRegion instead
+ $( this ).data( "autocomplete" ).liveRegion.text( ui.item.label );
return false;
},
select: function( event, ui ) {
@@ -59,8 +59,9 @@
response( $.ui.autocomplete.filter(
availableTags, extractLast( request.term ) ) );
},
- focus: function() {
- // prevent value inserted on focus
+ focus: function( event, ui ) {
+ // prevent value inserted on focus, update liveRegion instead
+ $( this ).data( "autocomplete" ).liveRegion.text( ui.item.label );
return false;
},
select: function( event, ui ) {
@@ -4,6 +4,10 @@ TestHelpers.commonWidgetTests( "autocomplete", {
autoFocus: false,
delay: 300,
disabled: false,
+ messages: {
+ noResults: "No search results.",
+ results: $.ui.autocomplete.prototype.options.messages.results
+ },
minLength: 1,
position: {
my: "left top",
@@ -6,6 +6,7 @@ TestHelpers.commonWidgetTests( "menu", {
my: "left top",
at: "right top"
},
+ role: "menu",
// callbacks
blur: null,
@@ -60,13 +60,7 @@ $.widget( "ui.autocomplete", {
this.element
.addClass( "ui-autocomplete-input" )
- .attr( "autocomplete", "off" )
- // TODO verify these actually work as intended
- .attr({
- role: "textbox",
- "aria-autocomplete": "list",
- "aria-haspopup": "true"
- });
+ .attr( "autocomplete", "off" );
this._bind({
keydown: function( event ) {
@@ -188,7 +182,9 @@ $.widget( "ui.autocomplete", {
.appendTo( this.document.find( this.options.appendTo || "body" )[0] )
.menu({
// custom key handling for now
- input: $()
+ input: $(),
+ // disable ARIA support, the live region takes care of that
+ role: null
})
.zIndex( this.element.zIndex() + 1 )
.hide()
@@ -532,4 +528,40 @@ $.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 + ( amount > 1 ? " results are" : " result 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 );
+ },
+ __response: function( content ) {
+ var message;
+ this._superApply( arguments );
+ if ( this.options.disabled || this.cancelSearch) {
+ return;
+ }
+ if ( content && content.length ) {
+ message = this.options.messages.results( content.length );
+ } else {
+ message = this.options.messages.noResults;
+ }
+ this.liveRegion.text( message );
+ }
+});
+
+
}( jQuery ));
View
@@ -26,6 +26,7 @@ $.widget( "ui.menu", {
my: "left top",
at: "right top"
},
+ role: "menu",
// callbacks
blur: null,
@@ -42,7 +43,7 @@ $.widget( "ui.menu", {
.addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" )
.attr({
id: this.menuId,
- role: "menu",
+ role: this.options.role,
tabIndex: 0
})
// need to catch all clicks on disabled menu
@@ -267,7 +268,7 @@ $.widget( "ui.menu", {
.addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" )
.hide()
.attr({
- role: "menu",
+ role: this.options.role,
"aria-hidden": "true",
"aria-expanded": "false"
});
@@ -281,7 +282,7 @@ $.widget( "ui.menu", {
.children( "a" )
.addClass( "ui-corner-all" )
.attr( "tabIndex", -1 )
- .attr( "role", "menuitem" )
+ .attr( "role", this._itemRole() )
.attr( "id", function( i ) {
return menuId + "-" + i;
});
@@ -302,8 +303,15 @@ $.widget( "ui.menu", {
});
},
+ _itemRole: function() {
+ return {
+ menu: "menuitem",
+ listbox: "option"
+ }[ this.options.role ];
+ },
+
focus: function( event, item ) {
- var nested, borderTop, paddingTop, offset, scroll, elementHeight, itemHeight;
+ var nested, borderTop, paddingTop, offset, scroll, elementHeight, itemHeight, focused;
this.blur( event, event && event.type === "focus" );
if ( this._hasScroll() ) {
@@ -322,10 +330,12 @@ $.widget( "ui.menu", {
}
this.active = item.first();
- this.element.attr( "aria-activedescendant",
- this.active.children( "a" )
- .addClass( "ui-state-focus" )
- .attr( "id" ) );
+ focused = this.active.children( "a" ).addClass( "ui-state-focus" );
+ // only update aria-activedescendant if there's a role
+ // otherwise we assume focus is managed elsewhere
+ if ( this.options.role ) {
+ this.element.attr( "aria-activedescendant", focused.attr( "id" ) );
+ }
// highlight active parent menu item, if any
this.active.parent().closest( ".ui-menu-item" ).children( "a:first" ).addClass( "ui-state-active" );

0 comments on commit f4b2d7a

Please sign in to comment.