From 35c1de26b8a242c56b920a1aa3a778b333ea2ba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Tue, 18 Jun 2019 09:24:52 +0200 Subject: [PATCH] [IMP] component: allow component name in templates closes #186 --- doc/component.md | 60 +++++----- doc/qweb.md | 2 +- src/qweb_core.ts | 7 ++ src/qweb_extensions.ts | 2 +- tests/__snapshots__/animations.test.ts.snap | 2 +- tests/__snapshots__/component.test.ts.snap | 6 +- tests/animations.test.ts | 4 +- tests/component.test.ts | 115 +++++++++++--------- tests/store.test.ts | 22 ++-- tools/playground/samples.js | 38 +++---- tools/playground/templates.xml | 4 +- 11 files changed, 139 insertions(+), 123 deletions(-) diff --git a/doc/component.md b/doc/component.md index 0c4acdaf..b546929a 100644 --- a/doc/component.md +++ b/doc/component.md @@ -337,15 +337,14 @@ This is the opposite method of `mounted`. ### Composition -The example above shows a QWeb template with a `t-on-click` directive. Widget -templates are standard [QWeb](qweb.md) templates, but with an extra directive: -`t-widget`. With the `t-widget` directive, widget templates can declare sub -widgets: +The example above shows a QWeb template with a sub component. In a template, +components are declared with a tagname corresponding to the class name. It has +to be capitalized. ```xml
some text - +
``` @@ -357,22 +356,18 @@ class ParentWidget extends owl.Component { ``` In this example, the `ParentWidget`'s template creates a widget `MyWidget` just -after the span. The `info` key will be added to the subwidget's props. Each -props is a string which represents a javascript (QWeb) expression, so it is +after the span. The `info` key will be added to the subwidget's `props`. Each +`props` is a string which represents a javascript (QWeb) expression, so it is dynamic. If it is necessary to give a string, this can be done by quoting it: -`someString="'somevalue'"`. See the -[QWeb](qweb.md) documentation for more information on the `t-widget` directive. +`someString="'somevalue'"`. Note that the rendering context for the template is the widget itself. This means that the template can access `state`, `props`, `env`, or any methods defined in the widget. -The `t-widget` directive is the key to a declarative component -system. It allows a template to define where and how a sub widget is created -and/or updated. For example: ```xml
- +
``` @@ -397,12 +392,11 @@ the subwidget will also be updated automatically. Note that there are some restrictions on prop names: `class`, `style` and any string which starts with `t-` are not allowed. -The `t-widget` directive also accepts dynamic values with string interpolation -(like the [`t-attf-`](qweb.md#dynamic-attributes) directive): +The `t-widget` directive can also be used to accept dynamic values with string interpolation (like the [`t-attf-`](qweb.md#dynamic-attributes) directive): ```xml
- +
``` @@ -419,7 +413,7 @@ root widget element. ```xml
- +
``` @@ -431,7 +425,7 @@ class that need to be removed. This is why we only support the explicit syntax with a class object: ```xml - + ``` ### Event Handling @@ -463,7 +457,7 @@ event. A _business_ DOM event is triggered by a call to `trigger` on a component. ```xml - + ``` ```js @@ -538,8 +532,8 @@ class Form extends owl.Component { ```xml
- - + +
``` @@ -559,8 +553,8 @@ class Form extends owl.Component { ```xml
- - + +
``` @@ -586,7 +580,7 @@ The `t-model` directive works with ``, ``, - + @@ -604,7 +598,7 @@ Like event handling, the `t-model` directive accepts some modifiers: For example: ```xml - + ``` These modifiers can be combined. For instance, `t-model.lazy.number` will only @@ -627,7 +621,7 @@ There are three main use cases: ```xml - + ``` @@ -836,7 +830,7 @@ Like the `t-on` directive, it can work either on a DOM node, or on a component: ```xml
- +
``` @@ -894,14 +888,14 @@ Slots are defined by the caller, with the `t-set` directive: ```xml
some widget
- +
hey
-
+
``` @@ -933,9 +927,9 @@ be considered the `default` slot. For example: ```xml
- + some content - +
@@ -979,7 +973,7 @@ Here are a few tips on how to work with asynchronous widgets: ```xml
- - + +
``` diff --git a/doc/qweb.md b/doc/qweb.md index 60cde467..76244064 100644 --- a/doc/qweb.md +++ b/doc/qweb.md @@ -132,7 +132,7 @@ It's API is quite simple: ... class ParentWidget extends owl.Component { ... } - qweb.addTemplate("ParentWidget", "
"); + qweb.addTemplate("ParentWidget", "
"); ``` ## Reference diff --git a/src/qweb_core.ts b/src/qweb_core.ts index c3f4f4da..8005bb90 100644 --- a/src/qweb_core.ts +++ b/src/qweb_core.ts @@ -346,6 +346,13 @@ export class QWeb { return; } + const firstLetter = node.tagName[0]; + if (firstLetter === firstLetter.toUpperCase()) { + // this is a component, we modify in place the xml document to change + // to + node.setAttribute('t-widget', node.tagName); + node.nodeValue = 't'; + } const attributes = (node).attributes; const validDirectives: { diff --git a/src/qweb_extensions.ts b/src/qweb_extensions.ts index 28034076..9752f39e 100644 --- a/src/qweb_extensions.ts +++ b/src/qweb_extensions.ts @@ -208,7 +208,7 @@ const T_WIDGET_MODS_CODE = Object.assign({}, MODS_CODE, { * explanation of the code generated by the t-widget directive for the following * situation: * ```xml - * diff --git a/tests/__snapshots__/animations.test.ts.snap b/tests/__snapshots__/animations.test.ts.snap index 4b31d32c..e99d7742 100644 --- a/tests/__snapshots__/animations.test.ts.snap +++ b/tests/__snapshots__/animations.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`animations t-transition combined with t-widget 1`] = ` +exports[`animations t-transition combined with component 1`] = ` "function anonymous(context,extra ) { let utils = this.utils; diff --git a/tests/__snapshots__/component.test.ts.snap b/tests/__snapshots__/component.test.ts.snap index abada199..0c0c8049 100644 --- a/tests/__snapshots__/component.test.ts.snap +++ b/tests/__snapshots__/component.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`async rendering delayed t-widget with t-asyncroot directive 1`] = ` +exports[`async rendering delayed component with t-asyncroot directive 1`] = ` "function anonymous(context,extra ) { let utils = this.utils; @@ -81,7 +81,7 @@ exports[`async rendering delayed t-widget with t-asyncroot directive 1`] = ` }" `; -exports[`async rendering fast t-widget with t-asyncroot directive 1`] = ` +exports[`async rendering fast component with t-asyncroot directive 1`] = ` "function anonymous(context,extra ) { let utils = this.utils; @@ -650,7 +650,7 @@ exports[`other directives with t-widget t-on with prevent and self modifiers (or } } if (!w4) { - let widgetKey4 = \`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); diff --git a/tests/animations.test.ts b/tests/animations.test.ts index 8d806932..dcaf7cde 100644 --- a/tests/animations.test.ts +++ b/tests/animations.test.ts @@ -170,12 +170,12 @@ describe("animations", () => { expect(spanNode.className).toBe(""); }); - test("t-transition combined with t-widget", async () => { + test("t-transition combined with component", async () => { expect.assertions(5); env.qweb.addTemplate( "Parent", - `
` + `
` ); env.qweb.addTemplate("Child", `blue`); class Parent extends Widget { diff --git a/tests/component.test.ts b/tests/component.test.ts index e85adb9a..d62b1d18 100644 --- a/tests/component.test.ts +++ b/tests/component.test.ts @@ -468,7 +468,7 @@ describe("lifecycle hooks", () => { `
- +
` ); @@ -549,7 +549,7 @@ describe("lifecycle hooks", () => { let def = makeDeferred(); env.qweb.addTemplate( "Parent", - '' + '' ); class Parent extends Widget { state = { n: 1 }; @@ -601,7 +601,7 @@ describe("lifecycle hooks", () => { env.qweb.addTemplate( "Parent", - '
' + '
' ); class Parent extends Widget { state = { a: 1 }; @@ -644,7 +644,7 @@ describe("lifecycle hooks", () => { let shouldUpdate = false; env.qweb.addTemplate( "Parent", - `
` + `
` ); class Parent extends Widget { state = { val: 42 }; @@ -889,11 +889,26 @@ describe("composition", () => { delete QWeb.widgets["WidgetB"]; }); + test("can define widgets in template without t-widget", async () => { + env.qweb.addTemplates(` + +
+ +
`); + class C extends Widget {} + class P extends Widget { + widgets = {C}; + } + const parent = new P(env); + await parent.mount(fixture); + expect(fixture.innerHTML).toBe("
1
"); + }); + test("throw a nice error if it cannot find widget", async () => { expect.assertions(1); env.qweb.addTemplate( "Parent", - `
` + `
` ); class Parent extends Widget { widgets = { SomeWidget: Widget }; @@ -1074,11 +1089,11 @@ describe("composition", () => { test("sub widgets are destroyed if no longer in dom, then recreated", async () => { env.qweb.addTemplate( "ParentWidget", - `
` + `
` ); class ParentWidget extends Widget { state = { ok: true }; - widgets = { counter: Counter }; + widgets = { Counter }; } const widget = new ParentWidget(env); await widget.mount(fixture); @@ -1102,11 +1117,11 @@ describe("composition", () => { test("sub widgets with t-keepalive are not destroyed if no longer in dom", async () => { env.qweb.addTemplate( "ParentWidget", - `
` + `
` ); class ParentWidget extends Widget { state = { ok: true }; - widgets = { counter: Counter }; + widgets = { Counter }; } const widget = new ParentWidget(env); await widget.mount(fixture); @@ -1135,7 +1150,7 @@ describe("composition", () => { test("sub widgets dom state with t-keepalive is preserved", async () => { env.qweb.addTemplate( "ParentWidget", - `
` + `
` ); class ParentWidget extends Widget { state = { ok: true }; @@ -1163,7 +1178,7 @@ describe("composition", () => { env.qweb.addTemplates(`
- +
Hello
`); @@ -1275,7 +1290,7 @@ describe("composition", () => { `

hey

noo

- + test
` ); @@ -1723,18 +1738,18 @@ describe("other directives with t-widget", () => { env.qweb.addTemplates(`
- +
-
+
`); const steps: boolean[] = []; class ParentWidget extends Widget { - widgets = { child: Child }; + widgets = { Child }; onEv() {} } class Child extends Widget { - widgets = { child: GrandChild }; + widgets = { GrandChild }; } class GrandChild extends Widget {} const widget = new ParentWidget(env); @@ -1962,7 +1977,7 @@ describe("random stuff/miscellaneous", () => { steps.push(`${this.name}:destroy`); } } - env.qweb.addTemplate("A", `
A
`); + env.qweb.addTemplate("A", `
A
`); class A extends TestWidget { widgets = { B, C }; name = "A"; @@ -1978,9 +1993,9 @@ describe("random stuff/miscellaneous", () => { env.qweb.addTemplate( "C", ` -
C - - +
C + +
` ); class C extends TestWidget { @@ -2102,7 +2117,7 @@ describe("async rendering", () => { let n = 0; env.qweb.addTemplate( "W", - `
` + `
` ); class W extends Widget { widgets = { Child }; @@ -2156,8 +2171,8 @@ describe("async rendering", () => { "Parent", `
- - + +
` ); class Parent extends Widget { @@ -2203,8 +2218,8 @@ describe("async rendering", () => { "Parent", `
- - + +
` ); class Parent extends Widget { @@ -2254,7 +2269,7 @@ describe("async rendering", () => {
  • - +
@@ -2271,7 +2286,7 @@ describe("async rendering", () => { test("properly behave when destroyed/unmounted while rendering ", async () => { let def = Promise.resolve(); - env.qweb.addTemplate("Child", `
`); + env.qweb.addTemplate("Child", `
`); class Child extends Widget { widgets = { SubChild }; mounted() { @@ -2298,7 +2313,7 @@ describe("async rendering", () => { env.qweb.addTemplate( "Parent", ` -
` +
` ); class Parent extends Widget { widgets = { Child }; @@ -2331,8 +2346,8 @@ describe("async rendering", () => { b - - + + @@ -2368,14 +2383,14 @@ describe("async rendering", () => { expect(destroyCount).toBe(0); }); - test("delayed t-widget with t-asyncroot directive", async () => { + test("delayed component with t-asyncroot directive", async () => { env.qweb.addTemplates(`
- - + +
@@ -2423,14 +2438,14 @@ describe("async rendering", () => { ); }); - test("fast t-widget with t-asyncroot directive", async () => { + test("fast component with t-asyncroot directive", async () => { env.qweb.addTemplates(`
- - + +
@@ -2484,8 +2499,8 @@ describe("async rendering", () => {
- - + +
@@ -2617,9 +2632,9 @@ describe("updating environment", () => { }); test("updating parent env does modify child env, part 2", async () => { - env.qweb.addTemplate("ParentWidget", `
`); + env.qweb.addTemplate("ParentWidget", `
`); class ParentWidget extends Widget { - widgets = { child: Widget }; + widgets = { Child: Widget }; } const parent = new ParentWidget(env); await parent.mount(fixture); @@ -2642,7 +2657,7 @@ describe("updating environment", () => { }); test("updating env force rerendering children", async () => { - env.qweb.addTemplate("Parent", `
`); + env.qweb.addTemplate("Parent", `
`); class Parent extends Widget { widgets = { Child }; } @@ -2680,7 +2695,7 @@ describe("widget and observable state", () => { expect.assertions(1); env.qweb.addTemplate( "Parent", - `
` + `
` ); class Parent extends Widget { state = { obj: { coffee: 1 } }; @@ -2822,10 +2837,10 @@ describe("t-slot directive", () => { env.qweb.addTemplates(`
- + header footer - +
@@ -2852,9 +2867,9 @@ describe("t-slot directive", () => {
- + - +
@@ -2887,9 +2902,9 @@ describe("t-slot directive", () => {
- + - +
@@ -3153,9 +3168,9 @@ describe("t-model directive", () => { env.qweb.addTemplates(`
- + sts rocks - +
diff --git a/tests/store.test.ts b/tests/store.test.ts index da6d8a5f..592cd9fe 100644 --- a/tests/store.test.ts +++ b/tests/store.test.ts @@ -534,8 +534,8 @@ describe("connecting a component to store", () => { "App", `
- - + +
` ); @@ -624,8 +624,8 @@ describe("connecting a component to store", () => { env.qweb.addTemplates(`
- - + +
@@ -734,7 +734,7 @@ describe("connecting a component to store", () => { "TodoList", `
- +
` ); @@ -801,7 +801,7 @@ describe("connecting a component to store", () => { "TodoList", `
- +
` ); @@ -839,7 +839,7 @@ describe("connecting a component to store", () => { env.qweb.addTemplate( "App", `
- +
` ); class App extends Component { @@ -923,8 +923,8 @@ describe("connecting a component to store", () => { env.qweb.addTemplate( "App", `
- -
` + +
` ); class App extends Component { widgets = { ConnectedBeer }; @@ -995,7 +995,7 @@ describe("connecting a component to store", () => { env.qweb.addTemplate( "App", `
- +
` ); class App extends Component { @@ -1074,7 +1074,7 @@ describe("connecting a component to store", () => { "Parent", `
- +
` ); diff --git a/tools/playground/samples.js b/tools/playground/samples.js index 52f7cc71..c2bd1858 100644 --- a/tools/playground/samples.js +++ b/tools/playground/samples.js @@ -26,8 +26,8 @@ const COMPONENTS_XML = `
- - + +
`; @@ -88,7 +88,7 @@ const ANIMATION_XML = `
- +
@@ -98,7 +98,7 @@ const ANIMATION_XML = `
- +
@@ -236,7 +236,7 @@ const LIFECYCLE_DEMO_XML = `
- +
`; @@ -483,7 +483,7 @@ const TODO_APP_STORE_XML = `
    - +
@@ -995,17 +995,17 @@ const RESPONSIVE_XML = `
- - + +
- - + +
- - + +
@@ -1128,17 +1128,17 @@ const SLOTS_XML = `
- + Content of card 1... [] - - + +
Card 2... []
- +
-
+
`; @@ -1226,8 +1226,8 @@ app.mount(document.body); const ASYNC_COMPONENTS_XML = `
- - + +
diff --git a/tools/playground/templates.xml b/tools/playground/templates.xml index 9a4d82a7..1c67cc6d 100644 --- a/tools/playground/templates.xml +++ b/tools/playground/templates.xml @@ -24,14 +24,14 @@
-
-