diff --git a/addons/web/static/src/core/dialog/dialog_container.js b/addons/web/static/src/core/dialog/dialog_container.js index daf0f96cc99..c71610d6bf7 100644 --- a/addons/web/static/src/core/dialog/dialog_container.js +++ b/addons/web/static/src/core/dialog/dialog_container.js @@ -1,17 +1,8 @@ /** @odoo-module **/ -const { Component, tags } = owl; +import { ErrorHandler, NotUpdatable } from "../utils/components"; -class ErrorHandler extends Component { - catchError(error) { - this.props.callback(); - throw error; - } - shouldUpdate() { - return false; - } -} -ErrorHandler.template = tags.xml``; +const { Component, tags } = owl; export class DialogContainer extends Component { setup() { @@ -24,16 +15,24 @@ export class DialogContainer extends Component { } } - errorCallBack(id) { - return () => this.close(id); + handleError(error, dialogId) { + this.close(dialogId); + Promise.resolve().then(() => { + throw error; + }); } } -DialogContainer.components = { ErrorHandler }; +DialogContainer.components = { ErrorHandler, NotUpdatable }; DialogContainer.template = tags.xml`
- + + + + +
`; diff --git a/addons/web/static/src/core/utils/components.js b/addons/web/static/src/core/utils/components.js index eb86323c1cf..eedfab693e7 100644 --- a/addons/web/static/src/core/utils/components.js +++ b/addons/web/static/src/core/utils/components.js @@ -2,14 +2,18 @@ const { Component, tags } = owl; -export function makeNonUpdatableComponent(Comp) { - class NoUpdate extends Component { - shouldUpdate() { - return false; +export class NotUpdatable extends Component { + shouldUpdate() { + return false; + } +} +NotUpdatable.template = tags.xml``; + +export class ErrorHandler extends Component { + catchError(error) { + if (this.props.onError) { + this.props.onError(error); } } - NoUpdate.template = tags.xml``; - NoUpdate.components = { Comp }; - - return NoUpdate; } +ErrorHandler.template = tags.xml``; diff --git a/addons/web/static/src/webclient/navbar/navbar.js b/addons/web/static/src/webclient/navbar/navbar.js index 73d2aa98de3..e635feef055 100644 --- a/addons/web/static/src/webclient/navbar/navbar.js +++ b/addons/web/static/src/webclient/navbar/navbar.js @@ -6,6 +6,7 @@ import { useService } from "../../core/service_hook"; import { useEffect } from "../../core/effect_hook"; import { registry } from "../../core/registry"; import { debounce } from "../../core/utils/timing"; +import { ErrorHandler, NotUpdatable } from "../../core/utils/components"; const { Component, hooks } = owl; const { useExternalListener, useRef } = hooks; @@ -69,6 +70,14 @@ export class NavBar extends Component { this.env.bus.off("MENUS:APP-CHANGED", this); } + handleItemError(error, item) { + // remove the faulty component + item.isDisplayed = () => false; + Promise.resolve().then(() => { + throw error; + }); + } + get currentApp() { return this.menuService.getCurrentApp(); } @@ -183,4 +192,4 @@ export class NavBar extends Component { } } NavBar.template = "web.NavBar"; -NavBar.components = { MenuDropdown, MenuItem }; +NavBar.components = { MenuDropdown, MenuItem, NotUpdatable, ErrorHandler }; diff --git a/addons/web/static/src/webclient/navbar/navbar.xml b/addons/web/static/src/webclient/navbar/navbar.xml index 81564d4794d..f2da688d5c9 100644 --- a/addons/web/static/src/webclient/navbar/navbar.xml +++ b/addons/web/static/src/webclient/navbar/navbar.xml @@ -31,7 +31,11 @@ diff --git a/addons/web/static/src/webclient/webclient.js b/addons/web/static/src/webclient/webclient.js index 5a43cf9ff33..90bb8359cc5 100644 --- a/addons/web/static/src/webclient/webclient.js +++ b/addons/web/static/src/webclient/webclient.js @@ -6,7 +6,7 @@ import { useBus } from "../core/bus_hook"; import { ActionContainer } from "./actions/action_container"; import { NavBar } from "./navbar/navbar"; import { useEffect } from "@web/core/effect_hook"; -import { makeNonUpdatableComponent } from "../core/utils/components"; +import { ErrorHandler, NotUpdatable } from "../core/utils/components"; const { Component, hooks } = owl; const { useExternalListener } = hooks; @@ -20,11 +20,7 @@ export class WebClient extends Component { this.router = useService("router"); this.user = useService("user"); useService("legacy_service_provider"); - this.Components = mainComponentRegistry.getEntries().map(([name, elem]) => { - const { Component, props } = elem; - const NonUpdatableComp = makeNonUpdatableComponent(Component); - return [name, { Component: NonUpdatableComp, props }]; - }); + this.Components = mainComponentRegistry.getEntries(); this.title.setParts({ zopenerp: "Odoo" }); // zopenerp is easy to grep useBus(this.env.bus, "ROUTE_CHANGE", this.loadRouterState); useBus(this.env.bus, "ACTION_MANAGER:UI-UPDATED", (mode) => { @@ -47,6 +43,19 @@ export class WebClient extends Component { this.env.bus.trigger("WEB_CLIENT_READY"); } + handleComponentError(error, C) { + // remove the faulty component + this.Components.splice(this.Components.indexOf(C), 1); + /** + * we rethrow the error to notify the user something bad happened. + * We do it after a tick to make sure owl can properly finish its + * rendering + */ + Promise.resolve().then(() => { + throw error; + }); + } + async loadRouterState() { let stateLoaded = await this.actionService.loadState(); let menuId = Number(this.router.current.hash.menu_id || 0); @@ -106,5 +115,5 @@ export class WebClient extends Component { } } } -WebClient.components = { ActionContainer, NavBar }; +WebClient.components = { ActionContainer, NavBar, ErrorHandler, NotUpdatable }; WebClient.template = "web.WebClient"; diff --git a/addons/web/static/src/webclient/webclient.xml b/addons/web/static/src/webclient/webclient.xml index fb30583aadf..4617f6cbe00 100644 --- a/addons/web/static/src/webclient/webclient.xml +++ b/addons/web/static/src/webclient/webclient.xml @@ -3,11 +3,19 @@ - - + + + + + +
- + + + + +
diff --git a/addons/web/static/tests/core/dialog_service_tests.js b/addons/web/static/tests/core/dialog_service_tests.js index ac292b6375b..39417385478 100644 --- a/addons/web/static/tests/core/dialog_service_tests.js +++ b/addons/web/static/tests/core/dialog_service_tests.js @@ -250,6 +250,7 @@ QUnit.test("dialog component crashes", async (assert) => { env.services.dialog.add(FailingDialog); await prom; + assert.verifySteps(["error"]); assert.containsOnce(pseudoWebClient, ".modal"); assert.containsOnce(pseudoWebClient, ".modal .o_dialog_error"); diff --git a/addons/web/static/tests/core/utils/component_tests.js b/addons/web/static/tests/core/utils/component_tests.js index 0c90101c44b..9acb0643773 100644 --- a/addons/web/static/tests/core/utils/component_tests.js +++ b/addons/web/static/tests/core/utils/component_tests.js @@ -1,6 +1,6 @@ /** @odoo-module **/ -import { makeNonUpdatableComponent } from "@web/core/utils/components"; +import { NotUpdatable, ErrorHandler } from "@web/core/utils/components"; import { makeTestEnv } from "../../helpers/mock_env"; import { getFixture } from "../../helpers/utils"; @@ -9,7 +9,7 @@ const { Component, mount } = owl; QUnit.module("utils", () => { QUnit.module("components"); - QUnit.test("makeNonUpdatableComponent", async function (assert) { + QUnit.test("NotUpdatable component", async function (assert) { class Child extends Component { mounted() { assert.step("mounted"); @@ -20,8 +20,12 @@ QUnit.module("utils", () => { } Child.template = owl.tags.xml`
hey
`; class Parent extends Component {} - Parent.template = owl.tags.xml`
`; - Parent.components = { Child1: Child, Child2: makeNonUpdatableComponent(Child) }; + Parent.template = owl.tags.xml` +
+ + +
`; + Parent.components = { Child, NotUpdatable }; const target = getFixture(); const parent = await mount(Parent, { env: makeTestEnv(), target }); @@ -31,4 +35,36 @@ QUnit.module("utils", () => { assert.verifySteps(["willupdateprops"]); parent.destroy(); }); + + QUnit.test("ErrorHandler component", async function (assert) { + class Boom extends Component {} + Boom.template = owl.tags.xml`
`; + + class Parent extends Component { + setup() { + this.flag = true; + } + handleError() { + this.flag = false; + this.render(); + } + } + Parent.template = owl.tags.xml` +
+ + + + + + + not boom + +
`; + Parent.components = { Boom, ErrorHandler }; + + const target = getFixture(); + const parent = await mount(Parent, { env: makeTestEnv(), target }); + assert.strictEqual(target.innerHTML, "
not boom
"); + parent.destroy(); + }); });