diff --git a/addons/web/static/src/views/fields/relational_utils.js b/addons/web/static/src/views/fields/relational_utils.js index 26d1ace3f1e..a175ea72f5b 100644 --- a/addons/web/static/src/views/fields/relational_utils.js +++ b/addons/web/static/src/views/fields/relational_utils.js @@ -545,14 +545,14 @@ async function getFormViewInfo({ list, activeField, viewService, userService, en return formViewInfo; } -export function useAddInlineRecord({ position, addNew }) { +export function useAddInlineRecord({ addNew }) { let creatingRecord = false; - async function addInlineRecord({ context }) { + async function addInlineRecord({ context, editable }) { if (!creatingRecord) { creatingRecord = true; try { - await addNew({ context, mode: "edit", position }); + await addNew({ context, mode: "edit", position: editable }); } finally { creatingRecord = false; } diff --git a/addons/web/static/src/views/fields/x2many/x2many_field.js b/addons/web/static/src/views/fields/x2many/x2many_field.js index 245f2ef7a7c..e2998a018b7 100644 --- a/addons/web/static/src/views/fields/x2many/x2many_field.js +++ b/addons/web/static/src/views/fields/x2many/x2many_field.js @@ -34,8 +34,8 @@ export class X2ManyField extends Component { this.isMany2Many ); - const subView = this.activeField.views[this.viewMode]; - const subViewActiveActions = subView.activeActions; + const archInfo = this.activeField.views[this.viewMode]; + const subViewActiveActions = archInfo.activeActions; this.activeActions = useActiveActions({ crudOptions: Object.assign({}, this.activeField.options, { onDelete: removeRecord, @@ -50,12 +50,9 @@ export class X2ManyField extends Component { }, }); - if (subView.editable) { - this.addInLine = useAddInlineRecord({ - position: subView.editable, - addNew: (...args) => this.list.addNew(...args), - }); - } + this.addInLine = useAddInlineRecord({ + addNew: (...args) => this.list.addNew(...args), + }); const openRecord = useOpenX2ManyRecord({ resModel: this.list.resModel, @@ -194,13 +191,17 @@ export class X2ManyField extends Component { } }); + const editable = archInfo.editable || this.props.editable; props.activeActions = this.activeActions; props.archInfo = { ...archInfo, columns }; props.cycleOnTab = false; - props.editable = !this.props.readonly && archInfo.editable; + props.editable = !this.props.readonly && editable; props.nestedKeyOptionalFieldsData = this.nestedKeyOptionalFieldsData; - props.onAdd = this.onAdd.bind(this); - + props.onAdd = (params) => { + params.editable = + !this.props.readonly && ("editable" in params ? params.editable : editable); + this.onAdd(params); + }; return props; } @@ -211,7 +212,7 @@ export class X2ManyField extends Component { return false; } - async onAdd({ context } = {}) { + async onAdd({ context, editable } = {}) { const record = this.props.record; const domain = record.getFieldDomain(this.props.name).toList(); if (context) { @@ -220,7 +221,7 @@ export class X2ManyField extends Component { if (this.isMany2Many) { return this.selectCreate({ domain, context }); } - if (this.addInLine) { + if (editable) { if (this.list.editedRecord) { const proms = []; this.list.model.env.bus.trigger("RELATIONAL_MODEL:NEED_LOCAL_CHANGES", { proms }); @@ -228,7 +229,7 @@ export class X2ManyField extends Component { await this.list.editedRecord.switchMode("readonly"); } if (!this.list.editedRecord) { - return this.addInLine({ context }); + return this.addInLine({ context, editable }); } return; } @@ -243,6 +244,7 @@ X2ManyField.components = { Pager, KanbanRenderer, ListRenderer }; X2ManyField.props = { ...standardFieldProps, addLabel: { type: "string", optional: true }, + editable: { type: "string", optional: true }, }; X2ManyField.supportedTypes = ["one2many"]; X2ManyField.template = "web.X2ManyField"; diff --git a/addons/web/static/src/views/list/list_renderer.js b/addons/web/static/src/views/list/list_renderer.js index b0addd99383..c225700edcf 100644 --- a/addons/web/static/src/views/list/list_renderer.js +++ b/addons/web/static/src/views/list/list_renderer.js @@ -192,6 +192,10 @@ export class ListRenderer extends Component { return this.props.hasSelectors && !this.env.isSmall; } + add(params) { + this.props.onAdd(params); + } + // The following code manipulates the DOM directly to avoid having to wait for a // render + patch which would occur on the next frame and cause flickering. freezeColumnWidths() { @@ -831,7 +835,7 @@ export class ListRenderer extends Component { await recordAfterResequence(); await record.switchMode("edit"); this.cellToFocus = { column, record }; - } else if (this.props.editable) { + } else if (this.isInlineEditable(record)) { if (record.isInEdition) { this.focusCell(column); this.cellToFocus = null; @@ -927,6 +931,12 @@ export class ListRenderer extends Component { return futureCell && getElementToFocus(futureCell); } + isInlineEditable(record) { + // /!\ the keyboard navigation works under the hypothesis that all or + // none records are editable. + return !!this.props.editable; + } + /** * @param {KeyboardEvent} ev * @param { import('@web/views/relational_model').Group @@ -1076,7 +1086,7 @@ export class ListRenderer extends Component { record.checkValidity() && (isEnterBehavior || isTabBehavior) ) { - this.props.onAdd({ group }); + this.add({ group }); return true; } return false; @@ -1116,14 +1126,14 @@ export class ListRenderer extends Component { // add a line if (record.checkValidity()) { const { context } = this.creates[0]; - this.props.onAdd({ context }); + this.add({ context }); } } else if ( activeActions.create && !record.canBeAbandoned && (record.isDirty || this.lastIsDirty) ) { - this.props.onAdd({ group }); + this.add({ group }); } else if (cycleOnTab) { if (record.canBeAbandoned) { list.unselectRecord(true); @@ -1187,14 +1197,14 @@ export class ListRenderer extends Component { // add a line if (record.checkValidity()) { const { context } = this.creates[0]; - this.props.onAdd({ context }); + this.add({ context }); } } else if ( activeActions.create && !record.canBeAbandoned && (record.isDirty || this.lastIsDirty) ) { - this.props.onAdd({ group }); + this.add({ group }); } else if (cycleOnTab) { if (record.canBeAbandoned) { list.unselectRecord(true); @@ -1258,7 +1268,7 @@ export class ListRenderer extends Component { if (futureRecord) { futureRecord.switchMode("edit"); } else if (this.lastIsDirty || !record.canBeAbandoned || this.displayRowCreates) { - this.props.onAdd({ group }); + this.add({ group }); } else { futureRecord = list.records.at(0); futureRecord.switchMode("edit"); @@ -1395,7 +1405,7 @@ export class ListRenderer extends Component { return true; } - if (this.props.editable || applyMultiEditBehavior) { + if (this.isInlineEditable(record) || applyMultiEditBehavior) { const column = this.state.columns.find( (c) => c.name === cell.getAttribute("name") ); @@ -1427,7 +1437,7 @@ export class ListRenderer extends Component { if (this.createProm) { return; } - this.props.onAdd({ context }); + this.add({ context }); this.createProm = Promise.resolve(); this.createProm.then(() => { this.lastCreatingAction = true; diff --git a/addons/web/static/src/views/list/list_renderer.xml b/addons/web/static/src/views/list/list_renderer.xml index 465f9a90630..737f8982dd2 100644 --- a/addons/web/static/src/views/list/list_renderer.xml +++ b/addons/web/static/src/views/list/list_renderer.xml @@ -86,7 +86,7 @@ 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-on-click.stop.prevent="() => this.add({ context: create.context })" >