summaryrefslogtreecommitdiff
path: root/addons/web/static/src
diff options
context:
space:
mode:
Diffstat (limited to 'addons/web/static/src')
-rw-r--r--addons/web/static/src/js/views/list/list_controller.js9
-rw-r--r--addons/web/static/src/js/views/list/list_editable_renderer.js51
-rw-r--r--addons/web/static/src/js/views/list/list_renderer.js145
-rw-r--r--addons/web/static/src/scss/list_view.scss8
-rw-r--r--addons/web/static/src/scss/list_view_extra.scss4
5 files changed, 188 insertions, 29 deletions
diff --git a/addons/web/static/src/js/views/list/list_controller.js b/addons/web/static/src/js/views/list/list_controller.js
index c4d1ca8959f..5db0323c87d 100644
--- a/addons/web/static/src/js/views/list/list_controller.js
+++ b/addons/web/static/src/js/views/list/list_controller.js
@@ -706,9 +706,16 @@ var ListController = BasicController.extend({
* @param {OdooEvent} ev
*/
_onToggleGroup: function (ev) {
+ var self = this;
this.model
.toggleGroup(ev.data.group.id)
- .then(this.update.bind(this, {}, {keepSelection: true, reload: false}));
+ .then(function () {
+ self.update({}, {keepSelection: true, reload: false}).then(function () {
+ if (ev.data.onSuccess) {
+ ev.data.onSuccess();
+ }
+ });
+ });
},
});
diff --git a/addons/web/static/src/js/views/list/list_editable_renderer.js b/addons/web/static/src/js/views/list/list_editable_renderer.js
index 040a186d4ee..9f37fcecbea 100644
--- a/addons/web/static/src/js/views/list/list_editable_renderer.js
+++ b/addons/web/static/src/js/views/list/list_editable_renderer.js
@@ -645,7 +645,7 @@ ListRenderer.include({
// row of the group)
if (options.forceCreate || record.isDirty()) {
// if we modified the current record, add a row to create a new record
- groupId = $nextRow.data('groupID');
+ groupId = $nextRow.data('group-id');
} else {
// if we didn't change anything to the current line (e.g. we pressed TAB on
// each cell without modifying/entering any data), we discard that line (if
@@ -742,13 +742,14 @@ ListRenderer.include({
var $groupBody = result[0];
var $a = $('<a href="#" role="button">')
.text(_t("Add a line"))
- .data('groupID', group.id);
+ .attr('data-group-id', group.id);
var $td = $('<td>')
.attr('colspan', this._getNumberOfCols())
.addClass('o_group_field_row_add')
+ .attr('tabindex', -1)
.append($a);
var $tr = $('<tr>', {class: 'o_add_record_row'})
- .data('groupID', group.id)
+ .attr('data-group-id', group.id)
.append($td);
$groupBody.append($tr.prepend($('<td>').html('&nbsp;')));
}
@@ -1037,7 +1038,8 @@ ListRenderer.include({
ev.stopPropagation();
var self = this;
- var groupId = $(ev.target).data('groupID');
+ var groupId = $(ev.target).data('group-id');
+ this.currentGroupId = groupId;
this.unselectRow().then(function () {
self.trigger_up('add_record', {
groupId: groupId,
@@ -1098,6 +1100,35 @@ ListRenderer.include({
this.unselectRow();
},
/**
+ * Manages the keyboard events on the list. If the list is not editable, when the user navigates to
+ * a cell using the keyboard, if he presses enter, enter the model represented by the line
+ *
+ * @private
+ * @param {KeyboardEvent} ev
+ * @override
+ */
+ _onKeyDown: function (ev) {
+ var $target = $(ev.currentTarget);
+ var $tr = $target.closest('tr');
+
+ if (this.editable && ev.keyCode === $.ui.keyCode.ENTER && $tr.hasClass('o_selected_row')) {
+ // enter on a textarea for example, let it bubble
+ return;
+ }
+
+ if (this.editable && ev.keyCode === $.ui.keyCode.ENTER && !$tr.hasClass('o_selected_row') && !$tr.hasClass('o_group_header')) {
+ ev.stopPropagation();
+ ev.preventDefault();
+ if ($target.closest('td').hasClass('o_group_field_row_add')) {
+ this._onAddRecordToGroup(ev);
+ } else {
+ this._onCellClick(ev);
+ }
+ } else {
+ this._super.apply(this, arguments);
+ }
+ },
+ /**
* @private
* @param {KeyDownEvent} e
*/
@@ -1180,10 +1211,20 @@ ListRenderer.include({
// prevent from closing the potential dialog containing this list
// also auto-focus the 1st control, if any.
ev.data.originalEvent.stopPropagation();
+ var rowIndex = this.currentRow;
+ var cellIndex = this.currentFieldIndex + 1;
this.trigger_up('discard_changes', {
recordID: ev.target.dataPointID,
onSuccess: function () {
- self.$('.o_field_x2many_list_row_add a:first').focus(); // FIXME
+ var recordId = self._getRecordID(rowIndex);
+ if (recordId) {
+ var correspondingRow = self._getRow(recordId);
+ correspondingRow.children().eq(cellIndex).focus();
+ } else if (self.currentGroupId) {
+ self.$('a[data-group-id=' + self.currentGroupId + ']').focus();
+ } else {
+ self.$('.o_field_x2many_list_row_add a:first').focus(); // FIXME
+ }
}
});
break;
diff --git a/addons/web/static/src/js/views/list/list_renderer.js b/addons/web/static/src/js/views/list/list_renderer.js
index 7a46e3b3cf2..471598ca4e4 100644
--- a/addons/web/static/src/js/views/list/list_renderer.js
+++ b/addons/web/static/src/js/views/list/list_renderer.js
@@ -39,8 +39,8 @@ var ListRenderer = BasicRenderer.extend({
'click .o_group_header': '_onToggleGroup',
'click thead .o_list_record_selector input': '_onToggleSelection',
'keypress thead tr td': '_onKeyPress',
- 'keydown tr': '_onKeyDown',
- 'keydown thead tr': '_onKeyDown',
+ 'keydown td': '_onKeyDown',
+ 'keydown th': '_onKeyDown',
},
/**
* @constructor
@@ -78,7 +78,7 @@ var ListRenderer = BasicRenderer.extend({
* @public
*/
giveFocus: function () {
- this.$('tbody .o_list_record_selector input:first()').focus();
+ this.$('th:eq(0) input, th:eq(1)').first().focus();
},
/**
* @override
@@ -169,6 +169,56 @@ var ListRenderer = BasicRenderer.extend({
}
},
/**
+ *
+ * @private
+ * @param {jQuery} $cell
+ * @param {string} direction
+ * @param {integer} colIndex
+ * @returns {jQuery|null}
+ */
+ _findConnectedCell: function ($cell, direction, colIndex) {
+ var $connectedRow = $cell.closest('tr')[direction]('tr');
+
+ if (!$connectedRow.length) {
+ // Is there another group ? Look at our parent's sibling
+ // We can have th in tbody so we can't simply look for thead
+ // if cell is a th and tbody instead
+ var tbody = $cell.closest('tbody, thead');
+ var $connectedGroup = tbody[direction]('tbody, thead');
+ if ($connectedGroup.length) {
+ // Found another group
+ var $connectedRows = $connectedGroup.find('tr');
+ var rowIndex;
+ if (direction === 'prev') {
+ rowIndex = $connectedRows.length - 1;
+ } else {
+ rowIndex = 0;
+ }
+ $connectedRow = $connectedRows.eq(rowIndex);
+ } else {
+ // End of the table
+ return;
+ }
+ }
+
+ var $connectedCell;
+ if ($connectedRow.hasClass('o_group_header')) {
+ $connectedCell = $connectedRow.children();
+ this.currentColIndex = colIndex;
+ } else if ($connectedRow.has('td.o_group_field_row_add').length) {
+ $connectedCell = $connectedRow.find('.o_group_field_row_add');
+ this.currentColIndex = colIndex;
+ } else {
+ var connectedRowChildren = $connectedRow.children();
+ if (colIndex === -1) {
+ colIndex = connectedRowChildren.length - 1;
+ }
+ $connectedCell = connectedRowChildren.eq(colIndex);
+ }
+
+ return $connectedCell;
+ },
+ /**
* return the number of visible columns. Note that this number depends on
* the state of the renderer. For example, in editable mode, it could be
* one more that in non editable mode, because there may be a visible 'trash
@@ -283,7 +333,7 @@ var ListRenderer = BasicRenderer.extend({
tdClassName += (' o_' + node.attrs.widget + '_cell');
}
}
- var $td = $('<td>', { class: tdClassName });
+ var $td = $('<td>', { class: tdClassName, tabindex: -1 });
// We register modifiers on the <td> element so that it gets the correct
// modifiers classes (for styling)
@@ -492,6 +542,7 @@ var ListRenderer = BasicRenderer.extend({
}
var $th = $('<th>')
.addClass('o_group_name')
+ .attr('tabindex', -1)
.text(name + ' (' + group.count + ')')
var $arrow = $('<span>')
.css('padding-left', (groupLevel * 20) + 'px')
@@ -649,6 +700,7 @@ var ListRenderer = BasicRenderer.extend({
}
$th.text(description)
.attr('data-name', name)
+ .attr('tabindex', -1)
.toggleClass('o-sort-down', isNodeSorted ? !order[0].asc : false)
.toggleClass('o-sort-up', isNodeSorted ? order[0].asc : false)
.addClass(field.sortable && 'o_column_sortable');
@@ -848,23 +900,59 @@ var ListRenderer = BasicRenderer.extend({
* @param {KeyboardEvent} ev
*/
_onKeyDown: function (ev) {
- if (!this.editable) {
- switch (ev.which) {
- case $.ui.keyCode.DOWN:
- $(ev.currentTarget).next().find('input').focus();
- ev.preventDefault();
- break;
- case $.ui.keyCode.UP:
- $(ev.currentTarget).prev().find('input').focus();
- ev.preventDefault();
- break;
- case $.ui.keyCode.ENTER:
- ev.preventDefault();
- var id = $(ev.currentTarget).data('id');
+ var $cell = $(ev.currentTarget);
+ var $tr;
+ var $futureCell;
+ var colIndex;
+ switch (ev.keyCode) {
+ case $.ui.keyCode.LEFT:
+ ev.preventDefault();
+ $tr = $cell.closest('tr');
+ if ($tr.hasClass('o_group_header') && $tr.hasClass('o_group_open')) {
+ this._onToggleGroup(ev);
+ } else {
+ $futureCell = $cell.prev();
+ }
+ break;
+ case $.ui.keyCode.RIGHT:
+ ev.preventDefault();
+ $tr = $cell.closest('tr');
+ if ($tr.hasClass('o_group_header') && !$tr.hasClass('o_group_open')) {
+ this._onToggleGroup(ev);
+ } else {
+ $futureCell = $cell.next();
+ }
+ break;
+ case $.ui.keyCode.UP:
+ ev.preventDefault();
+ colIndex = this.currentColIndex || $cell.index();
+ $futureCell = this._findConnectedCell($cell, 'prev', colIndex);
+ break;
+ case $.ui.keyCode.DOWN:
+ ev.preventDefault();
+ colIndex = this.currentColIndex || $cell.index();
+ $futureCell = this._findConnectedCell($cell, 'next', colIndex);
+ break;
+ case $.ui.keyCode.ENTER:
+ ev.preventDefault();
+ $tr = $cell.closest('tr');
+ if ($tr.hasClass('o_group_header')) {
+ this._onToggleGroup(ev);
+ } else {
+ var id = $tr.data('id');
if (id) {
this.trigger_up('open_record', { id: id, target: ev.target });
}
- break;
+ }
+ break;
+ }
+ if ($futureCell) {
+ // If the cell contains activable elements, focus them instead
+ var $activables = $futureCell.find(':focusable');
+ if ($activables.length) {
+ $activables[0].focus();
+ } else {
+ $futureCell.focus();
}
}
},
@@ -903,12 +991,27 @@ var ListRenderer = BasicRenderer.extend({
},
/**
* @private
- * @param {MouseEvent} ev
+ * @param {DOMEvent} ev
*/
_onToggleGroup: function (ev) {
- var group = $(ev.currentTarget).data('group');
+ ev.preventDefault();
+ ev.stopPropagation();
+ var group = $(ev.currentTarget).closest('tr').data('group');
if (group.count) {
- this.trigger_up('toggle_group', { group: group });
+ this.trigger_up('toggle_group', {
+ group: group,
+ onSuccess: function() {
+ // Refocus the header after re-render unless the user
+ // already focused something else by now
+ if (document.activeElement.tagName === 'BODY') {
+ var groupHeaders = $('tr.o_group_header:data("group")');
+ var header = groupHeaders.filter(function () {
+ return $(this).data('group').id === group.id;
+ });
+ header.find('.o_group_name').focus();
+ }
+ },
+ });
}
},
/**
diff --git a/addons/web/static/src/scss/list_view.scss b/addons/web/static/src/scss/list_view.scss
index 16f14f3717e..07da78731f3 100644
--- a/addons/web/static/src/scss/list_view.scss
+++ b/addons/web/static/src/scss/list_view.scss
@@ -136,11 +136,15 @@
border-top: none; // Override bootstrap for grouped list views
}
- tbody {
+ &, &.table-striped {
tr:focus-within {
- background-color: $o-form-lightsecondary;
+ background-color: lighten($o-form-lightsecondary, 10%);
}
}
+ thead th:focus-within, tbody td:focus-within {
+ background-color: $o-form-lightsecondary;
+ outline: none;
+ }
.o_data_cell.o_text_overflow,
.o_data_cell.o_invisible_modifier {
diff --git a/addons/web/static/src/scss/list_view_extra.scss b/addons/web/static/src/scss/list_view_extra.scss
index 83791ce42dd..7532ec2084f 100644
--- a/addons/web/static/src/scss/list_view_extra.scss
+++ b/addons/web/static/src/scss/list_view_extra.scss
@@ -6,5 +6,9 @@
tbody > tr.o_group_header {
background-image: linear-gradient(to bottom, #fcfcfc, #dedede);
+
+ &:focus-within {
+ background-image: linear-gradient(to bottom, #fcfcfc, $o-form-lightsecondary);
+ }
}
}