[IMP] web: keyboard navigation between searchbar and renderer
This change allows direct keyboard navigation between the current action searchbar and renderer. Focus can now be toggled using DOWN arrow when focusing the searchbar or the UP arrow when focusing the top cell/record of a kanban or a list.
This commit is contained in:
@@ -559,15 +559,19 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
|
||||
ev.data.callback(state || {});
|
||||
},
|
||||
/**
|
||||
* Called mainly from the control panel when the focus should be given to
|
||||
* the controller
|
||||
* Called either from the control panel to focus the controller
|
||||
* or from the view to focus the search bar
|
||||
*
|
||||
* @private
|
||||
* @param {OdooEvent} ev
|
||||
*/
|
||||
_onNavigationMove: function (ev) {
|
||||
switch (ev.data.direction) {
|
||||
case 'down' :
|
||||
case 'up':
|
||||
ev.stopPropagation();
|
||||
this._controlPanel.focusSearchBar();
|
||||
break;
|
||||
case 'down':
|
||||
ev.stopPropagation();
|
||||
this.giveFocus();
|
||||
break;
|
||||
@@ -629,7 +633,6 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
|
||||
_onSwitchView: function (ev) {
|
||||
ev.data.controllerID = this.controllerID;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
return AbstractController;
|
||||
|
||||
@@ -54,6 +54,14 @@ var ControlPanelController = mvc.Controller.extend({
|
||||
exportState: function () {
|
||||
return this.model.exportState();
|
||||
},
|
||||
/**
|
||||
* Called by the abstract controller to give focus to the searchbar
|
||||
*/
|
||||
focusSearchBar: function () {
|
||||
if (this.renderer.searchBar) {
|
||||
this.renderer.searchBar.focus();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Compute the search related values that will be used to fetch data.
|
||||
*
|
||||
|
||||
@@ -221,6 +221,14 @@ var SearchBar = Widget.extend({
|
||||
this._focusFollowing();
|
||||
e.preventDefault();
|
||||
break;
|
||||
case $.ui.keyCode.DOWN:
|
||||
// if the searchbar dropdown is closed, try to focus the renderer
|
||||
const $dropdown = this.$('.o_searchview_autocomplete:visible');
|
||||
if (!$dropdown.length) {
|
||||
this.trigger_up('navigation_move', { direction: 'down' });
|
||||
e.preventDefault();
|
||||
}
|
||||
break;
|
||||
case $.ui.keyCode.BACKSPACE:
|
||||
if (this.$input.val() === '') {
|
||||
this.trigger_up('facet_removed');
|
||||
|
||||
@@ -262,13 +262,17 @@ var KanbanRenderer = BasicRenderer.extend({
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Tries to give focus to the previous card, and returns true if successful
|
||||
*
|
||||
* @private
|
||||
* @param {DOMElement} currentColumn
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_focusOnPreviousCard: function (currentCardElement) {
|
||||
var previousCard = currentCardElement.previousElementSibling;
|
||||
if (previousCard && previousCard.classList.contains("o_kanban_record")) { //previous element might be column title
|
||||
previousCard.focus();
|
||||
return true;
|
||||
}
|
||||
},
|
||||
/**
|
||||
@@ -611,7 +615,10 @@ var KanbanRenderer = BasicRenderer.extend({
|
||||
e.preventDefault();
|
||||
break;
|
||||
case $.ui.keyCode.UP:
|
||||
this._focusOnPreviousCard(e.currentTarget);
|
||||
const previousFocused = this._focusOnPreviousCard(e.currentTarget);
|
||||
if (!previousFocused) {
|
||||
this.trigger_up('navigation_move', { direction: 'up' });
|
||||
}
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
break;
|
||||
|
||||
@@ -1155,6 +1155,10 @@ ListRenderer.include({
|
||||
*/
|
||||
_onNavigationMove: function (ev) {
|
||||
var self = this;
|
||||
// Don't stop the propagation when navigating up while not editing any row
|
||||
if (this.currentRow === null && ev.data.direction === 'up') {
|
||||
return;
|
||||
}
|
||||
ev.stopPropagation(); // stop the event, the action is done by this renderer
|
||||
switch (ev.data.direction) {
|
||||
case 'previous':
|
||||
|
||||
@@ -1108,6 +1108,9 @@ var ListRenderer = BasicRenderer.extend({
|
||||
$cell.closest('tbody').addClass('o_keyboard_navigation');
|
||||
colIndex = this.currentColIndex || $cell.index();
|
||||
$futureCell = this._findConnectedCell($cell, 'prev', colIndex);
|
||||
if (!$futureCell) {
|
||||
this.trigger_up('navigation_move', { direction: 'up' });
|
||||
}
|
||||
break;
|
||||
case $.ui.keyCode.DOWN:
|
||||
ev.preventDefault();
|
||||
|
||||
@@ -5883,14 +5883,15 @@ QUnit.module('Views', {
|
||||
},
|
||||
});
|
||||
|
||||
assert.ok(document.activeElement.classList.contains('o_searchview_input'), 'default focus should be in search view');
|
||||
// switch focus to the create button in tests while it works on live
|
||||
$('.o_list_button_add').focus();
|
||||
assert.ok(document.activeElement.classList.contains('o_list_button_add'),
|
||||
'focus should now be in create button');
|
||||
assert.ok(document.activeElement.classList.contains('o_searchview_input'),
|
||||
'default focus should be in search view');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'down');
|
||||
assert.strictEqual(document.activeElement.tagName, 'INPUT',
|
||||
'focus should now be on the record selector');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'up');
|
||||
assert.ok(document.activeElement.classList.contains('o_searchview_input'),
|
||||
'focus should have come back to the search view');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'down');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'down');
|
||||
assert.strictEqual(document.activeElement.tagName, 'INPUT',
|
||||
'focus should now be in first row input');
|
||||
|
||||
Reference in New Issue
Block a user