From 0aba9748c9e67f9b40d1d3334668b1186f9348f4 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Fri, 10 May 2019 15:03:54 +0200 Subject: [PATCH] [IMP] add t-mounted directive closes #45 --- doc/qweb.md | 6 +++++ src/component.ts | 8 ++++++- src/qweb_extensions.ts | 39 +++++++++++++++++++++++++++++++ tests/component.test.ts | 52 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+), 1 deletion(-) diff --git a/doc/qweb.md b/doc/qweb.md index 6a11405d..e311b9af 100644 --- a/doc/qweb.md +++ b/doc/qweb.md @@ -19,6 +19,7 @@ - [`t-ref` directive](#t-ref-directive) - [`t-key` directive](#t-key-directive) - [`t-transition` directive](#t-transition-directive) + - [`t-mounted` directive](#t-mounted-directive) - [Debugging (`t-debug` and `t-log`)](#debugging-t-debug-and-t-log) - [White spaces](#white-spaces) - [Root nodes](#root-nodes) @@ -352,6 +353,11 @@ For example, a simple fade in/out effect can be done with this: Note: more information on animations are available [here](doc/animations.md). +### `t-mounted` directive + +The `t-mounted` directive allows to register a callback to execute when the node +is inserted into the DOM. + ### Debugging (`t-debug` and `t-log`) The javascript QWeb implementation provides two useful debugging directives: diff --git a/src/component.ts b/src/component.ts index 6b0b9a1e..db66796d 100644 --- a/src/component.ts +++ b/src/component.ts @@ -40,6 +40,7 @@ export interface Meta { boundHandlers: { [key: number]: any }; observer?: Observer; render?: CompiledTemplate; + mountedHandlers: { [key: number]: Function }; } //------------------------------------------------------------------------------ @@ -119,7 +120,8 @@ export class Component< renderId: 1, renderPromise: null, renderProps: props || null, - boundHandlers: {} + boundHandlers: {}, + mountedHandlers: {} }; } @@ -226,6 +228,9 @@ export class Component< } } this.__owl__.isMounted = true; + for (let key in this.__owl__.mountedHandlers) { + this.__owl__.mountedHandlers[key](); + } this.mounted(); } @@ -400,6 +405,7 @@ export class Component< let vnode = this.__owl__.render!(this, { promises, handlers: this.__owl__.boundHandlers, + mountedHandlers: this.__owl__.mountedHandlers, forceUpdate: force, patchQueue }); diff --git a/src/qweb_extensions.ts b/src/qweb_extensions.ts index 7d851b23..9b94b89c 100644 --- a/src/qweb_extensions.ts +++ b/src/qweb_extensions.ts @@ -10,6 +10,7 @@ import { QWeb, UTILS } from "./qweb_core"; * - t-ref * - t-transition * - t-widget/t-props/t-keepalive + * - t-mounted */ //------------------------------------------------------------------------------ @@ -325,3 +326,41 @@ QWeb.addDirective({ return true; } }); + +//------------------------------------------------------------------------------ +// t-mounted +//------------------------------------------------------------------------------ +QWeb.addDirective({ + name: "mounted", + priority: 97, + atNodeCreation({ ctx, fullName, value, nodeID }) { + ctx.rootContext.shouldDefineOwner = true; + const eventName = fullName.slice(5); + if (!eventName) { + throw new Error("Missing event name with t-on directive"); + } + let extraArgs; + let handler = value.replace(/\(.*\)/, function(args) { + extraArgs = args.slice(1, -1); + return ""; + }); + let error = `(function () {throw new Error('Missing handler \\'' + '${handler}' + \`\\' when evaluating template '${ctx.templateName.replace( + /`/g, + "'" + )}'\`)})()`; + if (extraArgs) { + ctx.addLine( + `extra.mountedHandlers[${nodeID}] = (context['${handler}'] || ${error}).bind(owner, ${ctx.formatExpression( + extraArgs + )});` + ); + } else { + ctx.addLine( + `extra.mountedHandlers[${nodeID}] = extra.mountedHandlers[${nodeID}] || (context['${handler}'] || ${error}).bind(owner);` + ); + } + ctx.addLine(`p${nodeID}.hook = { + insert: (vn) => { if (context.__owl__.isMounted) { extra.mountedHandlers[${nodeID}](); } }, + };`); + } +}); diff --git a/tests/component.test.ts b/tests/component.test.ts index a53a1e2e..11870718 100644 --- a/tests/component.test.ts +++ b/tests/component.test.ts @@ -2013,3 +2013,55 @@ describe("widget and observable state", () => { expect(fixture.innerHTML).toBe("
12
"); }); }); + +describe("t-mounted directive", () => { + test("callback is not called when not in DOM", async () => { + class TestWidget extends Widget { + inlineTemplate = `
`; + f() {} + } + const widget = new TestWidget(env); + widget.f = jest.fn(); + await widget.mount(document.createElement("div")); + expect(widget.f).toHaveBeenCalledTimes(0); + }); + + test("callback is called when in DOM", async () => { + class TestWidget extends Widget { + inlineTemplate = `
`; + f() {} + } + const widget = new TestWidget(env); + widget.f = jest.fn(); + await widget.mount(fixture); + expect(widget.f).toHaveBeenCalledTimes(1); + }); + + test("callback with args is called when in DOM", async () => { + class TestWidget extends Widget { + inlineTemplate = `
`; + f() {} + } + const widget = new TestWidget(env); + widget.f = jest.fn(); + await widget.mount(fixture); + expect(widget.f).toHaveBeenCalledTimes(1); + expect(widget.f).toHaveBeenCalledWith(2); + }); + + test("combined with a t-if", async () => { + class TestWidget extends Widget { + inlineTemplate = `
`; + state = { flag: false }; + f() {} + } + const widget = new TestWidget(env); + widget.f = jest.fn(); + await widget.mount(fixture); + expect(widget.f).toHaveBeenCalledTimes(0); + + widget.state.flag = true; + await nextTick(); + expect(widget.f).toHaveBeenCalledTimes(1); + }); +});