From 59b55e22218d395083f48db58eaa322919f4fbc9 Mon Sep 17 00:00:00 2001
From: Khoi Nguyen <bng@odoo.com>
Date: Mon, 2 Oct 2017 15:59:20 +0200
Subject: [PATCH] [FIX] web: fix TAB key in editable list views

Before this commit, two variables were used to store the current
position in a list view: currentCol and currentRow. This made it
difficult to handle incremental moves (eg: tab, shift+tab) inside a
row, since we have to ignore all buttons. Previously, this was done by
converting 'currentCol' to 'currentFieldIndex', but the conversion
between the two variables was incorrect and does not appear to be
that easy to fix. As an example, it was not possible to TAB-navigate to
the last column of the components list when creating a BoM.

This commit removes all mentions of 'currentCol' and uses
'currentFieldIndex' instead, removing the need for conversions. The TAB
navigation code is simpler as a result, and should now be correct.
---
 .../js/views/list/list_editable_renderer.js   | 54 +++++++------------
 addons/web/static/tests/views/list_tests.js   |  4 +-
 2 files changed, 22 insertions(+), 36 deletions(-)

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 8f0e73cb466c..9027fc3c9848 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
@@ -47,7 +47,6 @@ ListRenderer.include({
         this.addTrashIcon = params.addTrashIcon;
 
         this.currentRow = null;
-        this.currentCol = null;
         this.currentFieldIndex = null;
     },
     /**
@@ -175,7 +174,7 @@ ListRenderer.include({
             });
             if (self.currentRow !== null) {
                 self.currentRow = newRowIndex;
-                return self._selectCell(newRowIndex, self.currentCol, {force: true}).then(function () {
+                return self._selectCell(newRowIndex, self.currentFieldIndex, {force: true}).then(function () {
                     // restore the cursor position
                     currentRowID = self.state.data[newRowIndex].id;
                     currentWidget = self.allFieldWidgets[currentRowID][self.currentFieldIndex];
@@ -458,7 +457,6 @@ ListRenderer.include({
      */
     _render: function () {
         this.currentRow = null;
-        this.currentCol = null;
         this.currentFieldIndex = null;
         return this._super.apply(this, arguments);
     },
@@ -560,22 +558,22 @@ ListRenderer.include({
      * is, if necessary.
      *
      * @param {integer} rowIndex
-     * @param {integer} colIndex
+     * @param {integer} fieldIndex
      * @param {Object} [options]
      * @param {Event} [options.event] original target of the event which
      * @param {boolean} [options.wrap=true] if true and no widget could be
      *   triggered the cell selection
-     *   selected from the colIndex to the last column, then we wrap around and
+     *   selected from the fieldIndex to the last column, then we wrap around and
      *   try to select a widget starting from the beginning
      * @param {boolean} [options.force=false] if true, force selecting the cell
      *   even if seems to be already the selected one (useful after a re-
      *   rendering, to reset the focus on the correct field)
      * @return {Deferred} fails if no cell could be selected
      */
-    _selectCell: function (rowIndex, colIndex, options) {
+    _selectCell: function (rowIndex, fieldIndex, options) {
         options = options || {};
         // Do nothing if the user tries to select current cell
-        if (!options.force && rowIndex === this.currentRow && colIndex === this.currentCol) {
+        if (!options.force && rowIndex === this.currentRow && fieldIndex === this.currentFieldIndex) {
             return $.when();
         }
         var wrap = options.wrap === undefined ? true : options.wrap;
@@ -584,32 +582,18 @@ ListRenderer.include({
         var self = this;
         return this._selectRow(rowIndex).then(function () {
             var record = self.state.data[rowIndex];
-            var correctedIndex = colIndex - getNbButtonBefore(colIndex);
-            if (correctedIndex >= (self.allFieldWidgets[record.id] || []).length) {
+            if (fieldIndex >= (self.allFieldWidgets[record.id] || []).length) {
                 return $.Deferred().reject();
             }
-            var fieldIndex = self._activateFieldWidget(record, correctedIndex, {
+            fieldIndex = self._activateFieldWidget(record, fieldIndex, {
                 inc: 1,
                 wrap: wrap,
                 event: options && options.event,
             });
-
             if (fieldIndex < 0) {
                 return $.Deferred().reject();
             }
-
-            self.currentCol = fieldIndex + getNbButtonBefore(fieldIndex);
             self.currentFieldIndex = fieldIndex;
-
-            function getNbButtonBefore(index) {
-                var nbButtons = 0;
-                for (var i = 0 ; i < index ; i++) {
-                    if (self.columns[i].tag === 'button') {
-                        nbButtons++;
-                    }
-                }
-                return nbButtons;
-            }
         });
     },
     /**
@@ -682,8 +666,8 @@ ListRenderer.include({
         var $td = $(event.currentTarget);
         var $tr = $td.parent();
         var rowIndex = this.$('.o_data_row').index($tr);
-        var colIndex = $tr.find('.o_data_cell').index($td);
-        this._selectCell(rowIndex, colIndex, {event: event});
+        var fieldIndex = Math.max($tr.find('.o_data_cell').not('.o_list_button').index($td), 0);
+        this._selectCell(rowIndex, fieldIndex, {event: event});
     },
     /**
      * We need to manually unselect row, because noone else would do it
@@ -725,35 +709,35 @@ ListRenderer.include({
         switch (ev.data.direction) {
             case 'up':
                 if (this.currentRow > 0) {
-                    this._selectCell(this.currentRow - 1, this.currentCol);
+                    this._selectCell(this.currentRow - 1, this.currentFieldIndex);
                 }
                 break;
             case 'right':
-                if (this.currentCol + 1 < this.columns.length) {
-                    this._selectCell(this.currentRow, this.currentCol + 1);
+                if (this.currentFieldIndex + 1 < this.columns.length) {
+                    this._selectCell(this.currentRow, this.currentFieldIndex + 1);
                 }
                 break;
             case 'down':
                 if (this.currentRow < this.state.data.length - 1) {
-                    this._selectCell(this.currentRow + 1, this.currentCol);
+                    this._selectCell(this.currentRow + 1, this.currentFieldIndex);
                 }
                 break;
             case 'left':
-                if (this.currentCol > 0) {
-                    this._selectCell(this.currentRow, this.currentCol - 1);
+                if (this.currentFieldIndex > 0) {
+                    this._selectCell(this.currentRow, this.currentFieldIndex - 1);
                 }
                 break;
             case 'previous':
-                if (this.currentCol > 0) {
-                    this._selectCell(this.currentRow, this.currentCol - 1, {wrap: false})
+                if (this.currentFieldIndex > 0) {
+                    this._selectCell(this.currentRow, this.currentFieldIndex - 1, {wrap: false})
                         .fail(this._moveToPreviousLine.bind(this));
                 } else {
                     this._moveToPreviousLine();
                 }
                 break;
             case 'next':
-                if (this.currentCol + 1 < this.columns.length) {
-                    this._selectCell(this.currentRow, this.currentCol + 1, {wrap: false})
+                if (this.currentFieldIndex + 1 < this.columns.length) {
+                    this._selectCell(this.currentRow, this.currentFieldIndex + 1, {wrap: false})
                         .fail(this._moveToNextLine.bind(this));
                 } else {
                     this._moveToNextLine();
diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js
index 52149f008ebb..4a89a4a70e70 100644
--- a/addons/web/static/tests/views/list_tests.js
+++ b/addons/web/static/tests/views/list_tests.js
@@ -2390,6 +2390,8 @@ QUnit.module('Views', {
             model: 'foo',
             data: this.data,
             arch: '<tree editable="bottom">' +
+                    // Adding a button column makes conversions between column and field position trickier
+                    '<button name="kikou" string="Kikou" type="object"/>' +
                     '<field name="foo"/>' +
                     '<button name="kikou" string="Kikou" type="object"/>' +
                     '<field name="int_field"/>' +
@@ -2397,7 +2399,7 @@ QUnit.module('Views', {
             res_id: 1,
         });
 
-        list.$('tbody tr:eq(2) td:eq(1)').click();
+        list.$('tbody tr:eq(2) td:eq(2)').click();
         assert.strictEqual(list.$('tbody tr:eq(2) input[name="foo"]')[0], document.activeElement,
             "foo should be focused");
         list.$('tbody tr:eq(2) input[name="foo"]').trigger($.Event('keydown', {which: $.ui.keyCode.TAB}));
-- 
GitLab