[FIX] web: one2many contains at least 4 lines
An X2many always has at least 4 lines. If the X2many contains less than 4 filled lines (Record or "Add a line") empty lines are added to make up the difference. For example, an X2many in edit mode containing only one record: - Line containing a record - Line containing "Add a line - Empty line - Empty line closes odoo/odoo#97070 Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
@@ -670,10 +670,21 @@ export class ListRenderer extends Component {
|
||||
}
|
||||
|
||||
get getEmptyRowIds() {
|
||||
const nbEmptyRow = Math.max(0, 4 - this.props.list.records.length);
|
||||
let nbEmptyRow = Math.max(0, 4 - this.props.list.records.length);
|
||||
if (nbEmptyRow > 0 && this.displayRowCreates) {
|
||||
nbEmptyRow -= 1;
|
||||
}
|
||||
return Array.from(Array(nbEmptyRow).keys());
|
||||
}
|
||||
|
||||
get displayRowCreates() {
|
||||
const activeActions = this.props.activeActions;
|
||||
return (
|
||||
activeActions &&
|
||||
("canLink" in activeActions ? activeActions.canLink : activeActions.canCreate)
|
||||
);
|
||||
}
|
||||
|
||||
// Group headers logic:
|
||||
// if there are aggregates, the first th spans until the first
|
||||
// aggregate column then all cells between aggregates are rendered
|
||||
@@ -1064,7 +1075,7 @@ export class ListRenderer extends Component {
|
||||
switch (hotkey) {
|
||||
case "tab":
|
||||
// X2many add a line
|
||||
if (activeActions && (activeActions.canLink || activeActions.canCreate)) {
|
||||
if (this.displayRowCreates) {
|
||||
if (record.isNew && !record.isDirty) {
|
||||
list.unselectRecord(true);
|
||||
return false;
|
||||
@@ -1133,7 +1144,7 @@ export class ListRenderer extends Component {
|
||||
case "tab": {
|
||||
const index = list.records.indexOf(record);
|
||||
if (index === list.records.length - 1) {
|
||||
if (activeActions && (activeActions.canLink || activeActions.canCreate)) {
|
||||
if (this.displayRowCreates) {
|
||||
if (record.isNew && !record.isDirty) {
|
||||
list.unselectRecord(true);
|
||||
return false;
|
||||
@@ -1208,11 +1219,7 @@ export class ListRenderer extends Component {
|
||||
|
||||
if (futureRecord) {
|
||||
futureRecord.switchMode("edit");
|
||||
} else if (
|
||||
this.lastIsDirty ||
|
||||
!record.canBeAbandoned ||
|
||||
(activeActions && (activeActions.canLink || activeActions.canCreate))
|
||||
) {
|
||||
} else if (this.lastIsDirty || !record.canBeAbandoned || this.displayRowCreates) {
|
||||
this.props.onAdd({ group });
|
||||
} else {
|
||||
futureRecord = list.records.at(0);
|
||||
|
||||
@@ -34,26 +34,6 @@
|
||||
<t t-call="web.ListRenderer.Rows">
|
||||
<t t-set="list" t-value="props.list"/>
|
||||
</t>
|
||||
<t t-set="activeActions" t-value="props.activeActions" />
|
||||
<tr t-if="activeActions and ('canLink' in activeActions ? activeActions.canLink : activeActions.canCreate)">
|
||||
<td t-if="withHandleColumn"/>
|
||||
<td t-att-colspan="state.columns.length + 1"
|
||||
class="o_field_x2many_list_row_add"
|
||||
t-on-keydown.synthetic="(ev) => this.onCellKeydown(ev, null)"
|
||||
>
|
||||
<t t-foreach="creates" t-as="create" t-key="create_index">
|
||||
<a
|
||||
href="#"
|
||||
role="button"
|
||||
t-att-class="create_index !== 0 ? 'ml16' : ''"
|
||||
t-att-tabindex="props.list.editedRecord ? '-1' : '0'"
|
||||
t-on-click.stop.prevent="() => this.props.onAdd({context: create.context})"
|
||||
>
|
||||
<t t-esc="create.description"/>
|
||||
</a>
|
||||
</t>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot t-on-click="() => props.list.unselectRecord(true)" t-att-class="{o_sample_data_disabled: props.list.model.useSampleModel}">
|
||||
<tr>
|
||||
@@ -93,6 +73,25 @@
|
||||
<t t-foreach="list.records" t-as="record" t-key="record.id">
|
||||
<t t-call="web.ListRenderer.RecordRow"/>
|
||||
</t>
|
||||
<tr t-if="displayRowCreates">
|
||||
<td t-if="withHandleColumn"/>
|
||||
<td t-att-colspan="state.columns.length + 1"
|
||||
class="o_field_x2many_list_row_add"
|
||||
t-on-keydown.synthetic="(ev) => this.onCellKeydown(ev, null)"
|
||||
>
|
||||
<t t-foreach="creates" t-as="create" t-key="create_index">
|
||||
<a
|
||||
href="#"
|
||||
role="button"
|
||||
t-att-class="create_index !== 0 ? 'ml16' : ''"
|
||||
t-att-tabindex="props.list.editedRecord ? '-1' : '0'"
|
||||
t-on-click.stop.prevent="() => this.props.onAdd({context: create.context})"
|
||||
>
|
||||
<t t-esc="create.description"/>
|
||||
</a>
|
||||
</t>
|
||||
</td>
|
||||
</tr>
|
||||
<t t-if="!props.list.isGrouped">
|
||||
<tr t-foreach="getEmptyRowIds" t-as="emptyRowId" t-key="emptyRowId">
|
||||
<td t-att-colspan="state.columns.length + 1">​</td>
|
||||
|
||||
@@ -12252,4 +12252,50 @@ QUnit.module("Fields", (hooks) => {
|
||||
"localStorage getItem optional_fields,partner,form,100000001,turtles,list,display_name",
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"if there are less than 4 lines in a one2many, empty lines must be displayed to cover the difference.",
|
||||
async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="p">
|
||||
<tree editable="bottom">
|
||||
<field name="display_name"/>
|
||||
</tree>
|
||||
</field>
|
||||
</form>`,
|
||||
resId: 1,
|
||||
});
|
||||
|
||||
// Should contain 4 blank lines
|
||||
assert.containsNone(target, ".o_list_renderer tbody tr .o_data_row");
|
||||
assert.containsNone(target, ".o_list_renderer tbody tr .o_field_x2many_list_row_add");
|
||||
assert.containsN(target, ".o_list_renderer tbody tr", 4);
|
||||
|
||||
await clickEdit(target);
|
||||
// Should only contain the "Add a line" line and 3 blank lines
|
||||
assert.containsNone(target, ".o_list_renderer tbody tr .o_data_row");
|
||||
assert.containsOnce(target, ".o_list_renderer tbody tr .o_field_x2many_list_row_add");
|
||||
assert.hasClass(
|
||||
target.querySelector(".o_list_renderer tbody tr td"),
|
||||
"o_field_x2many_list_row_add"
|
||||
);
|
||||
assert.containsN(target, ".o_list_renderer tbody tr", 4);
|
||||
|
||||
await addRow(target);
|
||||
// Should only contain a new row, the "Add a line" line and 2 blank lines
|
||||
assert.containsOnce(target, ".o_list_renderer tbody tr.o_data_row");
|
||||
assert.hasClass(target.querySelector(".o_list_renderer tbody tr"), "o_data_row");
|
||||
assert.containsOnce(target, ".o_list_renderer tbody tr .o_field_x2many_list_row_add");
|
||||
assert.hasClass(
|
||||
target.querySelectorAll(".o_list_renderer tbody tr")[1].querySelector("td"),
|
||||
"o_field_x2many_list_row_add"
|
||||
);
|
||||
assert.containsN(target, ".o_list_renderer tbody tr", 4);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@@ -332,7 +332,7 @@ tour.stepUtils.autoExpandMoreButtons('.o_form_readonly'),
|
||||
position: 'bottom',
|
||||
}, {
|
||||
trigger: '.o_form_button_save',
|
||||
extra_trigger: ".o_field_widget[name=bom_line_ids] tr:nth-child(5):has(.o_field_x2many_list_row_add)",
|
||||
extra_trigger: ".o_field_widget[name=bom_line_ids] tr:nth-child(3):has(.o_field_x2many_list_row_add)",
|
||||
content: _t('Save the bom.'),
|
||||
position: 'bottom',
|
||||
}, {
|
||||
|
||||
Reference in New Issue
Block a user