[IMP] component: allow component name in templates

closes #186
This commit is contained in:
Géry Debongnie
2019-06-18 09:24:52 +02:00
parent d1cf6b1b8d
commit 35c1de26b8
11 changed files with 139 additions and 123 deletions
+27 -33
View File
@@ -337,15 +337,14 @@ This is the opposite method of `mounted`.
### Composition ### Composition
The example above shows a QWeb template with a `t-on-click` directive. Widget The example above shows a QWeb template with a sub component. In a template,
templates are standard [QWeb](qweb.md) templates, but with an extra directive: components are declared with a tagname corresponding to the class name. It has
`t-widget`. With the `t-widget` directive, widget templates can declare sub to be capitalized.
widgets:
```xml ```xml
<div t-name="ParentWidget"> <div t-name="ParentWidget">
<span>some text</span> <span>some text</span>
<t t-widget="MyWidget" info="13"/> <MyWidget info="13" />
</div> </div>
``` ```
@@ -357,22 +356,18 @@ class ParentWidget extends owl.Component {
``` ```
In this example, the `ParentWidget`'s template creates a widget `MyWidget` just 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 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 `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: dynamic. If it is necessary to give a string, this can be done by quoting it:
`someString="'somevalue'"`. See the `someString="'somevalue'"`.
[QWeb](qweb.md) documentation for more information on the `t-widget` directive.
Note that the rendering context for the template is the widget itself. This means 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. 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 ```xml
<div t-name="ParentWidget"> <div t-name="ParentWidget">
<t t-widget="ChildWidget" count="state.val"/> <ChildWidget count="state.val" />
</div> </div>
``` ```
@@ -397,12 +392,11 @@ the subwidget will also be updated automatically.
Note that there are some restrictions on prop names: `class`, `style` and any Note that there are some restrictions on prop names: `class`, `style` and any
string which starts with `t-` are not allowed. string which starts with `t-` are not allowed.
The `t-widget` directive also accepts dynamic values with string interpolation The `t-widget` directive can also be used to accept dynamic values with string interpolation (like the [`t-attf-`](qweb.md#dynamic-attributes) directive):
(like the [`t-attf-`](qweb.md#dynamic-attributes) directive):
```xml ```xml
<div t-name="ParentWidget"> <div t-name="ParentWidget">
<t t-widget="ChildWidget{{id}}"/> <t t-widget="ChildWidget{{id}}" />
</div> </div>
``` ```
@@ -419,7 +413,7 @@ root widget element.
```xml ```xml
<div t-name="ParentWidget"> <div t-name="ParentWidget">
<t t-widget="MyWidget" class="someClass" style="font-weight:bold;" info="13"/> <MyWidget class="someClass" style="font-weight:bold;" info="13" />
</div> </div>
``` ```
@@ -431,7 +425,7 @@ class that need to be removed. This is why we only support the explicit syntax
with a class object: with a class object:
```xml ```xml
<t t-widget="MyWidget" t-att-class="{a: state.flagA, b: state.flagB}" /> <MyWidget t-att-class="{a: state.flagA, b: state.flagB}" />
``` ```
### Event Handling ### Event Handling
@@ -463,7 +457,7 @@ event.
A _business_ DOM event is triggered by a call to `trigger` on a component. A _business_ DOM event is triggered by a call to `trigger` on a component.
```xml ```xml
<t t-widget="MyWidget" t-on-menu-loaded="someMethod"/> <MyWidget t-on-menu-loaded="someMethod" />
``` ```
```js ```js
@@ -538,8 +532,8 @@ class Form extends owl.Component {
```xml ```xml
<div> <div>
<input t-on-input="_updateInputValue"/> <input t-on-input="_updateInputValue" />
<span t-esc="state.text"/> <span t-esc="state.text" />
</div> </div>
``` ```
@@ -559,8 +553,8 @@ class Form extends owl.Component {
```xml ```xml
<div> <div>
<input t-model="text"/> <input t-model="text" />
<span t-esc="state.text"/> <span t-esc="state.text" />
</div> </div>
``` ```
@@ -586,7 +580,7 @@ The `t-model` directive works with `<input>`, `<input type="checkbox">`,
<label for="red">Red</label> <label for="red">Red</label>
</span> </span>
<span> <span>
<input type="radio" name="color" id="blue" value="blue" t-model="color"/> <input type="radio" name="color" id="blue" value="blue" t-model="color" />
<label for="blue">Blue</label> <label for="blue">Blue</label>
</span> </span>
</div> </div>
@@ -604,7 +598,7 @@ Like event handling, the `t-model` directive accepts some modifiers:
For example: For example:
```xml ```xml
<input t-model.lazy="someVal"/> <input t-model.lazy="someVal" />
``` ```
These modifiers can be combined. For instance, `t-model.lazy.number` will only These modifiers can be combined. For instance, `t-model.lazy.number` will only
@@ -627,7 +621,7 @@ There are three main use cases:
```xml ```xml
<span t-foreach="todos" t-as="todo" t-key="todo.id"> <span t-foreach="todos" t-as="todo" t-key="todo.id">
<t t-esc="todo.text"/> <t t-esc="todo.text" />
</span> </span>
``` ```
@@ -836,7 +830,7 @@ Like the `t-on` directive, it can work either on a DOM node, or on a component:
```xml ```xml
<div> <div>
<div t-ref="someDiv"/> <div t-ref="someDiv"/>
<t t-widget="SubWidget" t-ref="someWidget"/> <SubWidget t-ref="someWidget"/>
</div> </div>
``` ```
@@ -894,14 +888,14 @@ Slots are defined by the caller, with the `t-set` directive:
```xml ```xml
<div t-name="SomeWidget"> <div t-name="SomeWidget">
<div>some widget</div> <div>some widget</div>
<t t-widget="Dialog" title="Some Dialog"> <Dialog title="Some Dialog">
<t t-set="content"> <t t-set="content">
<div>hey</div> <div>hey</div>
</t> </t>
<t t-set="footer"> <t t-set="footer">
<button t-on-click="doSomething">ok</button> <button t-on-click="doSomething">ok</button>
</t> </t>
</t> </Dialog>
</div> </div>
``` ```
@@ -933,9 +927,9 @@ be considered the `default` slot. For example:
```xml ```xml
<div t-name="Parent"> <div t-name="Parent">
<t t-widget="Child"> <Child>
<span>some content</span> <span>some content</span>
</t> </Child>
</div> </div>
<div t-name="Child"> <div t-name="Child">
@@ -979,7 +973,7 @@ Here are a few tips on how to work with asynchronous widgets:
```xml ```xml
<div t-name="ParentWidget"> <div t-name="ParentWidget">
<t t-widget="SyncChild"/> <SyncChild />
<t t-widget="AsyncChild" t-asyncroot="1"/> <AsyncChild t-asyncroot="1"/>
</div> </div>
``` ```
+1 -1
View File
@@ -132,7 +132,7 @@ It's API is quite simple:
... ...
class ParentWidget extends owl.Component { ... } class ParentWidget extends owl.Component { ... }
qweb.addTemplate("ParentWidget", "<div><t t-widget='Dialog'/></div>"); qweb.addTemplate("ParentWidget", "<div><Dialog/></div>");
``` ```
## Reference ## Reference
+7
View File
@@ -346,6 +346,13 @@ export class QWeb {
return; return;
} }
const firstLetter = node.tagName[0];
if (firstLetter === firstLetter.toUpperCase()) {
// this is a component, we modify in place the xml document to change
// <SomeComponent ... /> to <t t-widget="SomeComponent" ... />
node.setAttribute('t-widget', node.tagName);
node.nodeValue = 't';
}
const attributes = (<Element>node).attributes; const attributes = (<Element>node).attributes;
const validDirectives: { const validDirectives: {
+1 -1
View File
@@ -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 * explanation of the code generated by the t-widget directive for the following
* situation: * situation:
* ```xml * ```xml
* <t t-widget="child" * <Child
* t-key="'somestring'" * t-key="'somestring'"
* flag="state.flag" * flag="state.flag"
* t-transition="fade"/> * t-transition="fade"/>
+1 -1
View File
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // 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 "function anonymous(context,extra
) { ) {
let utils = this.utils; let utils = this.utils;
+3 -3
View File
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // 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 "function anonymous(context,extra
) { ) {
let utils = this.utils; 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 "function anonymous(context,extra
) { ) {
let utils = this.utils; let utils = this.utils;
@@ -650,7 +650,7 @@ exports[`other directives with t-widget t-on with prevent and self modifiers (or
} }
} }
if (!w4) { if (!w4) {
let widgetKey4 = \`child\`; let widgetKey4 = \`Child\`;
let W4 = context.widgets && context.widgets[widgetKey4] || QWeb.widgets[widgetKey4]; let W4 = context.widgets && context.widgets[widgetKey4] || QWeb.widgets[widgetKey4];
if (!W4) {throw new Error('Cannot find the definition of widget \\"' + widgetKey4 + '\\"')} if (!W4) {throw new Error('Cannot find the definition of widget \\"' + widgetKey4 + '\\"')}
w4 = new W4(owner, props4); w4 = new W4(owner, props4);
+2 -2
View File
@@ -170,12 +170,12 @@ describe("animations", () => {
expect(spanNode.className).toBe(""); expect(spanNode.className).toBe("");
}); });
test("t-transition combined with t-widget", async () => { test("t-transition combined with component", async () => {
expect.assertions(5); expect.assertions(5);
env.qweb.addTemplate( env.qweb.addTemplate(
"Parent", "Parent",
`<div><t t-widget="Child" t-transition="chimay"/></div>` `<div><Child t-transition="chimay"/></div>`
); );
env.qweb.addTemplate("Child", `<span>blue</span>`); env.qweb.addTemplate("Child", `<span>blue</span>`);
class Parent extends Widget { class Parent extends Widget {
+65 -50
View File
@@ -468,7 +468,7 @@ describe("lifecycle hooks", () => {
` `
<div> <div>
<div t-if="state.flag"> <div t-if="state.flag">
<t t-widget="ChildWidget" n="state.n"/> <ChildWidget n="state.n"/>
</div> </div>
</div>` </div>`
); );
@@ -549,7 +549,7 @@ describe("lifecycle hooks", () => {
let def = makeDeferred(); let def = makeDeferred();
env.qweb.addTemplate( env.qweb.addTemplate(
"Parent", "Parent",
'<span><t t-widget="Child" n="state.n"/></span>' '<span><Child n="state.n"/></span>'
); );
class Parent extends Widget { class Parent extends Widget {
state = { n: 1 }; state = { n: 1 };
@@ -601,7 +601,7 @@ describe("lifecycle hooks", () => {
env.qweb.addTemplate( env.qweb.addTemplate(
"Parent", "Parent",
'<div><t t-widget="Child" a="state.a"/></div>' '<div><Child a="state.a"/></div>'
); );
class Parent extends Widget { class Parent extends Widget {
state = { a: 1 }; state = { a: 1 };
@@ -644,7 +644,7 @@ describe("lifecycle hooks", () => {
let shouldUpdate = false; let shouldUpdate = false;
env.qweb.addTemplate( env.qweb.addTemplate(
"Parent", "Parent",
`<div><t t-widget="Child" val="state.val"/></div>` `<div><Child val="state.val"/></div>`
); );
class Parent extends Widget { class Parent extends Widget {
state = { val: 42 }; state = { val: 42 };
@@ -889,11 +889,26 @@ describe("composition", () => {
delete QWeb.widgets["WidgetB"]; delete QWeb.widgets["WidgetB"];
}); });
test("can define widgets in template without t-widget", async () => {
env.qweb.addTemplates(`
<templates>
<div t-name="P"><C a="1"/></div>
<span t-name="C"><t t-esc="props.a"/></span>
</templates>`);
class C extends Widget {}
class P extends Widget {
widgets = {C};
}
const parent = new P(env);
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>1</span></div>");
});
test("throw a nice error if it cannot find widget", async () => { test("throw a nice error if it cannot find widget", async () => {
expect.assertions(1); expect.assertions(1);
env.qweb.addTemplate( env.qweb.addTemplate(
"Parent", "Parent",
`<div><t t-widget="SomeMispelledWidget"/></div>` `<div><SomeMispelledWidget /></div>`
); );
class Parent extends Widget { class Parent extends Widget {
widgets = { SomeWidget: Widget }; widgets = { SomeWidget: Widget };
@@ -1074,11 +1089,11 @@ describe("composition", () => {
test("sub widgets are destroyed if no longer in dom, then recreated", async () => { test("sub widgets are destroyed if no longer in dom, then recreated", async () => {
env.qweb.addTemplate( env.qweb.addTemplate(
"ParentWidget", "ParentWidget",
`<div><t t-if="state.ok"><t t-widget="counter"/></t></div>` `<div><t t-if="state.ok"><Counter /></t></div>`
); );
class ParentWidget extends Widget { class ParentWidget extends Widget {
state = { ok: true }; state = { ok: true };
widgets = { counter: Counter }; widgets = { Counter };
} }
const widget = new ParentWidget(env); const widget = new ParentWidget(env);
await widget.mount(fixture); 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 () => { test("sub widgets with t-keepalive are not destroyed if no longer in dom", async () => {
env.qweb.addTemplate( env.qweb.addTemplate(
"ParentWidget", "ParentWidget",
`<div><t t-if="state.ok"><t t-widget="counter" t-keepalive="1"/></t></div>` `<div><t t-if="state.ok"><Counter t-keepalive="1"/></t></div>`
); );
class ParentWidget extends Widget { class ParentWidget extends Widget {
state = { ok: true }; state = { ok: true };
widgets = { counter: Counter }; widgets = { Counter };
} }
const widget = new ParentWidget(env); const widget = new ParentWidget(env);
await widget.mount(fixture); await widget.mount(fixture);
@@ -1135,7 +1150,7 @@ describe("composition", () => {
test("sub widgets dom state with t-keepalive is preserved", async () => { test("sub widgets dom state with t-keepalive is preserved", async () => {
env.qweb.addTemplate( env.qweb.addTemplate(
"ParentWidget", "ParentWidget",
`<div><t t-if="state.ok"><t t-widget="InputWidget" t-keepalive="1"/></t></div>` `<div><t t-if="state.ok"><InputWidget t-keepalive="1"/></t></div>`
); );
class ParentWidget extends Widget { class ParentWidget extends Widget {
state = { ok: true }; state = { ok: true };
@@ -1163,7 +1178,7 @@ describe("composition", () => {
env.qweb.addTemplates(` env.qweb.addTemplates(`
<templates> <templates>
<div t-name="ParentWidget"> <div t-name="ParentWidget">
<t t-if="state.ok"><t t-widget="ChildWidget" t-ref="child" t-keepalive="1"/></t> <t t-if="state.ok"><ChildWidget t-ref="child" t-keepalive="1"/></t>
</div> </div>
<span t-name="ChildWidget">Hello</span> <span t-name="ChildWidget">Hello</span>
</templates>`); </templates>`);
@@ -1275,7 +1290,7 @@ describe("composition", () => {
`<div> `<div>
<h1 t-if="state.flag">hey</h1> <h1 t-if="state.flag">hey</h1>
<h2 t-else="1">noo</h2> <h2 t-else="1">noo</h2>
<span><t t-widget="ChildWidget"/></span> <span><ChildWidget/></span>
<t t-if="state.flag"><span>test</span></t> <t t-if="state.flag"><span>test</span></t>
</div>` </div>`
); );
@@ -1723,18 +1738,18 @@ describe("other directives with t-widget", () => {
env.qweb.addTemplates(` env.qweb.addTemplates(`
<templates> <templates>
<div t-name="ParentWidget"> <div t-name="ParentWidget">
<t t-widget="child" t-on-ev.prevent.self="onEv"/> <Child t-on-ev.prevent.self="onEv"/>
</div> </div>
<div t-name="Child"><t t-widget="child"/></div> <div t-name="Child"><GrandChild/></div>
</templates> </templates>
`); `);
const steps: boolean[] = []; const steps: boolean[] = [];
class ParentWidget extends Widget { class ParentWidget extends Widget {
widgets = { child: Child }; widgets = { Child };
onEv() {} onEv() {}
} }
class Child extends Widget { class Child extends Widget {
widgets = { child: GrandChild }; widgets = { GrandChild };
} }
class GrandChild extends Widget {} class GrandChild extends Widget {}
const widget = new ParentWidget(env); const widget = new ParentWidget(env);
@@ -1962,7 +1977,7 @@ describe("random stuff/miscellaneous", () => {
steps.push(`${this.name}:destroy`); steps.push(`${this.name}:destroy`);
} }
} }
env.qweb.addTemplate("A", `<div>A<t t-widget="B"/><t t-widget="C"/></div>`); env.qweb.addTemplate("A", `<div>A<B /><C /></div>`);
class A extends TestWidget { class A extends TestWidget {
widgets = { B, C }; widgets = { B, C };
name = "A"; name = "A";
@@ -1978,9 +1993,9 @@ describe("random stuff/miscellaneous", () => {
env.qweb.addTemplate( env.qweb.addTemplate(
"C", "C",
` `
<div>C<t t-widget="D"/> <div>C<D />
<t t-if="state.flag" t-widget="E"/> <E t-if="state.flag" />
<t t-else="!state.flag" t-widget="F"/> <F t-else="!state.flag" />
</div>` </div>`
); );
class C extends TestWidget { class C extends TestWidget {
@@ -2102,7 +2117,7 @@ describe("async rendering", () => {
let n = 0; let n = 0;
env.qweb.addTemplate( env.qweb.addTemplate(
"W", "W",
`<div><t t-if="state.val > 1"><t t-widget="Child" val="state.val"/></t></div>` `<div><t t-if="state.val > 1"><Child val="state.val"/></t></div>`
); );
class W extends Widget { class W extends Widget {
widgets = { Child }; widgets = { Child };
@@ -2156,8 +2171,8 @@ describe("async rendering", () => {
"Parent", "Parent",
` `
<div> <div>
<t t-if="state.flagA"><t t-widget="ChildA"/></t> <t t-if="state.flagA"><ChildA /></t>
<t t-if="state.flagB"><t t-widget="ChildB"/></t> <t t-if="state.flagB"><ChildB /></t>
</div>` </div>`
); );
class Parent extends Widget { class Parent extends Widget {
@@ -2203,8 +2218,8 @@ describe("async rendering", () => {
"Parent", "Parent",
` `
<div> <div>
<t t-widget="ChildA" val="state.valA"/> <ChildA val="state.valA"/>
<t t-if="state.flagB"><t t-widget="ChildB" val="state.valB"/></t> <t t-if="state.flagB"><ChildB val="state.valB"/></t>
</div>` </div>`
); );
class Parent extends Widget { class Parent extends Widget {
@@ -2254,7 +2269,7 @@ describe("async rendering", () => {
<ul> <ul>
<t t-foreach="items" t-as="item"> <t t-foreach="items" t-as="item">
<li t-key="'li_'+item"> <li t-key="'li_'+item">
<t t-widget="Child" item="item"/> <Child item="item"/>
</li> </li>
</t> </t>
</ul> </ul>
@@ -2271,7 +2286,7 @@ describe("async rendering", () => {
test("properly behave when destroyed/unmounted while rendering ", async () => { test("properly behave when destroyed/unmounted while rendering ", async () => {
let def = Promise.resolve(); let def = Promise.resolve();
env.qweb.addTemplate("Child", `<div><t t-widget="SubChild"/></div>`); env.qweb.addTemplate("Child", `<div><SubChild /></div>`);
class Child extends Widget { class Child extends Widget {
widgets = { SubChild }; widgets = { SubChild };
mounted() { mounted() {
@@ -2298,7 +2313,7 @@ describe("async rendering", () => {
env.qweb.addTemplate( env.qweb.addTemplate(
"Parent", "Parent",
` `
<div><t t-if="state.flag"><t t-widget="Child" val="state.val"/></t></div>` <div><t t-if="state.flag"><Child val="state.val"/></t></div>`
); );
class Parent extends Widget { class Parent extends Widget {
widgets = { Child }; widgets = { Child };
@@ -2331,8 +2346,8 @@ describe("async rendering", () => {
<span t-name="ChildB">b<t t-esc="props.val"/></span> <span t-name="ChildB">b<t t-esc="props.val"/></span>
<span t-name="Parent"> <span t-name="Parent">
<t t-if="state.flag"> <t t-if="state.flag">
<t t-widget="ChildA" val="state.valA"/> <ChildA val="state.valA"/>
<t t-widget="ChildB" val="state.valB"/> <ChildB val="state.valB"/>
</t> </t>
</span> </span>
</templates> </templates>
@@ -2368,14 +2383,14 @@ describe("async rendering", () => {
expect(destroyCount).toBe(0); expect(destroyCount).toBe(0);
}); });
test("delayed t-widget with t-asyncroot directive", async () => { test("delayed component with t-asyncroot directive", async () => {
env.qweb.addTemplates(` env.qweb.addTemplates(`
<templates> <templates>
<div t-name="Parent"> <div t-name="Parent">
<button t-on-click="updateApp">Update App State</button> <button t-on-click="updateApp">Update App State</button>
<div class="children"> <div class="children">
<t t-widget="Child" val="state.val"/> <Child val="state.val"/>
<t t-widget="AsyncChild" t-asyncroot="1" val="state.val"/> <AsyncChild t-asyncroot="1" val="state.val"/>
</div> </div>
</div> </div>
<span t-name="Child"><t t-esc="props.val"/></span> <span t-name="Child"><t t-esc="props.val"/></span>
@@ -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(` env.qweb.addTemplates(`
<templates> <templates>
<div t-name="Parent"> <div t-name="Parent">
<button t-on-click="updateApp">Update App State</button> <button t-on-click="updateApp">Update App State</button>
<div class="children"> <div class="children">
<t t-widget="Child" t-asyncroot="1" val="state.val"/> <Child t-asyncroot="1" val="state.val"/>
<t t-widget="AsyncChild" val="state.val"/> <AsyncChild val="state.val"/>
</div> </div>
</div> </div>
<span t-name="Child"><t t-esc="props.val"/></span> <span t-name="Child"><t t-esc="props.val"/></span>
@@ -2484,8 +2499,8 @@ describe("async rendering", () => {
<div t-name="Parent"> <div t-name="Parent">
<button t-on-click="updateApp">Update App State</button> <button t-on-click="updateApp">Update App State</button>
<div class="children"> <div class="children">
<t t-widget="Child" val="state.val"/> <Child val="state.val"/>
<t t-widget="AsyncChild" t-asyncroot="1" val="state.val"/> <AsyncChild t-asyncroot="1" val="state.val"/>
</div> </div>
</div> </div>
<span t-name="Child" t-on-click="increment"> <span t-name="Child" t-on-click="increment">
@@ -2617,9 +2632,9 @@ describe("updating environment", () => {
}); });
test("updating parent env does modify child env, part 2", async () => { test("updating parent env does modify child env, part 2", async () => {
env.qweb.addTemplate("ParentWidget", `<div><t t-widget="child"/></div>`); env.qweb.addTemplate("ParentWidget", `<div><Child/></div>`);
class ParentWidget extends Widget { class ParentWidget extends Widget {
widgets = { child: Widget }; widgets = { Child: Widget };
} }
const parent = new ParentWidget(env); const parent = new ParentWidget(env);
await parent.mount(fixture); await parent.mount(fixture);
@@ -2642,7 +2657,7 @@ describe("updating environment", () => {
}); });
test("updating env force rerendering children", async () => { test("updating env force rerendering children", async () => {
env.qweb.addTemplate("Parent", `<div><t t-widget="Child"/></div>`); env.qweb.addTemplate("Parent", `<div><Child /></div>`);
class Parent extends Widget { class Parent extends Widget {
widgets = { Child }; widgets = { Child };
} }
@@ -2680,7 +2695,7 @@ describe("widget and observable state", () => {
expect.assertions(1); expect.assertions(1);
env.qweb.addTemplate( env.qweb.addTemplate(
"Parent", "Parent",
`<div><t t-widget="Child" obj="state.obj"/></div>` `<div><Child obj="state.obj"/></div>`
); );
class Parent extends Widget { class Parent extends Widget {
state = { obj: { coffee: 1 } }; state = { obj: { coffee: 1 } };
@@ -2822,10 +2837,10 @@ describe("t-slot directive", () => {
env.qweb.addTemplates(` env.qweb.addTemplates(`
<templates> <templates>
<div t-name="Parent"> <div t-name="Parent">
<t t-widget="Dialog"> <Dialog>
<t t-set="header"><span>header</span></t> <t t-set="header"><span>header</span></t>
<t t-set="footer"><span>footer</span></t> <t t-set="footer"><span>footer</span></t>
</t> </Dialog>
</div> </div>
<div t-name="Dialog"> <div t-name="Dialog">
<div><t t-slot="header"/></div> <div><t t-slot="header"/></div>
@@ -2852,9 +2867,9 @@ describe("t-slot directive", () => {
<templates> <templates>
<div t-name="Parent"> <div t-name="Parent">
<span class="counter"><t t-esc="state.val"/></span> <span class="counter"><t t-esc="state.val"/></span>
<t t-widget="Dialog"> <Dialog>
<t t-set="footer"><button t-on-click="doSomething">do something</button></t> <t t-set="footer"><button t-on-click="doSomething">do something</button></t>
</t> </Dialog>
</div> </div>
<span t-name="Dialog"><t t-slot="footer"/></span> <span t-name="Dialog"><t t-slot="footer"/></span>
</templates> </templates>
@@ -2887,9 +2902,9 @@ describe("t-slot directive", () => {
<templates> <templates>
<div t-name="Parent"> <div t-name="Parent">
<span class="counter"><t t-esc="state.val"/></span> <span class="counter"><t t-esc="state.val"/></span>
<t t-widget="Dialog"> <Dialog>
<t t-set="footer"><button t-ref="myButton" t-on-click="doSomething">do something</button></t> <t t-set="footer"><button t-ref="myButton" t-on-click="doSomething">do something</button></t>
</t> </Dialog>
</div> </div>
<span t-name="Dialog"><t t-slot="footer"/></span> <span t-name="Dialog"><t t-slot="footer"/></span>
</templates> </templates>
@@ -3153,9 +3168,9 @@ describe("t-model directive", () => {
env.qweb.addTemplates(` env.qweb.addTemplates(`
<templates> <templates>
<div t-name="Parent"> <div t-name="Parent">
<t t-widget="Dialog"> <Dialog>
<span>sts rocks</span> <span>sts rocks</span>
</t> </Dialog>
</div> </div>
<div t-name="Dialog"><t t-slot="default"/></div> <div t-name="Dialog"><t t-slot="default"/></div>
</templates> </templates>
+11 -11
View File
@@ -534,8 +534,8 @@ describe("connecting a component to store", () => {
"App", "App",
` `
<div> <div>
<t t-foreach="props.todos" t-as="todo" t-key="todo"> <t t-foreach="props.todos" t-as="todo" >
<t t-widget="Todo" msg="todo.msg"/> <Todo msg="todo.msg" t-key="todo"/>
</t> </t>
</div>` </div>`
); );
@@ -624,8 +624,8 @@ describe("connecting a component to store", () => {
env.qweb.addTemplates(` env.qweb.addTemplates(`
<templates> <templates>
<div t-name="App"> <div t-name="App">
<t t-foreach="props.todos" t-as="todo" t-key="todo"> <t t-foreach="props.todos" t-as="todo">
<t t-widget="Todo" msg="todo.msg"/> <Todo msg="todo.msg" t-key="todo" />
</t> </t>
</div> </div>
<span t-name="Todo"><t t-esc="props.msg"/></span> <span t-name="Todo"><t t-esc="props.msg"/></span>
@@ -734,7 +734,7 @@ describe("connecting a component to store", () => {
"TodoList", "TodoList",
`<div> `<div>
<t t-foreach="props.todos" t-as="todo"> <t t-foreach="props.todos" t-as="todo">
<t t-widget="ConnectedTodo" id="todo.id"/> <ConnectedTodo id="todo.id" t-key="todo.id"/>
</t> </t>
</div>` </div>`
); );
@@ -801,7 +801,7 @@ describe("connecting a component to store", () => {
"TodoList", "TodoList",
`<div> `<div>
<t t-foreach="props.todos" t-as="todo"> <t t-foreach="props.todos" t-as="todo">
<t t-widget="ConnectedTodo" id="todo.id"/> <ConnectedTodo id="todo.id" t-key="todo.id"/>
</t> </t>
</div>` </div>`
); );
@@ -839,7 +839,7 @@ describe("connecting a component to store", () => {
env.qweb.addTemplate( env.qweb.addTemplate(
"App", "App",
`<div> `<div>
<t t-widget="ConnectedBeer" id="state.beerId"/> <ConnectedBeer id="state.beerId"/>
</div>` </div>`
); );
class App extends Component<any, any, any> { class App extends Component<any, any, any> {
@@ -923,8 +923,8 @@ describe("connecting a component to store", () => {
env.qweb.addTemplate( env.qweb.addTemplate(
"App", "App",
`<div> `<div>
<t t-widget="ConnectedBeer" id="state.beerId"/> <ConnectedBeer id="state.beerId"/>
</div>` </div>`
); );
class App extends Component<any, any, any> { class App extends Component<any, any, any> {
widgets = { ConnectedBeer }; widgets = { ConnectedBeer };
@@ -995,7 +995,7 @@ describe("connecting a component to store", () => {
env.qweb.addTemplate( env.qweb.addTemplate(
"App", "App",
`<div> `<div>
<t t-widget="ConnectedBeer" id="state.beerId"/> <ConnectedBeer id="state.beerId"/>
</div>` </div>`
); );
class App extends Component<any, any, any> { class App extends Component<any, any, any> {
@@ -1074,7 +1074,7 @@ describe("connecting a component to store", () => {
"Parent", "Parent",
` `
<div> <div>
<t t-widget="Child" key="props.current"/> <Child key="props.current"/>
</div> </div>
` `
); );
+19 -19
View File
@@ -26,8 +26,8 @@ const COMPONENTS_XML = `<templates>
</button> </button>
<div t-name="App"> <div t-name="App">
<t t-widget="Counter"/> <Counter />
<t t-widget="Counter"/> <Counter />
</div> </div>
</templates>`; </templates>`;
@@ -88,7 +88,7 @@ const ANIMATION_XML = `<templates>
<div class="demo"> <div class="demo">
<button t-on-click="toggle('widgetFlag')">Toggle widget</button> <button t-on-click="toggle('widgetFlag')">Toggle widget</button>
<div> <div>
<t t-widget="Counter" t-if="state.widgetFlag" t-transition="fade"/> <Counter t-if="state.widgetFlag" t-transition="fade"/>
</div> </div>
</div> </div>
@@ -98,7 +98,7 @@ const ANIMATION_XML = `<templates>
<button t-on-click="addNumber">Add a number</button> <button t-on-click="addNumber">Add a number</button>
<div> <div>
<t t-foreach="state.numbers" t-as="n"> <t t-foreach="state.numbers" t-as="n">
<span t-transition="fade" class="numberspan"><t t-esc="n"/></span> <span t-transition="fade" class="numberspan" t-key="n"><t t-esc="n"/></span>
</t> </t>
</div> </div>
</div> </div>
@@ -236,7 +236,7 @@ const LIFECYCLE_DEMO_XML = `<templates>
<button t-on-click="increment">Increment Parent State</button> <button t-on-click="increment">Increment Parent State</button>
<button t-on-click="toggleSubWidget">Toggle SubWidget</button> <button t-on-click="toggleSubWidget">Toggle SubWidget</button>
<div t-if="state.flag"> <div t-if="state.flag">
<t t-widget="DemoWidget" n="state.n"/> <DemoWidget n="state.n"/>
</div> </div>
</div> </div>
</templates>`; </templates>`;
@@ -483,7 +483,7 @@ const TODO_APP_STORE_XML = `<templates>
<label for="toggle-all"></label> <label for="toggle-all"></label>
<ul class="todo-list"> <ul class="todo-list">
<t t-foreach="visibleTodos" t-as="todo"> <t t-foreach="visibleTodos" t-as="todo">
<t t-widget="TodoItem" t-key="todo.id" id="todo.id" completed="todo.completed" title="todo.title"/> <TodoItem t-key="todo.id" id="todo.id" completed="todo.completed" title="todo.title"/>
</t> </t>
</ul> </ul>
</section> </section>
@@ -995,17 +995,17 @@ const RESPONSIVE_XML = `<templates>
</div> </div>
<div t-name="App" class="app" t-att-class="{mobile: env.isMobile, desktop: !env.isMobile}"> <div t-name="App" class="app" t-att-class="{mobile: env.isMobile, desktop: !env.isMobile}">
<t t-widget="Navbar"/> <Navbar/>
<t t-widget="ControlPanel"/> <ControlPanel/>
<div class="content-wrapper" t-if="!env.isMobile"> <div class="content-wrapper" t-if="!env.isMobile">
<div class="content"> <div class="content">
<t t-widget="FormView"/> <FormView />
<t t-widget="Chatter"/> <Chatter />
</div> </div>
</div> </div>
<t t-else="1"> <t t-else="1">
<t t-widget="FormView"/> <FormView />
<t t-widget="Chatter"/> <Chatter />
</t> </t>
</div> </div>
</templates> </templates>
@@ -1128,17 +1128,17 @@ const SLOTS_XML = `<templates>
</div> </div>
<div t-name="App" class="main"> <div t-name="App" class="main">
<t t-widget="Card" title="'Title card A'"> <Card title="'Title card A'">
<t t-set="content">Content of card 1... [<t t-esc="state.a"/>]</t> <t t-set="content">Content of card 1... [<t t-esc="state.a"/>]</t>
<t t-set="footer"><button t-on-click="inc('a', 1)">Increment A</button></t> <t t-set="footer"><button t-on-click="inc('a', 1)">Increment A</button></t>
</t> </Card>
<t t-widget="Card" title="'Title card B'"> <Card title="'Title card B'">
<div t-set="content"> <div t-set="content">
<div>Card 2... [<t t-esc="state.b"/>]</div> <div>Card 2... [<t t-esc="state.b"/>]</div>
<t t-widget="Counter"/> <Counter />
</div> </div>
<t t-set="footer"><button t-on-click="inc('b', -1)">Decrement B</button></t> <t t-set="footer"><button t-on-click="inc('b', -1)">Decrement B</button></t>
</t> </Card>
</div> </div>
</templates>`; </templates>`;
@@ -1226,8 +1226,8 @@ app.mount(document.body);
const ASYNC_COMPONENTS_XML = `<templates> const ASYNC_COMPONENTS_XML = `<templates>
<div t-name="App" class="app"> <div t-name="App" class="app">
<button t-on-click="increment">Increment</button> <button t-on-click="increment">Increment</button>
<t t-widget="SlowWidget" value="state.value"/> <SlowWidget value="state.value"/>
<t t-widget="NotificationList" t-asyncroot="1" notifications="state.notifs"/> <NotificationList t-asyncroot="1" notifications="state.notifs"/>
</div> </div>
<div t-name="SlowWidget" class="value" > <div t-name="SlowWidget" class="value" >
+2 -2
View File
@@ -24,14 +24,14 @@
<a class="btn flash" t-on-click="downloadCode" title="Download a Zip with this Code"><i class="fas fa-download"></i></a> <a class="btn flash" t-on-click="downloadCode" title="Download a Zip with this Code"><i class="fas fa-download"></i></a>
<a class="layout-selector flash" t-on-click="toggleLayout" title="Toggle Layout"><i class="fas" t-att-class="state.splitLayout ? 'fa-toggle-on' : 'fa-toggle-off'"></i></a> <a class="layout-selector flash" t-on-click="toggleLayout" title="Toggle Layout"><i class="fas" t-att-class="state.splitLayout ? 'fa-toggle-on' : 'fa-toggle-off'"></i></a>
</div> </div>
<t t-widget="TabbedEditor" <TabbedEditor
js="state.js" js="state.js"
css="!state.splitLayout and state.css" css="!state.splitLayout and state.css"
xml="!state.splitLayout and state.js" xml="!state.splitLayout and state.js"
t-att-style="topEditorStyle"/> t-att-style="topEditorStyle"/>
<t t-if="state.splitLayout"> <t t-if="state.splitLayout">
<div class="separator horizontal"/> <div class="separator horizontal"/>
<t t-widget="TabbedEditor" t-keepalive="1" <TabbedEditor t-keepalive="1"
js="false" js="false"
css="state.css" css="state.css"
xml="state.xml" xml="state.xml"