diff options
Diffstat (limited to 'addons/web/static/src')
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(' '))); } @@ -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); + } } } |
