[FIX] web: list: prevent tables from overflowing
In an editable list, select a very long value in a many2one... Before this commit, the table stretched as much as it needed to to display all of the columns content. Now, the largest columns are trimmed in order to keep the table as wide as the list container (meaning it won't overflow anymore unless there is a huge load of columns). Also ensured that values trimmed by this feature are followed by an ellipsis. Task 2068261 Co-authored-by: Aaron Bohy <aab@odoo.com>
This commit is contained in:
committed by
Aaron Bohy
co-authored by
Aaron Bohy
parent
d99e80f92a
commit
0ebacff465
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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: `
|
||||
<tree editable="top">
|
||||
<field name="titi"/>
|
||||
<field name="grosminet" widget="char"/>
|
||||
</tree>`,
|
||||
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: `
|
||||
<tree editable="top">
|
||||
<field name="titi"/>
|
||||
<field name="grosminet1" class="large"/>
|
||||
<field name="grosminet3" class="large"/>
|
||||
<field name="grosminet2" class="large"/>
|
||||
</tree>`,
|
||||
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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user