diff --git a/addons/web/static/src/views/fields/relational_utils.js b/addons/web/static/src/views/fields/relational_utils.js index 2a41ca35c98..56d9701dea0 100644 --- a/addons/web/static/src/views/fields/relational_utils.js +++ b/addons/web/static/src/views/fields/relational_utils.js @@ -571,15 +571,18 @@ export class X2ManyFieldDialog extends Component { } async save({ saveAndNew }) { + const disabledButtons = this.disableButtons(); if (await this.record.checkValidity()) { this.record = (await this.props.save(this.record, { saveAndNew })) || this.record; } else { this.record.openInvalidFieldsNotification(); + this.enableButtons(disabledButtons); return false; } if (!saveAndNew) { this.props.close(); } + this.enableButtons(disabledButtons); return true; } @@ -589,9 +592,7 @@ export class X2ManyFieldDialog extends Component { } async saveAndNew() { - const disabledButtons = this.disableButtons(); const saved = await this.save({ saveAndNew: true }); - this.enableButtons(disabledButtons); if (saved) { if (this.title) { this.title = this.title.replace(this.env._t("Open:"), this.env._t("New:")); diff --git a/addons/web/static/src/views/form/form_controller.js b/addons/web/static/src/views/form/form_controller.js index f32b34892c5..7026cfb04f8 100644 --- a/addons/web/static/src/views/form/form_controller.js +++ b/addons/web/static/src/views/form/form_controller.js @@ -117,8 +117,6 @@ export class FormController extends Component { this.canCreate = create && !this.props.preventCreate; this.canEdit = edit && !this.props.preventEdit; - this.disabledButtons = null; - let mode = this.props.mode || "edit"; if (!this.canEdit) { mode = "readonly"; @@ -413,18 +411,17 @@ export class FormController extends Component { } disableButtons() { - const btns = [...this.rootRef.el.querySelectorAll("button:not([disabled])")]; + const btns = [...this.ui.activeElement.querySelectorAll("button:not([disabled])")]; for (const btn of btns) { - btn.setAttribute("disabled", "1"); + btn.setAttribute("disabled", ""); } - this.disabledButtons = btns; + return btns; } - enableButtons() { - for (const btn of this.disabledButtons) { + enableButtons(btns) { + for (const btn of btns) { btn.removeAttribute("disabled"); } - this.disabledButtons = null; } async beforeExecuteActionButton(clickParams) { @@ -459,14 +456,14 @@ export class FormController extends Component { }); } if (canProceed) { - this.disableButtons(); + const btns = this.disableButtons(); await this.model.load({ resId: null }); - this.enableButtons(); + this.enableButtons(btns); } } async saveButtonClicked(params = {}) { - this.disableButtons(); + const btns = this.disableButtons(); const record = this.model.root; let saved = false; @@ -475,7 +472,7 @@ export class FormController extends Component { } else { saved = await record.save(params); } - this.enableButtons(); + this.enableButtons(btns); if (saved && this.props.onSave) { this.props.onSave(record, params); } diff --git a/addons/web/static/src/views/form/form_controller.xml b/addons/web/static/src/views/form/form_controller.xml index 22a93003f4d..3f4ec888481 100644 --- a/addons/web/static/src/views/form/form_controller.xml +++ b/addons/web/static/src/views/form/form_controller.xml @@ -14,7 +14,7 @@ - + diff --git a/addons/web/static/tests/views/fields/one2many_field_tests.js b/addons/web/static/tests/views/fields/one2many_field_tests.js index 71c11f3b1c4..e630a9d858f 100644 --- a/addons/web/static/tests/views/fields/one2many_field_tests.js +++ b/addons/web/static/tests/views/fields/one2many_field_tests.js @@ -402,6 +402,73 @@ QUnit.module("Fields", (hooks) => { await click(target, ".o_field_many2one input"); }); + QUnit.test("O2M modal buttons are disabled on click", async function (assert) { + // Records in an o2m can have a m2o pointing to themselves. + // In that case, a domain evaluation on that field followed by name_search + // shouldn't send virtual_ids to the server. + + patchWithCleanup(browser, { + setTimeout: (fn) => fn(), + }); + + serverData.models.turtle.fields.parent_id = { + string: "Parent", + type: "many2one", + relation: "turtle", + }; + serverData.views = { + "turtle,false,form": ` +
+ + `, + }; + const def = makeDeferred(); + await makeView({ + type: "form", + resModel: "partner", + serverData, + arch: ` +
+ + + + + + + + + `, + async mockRPC(route, args) { + if (args.method === "create") { + await def; + } + }, + }); + + await addRow(target); + + await clickOpenM2ODropdown(target, "parent_id"); + await editInput(target, ".o_field_widget[name=parent_id] input", "ABC"); + await clickOpenedDropdownItem(target, "parent_id", "Create and edit..."); + await click( + target.querySelector(".modal:not(.o_inactive_modal) .modal-footer .o_form_button_save") + ); + assert.strictEqual( + target + .querySelector(".modal:not(.o_inactive_modal) .modal-footer .o_form_button_save") + .getAttribute("disabled"), + "" + ); + def.resolve(); + await nextTick(); + // close all dialogs + await click( + target.querySelector(".modal:not(.o_inactive_modal) .modal-footer .o_form_button_save") + ); + await nextTick(); + assert.containsNone(target, ".o_dialog .o_form_view"); + }); + QUnit.test("resequence a x2m in a form view dialog from another x2m", async function (assert) { await makeView({ type: "form", diff --git a/addons/web/static/tests/views/view_dialogs/form_view_dialog_tests.js b/addons/web/static/tests/views/view_dialogs/form_view_dialog_tests.js index c1d1eb3ba0c..4a17446afc7 100644 --- a/addons/web/static/tests/views/view_dialogs/form_view_dialog_tests.js +++ b/addons/web/static/tests/views/view_dialogs/form_view_dialog_tests.js @@ -2,10 +2,12 @@ import { click, + editInput, getFixture, nextTick, patchWithCleanup, triggerHotkey, + makeDeferred, } from "@web/../tests/helpers/utils"; import { makeViewInDialog } from "@web/../tests/views/helpers"; import { createWebClient } from "@web/../tests/webclient/helpers"; @@ -341,4 +343,48 @@ QUnit.module("ViewDialogs", (hooks) => { assert.verifySteps(["remove"]); assert.containsNone(target, ".o_dialog .o_form_view"); }); + + QUnit.test("Buttons are set as disabled on click", async function (assert) { + serverData.views = { + "partner,false,form": ` +
+ + + + + +
+ `, + }; + const def = makeDeferred(); + async function mockRPC(route, args) { + if (args.method === "write") { + await def; + } + } + const webClient = await createWebClient({ serverData, mockRPC }); + webClient.env.services.dialog.add(FormViewDialog, { + resModel: "partner", + resId: 1, + }); + + await nextTick(); + await editInput( + target.querySelector(".o_dialog .o_content .o_field_char .o_input"), + "", + "test" + ); + + await click(target.querySelector(".o_dialog .modal-footer .o_form_button_save")); + assert.strictEqual( + target + .querySelector(".o_dialog .modal-footer .o_form_button_save") + .getAttribute("disabled"), + "" + ); + + def.resolve(); + await nextTick(); + assert.containsNone(target, ".o_dialog .o_form_view"); + }); });