diff --git a/src/qweb_core.ts b/src/qweb_core.ts index 334a8dca..93fe9565 100644 --- a/src/qweb_core.ts +++ b/src/qweb_core.ts @@ -31,7 +31,6 @@ interface Template { } interface CompilationInfo { - nodeID?: number; node: Element; qweb: QWeb; ctx: Context; @@ -39,6 +38,11 @@ interface CompilationInfo { value: string; } +interface NodeCreationCompilationInfo extends CompilationInfo { + nodeID: number; + addNodeHook: Function; +} + export interface Directive { name: string; extraNames?: string[]; @@ -46,7 +50,7 @@ export interface Directive { // if return true, then directive is fully applied and there is no need to // keep processing node. Otherwise, we keep going. atNodeEncounter?(info: CompilationInfo): boolean | void; - atNodeCreation?(info: CompilationInfo): void; + atNodeCreation?(info: NodeCreationCompilationInfo): void; finalize?(info: CompilationInfo): void; } @@ -87,6 +91,12 @@ const DIRECTIVE_NAMES = { const DIRECTIVES: Directive[] = []; +const NODE_HOOKS_PARAMS = { + create: "(_, n)", + insert: "vn", + remove: "(vn, rm)" +}; + export const UTILS = { h: h, objectToAttrString(obj: Object): string { @@ -497,6 +507,11 @@ export class QWeb { if (node.nodeName !== "t") { let nodeID = this._compileGenericNode(node, ctx, withHandlers); ctx = ctx.withParent(nodeID); + let nodeHooks = {}; + let addNodeHook = function(hook, handler) { + nodeHooks[hook] = nodeHooks[hook] || []; + nodeHooks[hook].push(handler); + }; for (let { directive, value, fullName } of validDirectives) { if (directive.atNodeCreation) { @@ -506,10 +521,23 @@ export class QWeb { ctx, fullName, value, - nodeID + nodeID, + addNodeHook }); } } + + if (Object.keys(nodeHooks).length) { + ctx.addLine(`p${nodeID}.hook = {`); + for (let hook in nodeHooks) { + ctx.addLine(` ${hook}: ${NODE_HOOKS_PARAMS[hook]} => {`); + for (let handler of nodeHooks[hook]) { + ctx.addLine(` ${handler}`); + } + ctx.addLine(` },`); + } + ctx.addLine(`};`); + } } if (node.nodeName === "pre") { ctx = ctx.subContext("inPreTag", true); diff --git a/src/qweb_extensions.ts b/src/qweb_extensions.ts index 9b94b89c..de5f9b4d 100644 --- a/src/qweb_extensions.ts +++ b/src/qweb_extensions.ts @@ -57,12 +57,10 @@ QWeb.addDirective({ QWeb.addDirective({ name: "ref", priority: 95, - atNodeCreation({ ctx, nodeID, value }) { + atNodeCreation({ ctx, nodeID, value, addNodeHook }) { const refKey = `ref${ctx.generateID()}`; ctx.addLine(`const ${refKey} = ${ctx.formatExpression(value)}`); - ctx.addLine(`p${nodeID}.hook = { - create: (_, n) => context.refs[${refKey}] = n.elm, - };`); + addNodeHook("create", `context.refs[${refKey}] = n.elm;`); } }); @@ -148,19 +146,16 @@ function whenTransitionEnd(elm: HTMLElement, cb) { QWeb.addDirective({ name: "transition", priority: 96, - atNodeCreation({ ctx, value, nodeID }) { + atNodeCreation({ ctx, value, addNodeHook }) { let name = value; - ctx.addLine(`p${nodeID}.hook = { - create: (_, n) => { - this.utils.transitionCreate(n.elm, '${name}'); - }, - insert: vn => { - this.utils.transitionInsert(vn.elm, '${name}'); - }, - remove: (vn, rm) => { - this.utils.transitionRemove(vn.elm, '${name}', rm); - } - };`); + const hooks = { + create: `this.utils.transitionCreate(n.elm, '${name}');`, + insert: `this.utils.transitionInsert(vn.elm, '${name}');`, + remove: `this.utils.transitionRemove(vn.elm, '${name}', rm);` + }; + for (let hookName in hooks) { + addNodeHook(hookName, hooks[hookName]); + } } }); @@ -333,7 +328,7 @@ QWeb.addDirective({ QWeb.addDirective({ name: "mounted", priority: 97, - atNodeCreation({ ctx, fullName, value, nodeID }) { + atNodeCreation({ ctx, fullName, value, nodeID, addNodeHook }) { ctx.rootContext.shouldDefineOwner = true; const eventName = fullName.slice(5); if (!eventName) { @@ -359,8 +354,9 @@ QWeb.addDirective({ `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}](); } }, - };`); + addNodeHook( + "insert", + `if (context.__owl__.isMounted) { extra.mountedHandlers[${nodeID}](); }` + ); } }); diff --git a/tests/__snapshots__/qweb.test.ts.snap b/tests/__snapshots__/qweb.test.ts.snap index 6cb5c869..9cfccc1e 100644 --- a/tests/__snapshots__/qweb.test.ts.snap +++ b/tests/__snapshots__/qweb.test.ts.snap @@ -10,16 +10,16 @@ exports[`animations t-transition, on a simple node 1`] = ` var vn2 = h('span', p2, c2); c1.push(vn2); p2.hook = { - create: (_, n) => { - this.utils.transitionCreate(n.elm, 'chimay'); - }, - insert: vn => { - this.utils.transitionInsert(vn.elm, 'chimay'); - }, - remove: (vn, rm) => { - this.utils.transitionRemove(vn.elm, 'chimay', rm); - } - }; + create: (_, n) => { + this.utils.transitionCreate(n.elm, 'chimay'); + }, + insert: vn => { + this.utils.transitionInsert(vn.elm, 'chimay'); + }, + remove: (vn, rm) => { + this.utils.transitionRemove(vn.elm, 'chimay', rm); + }, + }; c2.push({text: \`blue\`}); return vn1; }" @@ -1356,8 +1356,10 @@ exports[`t-ref can get a dynamic ref on a node 1`] = ` c1.push(vn2); const ref3 = 'myspan' + 3 p2.hook = { - create: (_, n) => context.refs[ref3] = n.elm, - }; + create: (_, n) => { + context.refs[ref3] = n.elm; + }, + }; return vn1; }" `; @@ -1373,8 +1375,10 @@ exports[`t-ref can get a ref on a node 1`] = ` c1.push(vn2); const ref3 = 'myspan' p2.hook = { - create: (_, n) => context.refs[ref3] = n.elm, - }; + create: (_, n) => { + context.refs[ref3] = n.elm; + }, + }; return vn1; }" `; @@ -1403,7 +1407,9 @@ exports[`t-ref refs in a loop 1`] = ` c1.push(vn5); const ref6 = context['item'] p5.hook = { - create: (_, n) => context.refs[ref6] = n.elm, + create: (_, n) => { + context.refs[ref6] = n.elm; + }, }; var _7 = context['item']; if (_7 || _7 === 0) { diff --git a/tests/component.test.ts b/tests/component.test.ts index 11870718..1405a44e 100644 --- a/tests/component.test.ts +++ b/tests/component.test.ts @@ -2064,4 +2064,32 @@ describe("t-mounted directive", () => { await nextTick(); expect(widget.f).toHaveBeenCalledTimes(1); }); + + test("combined with a t-ref", async () => { + class TestWidget extends Widget { + inlineTemplate = `
`; + f() {} + } + const widget = new TestWidget(env); + widget.f = jest.fn(); + await widget.mount(fixture); + expect(widget.refs.input).toBeDefined(); + expect(widget.f).toHaveBeenCalledTimes(1); + }); + + test("combined with a t-transition", async () => { + class TestWidget extends Widget { + inlineTemplate = `
blue
`; + 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); + }); });