[IMP] web: expose before and after execute action on controllers

Make it possible to override both before and after execute actions on
all view controllers using `useViewButtons`.
This avoids having to change a value in the env to override the function
being called when clicking on a button.

closes odoo/odoo#101310

X-original-commit: 9f6c2b8e969635f35786d58ee89ca767ff57144d
Related: odoo/enterprise#31860
Signed-off-by: William Braeckman (wbr) <wbr@odoo.com>
This commit is contained in:
William Braeckman
2022-09-27 18:14:49 +02:00
parent dc930e8e2a
commit 6a0efdb7c4
9 changed files with 136 additions and 109 deletions
@@ -7,35 +7,35 @@ import { formView } from "@web/views/form/form_view";
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
const { useSubEnv } = owl;
export class ExpenseFormController extends FormController {
setup() {
super.setup();
const dialogService = useService("dialog");
const orm = useService("orm");
const t = this.env._t;
const previousOnClickViewButton = this.env.onClickViewButton;
useSubEnv({
async onClickViewButton(params) {
const record = this.model.root;
if (
params.clickParams.name === "action_submit_expenses" &&
record.data.duplicate_expense_ids.count
) {
dialogService.add(ConfirmationDialog, {
body: t("An expense of same category, amount and date already exists."),
confirm: async () => {
orm.call("hr.expense", "action_approve_duplicates", [record.resId]);
previousOnClickViewButton(params);
},
cancel: () => {},
});
} else {
previousOnClickViewButton(params);
}
},
});
this.dialogService = useService("dialog");
this.orm = useService("orm");
}
/**
* @override
*/
async beforeExecuteActionButton(clickParams) {
const record = this.model.root;
if (
clickParams.name === "action_submit_expenses" &&
record.data.duplicate_expense_ids.count
) {
return new Promise((resolve) => {
this.dialogService.add(ConfirmationDialog, {
body: this.env._t("An expense of same category, amount and date already exists."),
confirm: async () => {
await this.orm.call("hr.expense", "action_approve_duplicates", [record.resId]);
resolve(true);
},
}, {
onClose: resolve.bind(null, false),
});
});
}
return super.beforeExecuteActionButton(...arguments);
}
}
@@ -1,13 +1,13 @@
/** @odoo-module **/
import { PortalWizardUserListRenderer } from "../list/portal_wizard_user_list_renderer";
import { PortalWizardUserListController } from "../list/portal_wizard_user_list_controller";
import { X2ManyField } from "@web/views/fields/x2many/x2many_field";
import { registry } from "@web/core/registry";
export class PortalUserX2ManyField extends X2ManyField {}
PortalUserX2ManyField.components = {
...X2ManyField.components,
ListRenderer: PortalWizardUserListRenderer,
Controller: PortalWizardUserListController,
};
registry.category("fields").add("portal_wizard_user_one2many", PortalUserX2ManyField);
@@ -0,0 +1,28 @@
/** @odoo-module **/
import { ListController } from "@web/views/list/list_controller";
export class PortalWizardUserListController extends ListController {
setup() {
super.setup();
this.isPortalActionOngoing = false;
}
/**
* @override
*/
async beforeExecuteActionButton(clickParams) {
if (clickParams.name === 'action_refresh_modal' || this.isPortalActionOngoing) {
return false;
}
this.isPortalActionOngoing = true;
return super.beforeExecuteActionButton(clickParams);
}
/**
* @override
*/
async afterExecuteActionButton(clickParams) {
this.isPortalActionOngoing = false;
}
}
@@ -1,23 +0,0 @@
/** @odoo-module **/
import { ListRenderer } from "@web/views/list/list_renderer";
const { useSubEnv } = owl;
export class PortalWizardUserListRenderer extends ListRenderer {
setup() {
super.setup();
this.isPortalActionOngoing = false;
const defaultOnClickViewButton = this.env.onClickViewButton;
useSubEnv({
onClickViewButton: async (params) => {
if (params.clickParams.name === 'action_refresh_modal' || this.isPortalActionOngoing) {
return false;
}
this.isPortalActionOngoing = true;
await defaultOnClickViewButton(params);
this.isPortalActionOngoing = false;
},
});
}
}
@@ -172,20 +172,11 @@ export class FormController extends Component {
this.archInfo.arch = this.archInfo.xmlDoc.outerHTML;
}
const beforeExecuteAction = (clickParams) => {
if (clickParams.special !== "cancel") {
return this.model.root.save({ stayInEdition: true }).then((saved) => {
if (saved && this.props.onSave) {
this.props.onSave(this.model.root);
}
return saved;
});
} else if (this.props.onDiscard) {
this.props.onDiscard(this.model.root);
}
};
const rootRef = useRef("root");
useViewButtons(this.model, rootRef, { beforeExecuteAction });
useViewButtons(this.model, rootRef, {
beforeExecuteAction: this.beforeExecuteActionButton.bind(this),
afterExecuteAction: this.afterExecuteActionButton.bind(this),
});
useSetupView({
rootRef,
beforeLeave: () => {
@@ -354,6 +345,21 @@ export class FormController extends Component {
this.state.isDisabled = false;
}
async beforeExecuteActionButton(clickParams) {
if (clickParams.special !== "cancel") {
return this.model.root.save({ stayInEdition: true }).then((saved) => {
if (saved && this.props.onSave) {
this.props.onSave(this.model.root);
}
return saved;
});
} else if (this.props.onDiscard) {
this.props.onDiscard(this.model.root);
}
}
async afterExecuteActionButton(clickParams) {}
async edit() {
await this.model.root.switchMode("edit");
}
@@ -35,7 +35,10 @@ export class KanbanController extends Component {
});
const rootRef = useRef("root");
useViewButtons(this.model, rootRef);
useViewButtons(this.model, rootRef, {
beforeExecuteAction: this.beforeExecuteActionButton.bind(this),
afterExecuteAction: this.afterExecuteActionButton.bind(this),
});
useSetupView({
rootRef,
getGlobalState: () => {
@@ -110,6 +113,10 @@ export class KanbanController extends Component {
}
return list.isGrouped ? list.groups.length > 0 || !groupCreate : true;
}
async beforeExecuteActionButton(clickParams) {}
async afterExecuteActionButton(clickParams) {}
}
KanbanController.template = `web.KanbanView`;
@@ -86,7 +86,10 @@ export class ListController extends Component {
? !fields.x_active.readonly
: false;
useSubEnv({ model: this.model }); // do this in useModel?
useViewButtons(this.model, this.rootRef);
useViewButtons(this.model, this.rootRef, {
beforeExecuteAction: this.beforeExecuteActionButton.bind(this),
afterExecuteAction: this.afterExecuteActionButton.bind(this),
});
useSetupView({
rootRef: this.rootRef,
beforeLeave: async () => {
@@ -451,6 +454,10 @@ export class ListController extends Component {
record.toggleSelection(false);
});
}
async beforeExecuteActionButton(clickParams) {}
async afterExecuteActionButton(clickParams) {}
}
ListController.template = `web.ListView`;
@@ -3,7 +3,6 @@
import { useAutofocus } from "@web/core/utils/hooks";
import { pick } from "@web/core/utils/objects";
import { formView } from "@web/views/form/form_view";
import { useViewButtons } from "@web/views/view_button/view_button_hook";
import { SettingsConfirmationDialog } from "./settings_confirmation_dialog";
import { SettingsFormRenderer } from "./settings_form_renderer";
@@ -12,46 +11,6 @@ const { useSubEnv, useState, useRef, useEffect } = owl;
export class SettingsFormController extends formView.Controller {
setup() {
super.setup();
const beforeExecuteAction = async (clickParams) => {
if (clickParams.name === "cancel") {
return true;
}
let _continue = true;
if (
this.model.root.isDirty &&
!["execute"].includes(clickParams.name) &&
!clickParams.noSaveDialog
) {
const message = this.env._t("Would you like to save your changes?");
await new Promise((resolve) => {
this.dialogService.add(SettingsConfirmationDialog, {
body: message,
confirm: async () => {
await this.model.root.save({ stayInEdition: true });
await this.save();
// It doesn't make sense to do the action of the button
// as the res.config.settings `execute` method will trigger a reload.
_continue = false;
resolve();
},
cancel: async () => {
await this.model.root.discard();
await this.model.root.save({ stayInEdition: true });
_continue = true;
resolve();
},
stayHere: () => {
_continue = false;
resolve();
},
});
});
} else {
_continue = await this.model.root.save({ stayInEdition: true });
}
return _continue;
};
useViewButtons(this.model, useRef("root"), { beforeExecuteAction });
useAutofocus();
this.state = useState({ displayNoContent: false });
this.searchState = useState({ value: "" });
@@ -84,6 +43,49 @@ export class SettingsFormController extends formView.Controller {
this.initialApp = "module" in this.props.context && this.props.context.module;
}
/**
* @override
*/
async beforeExecuteActionButton(clickParams) {
if (clickParams.name === "cancel") {
return true;
}
let _continue = true;
if (
this.model.root.isDirty &&
!["execute"].includes(clickParams.name) &&
!clickParams.noSaveDialog
) {
const message = this.env._t("Would you like to save your changes?");
await new Promise((resolve) => {
this.dialogService.add(SettingsConfirmationDialog, {
body: message,
confirm: async () => {
await this.model.root.save({ stayInEdition: true });
await this.save();
// It doesn't make sense to do the action of the button
// as the res.config.settings `execute` method will trigger a reload.
_continue = false;
resolve();
},
cancel: async () => {
await this.model.root.discard();
await this.model.root.save({ stayInEdition: true });
_continue = true;
resolve();
},
stayHere: () => {
_continue = false;
resolve();
},
});
});
} else {
_continue = await this.model.root.save({ stayInEdition: true });
}
return _continue;
}
displayName() {
return this.env._t("Settings");
}
@@ -2807,10 +2807,10 @@ QUnit.module("Views", (hooks) => {
assert.verifySteps(["read1", "willStart"]);
await click(target.querySelector(".o_form_statusbar button.p"));
assert.verifySteps(["read2", "willUpdateProps", "willUpdateProps"]);
assert.verifySteps(["willUpdateProps", "read2", "willUpdateProps", "willUpdateProps"]);
await click(target.querySelector(".o_form_statusbar button.p"));
assert.verifySteps(["willUpdateProps", "read3", "willUpdateProps", "willUpdateProps"]);
assert.verifySteps(["willUpdateProps", "willUpdateProps", "read3", "willUpdateProps", "willUpdateProps"]);
});
QUnit.test("buttons in form view, new record", async function (assert) {