From 53fc6ea69139ca0cbc72057109b4d990db75f67a Mon Sep 17 00:00:00 2001 From: "Lucas Perais (lpe)" Date: Fri, 29 Apr 2022 08:07:31 +0000 Subject: [PATCH] [FIX] web: owlCompatibility: standaloneAdapter can trigger DOM events Before this commit, when spawning a LegacyWidget with the standaloneAdapter, the legacyWidget couldn't re-trigger an OdooEvent (trigger_up) in the DOM forbidding any attempts to, for instance, execute an action button (a stat button in form view) After this commit, not only standaloneAdapter takes an optional owl ref to trigger onto (defaults to document.body), but the last resort legacy_provider handles execute-action DOM event. closes odoo/odoo#90172 X-original-commit: 616d0761a8e15dfe49db9bacdfa6f7df75587dff Signed-off-by: Lucas Perais (lpe) Signed-off-by: Aaron Bohy (aab) --- .../static/src/legacy/js/owl_compatibility.js | 15 +++- .../src/legacy/legacy_service_provider.js | 24 +++++++ .../tests/legacy/owl_compatibility_tests.js | 68 +++++++++++++++++++ 3 files changed, 104 insertions(+), 3 deletions(-) diff --git a/addons/web/static/src/legacy/js/owl_compatibility.js b/addons/web/static/src/legacy/js/owl_compatibility.js index 809f81c6446..5fb07bec237 100644 --- a/addons/web/static/src/legacy/js/owl_compatibility.js +++ b/addons/web/static/src/legacy/js/owl_compatibility.js @@ -289,7 +289,8 @@ odoo.define('web.OwlCompatibility', function (require) { } } - function standaloneAdapter(props) { + const bodyRef = { get el() { return document.body } }; + function standaloneAdapter(props = {}, ref=bodyRef) { const env = owl.Component.env; const app = new App(null, { templates: window.__OWL_TEMPLATES__, @@ -298,8 +299,16 @@ odoo.define('web.OwlCompatibility', function (require) { translatableAttributes: ["data-tooltip"], translateFn: env._t, }); - const node = app.makeNode(ComponentAdapter, props); - return node.component; + if (!("Component" in props)) { + props.Component = owl.Component; + } + const component = app.makeNode(ComponentAdapter, props).component; + Object.defineProperty(component, "el", { + get() { + return ref.el; + } + }); + return component; } /** diff --git a/addons/web/static/src/legacy/legacy_service_provider.js b/addons/web/static/src/legacy/legacy_service_provider.js index a776135a7b8..a2aac62617f 100644 --- a/addons/web/static/src/legacy/legacy_service_provider.js +++ b/addons/web/static/src/legacy/legacy_service_provider.js @@ -6,6 +6,7 @@ import Context from "web.Context"; import { browser } from "../core/browser/browser"; import { registry } from "../core/registry"; import { mapDoActionOptionAPI } from "./backend_utils"; +import { wrapSuccessOrFail } from "@web/legacy/utils"; export const legacyServiceProvider = { dependencies: ["effect", "action"], @@ -25,6 +26,29 @@ export const legacyServiceProvider = { const legacyOptions = mapDoActionOptionAPI(payload.options); services.action.doAction(payload.action, legacyOptions); }); + + browser.addEventListener("execute-action", (ev) => { + const payload = ev.detail; + const buttonContext = new Context(payload.action_data.context).eval(); + const envContext = new Context(payload.env.context).eval(); + wrapSuccessOrFail( + services.action.doActionButton({ + args: payload.action_data.args, + buttonContext: buttonContext, + context: envContext, + close: payload.action_data.close, + resModel: payload.env.model, + name: payload.action_data.name, + resId: payload.env.currentID || null, + resIds: payload.env.resIDs, + special: payload.action_data.special, + type: payload.action_data.type, + onClose: payload.on_closed, + effect: payload.action_data.effect, + }), + payload + ); + }); }, }; diff --git a/addons/web/static/tests/legacy/owl_compatibility_tests.js b/addons/web/static/tests/legacy/owl_compatibility_tests.js index 8296fd01041..c6355e1b133 100644 --- a/addons/web/static/tests/legacy/owl_compatibility_tests.js +++ b/addons/web/static/tests/legacy/owl_compatibility_tests.js @@ -9,13 +9,18 @@ odoo.define('web.OwlCompatibilityTests', function (require) { ComponentAdapter, ComponentWrapper, WidgetAdapterMixin, + standaloneAdapter, } = require('web.OwlCompatibility'); const testUtils = require('web.test_utils'); const Widget = require('web.Widget'); + const Dialog = require("web.Dialog"); const { registry } = require("@web/core/registry"); const { LegacyComponent } = require("@web/legacy/legacy_component"); const { mapLegacyEnvToWowlEnv, useWowlService } = require("@web/legacy/utils"); + const { legacyServiceProvider } = require("@web/legacy/legacy_service_provider"); + const { click } = require("@web/../tests/helpers/utils"); + const makeTestEnvironment = require("web.test_env"); const { makeTestEnv } = require("@web/../tests/helpers/mock_env"); const { getFixture, mount, useLogLifeCycle, destroy } = require("@web/../tests/helpers/utils"); @@ -807,6 +812,69 @@ odoo.define('web.OwlCompatibilityTests', function (require) { assert.verifySteps(["GED", "AAB", "MCM"]); }); + QUnit.test("standaloneAdapter can trigger in the DOM and execute action", async (assert) => { + assert.expect(3) + const done = assert.async(); + + const MyDialog = Dialog.extend({ + async start() { + const res = await this._super(...arguments); + const btn = document.createElement("button"); + btn.classList.add("myButton"); + btn.addEventListener("click", () => { + this.trigger_up("execute_action", { + action_data: {}, + env: {}, + }); + }); + this.el.appendChild(btn); + return res; + } + }); + + const dialogOpened = makeTestPromise(); + class MyComp extends Component { + setup() { + onWillDestroy(() => { + this.dialog.destroy(); + }) + } + async spawnDialog() { + const parent = standaloneAdapter(); + this.dialog = new MyDialog(parent); + await this.dialog.open(); + dialogOpened.resolve(); + } + } + MyComp.template = xml`