summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/fields
diff options
context:
space:
mode:
authorLuis González <lgonzalez@vauxoo.com>2020-04-13 03:36:19 +0000
committerNicolas Lempereur <nle@odoo.com>2020-09-24 11:50:15 +0000
commit960feafeb80f097174c9b934a682a729a4bc19a2 (patch)
tree1d674ed44ff288764f50f0c0be6f7aa8279d70d3 /addons/web/static/src/js/fields
parentebd0c90a146145079061e79c2d4c37fa182a2975 (diff)
[FIX] web: Get correctly focusable elements on selection widgets
When navigating fields using the keyboard, it's not possible to reach most of widgets for selection fields. That is because most of them consist of one main element containing several other ones, one per possible selectable value (e.g. buttons). When their focusable element is computed, the main element is taken, instead of its sub-elements, which causes the field to be skipped when navigating. This commit fixes the above, as well as some missing aria attributes and keyboard events, for the following widgets: - `priority` - `state_selection` - `statusbar` - `radio` opw-231112 closes #58302 closes odoo/odoo#58418 X-original-commit: 2471e7c176c5df0f310f2939f8a6cf6948c0ac0c Signed-off-by: Nicolas Lempereur (nle) <nle@odoo.com>
Diffstat (limited to 'addons/web/static/src/js/fields')
-rw-r--r--addons/web/static/src/js/fields/basic_fields.js69
-rw-r--r--addons/web/static/src/js/fields/relational_fields.js24
2 files changed, 91 insertions, 2 deletions
diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js
index cdb4c000e57..774fb6462fd 100644
--- a/addons/web/static/src/js/fields/basic_fields.js
+++ b/addons/web/static/src/js/fields/basic_fields.js
@@ -2215,10 +2215,14 @@ var PriorityWidget = AbstractField.extend({
// the current implementation of this widget makes it
// only usable for fields of type selection
className: "o_priority",
+ attributes: {
+ 'role': 'radiogroup',
+ },
events: {
'mouseover > a': '_onMouseOver',
'mouseout > a': '_onMouseOut',
'click > a': '_onClick',
+ 'keydown > a': '_onKeydown',
},
supportedFieldTypes: ['selection'],
@@ -2236,6 +2240,17 @@ var PriorityWidget = AbstractField.extend({
return true;
},
+ /**
+ * Returns the currently-checked star, or the first one if no star is
+ * checked.
+ *
+ * @override
+ */
+ getFocusableElement: function () {
+ var checked = this.$("[aria-checked='true']");
+ return checked.length ? checked : this.$("[data-index='1']");
+ },
+
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
@@ -2253,8 +2268,9 @@ var PriorityWidget = AbstractField.extend({
}) : 0;
this.$el.empty();
this.empty_value = this.field.selection[0][0];
+ this.$el.attr('aria-label', this.string);
_.each(this.field.selection.slice(1), function (choice, index) {
- self.$el.append(self._renderStar('<a href="#">', index_value >= index+1, index+1, choice[1]));
+ self.$el.append(self._renderStar('<a href="#">', index_value >= index+1, index+1, choice[1], index_value));
});
},
@@ -2265,12 +2281,18 @@ var PriorityWidget = AbstractField.extend({
* @param {boolean} isFull whether the star is a full star or not
* @param {integer} index the index of the star in the series
* @param {string} tip tooltip for this star's meaning
+ * @param {integer} indexValue the index of the last full star or 0
* @private
*/
- _renderStar: function (tag, isFull, index, tip) {
+ _renderStar: function (tag, isFull, index, tip, indexValue) {
+ var isChecked = indexValue === index;
+ var defaultFocus = indexValue === 0 && index === 1;
return $(tag)
+ .attr('role', 'radio')
+ .attr('aria-checked', isChecked)
.attr('title', tip)
.attr('aria-label', tip)
+ .attr('tabindex', isChecked || defaultFocus ? 0 : -1)
.attr('data-index', index)
.addClass('o_priority_star fa')
.toggleClass('fa-star', isFull)
@@ -2322,6 +2344,36 @@ var PriorityWidget = AbstractField.extend({
this.$('.o_priority_star').removeClass('fa-star-o').addClass('fa-star');
$(event.currentTarget).nextAll().removeClass('fa-star').addClass('fa-star-o');
},
+
+ /**
+ * Runs the default behavior when <enter> is pressed over a star
+ * (the same as if it was clicked); otherwise forwards event to the widget.
+ *
+ * @param {KeydownEvent} event
+ * @private
+ */
+ _onKeydown: function (event) {
+ if (event.which === $.ui.keyCode.ENTER) {
+ return;
+ }
+ this._super.apply(this, arguments);
+ },
+
+ _onNavigationMove: function (ev) {
+ var $curControl = this.$('a:focus');
+ var $nextControl;
+ if (ev.data.direction === 'right' || ev.data.direction === 'down') {
+ $nextControl = $curControl.next('a');
+ } else if (ev.data.direction === 'left' || ev.data.direction === 'up') {
+ $nextControl = $curControl.prev('a');
+ }
+ if ($nextControl && $nextControl.length) {
+ ev.stopPropagation();
+ $nextControl.focus();
+ return;
+ }
+ this._super.apply(this, arguments);
+ },
});
var AttachmentImage = AbstractField.extend({
@@ -2355,6 +2407,19 @@ var StateSelectionWidget = AbstractField.extend({
supportedFieldTypes: ['selection'],
//--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * Returns the drop down button.
+ *
+ * @override
+ */
+ getFocusableElement: function () {
+ return this.$("a[data-toggle='dropdown']");
+ },
+
+ //--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js
index 432961bc0bf..c6db91778ec 100644
--- a/addons/web/static/src/js/fields/relational_fields.js
+++ b/addons/web/static/src/js/fields/relational_fields.js
@@ -3068,6 +3068,27 @@ var FieldRadio = FieldSelection.extend({
return true;
},
+ /**
+ * Returns the currently-checked radio button, or the first one if no radio
+ * button is checked.
+ *
+ * @override
+ */
+ getFocusableElement: function () {
+ var checked = this.$("[checked='true']");
+ return checked.length ? checked : this.$("[data-index='0']");
+ },
+
+ /**
+ * Associates the 'for' attribute to the radiogroup, instead of the selected
+ * radio button.
+ *
+ * @param {string} id
+ */
+ setIDForLabel: function (id) {
+ this.$el.attr('id', id);
+ },
+
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
@@ -3085,11 +3106,14 @@ var FieldRadio = FieldSelection.extend({
currentValue = this.value;
}
this.$el.empty();
+ this.$el.attr('role', 'radiogroup')
+ .attr('aria-label', this.string);
_.each(this.values, function (value, index) {
self.$el.append(qweb.render('FieldRadio.button', {
checked: value[0] === currentValue,
id: self.unique_id + '_' + value[0],
index: index,
+ name: self.unique_id,
value: value,
}));
});