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 a134faccc81..c9fd1cea92b 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
@@ -411,18 +411,6 @@ ListRenderer.include({
var $tds = $row.children('.o_data_cell');
var oldWidgets = _.clone(this.allFieldWidgets[record.id]);
- // When switching to edit mode, force the dimensions of all cells to
- // their current value so that they won't change if their content
- // changes, to prevent the view from flickering.
- // We need to use getBoundingClientRect instead of outerWidth to
- // prevent a rounding issue on Firefox.
- if (editMode) {
- $tds.each(function () {
- var $td = $(this);
- $td.css({width: $td[0].getBoundingClientRect().width});
- });
- }
-
// Prepare options for cell rendering (this depends on the mode)
var options = {
renderInvisible: editMode,
@@ -608,19 +596,25 @@ ListRenderer.include({
}
const table = this.el.getElementsByTagName('table')[0];
const thead = table.getElementsByTagName('thead')[0];
-
- // Set table layout auto and remove inline style to make sure that css
- // rules apply (e.g. fixed width of record selector)
- table.style.tableLayout = 'auto';
- [...table.getElementsByTagName('th')].forEach((th) => {
- th.style.width = null;
- });
-
- // Freeze each th width according to their size in auto layout
const thElements = [...thead.getElementsByTagName('th')];
- const thWidths = thElements.map((th) => th.offsetWidth);
+
+ let columnWidths = this.columnWidths;
+ if (!columnWidths) { // no column widths to restore
+ // Set table layout auto and remove inline style to make sure that css
+ // rules apply (e.g. fixed width of record selector)
+ table.style.tableLayout = 'auto';
+ thElements.forEach((th) => {
+ th.style.width = null;
+ th.style.maxWidth = null;
+ });
+
+ // Squeeze the table by applying a max-width on largest columns to
+ // ensure that it doesn't overflow
+ columnWidths = this._squeezeTable();
+ }
+
thElements.forEach((th, index) => {
- th.style.width = `${this.columnWidths ? this.columnWidths[index] : thWidths[index]}px`;
+ th.style.width = `${columnWidths[index]}px`;
});
// Set the table layout to fixed
@@ -1256,6 +1250,67 @@ ListRenderer.include({
});
});
},
+ /**
+ * Set a maximum width on the largest columns in the list in case the table
+ * is overflowing. The idea is to shrink largest columns first, but to
+ * ensure that they are still the largest at the end (maybe in equal measure
+ * with other columns).
+ *
+ * @private
+ * @returns {integer[]} width (in px) of each column s.t. the table doesn't
+ * overflow
+ */
+ _squeezeTable: function () {
+ const table = this.el.getElementsByTagName('table')[0];
+ const thead = table.getElementsByTagName('thead')[0];
+ const thElements = [...thead.getElementsByTagName('th')];
+ const columnWidths = thElements.map(th => th.offsetWidth);
+ const getWidth = th => columnWidths[thElements.indexOf(th)] || 0;
+ const getTotalWidth = () => thElements.reduce((tot, th, i) => tot + columnWidths[i], 0);
+ const shrinkColumns = (columns, width) => {
+ let thresholdReached = false;
+ columns.forEach(th => {
+ const index = thElements.indexOf(th);
+ let maxWidth = columnWidths[index] - Math.ceil(width / columns.length);
+ if (maxWidth < 92) { // prevent the columns from shrinking under 92px (~ date field)
+ maxWidth = 92;
+ thresholdReached = true;
+ }
+ th.style.maxWidth = `${maxWidth}px`;
+ columnWidths[index] = maxWidth;
+ });
+ return thresholdReached;
+ };
+ // Sort columns, largest first
+ const sortedThs = [...thead.getElementsByTagName('th')]
+ .sort((a, b) => getWidth(b) - getWidth(a));
+ const allowedWidth = table.parentNode.offsetWidth;
+
+ let totalWidth = getTotalWidth();
+ let stop = false;
+ let index = 0;
+ while (totalWidth > allowedWidth && !stop) {
+ // Find the largest columns
+ index++;
+ const largests = sortedThs.slice(0, index);
+ while (getWidth(largests[0]) === getWidth(sortedThs[index])) {
+ largests.push(sortedThs[index]);
+ index++;
+ }
+
+ // Compute the number of px to remove from the largest columns
+ const nextLargest = sortedThs[index]; // largest column when omitting those in largests
+ const totalToRemove = totalWidth - allowedWidth;
+ const canRemove = (getWidth(largests[0]) - getWidth(nextLargest)) * largests.length;
+
+ // Shrink the largests columns
+ stop = shrinkColumns(largests, Math.min(totalToRemove, canRemove));
+
+ totalWidth = getTotalWidth();
+ }
+
+ return columnWidths;
+ },
/**
* @private
*/
diff --git a/addons/web/static/src/scss/list_view.scss b/addons/web/static/src/scss/list_view.scss
index 6c3d452fb47..4ee916f9a1f 100644
--- a/addons/web/static/src/scss/list_view.scss
+++ b/addons/web/static/src/scss/list_view.scss
@@ -211,10 +211,6 @@
.o_list_record_remove_header {
width: 32px;
}
- &>tbody>tr>td.o_list_char,
- &>tbody>tr>td.o_list_many2one {
- white-space: nowrap;
- }
// checkboxes in editable lists should be clickable and activate the row
.o_data_row:not(.o_selected_row) .o_data_cell {
&.o_toggle_button_cell:not(.o_readonly_modifier) button, // registry: toggle_button
@@ -224,6 +220,17 @@
}
}
+ .o_data_row:not(.o_selected_row) {
+ .o_list_many2one,
+ .o_list_char,
+ .o_list_number {
+ white-space: nowrap;
+ .o_field_widget {
+ display: inline;
+ }
+ }
+ }
+
.o_data_row.o_selected_row > .o_data_cell:not(.o_readonly_modifier):not(.o_invisible_modifier) {
position: relative; // for o_field_translate
background-color: white;
diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js
index 277a980bb4d..540606d9770 100644
--- a/addons/web/static/tests/views/list_tests.js
+++ b/addons/web/static/tests/views/list_tests.js
@@ -1636,6 +1636,101 @@ QUnit.module('Views', {
form.destroy();
});
+ QUnit.test('editable list: overflowing table', async function (assert) {
+ assert.expect(1);
+
+ this.data.bar = {
+ fields: {
+ titi: { string: "Small char", type: "char", sortable: true },
+ grosminet: { string: "Beeg char", type: "char", sortable: true },
+ },
+ records: [
+ {
+ id: 1,
+ titi: "Tiny text",
+ grosminet:
+ // Just want to make sure that the table is overflowed
+ `Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ Donec est massa, gravida eget dapibus ac, eleifend eget libero.
+ Suspendisse feugiat sed massa eleifend vestibulum. Sed tincidunt
+ velit sed lacinia lacinia. Nunc in fermentum nunc. Vestibulum ante
+ ipsum primis in faucibus orci luctus et ultrices posuere cubilia
+ Curae; Nullam ut nisi a est ornare molestie non vulputate orci.
+ Nunc pharetra porta semper. Mauris dictum eu nulla a pulvinar. Duis
+ eleifend odio id ligula congue sollicitudin. Curabitur quis aliquet
+ nunc, ut aliquet enim. Suspendisse malesuada felis non metus
+ efficitur aliquet.`,
+ },
+ ],
+ };
+ const list = await createView({
+ arch: `
+
+
+
+ `,
+ data: this.data,
+ model: 'bar',
+ View: ListView,
+ });
+
+ assert.strictEqual(list.$('table').width(), list.$('.o_list_view').width(),
+ "Table should not be stretched by its content");
+
+ list.destroy();
+ });
+
+ QUnit.test('editable list: overflowing table (3 columns)', async function (assert) {
+ assert.expect(4);
+
+ const longText = `Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ Donec est massa, gravida eget dapibus ac, eleifend eget libero.
+ Suspendisse feugiat sed massa eleifend vestibulum. Sed tincidunt
+ velit sed lacinia lacinia. Nunc in fermentum nunc. Vestibulum ante
+ ipsum primis in faucibus orci luctus et ultrices posuere cubilia
+ Curae; Nullam ut nisi a est ornare molestie non vulputate orci.
+ Nunc pharetra porta semper. Mauris dictum eu nulla a pulvinar. Duis
+ eleifend odio id ligula congue sollicitudin. Curabitur quis aliquet
+ nunc, ut aliquet enim. Suspendisse malesuada felis non metus
+ efficitur aliquet.`;
+
+ this.data.bar = {
+ fields: {
+ titi: { string: "Small char", type: "char", sortable: true },
+ grosminet1: { string: "Beeg char 1", type: "char", sortable: true },
+ grosminet2: { string: "Beeg char 2", type: "char", sortable: true },
+ grosminet3: { string: "Beeg char 3", type: "char", sortable: true },
+ },
+ records: [{
+ id: 1,
+ titi: "Tiny text",
+ grosminet1: longText,
+ grosminet2: longText + longText,
+ grosminet3: longText + longText + longText,
+ }],
+ };
+ const list = await createView({
+ arch: `
+
+
+
+
+
+ `,
+ data: this.data,
+ model: 'bar',
+ View: ListView,
+ });
+
+ assert.strictEqual(list.$('table').width(), list.$('.o_list_view').width());
+ const largeCells = list.$('.o_data_cell.large');
+ assert.strictEqual(largeCells[0].offsetWidth, largeCells[1].offsetWidth);
+ assert.strictEqual(largeCells[1].offsetWidth, largeCells[2].offsetWidth);
+ assert.ok(list.$('.o_data_cell:not(.large)')[0].offsetWidth < largeCells[0].offsetWidth);
+
+ list.destroy();
+ });
+
QUnit.test('editable list: list view in an initially unselected notebook page', async function (assert) {
assert.expect(8);