From bf7070016d3128c5d92182ea5dcab4e40990f8bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Sun, 3 Feb 2019 09:42:35 +0100 Subject: [PATCH] add inlineTemplate key to widget --- web/static/src/ts/core/Widget.ts | 8 +- web/static/src/ts/core/qweb_vdom.ts | 12 ++- web/static/tests/core/widget.test.ts | 150 +++++++-------------------- 3 files changed, 55 insertions(+), 115 deletions(-) diff --git a/web/static/src/ts/core/Widget.ts b/web/static/src/ts/core/Widget.ts index abb8de03..0eb93031 100644 --- a/web/static/src/ts/core/Widget.ts +++ b/web/static/src/ts/core/Widget.ts @@ -43,6 +43,7 @@ export interface Type extends Function { export class Widget extends EventBus { __widget__: Meta; template: string = "default"; + inlineTemplate: string | null = null; get el(): HTMLElement | null { return this.__widget__.vnode ? (this).__widget__.vnode.elm : null; @@ -60,6 +61,7 @@ export class Widget extends EventBus { constructor(parent: Widget | T, props?: Props) { super(); wl.push(this); + // is this a good idea? // Pro: if props is empty, we can create easily a widget // Con: this is not really safe @@ -210,11 +212,15 @@ export class Widget extends EventBus { if (!this.__widget__.isDestroyed) { this.__widget__.isStarted = true; } + if (this.inlineTemplate) { + this.env.qweb.addTemplate(this.inlineTemplate, this.inlineTemplate, true); + } } private async _render(): Promise { const promises: Promise[] = []; - let vnode = this.env.qweb.render(this.template, this, { promises }); + const template = this.inlineTemplate || this.template; + let vnode = this.env.qweb.render(template, this, { promises }); // this part is critical for the patching process to be done correctly. The // tricky part is that a child widget can be rerendered on its own, which diff --git a/web/static/src/ts/core/qweb_vdom.ts b/web/static/src/ts/core/qweb_vdom.ts index 18fd4da6..cf3b0faf 100644 --- a/web/static/src/ts/core/qweb_vdom.ts +++ b/web/static/src/ts/core/qweb_vdom.ts @@ -132,9 +132,17 @@ export class QWeb { * Add a template to the internal template map. Note that it is not * immediately compiled. */ - addTemplate(name: string, template: RawTemplate) { + addTemplate( + name: string, + template: RawTemplate, + allowDuplicates: boolean = false + ) { if (name in this.processedTemplates) { - throw new Error(`Template ${name} already defined`); + if (allowDuplicates) { + return; + } else { + throw new Error(`Template ${name} already defined`); + } } const parser = new DOMParser(); const doc = parser.parseFromString(template, "text/xml"); diff --git a/web/static/tests/core/widget.test.ts b/web/static/tests/core/widget.test.ts index e99c8eec..4372705d 100644 --- a/web/static/tests/core/widget.test.ts +++ b/web/static/tests/core/widget.test.ts @@ -88,12 +88,8 @@ describe("basic widget properties", () => { }); test("widget style and classname", async () => { - env.qweb.addTemplate( - "styledwidget", - `
world
` - ); class StyledWidget extends Widget { - template = "styledwidget"; + inlineTemplate = `
world
`; } const widget = new StyledWidget(env); await widget.mount(fixture); @@ -183,9 +179,8 @@ describe("lifecycle hooks", () => { test("willStart hook is called on subwidget", async () => { let ok = false; - env.qweb.addTemplate("parent", `
`); class ParentWidget extends Widget { - template = "parent"; + inlineTemplate = `
`; widgets = { child: ChildWidget }; } class ChildWidget extends Widget { @@ -202,9 +197,8 @@ describe("lifecycle hooks", () => { expect.assertions(4); let parentMounted = false; let childMounted = false; - env.qweb.addTemplate("parent", `
`); class ParentWidget extends Widget { - template = "parent"; + inlineTemplate = `
`; widgets = { child: ChildWidget }; mounted() { expect(childMounted).toBe(false); @@ -226,9 +220,11 @@ describe("lifecycle hooks", () => { test("willStart, mounted on subwidget rendered after main is mounted in some other position", async () => { expect.assertions(3); let hookCounter = 0; - env.qweb.addTemplate( - "parent", - ` + // the t-else part in the template is important. This is + // necessary to have a situation that could confuse the vdom + // patching algorithm + class ParentWidget extends Widget { + inlineTemplate = `
@@ -236,14 +232,8 @@ describe("lifecycle hooks", () => {
-
` - ); // the t-else part in this template is important. This is - // necessary to have a situation that could confuse the vdom - // patching algorithm - class ParentWidget extends Widget { - name = "a"; +
`; state = { ok: false }; - template = "parent"; widgets = { child: ChildWidget }; } class ChildWidget extends Widget { @@ -272,7 +262,6 @@ describe("lifecycle hooks", () => { const target = document.createElement("div"); document.body.appendChild(target); class ParentWidget extends Widget { - name = "a"; mounted() { const child = new ChildWidget(this); child.mount(this.el!); @@ -290,15 +279,11 @@ describe("lifecycle hooks", () => { test("widgets are unmounted and destroyed if no longer in DOM", async () => { let steps: string[] = []; - env.qweb.addTemplate( - "parent", - `
- -
` - ); class ParentWidget extends Widget { state = { ok: true }; - template = "parent"; + inlineTemplate = `
+ +
`; widgets = { child: ChildWidget }; } @@ -335,9 +320,8 @@ describe("lifecycle hooks", () => { test("hooks are called in proper order in widget creation/destruction", async () => { let steps: string[] = []; - env.qweb.addTemplate("parent", `
`); class ParentWidget extends Widget { - template = "parent"; + inlineTemplate = `
`; widgets = { child: ChildWidget }; constructor(parent) { super(parent); @@ -394,9 +378,8 @@ describe("lifecycle hooks", () => { test("shouldUpdate hook prevent rerendering", async () => { let shouldUpdate = false; - env.qweb.addTemplate("testw", `
`); class TestWidget extends Widget { - template = "testw"; + inlineTemplate = `
`; shouldUpdate() { return shouldUpdate; } @@ -507,12 +490,8 @@ describe("composition", () => { }); test("t-refs on widget are widgets", async () => { - env.qweb.addTemplate( - "widgetc", - `
Hello
` - ); class WidgetC extends Widget { - template = "widgetc"; + inlineTemplate = `
Hello
`; widgets = { b: WidgetB }; } const widget = new WidgetC(env); @@ -521,9 +500,8 @@ describe("composition", () => { }); test("modifying a sub widget", async () => { - env.qweb.addTemplate("parent", `
`); class ParentWidget extends Widget { - template = "parent"; + inlineTemplate = `
`; widgets = { Counter }; } const widget = new ParentWidget(env); @@ -560,9 +538,8 @@ describe("composition", () => { }); test("rerendering a widget with a sub widget", async () => { - env.qweb.addTemplate("parent", `
`); class ParentWidget extends Widget { - template = "parent"; + inlineTemplate = `
`; widgets = { Counter }; } const widget = new ParentWidget(env); @@ -580,13 +557,9 @@ describe("composition", () => { }); test("sub widgets are destroyed if no longer in dom, then recreated", async () => { - env.qweb.addTemplate( - "parent", - `
` - ); class ParentWidget extends Widget { state = { ok: true }; - template = "parent"; + inlineTemplate = `
`; widgets = { counter: Counter }; } const widget = new ParentWidget(env); @@ -606,14 +579,9 @@ describe("composition", () => { }); test("sub widgets with t-keep-alive are not destroyed if no longer in dom", async () => { - env.qweb.addTemplate( - "parent", - ` -
` - ); class ParentWidget extends Widget { state = { ok: true }; - template = "parent"; + inlineTemplate = `
`; widgets = { counter: Counter }; } const widget = new ParentWidget(env); @@ -637,19 +605,13 @@ describe("composition", () => { }); test("sub widgets dom state with t-keep-alive is preserved", async () => { - env.qweb.addTemplate( - "parent", - ` -
` - ); - env.qweb.addTemplate("inputwidget", ""); class ParentWidget extends Widget { state = { ok: true }; - template = "parent"; + inlineTemplate = `
`; widgets = { InputWidget }; } class InputWidget extends Widget { - template = "inputwidget"; + inlineTemplate = ""; } const widget = new ParentWidget(env); await widget.mount(fixture); @@ -665,21 +627,17 @@ describe("composition", () => { }); test("sub widgets rendered in a loop", async () => { - env.qweb.addTemplate("child", ``); class ChildWidget extends Widget { - template = "child"; + inlineTemplate = ``; } - env.qweb.addTemplate( - "parent", - ` + + class Parent extends Widget { + inlineTemplate = `
-
` - ); - class Parent extends Widget { - template = "parent"; + `; state = { numbers: [1, 2, 3] }; @@ -700,9 +658,8 @@ describe("composition", () => { test("sub widgets with some state rendered in a loop", async () => { let n = 1; - env.qweb.addTemplate("child", ``); class ChildWidget extends Widget { - template = "child"; + inlineTemplate = ``; constructor(parent) { super(parent); this.state = { n }; @@ -710,17 +667,12 @@ describe("composition", () => { } } - env.qweb.addTemplate( - "parent", - ` -
+ class Parent extends Widget { + inlineTemplate = `
-
` - ); - class Parent extends Widget { - template = "parent"; +
`; state = { numbers: [1, 2, 3] }; @@ -742,19 +694,14 @@ describe("composition", () => { describe("props evaluation (with t-props directive)", () => { test("explicit object prop", async () => { - env.qweb.addTemplate( - "parent", - `
` - ); class Parent extends Widget { - template = "parent"; + inlineTemplate = `
`; widgets = { child: Child }; state = { val: 42 }; } - env.qweb.addTemplate("child", ``); class Child extends Widget { - template = "child"; + inlineTemplate = ``; state: { someval: number }; constructor(parent: Parent, props: { value: number }) { super(parent); @@ -768,19 +715,14 @@ describe("props evaluation (with t-props directive)", () => { }); test("object prop value", async () => { - env.qweb.addTemplate( - "parent", - `
` - ); class Parent extends Widget { - template = "parent"; + inlineTemplate = `
`; widgets = { child: Child }; state = { val: 42 }; } - env.qweb.addTemplate("child", ``); class Child extends Widget { - template = "child"; + inlineTemplate = ``; state: { someval: number }; constructor(parent: Parent, props: { val: number }) { super(parent); @@ -797,12 +739,8 @@ describe("props evaluation (with t-props directive)", () => { describe("t-on directive on widgets", () => { test("t-on works as expected", async () => { let n = 0; - env.qweb.addTemplate( - "parent", - `
` - ); class ParentWidget extends Widget { - template = "parent"; + inlineTemplate = `
`; widgets = { child: Child }; someMethod(arg) { expect(arg).toBe(43); @@ -827,12 +765,8 @@ describe("random stuff/miscellaneous", () => { // this test makes sure that the foreach directive does not pollute sub // context with the inLoop variable, which is then used in the t-widget // directive as a key - env.qweb.addTemplate( - "test", - `
txt
` - ); class Test extends Widget { - template = "test"; + inlineTemplate = `
txt
`; widgets = { widget: Widget }; } const widget = new Test(env); @@ -844,22 +778,14 @@ describe("random stuff/miscellaneous", () => { // in this situation, we protect against a bug that occurred: because of the // interplay between widgets and vnodes, a sub widget vnode was patched // twice. - env.qweb.addTemplate( - "parent", - `
` - ); class Parent extends Widget { - template = "parent"; + inlineTemplate = `
`; widgets = { child: Child }; state = { flag: false }; } - env.qweb.addTemplate( - "child", - `abcdef` - ); class Child extends Widget { - template = "child"; + inlineTemplate = `abcdef`; } const widget = new Parent(env);