1
0
mirror of synced 2024-11-26 14:56:07 +03:00

Single select accessibility

Now the Select2 instance is correctly recognized as a combobox, and
the selected option is read aloud when it is focused, just like in
a standard select box.

This works by generating semi-random ids that are used for the
ARIA attributes.  These are not intended to be consistent by any
means, they are just generated to make the instance accessible by
screen readers.
This commit is contained in:
Kevin Brown 2014-10-19 16:13:57 -04:00
parent 8dfd6d6960
commit 9ada3712f7
10 changed files with 283 additions and 13 deletions

View File

@ -258,7 +258,11 @@ define('select2/results',[
} }
if (data.id == null) { if (data.id == null) {
$option.removeClass('aria-selected'); $option.removeAttr('aria-selected');
}
if (data._resultId != null) {
$option.attr('id', data._resultId);
} }
$option.data('data', data); $option.data('data', data);
@ -363,6 +367,8 @@ define('select2/results',[
var $next = $options.eq(nextIndex); var $next = $options.eq(nextIndex);
$next.trigger('mouseenter'); $next.trigger('mouseenter');
console.log($next.offset().top, self.$results.parent().scrollTop());
//self.$results.parents().scrollTop($next.offset().top);
}); });
this.$results.on('mouseup', '.option[aria-selected]', function (evt) { this.$results.on('mouseup', '.option[aria-selected]', function (evt) {
@ -482,13 +488,24 @@ define('select2/selection/single',[
SingleSelection.prototype.render = function () { SingleSelection.prototype.render = function () {
var $selection = $( var $selection = $(
'<span class="single-select" tabindex="0">' + '<span class="single-select" tabindex="0" role="combobox" ' +
'aria-autocomplete="list" aria-haspopup="true" aria-expanded="false">' +
'<span class="rendered-selection"></span>' + '<span class="rendered-selection"></span>' +
'</span>' '</span>'
); );
$selection.attr('title', this.$element.attr('title')); $selection.attr('title', this.$element.attr('title'));
var id = 'select2-container-';
for (var i = 0; i < 4; i++) {
var r = Math.floor(Math.random() * 16);
id += r.toString(16);
}
$selection.find('.rendered-selection').attr('id', id);
$selection.attr('aria-labelledby', id);
this.$selection = $selection; this.$selection = $selection;
return $selection; return $selection;
@ -510,6 +527,16 @@ define('select2/selection/single',[
}); });
}); });
container.on('open', function () {
// When the dropdown is open, aria-expended="true"
self.$selection.attr('aria-expanded', 'true');
});
container.on('close', function () {
// When the dropdown is closed, aria-expended="false"
self.$selection.attr('aria-expanded', 'false');
});
this.$selection.on('focus', function (evt) { this.$selection.on('focus', function (evt) {
// User focuses on the container // User focuses on the container
}); });
@ -564,6 +591,10 @@ define('select2/selection/single',[
var formatted = this.display(selection); var formatted = this.display(selection);
this.$selection.find('.rendered-selection').html(formatted); this.$selection.find('.rendered-selection').html(formatted);
if (data[0]._resultId != null) {
this.$selection.attr('aria-activedescendent', data[0]._resultId);
}
}; };
return SingleSelection; return SingleSelection;
@ -727,6 +758,25 @@ define('select2/data/base',[
// Can be implemented in subclasses // Can be implemented in subclasses
}; };
BaseAdapter.prototype.generateResultId = function (data) {
var id = '';
for (var i = 0; i < 4; i++) {
var r = Math.floor(Math.random() * 16);
id += r.toString(16);
}
if (data.id != null) {
id += '-' + data.id.toString();
} else {
for (var s = 0; s < 4; s++) {
var idChar = Math.floor(Math.random() * 16);
id += idChar.toString(16);
}
}
return id;
};
return BaseAdapter; return BaseAdapter;
}); });
@ -878,6 +928,10 @@ define('select2/data/select',[
data.children = children; data.children = children;
} }
if (data.id) {
data._resultId = this.generateResultId(data);
}
$option.data('data', data); $option.data('data', data);
} }

View File

@ -258,7 +258,11 @@ define('select2/results',[
} }
if (data.id == null) { if (data.id == null) {
$option.removeClass('aria-selected'); $option.removeAttr('aria-selected');
}
if (data._resultId != null) {
$option.attr('id', data._resultId);
} }
$option.data('data', data); $option.data('data', data);
@ -363,6 +367,8 @@ define('select2/results',[
var $next = $options.eq(nextIndex); var $next = $options.eq(nextIndex);
$next.trigger('mouseenter'); $next.trigger('mouseenter');
console.log($next.offset().top, self.$results.parent().scrollTop());
//self.$results.parents().scrollTop($next.offset().top);
}); });
this.$results.on('mouseup', '.option[aria-selected]', function (evt) { this.$results.on('mouseup', '.option[aria-selected]', function (evt) {
@ -482,13 +488,24 @@ define('select2/selection/single',[
SingleSelection.prototype.render = function () { SingleSelection.prototype.render = function () {
var $selection = $( var $selection = $(
'<span class="single-select" tabindex="0">' + '<span class="single-select" tabindex="0" role="combobox" ' +
'aria-autocomplete="list" aria-haspopup="true" aria-expanded="false">' +
'<span class="rendered-selection"></span>' + '<span class="rendered-selection"></span>' +
'</span>' '</span>'
); );
$selection.attr('title', this.$element.attr('title')); $selection.attr('title', this.$element.attr('title'));
var id = 'select2-container-';
for (var i = 0; i < 4; i++) {
var r = Math.floor(Math.random() * 16);
id += r.toString(16);
}
$selection.find('.rendered-selection').attr('id', id);
$selection.attr('aria-labelledby', id);
this.$selection = $selection; this.$selection = $selection;
return $selection; return $selection;
@ -510,6 +527,16 @@ define('select2/selection/single',[
}); });
}); });
container.on('open', function () {
// When the dropdown is open, aria-expended="true"
self.$selection.attr('aria-expanded', 'true');
});
container.on('close', function () {
// When the dropdown is closed, aria-expended="false"
self.$selection.attr('aria-expanded', 'false');
});
this.$selection.on('focus', function (evt) { this.$selection.on('focus', function (evt) {
// User focuses on the container // User focuses on the container
}); });
@ -564,6 +591,10 @@ define('select2/selection/single',[
var formatted = this.display(selection); var formatted = this.display(selection);
this.$selection.find('.rendered-selection').html(formatted); this.$selection.find('.rendered-selection').html(formatted);
if (data[0]._resultId != null) {
this.$selection.attr('aria-activedescendent', data[0]._resultId);
}
}; };
return SingleSelection; return SingleSelection;
@ -727,6 +758,25 @@ define('select2/data/base',[
// Can be implemented in subclasses // Can be implemented in subclasses
}; };
BaseAdapter.prototype.generateResultId = function (data) {
var id = '';
for (var i = 0; i < 4; i++) {
var r = Math.floor(Math.random() * 16);
id += r.toString(16);
}
if (data.id != null) {
id += '-' + data.id.toString();
} else {
for (var s = 0; s < 4; s++) {
var idChar = Math.floor(Math.random() * 16);
id += idChar.toString(16);
}
}
return id;
};
return BaseAdapter; return BaseAdapter;
}); });
@ -878,6 +928,10 @@ define('select2/data/select',[
data.children = children; data.children = children;
} }
if (data.id) {
data._resultId = this.generateResultId(data);
}
$option.data('data', data); $option.data('data', data);
} }

View File

@ -9796,7 +9796,11 @@ define('select2/results',[
} }
if (data.id == null) { if (data.id == null) {
$option.removeClass('aria-selected'); $option.removeAttr('aria-selected');
}
if (data._resultId != null) {
$option.attr('id', data._resultId);
} }
$option.data('data', data); $option.data('data', data);
@ -9901,6 +9905,8 @@ define('select2/results',[
var $next = $options.eq(nextIndex); var $next = $options.eq(nextIndex);
$next.trigger('mouseenter'); $next.trigger('mouseenter');
console.log($next.offset().top, self.$results.parent().scrollTop());
//self.$results.parents().scrollTop($next.offset().top);
}); });
this.$results.on('mouseup', '.option[aria-selected]', function (evt) { this.$results.on('mouseup', '.option[aria-selected]', function (evt) {
@ -10020,13 +10026,24 @@ define('select2/selection/single',[
SingleSelection.prototype.render = function () { SingleSelection.prototype.render = function () {
var $selection = $( var $selection = $(
'<span class="single-select" tabindex="0">' + '<span class="single-select" tabindex="0" role="combobox" ' +
'aria-autocomplete="list" aria-haspopup="true" aria-expanded="false">' +
'<span class="rendered-selection"></span>' + '<span class="rendered-selection"></span>' +
'</span>' '</span>'
); );
$selection.attr('title', this.$element.attr('title')); $selection.attr('title', this.$element.attr('title'));
var id = 'select2-container-';
for (var i = 0; i < 4; i++) {
var r = Math.floor(Math.random() * 16);
id += r.toString(16);
}
$selection.find('.rendered-selection').attr('id', id);
$selection.attr('aria-labelledby', id);
this.$selection = $selection; this.$selection = $selection;
return $selection; return $selection;
@ -10048,6 +10065,16 @@ define('select2/selection/single',[
}); });
}); });
container.on('open', function () {
// When the dropdown is open, aria-expended="true"
self.$selection.attr('aria-expanded', 'true');
});
container.on('close', function () {
// When the dropdown is closed, aria-expended="false"
self.$selection.attr('aria-expanded', 'false');
});
this.$selection.on('focus', function (evt) { this.$selection.on('focus', function (evt) {
// User focuses on the container // User focuses on the container
}); });
@ -10102,6 +10129,10 @@ define('select2/selection/single',[
var formatted = this.display(selection); var formatted = this.display(selection);
this.$selection.find('.rendered-selection').html(formatted); this.$selection.find('.rendered-selection').html(formatted);
if (data[0]._resultId != null) {
this.$selection.attr('aria-activedescendent', data[0]._resultId);
}
}; };
return SingleSelection; return SingleSelection;
@ -10265,6 +10296,25 @@ define('select2/data/base',[
// Can be implemented in subclasses // Can be implemented in subclasses
}; };
BaseAdapter.prototype.generateResultId = function (data) {
var id = '';
for (var i = 0; i < 4; i++) {
var r = Math.floor(Math.random() * 16);
id += r.toString(16);
}
if (data.id != null) {
id += '-' + data.id.toString();
} else {
for (var s = 0; s < 4; s++) {
var idChar = Math.floor(Math.random() * 16);
id += idChar.toString(16);
}
}
return id;
};
return BaseAdapter; return BaseAdapter;
}); });
@ -10416,6 +10466,10 @@ define('select2/data/select',[
data.children = children; data.children = children;
} }
if (data.id) {
data._resultId = this.generateResultId(data);
}
$option.data('data', data); $option.data('data', data);
} }

File diff suppressed because one or more lines are too long

58
dist/js/select2.js vendored
View File

@ -687,7 +687,11 @@ define('select2/results',[
} }
if (data.id == null) { if (data.id == null) {
$option.removeClass('aria-selected'); $option.removeAttr('aria-selected');
}
if (data._resultId != null) {
$option.attr('id', data._resultId);
} }
$option.data('data', data); $option.data('data', data);
@ -792,6 +796,8 @@ define('select2/results',[
var $next = $options.eq(nextIndex); var $next = $options.eq(nextIndex);
$next.trigger('mouseenter'); $next.trigger('mouseenter');
console.log($next.offset().top, self.$results.parent().scrollTop());
//self.$results.parents().scrollTop($next.offset().top);
}); });
this.$results.on('mouseup', '.option[aria-selected]', function (evt) { this.$results.on('mouseup', '.option[aria-selected]', function (evt) {
@ -911,13 +917,24 @@ define('select2/selection/single',[
SingleSelection.prototype.render = function () { SingleSelection.prototype.render = function () {
var $selection = $( var $selection = $(
'<span class="single-select" tabindex="0">' + '<span class="single-select" tabindex="0" role="combobox" ' +
'aria-autocomplete="list" aria-haspopup="true" aria-expanded="false">' +
'<span class="rendered-selection"></span>' + '<span class="rendered-selection"></span>' +
'</span>' '</span>'
); );
$selection.attr('title', this.$element.attr('title')); $selection.attr('title', this.$element.attr('title'));
var id = 'select2-container-';
for (var i = 0; i < 4; i++) {
var r = Math.floor(Math.random() * 16);
id += r.toString(16);
}
$selection.find('.rendered-selection').attr('id', id);
$selection.attr('aria-labelledby', id);
this.$selection = $selection; this.$selection = $selection;
return $selection; return $selection;
@ -939,6 +956,16 @@ define('select2/selection/single',[
}); });
}); });
container.on('open', function () {
// When the dropdown is open, aria-expended="true"
self.$selection.attr('aria-expanded', 'true');
});
container.on('close', function () {
// When the dropdown is closed, aria-expended="false"
self.$selection.attr('aria-expanded', 'false');
});
this.$selection.on('focus', function (evt) { this.$selection.on('focus', function (evt) {
// User focuses on the container // User focuses on the container
}); });
@ -993,6 +1020,10 @@ define('select2/selection/single',[
var formatted = this.display(selection); var formatted = this.display(selection);
this.$selection.find('.rendered-selection').html(formatted); this.$selection.find('.rendered-selection').html(formatted);
if (data[0]._resultId != null) {
this.$selection.attr('aria-activedescendent', data[0]._resultId);
}
}; };
return SingleSelection; return SingleSelection;
@ -1156,6 +1187,25 @@ define('select2/data/base',[
// Can be implemented in subclasses // Can be implemented in subclasses
}; };
BaseAdapter.prototype.generateResultId = function (data) {
var id = '';
for (var i = 0; i < 4; i++) {
var r = Math.floor(Math.random() * 16);
id += r.toString(16);
}
if (data.id != null) {
id += '-' + data.id.toString();
} else {
for (var s = 0; s < 4; s++) {
var idChar = Math.floor(Math.random() * 16);
id += idChar.toString(16);
}
}
return id;
};
return BaseAdapter; return BaseAdapter;
}); });
@ -1307,6 +1357,10 @@ define('select2/data/select',[
data.children = children; data.children = children;
} }
if (data.id) {
data._resultId = this.generateResultId(data);
}
$option.data('data', data); $option.data('data', data);
} }

File diff suppressed because one or more lines are too long

View File

@ -19,5 +19,24 @@ define([
// Can be implemented in subclasses // Can be implemented in subclasses
}; };
BaseAdapter.prototype.generateResultId = function (data) {
var id = '';
for (var i = 0; i < 4; i++) {
var r = Math.floor(Math.random() * 16);
id += r.toString(16);
}
if (data.id != null) {
id += '-' + data.id.toString();
} else {
for (var s = 0; s < 4; s++) {
var idChar = Math.floor(Math.random() * 16);
id += idChar.toString(16);
}
}
return id;
};
return BaseAdapter; return BaseAdapter;
}); });

View File

@ -146,6 +146,10 @@ define([
data.children = children; data.children = children;
} }
if (data.id) {
data._resultId = this.generateResultId(data);
}
$option.data('data', data); $option.data('data', data);
} }

View File

@ -119,7 +119,11 @@ define([
} }
if (data.id == null) { if (data.id == null) {
$option.removeClass('aria-selected'); $option.removeAttr('aria-selected');
}
if (data._resultId != null) {
$option.attr('id', data._resultId);
} }
$option.data('data', data); $option.data('data', data);
@ -224,6 +228,8 @@ define([
var $next = $options.eq(nextIndex); var $next = $options.eq(nextIndex);
$next.trigger('mouseenter'); $next.trigger('mouseenter');
console.log($next.offset().top, self.$results.parent().scrollTop());
//self.$results.parents().scrollTop($next.offset().top);
}); });
this.$results.on('mouseup', '.option[aria-selected]', function (evt) { this.$results.on('mouseup', '.option[aria-selected]', function (evt) {

View File

@ -11,13 +11,24 @@ define([
SingleSelection.prototype.render = function () { SingleSelection.prototype.render = function () {
var $selection = $( var $selection = $(
'<span class="single-select" tabindex="0">' + '<span class="single-select" tabindex="0" role="combobox" ' +
'aria-autocomplete="list" aria-haspopup="true" aria-expanded="false">' +
'<span class="rendered-selection"></span>' + '<span class="rendered-selection"></span>' +
'</span>' '</span>'
); );
$selection.attr('title', this.$element.attr('title')); $selection.attr('title', this.$element.attr('title'));
var id = 'select2-container-';
for (var i = 0; i < 4; i++) {
var r = Math.floor(Math.random() * 16);
id += r.toString(16);
}
$selection.find('.rendered-selection').attr('id', id);
$selection.attr('aria-labelledby', id);
this.$selection = $selection; this.$selection = $selection;
return $selection; return $selection;
@ -39,6 +50,16 @@ define([
}); });
}); });
container.on('open', function () {
// When the dropdown is open, aria-expended="true"
self.$selection.attr('aria-expanded', 'true');
});
container.on('close', function () {
// When the dropdown is closed, aria-expended="false"
self.$selection.attr('aria-expanded', 'false');
});
this.$selection.on('focus', function (evt) { this.$selection.on('focus', function (evt) {
// User focuses on the container // User focuses on the container
}); });
@ -93,6 +114,10 @@ define([
var formatted = this.display(selection); var formatted = this.display(selection);
this.$selection.find('.rendered-selection').html(formatted); this.$selection.find('.rendered-selection').html(formatted);
if (data[0]._resultId != null) {
this.$selection.attr('aria-activedescendent', data[0]._resultId);
}
}; };
return SingleSelection; return SingleSelection;