Skip to content
Permalink
Browse files

Selectmenu: implement new ARIA spec

  • Loading branch information
fnagel committed Jan 22, 2012
1 parent 50c3083 commit 4dbbd0598a58277816289441f3278a65025515d3
Showing with 60 additions and 47 deletions.
  1. +25 −15 tests/unit/selectmenu/selectmenu_core.js
  2. +3 −1 tests/unit/selectmenu/selectmenu_methods.js
  3. +32 −31 ui/jquery.ui.selectmenu.js
@@ -12,22 +12,26 @@ test("accessibility", function () {
ul = menu.children("ul"),
links = ul.find("li.ui-menu-item a");

expect(9 + links.length * 2);
expect(13 + links.length * 2);

equals( "true", link.attr("aria-haspopup"), "button link aria-haspopup" );
equals( "button", link.attr("role"), "button link role" );
equals( ul.attr("id"), link.attr("aria-owns"), "button link aria-owns" );
equals( 0, link.attr("tabindex"), "button link tabindex" );
equals( link.attr("role"), "combobox", "button link role" );
equals( link.attr("aria-haspopup"), "true", "button link aria-haspopup" );
equals( link.attr("aria-expanded"), "false", "button link aria-expanded" );
equals( link.attr("aria-autocomplete"), "list", "button link aria-autocomplete" );
equals( link.attr("aria-activedescendant"), links.eq(element[0].selectedIndex).attr("id"), "button link aria-activedescendant" );
equals( link.attr("aria-owns"), ul.attr("id"), "button link aria-owns" );
equals( link.attr("tabindex"), 0, "button link tabindex" );

equals( "true", ul.attr("aria-hidden"), "menu aria-hidden" );
equals( link.attr("id"), ul.attr("aria-labelledby"), "menu aria-labelledby" );
equals( "menubox", ul.attr("role"), "menu role" );
equals( 0, ul.attr("tabindex"), "menu tabindex" );
equals( links.eq(element[0].selectedIndex).attr("id"), ul.attr("aria-activedescendant"), "menu aria-activedescendant" );
equals( ul.attr("role"), "listbox", "menu role" );
equals( ul.attr("aria-labelledby"), link.attr("id"), "menu aria-labelledby" );
equals( ul.attr("aria-hidden"), "true", "menu aria-hidden" );
equals( ul.attr("tabindex"), 0, "menu tabindex" );
equals( ul.attr("aria-activedescendant"), links.eq(element[0].selectedIndex).attr("id"), "menu aria-activedescendant" );
$.each( links, function(index){
equals( "option", $(this).attr("role"), "menu link #" + index +" role" );
equals( -1, $(this).attr("tabindex"), "menu link #" + index +" tabindex" );
equals( $(this).attr("role"), "option", "menu link #" + index +" role" );
equals( $(this).attr("tabindex"), -1, "menu link #" + index +" tabindex" );
});
equals( links.eq(element[0].selectedIndex).attr("aria-selected"), "true", "selected menu link aria-selected" );
});


@@ -42,23 +46,29 @@ $.each([
}
], function( i, settings ) {
test("state synchronization - " + settings.type, function () {
expect(5);
expect(10);

var element = $(settings.selector).selectmenu(),
widget = element.selectmenu("widget"),
button = widget.filter(".ui-selectmenu-button"),
menu = widget.filter(".ui-selectmenu-menu"),
link = button.find("a"),
ul = menu.children("ul"),
links = ul.find("li.ui-menu-item a"),
selected = element.find("option:selected");

equals( button.text(), selected.text(), "inital button text" );

link.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
equals( ul.attr("aria-activedescendant"), links.eq(element[0].selectedIndex).attr("id"), "after keydown menu aria-activedescendant" );
equals( link.attr("aria-activedescendant"), links.eq(element[0].selectedIndex).attr("id"), "after keydown button link aria-activedescendant" );
equals( links.eq(element[0].selectedIndex).attr("aria-selected"), "true", "after keydown selected menu link aria-selected" );
equals( element.find("option:selected").val(), selected.next("option").val() , "after keydown original select state" );
equals( button.text(), selected.next("option").text(), "after keydown button text" );

link.simulate( "click" );
menu.find("a").last().simulate( "mouseover" ).trigger( "click" );
equals( ul.attr("aria-activedescendant"), links.eq(element[0].selectedIndex).attr("id"), "after click menu aria-activedescendant" );
equals( link.attr("aria-activedescendant"), links.eq(element[0].selectedIndex).attr("id"), "after click button link aria-activedescendant" );
equals( links.eq(element[0].selectedIndex).attr("aria-selected"), "true", "after click selected menu link aria-selected" );
equals( element.find("option:selected").val(), element.find("option").last().val(), "after click original select state" );
equals( button.text(), element.find("option").last().text(), "after click button text" );
});
@@ -29,7 +29,7 @@ test( "open / close", function() {


test("enable / disable", function () {
expect(12);
expect(14);

var element = $('#speed').selectmenu(),
widget = element.selectmenu("widget"),
@@ -39,6 +39,7 @@ test("enable / disable", function () {

element.selectmenu("disable")
ok( element.selectmenu("option", "disabled"), "disable: widget option" );
equals( element.attr("disabled"), "disabled", "disable: native select disabled" );
equals( button.attr("aria-disabled"), "true", "disable: button wrapper ARIA" );
equals( link.attr("aria-disabled"), "true", "disable: button ARIA" );
equals( link.attr("tabindex"), -1, "disable: button tabindex" );
@@ -47,6 +48,7 @@ test("enable / disable", function () {

element.selectmenu("enable")
ok( !element.selectmenu("option", "disabled"), "enable: widget option" );
equals( element.attr("disabled"), undefined, "enable: native select disabled" );
equals( button.attr("aria-disabled"), "false", "enable: button wrapper ARIA" );
equals( link.attr("aria-disabled"), "false", "enable: button ARIA" );
equals( link.attr("tabindex"), 0, "enable: button tabindex" );
@@ -54,6 +54,7 @@ $.widget( "ui.selectmenu", {
this._bind( this.button, this._buttonEvents );

this._drawMenu();
this.refresh();

if ( this.options.disabled ) {
this.disable();
@@ -74,15 +75,18 @@ $.widget( "ui.selectmenu", {
css: {
width: this.element.outerWidth()
},
'aria-expanded': false,
'aria-autocomplete': 'list',
'aria-owns': this.ids.menu,
'aria-haspopup': true
})
.button({
label: this.element.find( "option:selected" ).text(),
icons: {
primary: ( this.options.dropdown ? 'ui-icon-triangle-1-s' : 'ui-icon-triangle-2-n-s' )
}
});
})
// change ARIA role
.attr( 'role', 'combobox' );

// wrap and insert new button
this.buttonWrap = $( '<span />' )
@@ -122,7 +126,9 @@ $.widget( "ui.selectmenu", {
var item = ui.item.data( "item.selectmenu" ),
oldIndex = that.element[0].selectedIndex;

that._setIndex( item.index );
// change native select element
that.element[0].selectedIndex = item.index;
that._setSelected();
that._trigger( "select", event, { item: item } );

if ( item.index != oldIndex ) {
@@ -145,7 +151,7 @@ $.widget( "ui.selectmenu", {
}
})
// change ARIA role
.attr( 'role', 'menubox' );
.attr( 'role', 'listbox' );

// change menu styles?
this._setOption( "dropdown", this.options.dropdown );
@@ -165,38 +171,27 @@ $.widget( "ui.selectmenu", {

this._readOptions();
this._renderMenu( this.menu, this.items );

this.menu.menu( "refresh" );
// button option label wont work here
this.button.children( '.ui-button-text' ).text( this.items[ this.element[0].selectedIndex ].label );

// adjust ARIA
this.menu.find( "li" ).not( '.ui-selectmenu-optgroup' ).find( 'a' ).attr( 'role', 'option' );
this.menu.attr( "aria-activedescendant" , this.menu.find( "li.ui-menu-item a" ).eq( this.element[0].selectedIndex ).attr( "id" ) );
this._getItems().find( 'a' ).attr( 'role', 'option' );
this._setSelected();

// set and transfer disabled state
this._getCreateOptions();
if ( this.options.disabled ) {
this.disable();
} else {
this.enable()
}
this._setOption( "disabled", this.options.disabled );
},

open: function( event ) {
if ( !this.options.disabled ) {
// init menu when initial opened
if ( !this.wasOpen ) {
this.refresh();
this.wasOpen = true;
}

var currentItem = this._getSelectedItem();

this._toggleButtonStyle();

this.menuWrap.addClass( 'ui-selectmenu-open' );
this.menu.attr("aria-hidden", false);
this.button.attr("aria-expanded", true);
// needs to be fired after the document click event has closed all other Selectmenus
// otherwise the current item is not indicated
// TODO check if this should be handled by Menu
@@ -236,7 +231,8 @@ $.widget( "ui.selectmenu", {
this._toggleButtonStyle();

this.menuWrap.removeClass( 'ui-selectmenu-open' );
this.menu.attr("aria-hidden", true);
this.menu.attr( "aria-hidden", true );
this.button.attr( "aria-expanded", false );
this.isOpen = false;

if ( focus ) {
@@ -282,14 +278,9 @@ $.widget( "ui.selectmenu", {
},

_move: function( direction, event ) {
// init menu when not done yet
if ( !this.wasOpen ) {
this.refresh();
this.wasOpen = true;
}
if ( direction == "first" || direction == "last" ) {
// set focus manually for first or last item
this.menu.menu( "focus", event, this.menu.find( "li" ).not( '.ui-selectmenu-optgroup' )[ direction ]() );
this.menu.menu( "focus", event, this._getItems()[ direction ]() );
} else {
// if menu is closed we need to focus the element first to indicate correct element
if ( !this.isOpen ) {
@@ -306,7 +297,11 @@ $.widget( "ui.selectmenu", {
},

_getSelectedItem: function() {
return this.menu.find( "li" ).not( '.ui-selectmenu-optgroup' ).eq( this.element[0].selectedIndex );
return this._getItems().eq( this.element[0].selectedIndex );
},

_getItems: function() {
return this.menu.find( "li" ).not( '.ui-selectmenu-optgroup' );
},

_toggle: function( event ) {
@@ -360,6 +355,7 @@ $.widget( "ui.selectmenu", {
break;
case $.ui.keyCode.HOME:
case $.ui.keyCode.PAGE_UP:
console.log("test");
this._move( "first", event );
break;
case $.ui.keyCode.END:
@@ -376,9 +372,14 @@ $.widget( "ui.selectmenu", {
}
},

_setIndex: function( index ) {
this.element[0].selectedIndex = index;
this.button.button( "option", "label", this.items[ index ].label );
_setSelected: function() {
var item = this._getSelectedItem().find("a");
// update button text
this.button.button( "option", "label", item.text() );
// change ARIA attr
this.button.add( this.menu ).attr( "aria-activedescendant" , item.attr( "id" ) );
this._getItems().find("a").attr( "aria-selected", false );
item.attr( "aria-selected", true );
},

_setOption: function( key, value ) {

1 comment on commit 4dbbd05

@fnagel

This comment has been minimized.

Copy link
Member Author

@fnagel fnagel commented on 4dbbd05 Jan 22, 2012

In order to fulfill the new ARIA specs I needed to remove lazy (menu widget) refresh (on first usage) because otherwise I have no ID attributes to use with aria-activedescendant.

Please sign in to comment.
You can’t perform that action at this time.