From d73309de28b2e646b7db7b64e37d7db95ca48edd Mon Sep 17 00:00:00 2001 From: Jorge Pinna Puissant Date: Mon, 31 May 2021 09:27:48 +0000 Subject: [PATCH] [IMP] web: use dialog service on action service Until now, the action container opened/closed an action dialog directly on the template, and relayed on the bus to communicate with the action service. Now the action service opens/closes the action dialogs using the action service. To do this, a new close method is created on the action service. This commit also separates the code of dialog service and dialog container to be more standard. closes odoo-dev/odoo#900 Related: odoo-dev/enterprise#156 Signed-off-by: Aaron Bohy (aab) --- .../src/core/dialog/dialog_container.js | 60 +++++++++ .../static/src/core/dialog/dialog_service.js | 81 +++--------- .../web/static/src/legacy/action_adapters.js | 125 ++++++++---------- .../src/webclient/actions/action_container.js | 39 +----- .../src/webclient/actions/action_service.js | 42 ++++-- .../static/tests/core/dialog_service_tests.js | 3 +- .../static/tests/webclient/actions/helpers.js | 1 + .../tests/webclient/actions/legacy_tests.js | 2 +- .../webclient/actions/report_action_tests.js | 4 +- .../webclient/actions/window_action_tests.js | 2 +- 10 files changed, 173 insertions(+), 186 deletions(-) create mode 100644 addons/web/static/src/core/dialog/dialog_container.js diff --git a/addons/web/static/src/core/dialog/dialog_container.js b/addons/web/static/src/core/dialog/dialog_container.js new file mode 100644 index 00000000000..87895912317 --- /dev/null +++ b/addons/web/static/src/core/dialog/dialog_container.js @@ -0,0 +1,60 @@ +/** @odoo-module **/ + +import { registry } from "../registry"; +import { useService } from "../service_hook"; + +const { Component, tags } = owl; + +class ErrorHandler extends Component { + catchError(error) { + this.props.callback(); + throw error; + } +} +ErrorHandler.template = tags.xml``; + +export class DialogContainer extends Component { + setup() { + this.dialogs = {}; + const { bus } = useService("dialog"); + bus.on("ADD", this, (dialog) => { + this.dialogs[dialog.id] = dialog; + this.render(); + }); + bus.on("CLOSE", this, (id) => { + this.closeDialog(id); + }); + } + + onDialogClosed(id) { + this.closeDialog(id); + } + + closeDialog(id) { + if (this.dialogs[id].options && this.dialogs[id].options.onCloseCallback) { + this.dialogs[id].options.onCloseCallback(); + } + delete this.dialogs[id]; + this.render(); + } + + errorCallBack(id) { + return () => this.closeDialog(id); + } + + __destroy() { + this.env.bus.off("ADD", this); + this.env.bus.off("CLOSE", this); + super.__destroy(); + } +} +DialogContainer.components = { ErrorHandler }; +DialogContainer.template = tags.xml` +
+ + + +
+ `; + +registry.category("main_components").add("DialogContainer", DialogContainer); diff --git a/addons/web/static/src/core/dialog/dialog_service.js b/addons/web/static/src/core/dialog/dialog_service.js index dc9f7e23666..5dcebc0b869 100644 --- a/addons/web/static/src/core/dialog/dialog_service.js +++ b/addons/web/static/src/core/dialog/dialog_service.js @@ -1,76 +1,33 @@ /** @odoo-module **/ import { registry } from "../registry"; -import { useService } from "../service_hook"; import { Dialog } from "./dialog"; -const { Component, core, tags, useState } = owl; +const { core } = owl; const { EventBus } = core; -class ErrorHandler extends Component { - catchError(error) { - this.props.callback(); - throw error; - } -} -ErrorHandler.template = tags.xml``; - -export class DialogContainer extends Component { - setup() { - this.dialogs = useState({}); - this.dialogId = 1; - const { bus } = useService("dialog"); - bus.on("UPDATE", this, (dialogClass, props, options) => { - this.addDialog(dialogClass, props, options); - }); - } - - addDialog(dialogClass, props, options) { - if (!(dialogClass.prototype instanceof Dialog)) { - throw new Error(dialogClass.name + " must be a subclass of Dialog"); - } - const id = this.dialogId++; - this.dialogs[id] = { - id, - class: dialogClass, - props, - options, - }; - } - - onDialogClosed(id) { - this.doCloseDialog(id); - } - - doCloseDialog(id) { - if (this.dialogs[id].options && this.dialogs[id].options.onCloseCallback) { - this.dialogs[id].options.onCloseCallback(); - } - delete this.dialogs[id]; - } - - errorCallBack(id) { - return () => this.doCloseDialog(id); - } -} -DialogContainer.components = { ErrorHandler }; -DialogContainer.template = tags.xml` -
- - - -
- `; - -registry.category("main_components").add("DialogContainer", DialogContainer); - export const dialogService = { - start(env) { + start() { const bus = new EventBus(); + let dialogId = 0; function open(dialogClass, props, options) { - bus.trigger("UPDATE", dialogClass, props, options); + if (!(dialogClass.prototype instanceof Dialog)) { + throw new Error(dialogClass.name + " must be a subclass of Dialog"); + } + const id = ++dialogId; + const dialog = { + id, + class: dialogClass, + props, + options, + }; + bus.trigger("ADD", dialog); + return id; } - return { open, bus }; + function close(id) { + bus.trigger("CLOSE", id); + } + return { open, close, bus }; }, }; diff --git a/addons/web/static/src/legacy/action_adapters.js b/addons/web/static/src/legacy/action_adapters.js index e1b4d6fcb25..fcd9ece0309 100644 --- a/addons/web/static/src/legacy/action_adapters.js +++ b/addons/web/static/src/legacy/action_adapters.js @@ -4,7 +4,6 @@ import Context from "web.Context"; import core from "web.core"; import { ComponentAdapter } from "web.OwlCompatibility"; import { objectToQuery } from "../core/browser/router_service"; -import { useBus } from "../core/bus_hook"; import { useDebugMenu } from "../core/debug/debug_menu"; import { Dialog } from "../core/dialog/dialog"; import { useEffect } from "../core/effect_hook"; @@ -39,34 +38,35 @@ class ActionAdapter extends ComponentAdapter { // This is protected in legacy (backward compatibility) but should not e supported in Wowl this.tempQuery = {}; let originalUpdateControlPanel; - useEffect(() => { - this.title.setParts({ action: this.widget.getTitle() }); - const query = objectToQuery(this.widget.getState()); - Object.assign(query, this.tempQuery); - this.tempQuery = null; - this.__widget = this.widget; - if (!this.wowlEnv.inDialog) { - this.router.pushState(query); - } - this.wowlEnv.bus.on("ACTION_MANAGER:UPDATE", this, (info) => { - if (info.type === "MAIN") { - this.env.bus.trigger("close_dialogs"); + useEffect( + () => { + this.title.setParts({ action: this.widget.getTitle() }); + const query = objectToQuery(this.widget.getState()); + Object.assign(query, this.tempQuery); + this.tempQuery = null; + this.__widget = this.widget; + if (!this.wowlEnv.inDialog) { + this.router.pushState(query); } - cleanDomFromBootstrap(); - }); - originalUpdateControlPanel = this.__widget.updateControlPanel.bind(this.__widget); - this.__widget.updateControlPanel = (newProps) => { + this.wowlEnv.bus.on("ACTION_MANAGER:UPDATE", this, () => { + this.env.bus.trigger("close_dialogs"); + cleanDomFromBootstrap(); + }); + originalUpdateControlPanel = this.__widget.updateControlPanel.bind(this.__widget); + this.__widget.updateControlPanel = (newProps) => { + this.trigger("controller-title-updated", this.__widget.getTitle()); + return originalUpdateControlPanel(newProps); + }; this.trigger("controller-title-updated", this.__widget.getTitle()); - return originalUpdateControlPanel(newProps); - }; - this.trigger("controller-title-updated", this.__widget.getTitle()); - core.bus.trigger("DOM_updated"); + core.bus.trigger("DOM_updated"); - return () => { - this.__widget.updateControlPanel = originalUpdateControlPanel; - this.wowlEnv.bus.off("ACTION_MANAGER:UPDATE", this); - }; - }, () => []); + return () => { + this.__widget.updateControlPanel = originalUpdateControlPanel; + this.wowlEnv.bus.off("ACTION_MANAGER:UPDATE", this); + }; + }, + () => [] + ); } _trigger_up(ev) { @@ -188,34 +188,37 @@ function useMagicLegacyReload() { let legacyReloadProm = null; const getReloadProm = () => legacyReloadProm; let manualReload; - useEffect(() => { - const widget = comp.widget; - const controllerReload = widget.reload; - widget.reload = function (...args) { - manualReload = true; - legacyReloadProm = controllerReload.call(widget, ...args); - return legacyReloadProm.then(() => { - if (manualReload) { - legacyReloadProm = null; - manualReload = false; - } - }); - }; - const controllerUpdate = widget.update; - widget.update = function (...args) { - const updateProm = controllerUpdate.call(widget, ...args); - const manualUpdate = !manualReload; - if (manualUpdate) { - legacyReloadProm = updateProm; - } - return updateProm.then(() => { + useEffect( + () => { + const widget = comp.widget; + const controllerReload = widget.reload; + widget.reload = function (...args) { + manualReload = true; + legacyReloadProm = controllerReload.call(widget, ...args); + return legacyReloadProm.then(() => { + if (manualReload) { + legacyReloadProm = null; + manualReload = false; + } + }); + }; + const controllerUpdate = widget.update; + widget.update = function (...args) { + const updateProm = controllerUpdate.call(widget, ...args); + const manualUpdate = !manualReload; if (manualUpdate) { - legacyReloadProm = null; + legacyReloadProm = updateProm; } - }); - }; - widget[magicReloadSymbol] = getReloadProm; - }, () => []); + return updateProm.then(() => { + if (manualUpdate) { + legacyReloadProm = null; + } + }); + }; + widget[magicReloadSymbol] = getReloadProm; + }, + () => [] + ); return getReloadProm; } @@ -226,7 +229,6 @@ export class ViewAdapter extends ActionAdapter { this.vm = useService("view"); this.shouldUpdateWidget = true; this.magicReload = useMagicLegacyReload(); - const envWowl = this.env; useDebugMenu("action", { action: this.props.viewParams.action, component: this, @@ -235,23 +237,6 @@ export class ViewAdapter extends ActionAdapter { if (this.props.viewInfo.type === "form") { useDebugMenu("form"); } - if (!envWowl.inDialog) { - useBus(envWowl.bus, "ACTION_MANAGER:UPDATE", (info) => { - switch (info.type) { - case "OPEN_DIALOG": { - // we are a main action, and a dialog is going to open: - // we should not reload - this.shouldUpdateWidget = false; - break; - } - case "CLOSE_DIALOG": { - this.shouldUpdateWidget = false; - info.closingProms.push(() => this.magicReload()); - break; - } - } - }); - } this.env = Component.env; } diff --git a/addons/web/static/src/webclient/actions/action_container.js b/addons/web/static/src/webclient/actions/action_container.js index e8f31ab27a4..7bb144f229d 100644 --- a/addons/web/static/src/webclient/actions/action_container.js +++ b/addons/web/static/src/webclient/actions/action_container.js @@ -9,36 +9,9 @@ const { Component, tags } = owl; // ----------------------------------------------------------------------------- export class ActionContainer extends Component { setup() { - this.main = {}; - this.dialog = {}; + this.info = {}; this.env.bus.on("ACTION_MANAGER:UPDATE", this, (info) => { - switch (info.type) { - case "MAIN": - this.main = info; - break; - case "OPEN_DIALOG": { - const { onClose } = this.dialog; - this.dialog = { - id: info.id, - props: info.props, - onClose: onClose || info.onClose, - }; - break; - } - case "CLOSE_DIALOG": { - let onClose; - if (this.dialog.id) { - onClose = this.dialog.onClose; - } else { - onClose = info.onClose; - } - if (onClose) { - onClose(info.onCloseInfo); - } - this.dialog = {}; - break; - } - } + this.info = info; this.render(); }); } @@ -47,17 +20,11 @@ export class ActionContainer extends Component { this.env.bus.off("ACTION_MANAGER:UPDATE", this); super.destroy(); } - - onDialogClosed() { - this.dialog = {}; - this.render(); - } } ActionContainer.components = { ActionDialog }; ActionContainer.template = tags.xml`
- - +
`; diff --git a/addons/web/static/src/webclient/actions/action_service.js b/addons/web/static/src/webclient/actions/action_service.js index 7cf9edb03d8..ee375c42b9a 100644 --- a/addons/web/static/src/webclient/actions/action_service.js +++ b/addons/web/static/src/webclient/actions/action_service.js @@ -1,6 +1,7 @@ /** @odoo-module **/ import { KeyNotFoundError } from "@web/core/registry"; +import { cleanDomFromBootstrap } from "@web/legacy/utils"; import { browser } from "../../core/browser/browser"; import { useBus } from "../../core/bus_hook"; import { makeContext } from "../../core/context"; @@ -9,6 +10,7 @@ import { evaluateExpr } from "../../core/py_js/py"; import { registry } from "../../core/registry"; import { KeepLast } from "../../core/utils/concurrency"; import { sprintf } from "../../core/utils/strings"; +import { ActionDialog } from "./action_dialog"; /** * @typedef {"current" | "fullscreen" | "new" | "self" | "inline"} ActionMode */ @@ -57,6 +59,7 @@ function makeActionManager(env) { let controllerStack = []; let dialogCloseProm; let actionCache = {}; + let dialog = {}; // The state action (or default user action if none) is loaded as soon as possible // so that the next "doAction" will have its action ready when needed. @@ -536,6 +539,7 @@ function makeActionManager(env) { ControllerComponent.Component = controller.Component; if (action.target === "new") { + cleanDomFromBootstrap(); const actionDialogProps = { // TODO add size ActionComponent: ControllerComponent, @@ -544,12 +548,16 @@ function makeActionManager(env) { if (action.name) { actionDialogProps.title = action.name; } - env.bus.trigger("ACTION_MANAGER:UPDATE", { - type: "OPEN_DIALOG", - id: ++id, - props: actionDialogProps, - onClose: options.onClose, - }); + + const { onClose } = dialog; + if (dialog.id) { + env.services.dialog.close(dialog.id); + } + const dialogId = env.services.dialog.open(ActionDialog, actionDialogProps); + dialog = { + id: dialogId, + onClose: onClose || options.onClose, + }; return currentActionProm; } @@ -571,7 +579,6 @@ function makeActionManager(env) { controller.props.breadcrumbs = _getBreadcrumbs(nextStack.slice(0, nextStack.length - 1)); const closingProm = _executeCloseAction(); env.bus.trigger("ACTION_MANAGER:UPDATE", { - type: "MAIN", id: ++id, Component: ControllerComponent, componentProps: controller.props, @@ -903,13 +910,20 @@ function makeActionManager(env) { } async function _executeCloseAction(params = {}) { - const closingProms = []; - env.bus.trigger("ACTION_MANAGER:UPDATE", { - type: "CLOSE_DIALOG", - closingProms, - ...params, - }); - await Promise.all([dialogCloseProm].concat(closingProms.map((fn) => fn()))); + cleanDomFromBootstrap(); + let onClose; + if (dialog.id) { + onClose = dialog.onClose; + env.services.dialog.close(dialog.id); + } else { + onClose = params.onClose; + } + dialog = {}; + if (onClose) { + await onClose(params.onCloseInfo); + } + + return dialogCloseProm; } // --------------------------------------------------------------------------- diff --git a/addons/web/static/tests/core/dialog_service_tests.js b/addons/web/static/tests/core/dialog_service_tests.js index e865ab91a84..4ccdf514036 100644 --- a/addons/web/static/tests/core/dialog_service_tests.js +++ b/addons/web/static/tests/core/dialog_service_tests.js @@ -1,6 +1,7 @@ /** @odoo-module **/ -import { DialogContainer, dialogService } from "@web/core/dialog/dialog_service"; +import { DialogContainer } from "@web/core/dialog/dialog_container"; +import { dialogService } from "@web/core/dialog/dialog_service"; import { ErrorDialog } from "@web/core/errors/error_dialogs"; import { errorService } from "@web/core/errors/error_service"; import { registry } from "@web/core/registry"; diff --git a/addons/web/static/tests/webclient/actions/helpers.js b/addons/web/static/tests/webclient/actions/helpers.js index 96ec2778fa2..82d1d24742b 100644 --- a/addons/web/static/tests/webclient/actions/helpers.js +++ b/addons/web/static/tests/webclient/actions/helpers.js @@ -19,6 +19,7 @@ import { effectService } from "@web/webclient/effects/effect_service"; import { hotkeyService } from "@web/webclient/hotkeys/hotkey_service"; import { menuService } from "@web/webclient/menu_service"; import { WebClient } from "@web/webclient/webclient"; +import { DialogContainer } from "@web/core/dialog/dialog_container"; // This import is needed because of it's sideeffects, for exemple : // web.test_utils easyload xml templates at line : 124:130. // Also it set the autocomplete delay time for the field Many2One at 0 for the tests at line : 132:137 diff --git a/addons/web/static/tests/webclient/actions/legacy_tests.js b/addons/web/static/tests/webclient/actions/legacy_tests.js index 7c3794ba6ef..5482360410c 100644 --- a/addons/web/static/tests/webclient/actions/legacy_tests.js +++ b/addons/web/static/tests/webclient/actions/legacy_tests.js @@ -1,6 +1,6 @@ /** @odoo-module **/ -import { DialogContainer } from "@web/core/dialog/dialog_service"; +import { DialogContainer } from "@web/core/dialog/dialog_container"; import { registry } from "@web/core/registry"; import { NotificationContainer } from "@web/core/notifications/notification_container"; import testUtils from "web.test_utils"; diff --git a/addons/web/static/tests/webclient/actions/report_action_tests.js b/addons/web/static/tests/webclient/actions/report_action_tests.js index 0c6869a3b9e..892f48eec3a 100644 --- a/addons/web/static/tests/webclient/actions/report_action_tests.js +++ b/addons/web/static/tests/webclient/actions/report_action_tests.js @@ -8,6 +8,7 @@ import { clearRegistryWithCleanup } from "../../helpers/mock_env"; import { makeFakeNotificationService, makeFakeUserService } from "../../helpers/mock_services"; import { createWebClient, doAction, getActionManagerTestConfig } from "./helpers"; import { mockDownload } from "@web/../tests/helpers/utils"; +import { DialogContainer } from "@web/core/dialog/dialog_container"; let testConfig; @@ -18,6 +19,7 @@ QUnit.module("ActionManager", (hooks) => { hooks.beforeEach(() => { testConfig = getActionManagerTestConfig(); clearRegistryWithCleanup(mainComponentRegistry); + mainComponentRegistry.add("DialogContainer", DialogContainer); }); QUnit.module("Report actions"); @@ -284,7 +286,7 @@ QUnit.module("ActionManager", (hooks) => { }; const webClient = await createWebClient({ testConfig, mockRPC }); let customHandlerCalled = false; - registry.category("ir.actions.report handlers").add("custom_handler", async action => { + registry.category("ir.actions.report handlers").add("custom_handler", async (action) => { if (action.id === 7 && !customHandlerCalled) { customHandlerCalled = true; assert.step("calling custom handler"); diff --git a/addons/web/static/tests/webclient/actions/window_action_tests.js b/addons/web/static/tests/webclient/actions/window_action_tests.js index cd0e749b391..ea77bd72e16 100644 --- a/addons/web/static/tests/webclient/actions/window_action_tests.js +++ b/addons/web/static/tests/webclient/actions/window_action_tests.js @@ -1,7 +1,7 @@ /** @odoo-module **/ import { debugService } from "@web/core/debug/debug_service"; -import { DialogContainer } from "@web/core/dialog/dialog_service"; +import { DialogContainer } from "@web/core/dialog/dialog_container"; import { registry } from "@web/core/registry"; import { editView } from "@web/legacy/debug_manager"; import { clearUncommittedChanges } from "@web/webclient/actions/action_service";