From 376f801541db6e12905ad7cc402600eaf9b437ad Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Tue, 28 May 2019 14:30:08 +0200 Subject: [PATCH] [IMP] qweb: t-widget: accept dyn values --- doc/qweb.md | 33 ++++++- src/qweb_extensions.ts | 5 +- tests/__snapshots__/animations.test.ts.snap | 10 ++- tests/__snapshots__/component.test.ts.snap | 97 ++++++++++++++++++--- tests/component.test.ts | 34 +++++++- 5 files changed, 157 insertions(+), 22 deletions(-) diff --git a/doc/qweb.md b/doc/qweb.md index 3d5a626b..1cfed1ad 100644 --- a/doc/qweb.md +++ b/doc/qweb.md @@ -13,7 +13,6 @@ - [`t-if` directive](#t-if-directive) - [Expression evaluation](#expression-evaluation) - [Dynamic attributes (`t-att` and `t-attf` directives)](#dynamic-attributes-t-att-and-t-attf-directives) - - [`t-attf` directive (dynamic attributes)](#t-att-directive-dynamic-attributes) - [`t-call` directive (sub templates)](#t-call-directive-sub-templates) - [JS/OWL Specific Extensions](#jsowl-specific-extensions) @@ -453,6 +452,30 @@ constructor. This will be assigned to the `props` variable, which can be accesse on the widget (and also, in the template). Whenever the state is updated, then the subwidget will also be updated automatically. +The `t-widget` directive also accepts dynamic values with string interpolation +(like the [`t-attf-`](#dynamic-attributes-t-att-and-t-attf-directives) directive): + +```xml +
+ +
+``` + +```js +class ParentWidget { + widgets = { ChildWidget1, ChildWidget2 }; + state = { id: 1 }; +} +``` + +Similarly to `t-attf-`, there is an alternate form of string interpolation: + +```xml +
+ +
+``` + ### `t-ref` directive The `t-ref` directive helps a component keep reference to some inside part of it. @@ -481,8 +504,9 @@ Note: if used on a component, the reference will be set in the `refs` variable between `willPatch` and `patched`. The `t-ref` directive also accepts dynamic values with string interpolation -(like the `t-attf-` directive). For example, if we have `id` set to 44 in the -rendering context, +(like the [`t-attf-`](#dynamic-attributes-t-att-and-t-attf-directives) and +[`t-widget-`](#component-t-widget-t-props) directives). For example, if we have +`id` set to 44 in the rendering context, ```xml
@@ -492,7 +516,8 @@ rendering context, this.refs.widget_44; ``` -Similarly to `t-attf-`, there is an alternate form of string interpolation: +Similarly to `t-attf-` and `t-widget`, there is an alternate form of string +interpolation: ```xml
diff --git a/src/qweb_extensions.ts b/src/qweb_extensions.ts index 0d03441c..58e79f40 100644 --- a/src/qweb_extensions.ts +++ b/src/qweb_extensions.ts @@ -459,13 +459,14 @@ QWeb.addDirective({ ctx.addIf(`!w${widgetID}`); // new widget + ctx.addLine(`let widgetKey${widgetID} = ${ctx.interpolate(value)};`); ctx.addLine( - `let W${widgetID} = context.widgets && context.widgets['${value}'] || QWeb.widgets['${value}'];` + `let W${widgetID} = context.widgets && context.widgets[widgetKey${widgetID}] || QWeb.widgets[widgetKey${widgetID}];` ); // maybe only do this in dev mode... ctx.addLine( - `if (!W${widgetID}) {throw new Error(\`Cannot find the definition of widget "${value}"\`)}` + `if (!W${widgetID}) {throw new Error('Cannot find the definition of widget "' + widgetKey${widgetID} + '"')}` ); ctx.addLine(`w${widgetID} = new W${widgetID}(owner, props${widgetID});`); ctx.addLine( diff --git a/tests/__snapshots__/animations.test.ts.snap b/tests/__snapshots__/animations.test.ts.snap index f6056be5..271d4621 100644 --- a/tests/__snapshots__/animations.test.ts.snap +++ b/tests/__snapshots__/animations.test.ts.snap @@ -20,8 +20,9 @@ exports[`animations t-transition combined with t-widget 1`] = ` w4 = false } if (!w4) { - let W4 = context.widgets && context.widgets['Child'] || QWeb.widgets['Child']; - if (!W4) {throw new Error(\`Cannot find the definition of widget \\"Child\\"\`)} + let widgetKey4 = \`Child\`; + let W4 = context.widgets && context.widgets[widgetKey4] || QWeb.widgets[widgetKey4]; + if (!W4) {throw new Error('Cannot find the definition of widget \\"' + widgetKey4 + '\\"')} w4 = new W4(owner, props4); context.__owl__.cmap[4] = w4.__owl__.id; def3 = w4._prepare(); @@ -59,8 +60,9 @@ exports[`animations t-transition combined with t-widget and t-if 1`] = ` w4 = false } if (!w4) { - let W4 = context.widgets && context.widgets['Child'] || QWeb.widgets['Child']; - if (!W4) {throw new Error(\`Cannot find the definition of widget \\"Child\\"\`)} + let widgetKey4 = \`Child\`; + let W4 = context.widgets && context.widgets[widgetKey4] || QWeb.widgets[widgetKey4]; + if (!W4) {throw new Error('Cannot find the definition of widget \\"' + widgetKey4 + '\\"')} w4 = new W4(owner, props4); context.__owl__.cmap[4] = w4.__owl__.id; def3 = w4._prepare(); diff --git a/tests/__snapshots__/component.test.ts.snap b/tests/__snapshots__/component.test.ts.snap index eb541ce7..08946eb1 100644 --- a/tests/__snapshots__/component.test.ts.snap +++ b/tests/__snapshots__/component.test.ts.snap @@ -21,8 +21,9 @@ exports[`class and style attributes with t-widget dynamic t-att-style is properl w4 = false } if (!w4) { - let W4 = context.widgets && context.widgets['child'] || QWeb.widgets['child']; - if (!W4) {throw new Error(\`Cannot find the definition of widget \\"child\\"\`)} + let widgetKey4 = \`child\`; + let W4 = context.widgets && context.widgets[widgetKey4] || QWeb.widgets[widgetKey4]; + if (!W4) {throw new Error('Cannot find the definition of widget \\"' + widgetKey4 + '\\"')} w4 = new W4(owner, props4); context.__owl__.cmap[4] = w4.__owl__.id; def3 = w4._prepare(); @@ -57,8 +58,9 @@ exports[`class and style attributes with t-widget t-att-class is properly added/ w4 = false } if (!w4) { - let W4 = context.widgets && context.widgets['child'] || QWeb.widgets['child']; - if (!W4) {throw new Error(\`Cannot find the definition of widget \\"child\\"\`)} + let widgetKey4 = \`child\`; + let W4 = context.widgets && context.widgets[widgetKey4] || QWeb.widgets[widgetKey4]; + if (!W4) {throw new Error('Cannot find the definition of widget \\"' + widgetKey4 + '\\"')} w4 = new W4(owner, props4); context.__owl__.cmap[4] = w4.__owl__.id; def3 = w4._prepare(); @@ -111,8 +113,9 @@ exports[`composition sub widgets with some state rendered in a loop 1`] = ` w7 = false } if (!w7) { - let W7 = context.widgets && context.widgets['ChildWidget'] || QWeb.widgets['ChildWidget']; - if (!W7) {throw new Error(\`Cannot find the definition of widget \\"ChildWidget\\"\`)} + let widgetKey7 = \`ChildWidget\`; + let W7 = context.widgets && context.widgets[widgetKey7] || QWeb.widgets[widgetKey7]; + if (!W7) {throw new Error('Cannot find the definition of widget \\"' + widgetKey7 + '\\"')} w7 = new W7(owner, props7); context.__owl__.cmap[key8] = w7.__owl__.id; def6 = w7._prepare(); @@ -127,6 +130,78 @@ exports[`composition sub widgets with some state rendered in a loop 1`] = ` }" `; +exports[`composition t-widget with dynamic value 1`] = ` +"function anonymous(context,extra +) { + let utils = this.utils; + let QWeb = this.constructor; + let owner = context; + var h = this.utils.h; + let c1 = [], p1 = {key:1}; + var vn1 = h('div', p1, c1); + //WIDGET + let _2_index = c1.length; + c1.push(null); + let def3; + let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false; + let props4 = {}; + if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode && props4 !== w4.__owl__.renderProps) { + w4.destroy(); + w4 = false + } + if (!w4) { + let widgetKey4 = context['state'].widget; + let W4 = context.widgets && context.widgets[widgetKey4] || QWeb.widgets[widgetKey4]; + if (!W4) {throw new Error('Cannot find the definition of widget \\"' + widgetKey4 + '\\"')} + w4 = new W4(owner, props4); + context.__owl__.cmap[4] = w4.__owl__.id; + def3 = w4._prepare(); + def3 = def3.then(vnode=>{let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;}); + } else { + def3 = w4._updateProps(props4, extra.forceUpdate, extra.patchQueue); + def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;}); + } + extra.promises.push(def3); + return vn1; +}" +`; + +exports[`composition t-widget with dynamic value 2 1`] = ` +"function anonymous(context,extra +) { + let utils = this.utils; + let QWeb = this.constructor; + let owner = context; + var h = this.utils.h; + let c1 = [], p1 = {key:1}; + var vn1 = h('div', p1, c1); + //WIDGET + let _2_index = c1.length; + c1.push(null); + let def3; + let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false; + let props4 = {}; + if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode && props4 !== w4.__owl__.renderProps) { + w4.destroy(); + w4 = false + } + if (!w4) { + let widgetKey4 = \`Widget\${context['state'].widget}\`; + let W4 = context.widgets && context.widgets[widgetKey4] || QWeb.widgets[widgetKey4]; + if (!W4) {throw new Error('Cannot find the definition of widget \\"' + widgetKey4 + '\\"')} + w4 = new W4(owner, props4); + context.__owl__.cmap[4] = w4.__owl__.id; + def3 = w4._prepare(); + def3 = def3.then(vnode=>{let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;}); + } else { + def3 = w4._updateProps(props4, extra.forceUpdate, extra.patchQueue); + def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;}); + } + extra.promises.push(def3); + return vn1; +}" +`; + exports[`random stuff/miscellaneous snapshotting compiled code 1`] = ` "function anonymous(context,extra ) { @@ -148,8 +223,9 @@ exports[`random stuff/miscellaneous snapshotting compiled code 1`] = ` w4 = false } if (!w4) { - let W4 = context.widgets && context.widgets['child'] || QWeb.widgets['child']; - if (!W4) {throw new Error(\`Cannot find the definition of widget \\"child\\"\`)} + let widgetKey4 = \`child\`; + let W4 = context.widgets && context.widgets[widgetKey4] || QWeb.widgets[widgetKey4]; + if (!W4) {throw new Error('Cannot find the definition of widget \\"' + widgetKey4 + '\\"')} w4 = new W4(owner, props4); context.__owl__.cmap[key5] = w4.__owl__.id; def3 = w4._prepare(); @@ -183,8 +259,9 @@ exports[`random stuff/miscellaneous t-props should not be undefined (snapshottin w4 = false } if (!w4) { - let W4 = context.widgets && context.widgets['child'] || QWeb.widgets['child']; - if (!W4) {throw new Error(\`Cannot find the definition of widget \\"child\\"\`)} + let widgetKey4 = \`child\`; + let W4 = context.widgets && context.widgets[widgetKey4] || QWeb.widgets[widgetKey4]; + if (!W4) {throw new Error('Cannot find the definition of widget \\"' + widgetKey4 + '\\"')} w4 = new W4(owner, props4); context.__owl__.cmap[4] = w4.__owl__.id; def3 = w4._prepare(); diff --git a/tests/component.test.ts b/tests/component.test.ts index 12d0b46c..b01c0307 100644 --- a/tests/component.test.ts +++ b/tests/component.test.ts @@ -857,7 +857,7 @@ describe("composition", () => { const widget = new ParentWidget(env); await widget.mount(fixture); expect(fixture.innerHTML).toBe("
world
"); - delete QWeb.widgets['WidgetB']; + delete QWeb.widgets["WidgetB"]; }); test("don't fallback to global registry if widget defined locally", async () => { @@ -871,7 +871,7 @@ describe("composition", () => { const widget = new ParentWidget(env); await widget.mount(fixture); expect(fixture.innerHTML).toBe("
Belgium
"); - delete QWeb.widgets['WidgetB']; + delete QWeb.widgets["WidgetB"]; }); test("throw a nice error if it cannot find widget", async () => { @@ -1254,6 +1254,36 @@ describe("composition", () => { `) ); }); + + test("t-widget with dynamic value", async () => { + env.qweb.addTemplate( + "ParentWidget", + `
` + ); + class ParentWidget extends Widget { + widgets = { WidgetB }; + state = { widget: "WidgetB" }; + } + const widget = new ParentWidget(env); + await widget.mount(fixture); + expect(fixture.innerHTML).toBe("
world
"); + expect(env.qweb.templates.ParentWidget.fn.toString()).toMatchSnapshot(); + }); + + test("t-widget with dynamic value 2", async () => { + env.qweb.addTemplate( + "ParentWidget", + `
` + ); + class ParentWidget extends Widget { + widgets = { WidgetB }; + state = { widget: "B" }; + } + const widget = new ParentWidget(env); + await widget.mount(fixture); + expect(fixture.innerHTML).toBe("
world
"); + expect(env.qweb.templates.ParentWidget.fn.toString()).toMatchSnapshot(); + }); }); describe("props evaluation (with t-props directive)", () => {