[IMP] Improve compatibility with screen readers (accessibility) (#24574)
Today, Odoo is really tricky to use without seeing the screen, it must be improved to be usable. This PR forbid to use labels without a "for" attribute, add some title, rule and aria attributes in HTML. With that, Odoo will be fully usable with a screen reader. * [IMP] Labels must have a for attribute. Improve accessibility. * [IMP] Better error message when trying to read a missing cached value * [FIX] Add some aria-label and title attributes for screen readers. * [FIX] Template name is not included in the error message in case of SyntaxError in QWeb * [FIX] Improve the Tour failed at step error message to be more explicit. * [IMP] Add aria-labels * [FIX] Add missing aria-label on failing test * [IMP] aria-hidden means hidden. Fix all bad aria-hidden and hide aria-hidden for all. * [IMP] Color names on kanban views and many2many tags * [IMP] Add some checks on views for accessibility. * [IMP] Add `alt` attribute on `img` tags. * [IMP] Add aria-label and title on non-described icons * [IMP] Add button role to widgets with btn class * [IMP] Translate aria and formatted attributes. * [IMP] Remove wrong aria-labelledby * [IMP] Add menu role on dropdowns * [IMP] Buttons must be focusable * [IMP] Add aria attributes on progress bars * [IMP] Improve accessibility of basic widgets * [IMP] Change main layout to more semantic tags * [IMP] Add menuitem role when missing * [IMP] Remove wrong role='presentation' * [IMP] Improve accessibility of tab panels * [IMP] Add aria-invalid on invalid fields * [IMP] Add aria-sort on ordered columns * [IMP] Add role on alerts * [IMP] Use dialog role, header, main and footer tags for modals * [IMP] Add labels on o_status * [IMP] Improve accessibility of kanban view with feeds and articles * [IMP] Add alerts in case of new messages * [IMP] Add widget, navigation or img role to aria-labelled items
This commit is contained in:
@@ -397,9 +397,10 @@ var LinkButton = AbstractField.extend({
|
||||
if (this.value) {
|
||||
var className = this.attrs.icon || 'fa-globe';
|
||||
|
||||
this.$el.html("<span />");
|
||||
this.$el.html("<span role='img'/>");
|
||||
this.$el.addClass("fa "+ className);
|
||||
this.$el.attr('title', this.value);
|
||||
this.$el.attr('aria-label', this.value);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1460,6 +1461,7 @@ var PriorityWidget = AbstractField.extend({
|
||||
_renderStar: function (tag, isFull, index, tip) {
|
||||
return $(tag)
|
||||
.attr('title', tip)
|
||||
.attr('aria-label', tip)
|
||||
.attr('data-index', index)
|
||||
.addClass('o_priority_star fa')
|
||||
.toggleClass('fa-star', isFull)
|
||||
@@ -1529,7 +1531,8 @@ var AttachmentImage = AbstractField.extend({
|
||||
if (this.value) {
|
||||
this.$el.empty().append($('<img>/', {
|
||||
src: "/web/image/" + this.value.data.id + "?unique=1",
|
||||
title: this.value.data.display_name
|
||||
title: this.value.data.display_name,
|
||||
alt: _("Image")
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -1598,7 +1601,9 @@ var StateSelectionWidget = AbstractField.extend({
|
||||
this.$('.o_status')
|
||||
.removeClass('o_status_red o_status_green')
|
||||
.addClass(currentState.state_class)
|
||||
.prop('special_click', true);
|
||||
.prop('special_click', true)
|
||||
.parent().attr('title', currentState.state_name)
|
||||
.attr('aria-label', currentState.state_name);
|
||||
|
||||
// Render "FormSelection.Items" and move it into "FormSelection"
|
||||
var $items = $(qweb.render('FormSelection.items', {
|
||||
@@ -1662,8 +1667,9 @@ var FavoriteWidget = AbstractField.extend({
|
||||
* @private
|
||||
*/
|
||||
_render: function () {
|
||||
var template = this.attrs.nolabel ? '<a href="#"><i class="fa %s" title="%s"></i></a>' : '<a href="#"><i class="fa %s"></i> %s</a>';
|
||||
this.$el.empty().append(_.str.sprintf(template, this.value ? 'fa-star' : 'fa-star-o', this.value ? _t('Remove from Favorites') : _t('Add to Favorites')));
|
||||
var tip = this.value ? _t('Remove from Favorites') : _t('Add to Favorites');
|
||||
var template = this.attrs.nolabel ? '<a href="#"><i class="fa %s" title="%s" aria-label="%s" role="img"></i></a>' : '<a href="#"><i class="fa %s" role="img" aria-label="%s"> %s</i></a>';
|
||||
this.$el.empty().append(_.str.sprintf(template, this.value ? 'fa-star' : 'fa-star-o', tip, tip));
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -2043,7 +2049,10 @@ var FieldProgressBar = AbstractField.extend({
|
||||
widthComplete = 100;
|
||||
}
|
||||
|
||||
this.$('.o_progress').toggleClass('o_progress_overflow', value > max_value);
|
||||
this.$('.o_progress').toggleClass('o_progress_overflow', value > max_value)
|
||||
.attr('aria-valuemin', '0')
|
||||
.attr('aria-valuemax', max_value)
|
||||
.attr('aria-valuenow', value);
|
||||
this.$('.o_progressbar_complete').css('width', widthComplete + '%');
|
||||
|
||||
if (!this.write_mode) {
|
||||
@@ -2100,6 +2109,7 @@ var FieldToggleBoolean = AbstractField.extend({
|
||||
.toggleClass('text-muted', !this.value);
|
||||
var title = this.value ? this.attrs.options.active : this.attrs.options.inactive;
|
||||
this.$el.attr('title', title);
|
||||
this.$el.attr('aria-pressed', this.value);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user