diff --git a/doc/component.md b/doc/component.md
index 10174103..b14fe2e2 100644
--- a/doc/component.md
+++ b/doc/component.md
@@ -10,6 +10,7 @@
- [Methods](#methods)
- [Lifecycle](#lifecycle)
- [Composition](#composition)
+ - [Asynchronous rendering](#asynchronous-rendering)
- [Event Handling](#event-handling)
- [`t-key` directive](#t-key-directive)
- [`t-mounted` directive](#t-mounted-directive)
@@ -309,7 +310,7 @@ the DOM. This is a good place to remove some listeners, for example.
This is the opposite method of `mounted`.
-## Composition
+### 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:
@@ -828,9 +829,25 @@ There are two different common problems with Owl asynchronous rendering model:
Here are a few tips on how to work with asynchronous widgets:
-1. minimize the use of asynchronous widgets!
+1. Minimize the use of asynchronous widgets!
2. Maybe move the asynchronous logic in a store, which then triggers (mostly)
synchronous renderings
3. Lazy loading external libraries is a good use case for async rendering. This
is mostly fine, because we can assume that it will only takes a fraction of a
second, and only once (see `owl.utils.loadJS`)
+4. For all the other cases, the `t-async` directive (to use alongside
+ `t-widget`) is there to help you. When this directive is met, a new rendering
+ sub tree is created, such that the rendering of that component (and its
+ children) is not tied to the rendering of the rest of the interface. It can
+ be used on an asynchronous component, to prevent it from delaying the
+ rendering of the whole interface, or on a synchronous one, such that its
+ rendering isn't delayed by other (asynchronous) components. Note that this
+ directive has no effect on the first rendering, but only on subsequent ones
+ (triggered by state or props changes).
+
+ ```xml
+
+
+
+
+ ```
diff --git a/doc/qweb.md b/doc/qweb.md
index 7f026de2..59226d50 100644
--- a/doc/qweb.md
+++ b/doc/qweb.md
@@ -65,15 +65,15 @@ We present here a list of all standard QWeb directives:
The component system in Owl requires additional directives, to express various
needs. Here is a list of all Owl specific directives:
-| Name | Description |
-| ------------------------- | --------------------------------------------------------------------------------------- |
-| `t-widget`, `t-keepalive` | [Defining a sub component](component.md#composition) |
-| `t-ref` | [Setting a reference to a dom node or a sub component](component.md#keeping-references) |
-| `t-key` | [Defining a key (to help virtual dom reconciliation)](component.md#t-key-directive) |
-| `t-on-*` | [Event handling](component.md#event-handling) |
-| `t-transition` | [Defining an animation](animations.md#css-transitions) |
-| `t-mounted` | [Callback when a node or component is mounted](component.md#t-mounted-directive) |
-| `t-slot` | [Rendering a slot](component.md#slots) |
+| Name | Description |
+| ------------------------------------ | --------------------------------------------------------------------------------------- |
+| `t-widget`, `t-keepalive`, `t-async` | [Defining a sub component](component.md#composition) |
+| `t-ref` | [Setting a reference to a dom node or a sub component](component.md#keeping-references) |
+| `t-key` | [Defining a key (to help virtual dom reconciliation)](component.md#t-key-directive) |
+| `t-on-*` | [Event handling](component.md#event-handling) |
+| `t-transition` | [Defining an animation](animations.md#css-transitions) |
+| `t-mounted` | [Callback when a node or component is mounted](component.md#t-mounted-directive) |
+| `t-slot` | [Rendering a slot](component.md#slots) |
## QWeb Engine
diff --git a/src/component.ts b/src/component.ts
index 8c5614ad..ab6d43b7 100644
--- a/src/component.ts
+++ b/src/component.ts
@@ -274,20 +274,7 @@ export class Component {
if (shouldPatch && __owl__.isMounted && renderId === __owl__.renderId) {
// we only update the vnode and the actual DOM if no other rendering
// occurred between now and when the render method was initially called.
- const patchLen = patchQueue!.length;
- for (let i = 0; i < patchLen; i++) {
- const patch = patchQueue![i];
- patch.push(patch[0].willPatch());
- }
- for (let i = 0; i < patchLen; i++) {
- const patch = patchQueue![i];
- patch[0]._patch(patch[1]);
- }
-
- for (let i = patchLen - 1; i >= 0; i--) {
- const patch = patchQueue![i];
- patch[0].patched(patch[2]);
- }
+ this._applyPatchQueue(patchQueue);
}
}
@@ -568,6 +555,29 @@ export class Component {
return props;
}
+ /**
+ * Apply the given patch queue. A patch is a pair [c, vn], where c is a
+ * Component instance and vn a VNode.
+ * 1) Call 'willPatch' on the component of each patch
+ * 2) Call '_patch' on the component of each patch
+ * 3) Call 'patched' on the component of each patch, in inverse order
+ */
+ _applyPatchQueue(patchQueue: any[]) {
+ const patchLen = patchQueue.length;
+ for (let i = 0; i < patchLen; i++) {
+ const patch = patchQueue[i];
+ patch.push(patch[0].willPatch());
+ }
+ for (let i = 0; i < patchLen; i++) {
+ const patch = patchQueue[i];
+ patch[0]._patch(patch[1]);
+ }
+ for (let i = patchLen - 1; i >= 0; i--) {
+ const patch = patchQueue[i];
+ patch[0].patched(patch[2]);
+ }
+ }
+
/**
* Validate the component props (or next props) against the (static) props
* description. This is potentially an expensive operation: it may needs to
diff --git a/src/qweb_extensions.ts b/src/qweb_extensions.ts
index 072138f2..27637db9 100644
--- a/src/qweb_extensions.ts
+++ b/src/qweb_extensions.ts
@@ -225,13 +225,6 @@ const T_WIDGET_MODS_CODE = Object.assign({}, MODS_CODE, {
* // t-widget directive: we start by evaluating the expression given by t-key:
* let key5 = "somestring";
*
- * // We keep the index of the position of the widget in the closure. We push
- * // null to reserve the slot, and will replace it later by the widget vnode,
- * // when it will be ready (do not forget that preparing/rendering a widget is
- * // asynchronous)
- * let _2_index = c1.length;
- * c1.push(null);
- *
* // def3 is the promise that will contain later either the new widget
* // creation, or the props update...
* let def3;
@@ -246,6 +239,13 @@ const T_WIDGET_MODS_CODE = Object.assign({}, MODS_CODE, {
* ? context.__owl__.children[context.__owl__.cmap[key5]]
* : false;
*
+ * // We keep the index of the position of the widget in the closure. We push
+ * // null to reserve the slot, and will replace it later by the widget vnode,
+ * // when it will be ready (do not forget that preparing/rendering a widget is
+ * // asynchronous)
+ * let _2_index = c1.length;
+ * c1.push(null);
+ *
* // we evaluate here the props given to the component. It is done here to be
* // able to easily reference it later, and also, it might be an expensive
* // computation, so it is certainly better to do it only once
@@ -360,7 +360,7 @@ const T_WIDGET_MODS_CODE = Object.assign({}, MODS_CODE, {
QWeb.addDirective({
name: "widget",
- extraNames: ["props", "keepalive"],
+ extraNames: ["props", "keepalive", "async"],
priority: 100,
atNodeEncounter({ ctx, value, node, qweb }): boolean {
ctx.addLine("//WIDGET");
@@ -368,6 +368,7 @@ QWeb.addDirective({
ctx.rootContext.shouldDefineQWeb = true;
ctx.rootContext.shouldDefineUtils = true;
let keepAlive = node.getAttribute("t-keepalive") ? true : false;
+ let async = node.getAttribute("t-async") ? true : false;
// t-on- events and t-transition
const events: [string, string[], string, string][] = [];
@@ -413,8 +414,6 @@ QWeb.addDirective({
// want to evaluate it only once)
ctx.addLine(`let key${keyID} = ${key};`);
}
- ctx.addLine(`let _${dummyID}_index = c${ctx.parentNode}.length;`);
- ctx.addLine(`c${ctx.parentNode}.push(null);`);
ctx.addLine(`let def${defID};`);
let templateID = key
? `key${keyID}`
@@ -505,6 +504,13 @@ QWeb.addDirective({
ctx.addLine(
`let w${widgetID} = ${templateID} in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[${templateID}]] : false;`
);
+ ctx.addLine(`let _${dummyID}_index = c${ctx.parentNode}.length;`);
+ if (async) {
+ ctx.addLine(`const patchQueue${widgetID} = [];`);
+ ctx.addLine(`c${ctx.parentNode}.push(w${widgetID} && w${widgetID}.__owl__.pvnode || null);`);
+ } else {
+ ctx.addLine(`c${ctx.parentNode}.push(null);`);
+ }
ctx.addLine(`let props${widgetID} = {${propStr}};`);
ctx.addIf(
`w${widgetID} && w${widgetID}.__owl__.renderPromise && !w${widgetID}.__owl__.vnode`
@@ -551,7 +557,6 @@ QWeb.addDirective({
ctx.addLine(`def${defID} = w${widgetID}._prepare();`);
// hack: specify empty remove hook to prevent the node from being removed from the DOM
- // FIXME: click to re-add widget during remove transition -> leak
ctx.addLine(
`def${defID} = def${defID}.then(vnode=>{${createHook}let pvnode=h(vnode.sel, {key: ${templateID}, hook: {insert(vn) {let nvn=w${widgetID}._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;${refExpr}${transitionsInsertCode}},remove() {},destroy(vn) {${finalizeWidgetCode}}}});c${
ctx.parentNode
@@ -560,8 +565,9 @@ QWeb.addDirective({
ctx.addElse();
// need to update widget
+ const patchQueueCode = async ? `patchQueue${widgetID}` : "extra.patchQueue";
ctx.addLine(
- `def${defID} = def${defID} || w${widgetID}._updateProps(props${widgetID}, extra.forceUpdate, extra.patchQueue);`
+ `def${defID} = def${defID} || w${widgetID}._updateProps(props${widgetID}, extra.forceUpdate, ${patchQueueCode});`
);
let keepAliveCode = "";
if (keepAlive) {
@@ -576,7 +582,13 @@ QWeb.addDirective({
);
ctx.closeIf();
- ctx.addLine(`extra.promises.push(def${defID});`);
+ if (async) {
+ ctx.addLine(
+ `def${defID}.then(w${widgetID}._applyPatchQueue.bind(w${widgetID}, patchQueue${widgetID}));`
+ );
+ } else {
+ ctx.addLine(`extra.promises.push(def${defID});`);
+ }
if (
node.hasAttribute("t-if") ||
diff --git a/tests/__snapshots__/animations.test.ts.snap b/tests/__snapshots__/animations.test.ts.snap
index b581c515..4b31d32c 100644
--- a/tests/__snapshots__/animations.test.ts.snap
+++ b/tests/__snapshots__/animations.test.ts.snap
@@ -10,10 +10,10 @@ exports[`animations t-transition combined with t-widget 1`] = `
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 _2_index = c1.length;
+ c1.push(null);
let props4 = {};
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
@@ -54,10 +54,10 @@ exports[`animations t-transition combined with t-widget and t-if 1`] = `
var vn1 = h('div', p1, c1);
if (context['state'].display) {
//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 _2_index = c1.length;
+ c1.push(null);
let props4 = {};
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
diff --git a/tests/__snapshots__/component.test.ts.snap b/tests/__snapshots__/component.test.ts.snap
index 5a83ecaa..c0bf79f3 100644
--- a/tests/__snapshots__/component.test.ts.snap
+++ b/tests/__snapshots__/component.test.ts.snap
@@ -1,5 +1,248 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`async rendering delayed t-widget with t-async directive 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);
+ let c2 = [], p2 = {key:2,on:{}};
+ var vn2 = h('button', p2, c2);
+ c1.push(vn2);
+ if (!context['updateApp']) {
+ throw new Error('Missing handler \\\\'' + 'updateApp' + \`\\\\' when evaluating template 'Parent'\`)
+ }
+ extra.handlers['click' + 2] = extra.handlers['click' + 2] || context['updateApp'].bind(owner);
+ p2.on['click'] = extra.handlers['click' + 2];
+ c2.push({text: \`Update App State\`});
+ var _3 = 'children';
+ let c4 = [], p4 = {key:4,attrs:{class: _3}};
+ var vn4 = h('div', p4, c4);
+ c1.push(vn4);
+ //WIDGET
+ let def6;
+ let w7 = 7 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[7]] : false;
+ let _5_index = c4.length;
+ c4.push(null);
+ let props7 = {val:context['state'].val};
+ if (w7 && w7.__owl__.renderPromise && !w7.__owl__.vnode) {
+ if (utils.shallowEqual(props7, w7.__owl__.renderProps)) {
+ def6 = w7.__owl__.renderPromise;
+ } else {
+ w7.destroy();
+ w7 = false;
+ }
+ }
+ if (!w7) {
+ let widgetKey7 = \`Child\`;
+ 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[7] = w7.__owl__.id;
+ def6 = w7._prepare();
+ def6 = def6.then(vnode=>{let pvnode=h(vnode.sel, {key: 7, hook: {insert(vn) {let nvn=w7._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w7.destroy();}}});c4[_5_index]=pvnode;w7.__owl__.pvnode = pvnode;});
+ } else {
+ def6 = def6 || w7._updateProps(props7, extra.forceUpdate, extra.patchQueue);
+ def6 = def6.then(()=>{if (w7.__owl__.isDestroyed) {return};let pvnode=w7.__owl__.pvnode;c4[_5_index]=pvnode;});
+ }
+ extra.promises.push(def6);
+ //WIDGET
+ let def9;
+ let w10 = 10 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[10]] : false;
+ let _8_index = c4.length;
+ const patchQueue10 = [];
+ c4.push(w10 && w10.__owl__.pvnode || null);
+ let props10 = {val:context['state'].val};
+ if (w10 && w10.__owl__.renderPromise && !w10.__owl__.vnode) {
+ if (utils.shallowEqual(props10, w10.__owl__.renderProps)) {
+ def9 = w10.__owl__.renderPromise;
+ } else {
+ w10.destroy();
+ w10 = false;
+ }
+ }
+ if (!w10) {
+ let widgetKey10 = \`AsyncChild\`;
+ let W10 = context.widgets && context.widgets[widgetKey10] || QWeb.widgets[widgetKey10];
+ if (!W10) {throw new Error('Cannot find the definition of widget \\"' + widgetKey10 + '\\"')}
+ w10 = new W10(owner, props10);
+ context.__owl__.cmap[10] = w10.__owl__.id;
+ def9 = w10._prepare();
+ def9 = def9.then(vnode=>{let pvnode=h(vnode.sel, {key: 10, hook: {insert(vn) {let nvn=w10._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w10.destroy();}}});c4[_8_index]=pvnode;w10.__owl__.pvnode = pvnode;});
+ } else {
+ def9 = def9 || w10._updateProps(props10, extra.forceUpdate, patchQueue10);
+ def9 = def9.then(()=>{if (w10.__owl__.isDestroyed) {return};let pvnode=w10.__owl__.pvnode;c4[_8_index]=pvnode;});
+ }
+ def9.then(w10._applyPatchQueue.bind(w10, patchQueue10));
+ return vn1;
+}"
+`;
+
+exports[`async rendering fast t-widget with t-async directive 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);
+ let c2 = [], p2 = {key:2,on:{}};
+ var vn2 = h('button', p2, c2);
+ c1.push(vn2);
+ if (!context['updateApp']) {
+ throw new Error('Missing handler \\\\'' + 'updateApp' + \`\\\\' when evaluating template 'Parent'\`)
+ }
+ extra.handlers['click' + 2] = extra.handlers['click' + 2] || context['updateApp'].bind(owner);
+ p2.on['click'] = extra.handlers['click' + 2];
+ c2.push({text: \`Update App State\`});
+ var _3 = 'children';
+ let c4 = [], p4 = {key:4,attrs:{class: _3}};
+ var vn4 = h('div', p4, c4);
+ c1.push(vn4);
+ //WIDGET
+ let def6;
+ let w7 = 7 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[7]] : false;
+ let _5_index = c4.length;
+ const patchQueue7 = [];
+ c4.push(w7 && w7.__owl__.pvnode || null);
+ let props7 = {val:context['state'].val};
+ if (w7 && w7.__owl__.renderPromise && !w7.__owl__.vnode) {
+ if (utils.shallowEqual(props7, w7.__owl__.renderProps)) {
+ def6 = w7.__owl__.renderPromise;
+ } else {
+ w7.destroy();
+ w7 = false;
+ }
+ }
+ if (!w7) {
+ let widgetKey7 = \`Child\`;
+ 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[7] = w7.__owl__.id;
+ def6 = w7._prepare();
+ def6 = def6.then(vnode=>{let pvnode=h(vnode.sel, {key: 7, hook: {insert(vn) {let nvn=w7._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w7.destroy();}}});c4[_5_index]=pvnode;w7.__owl__.pvnode = pvnode;});
+ } else {
+ def6 = def6 || w7._updateProps(props7, extra.forceUpdate, patchQueue7);
+ def6 = def6.then(()=>{if (w7.__owl__.isDestroyed) {return};let pvnode=w7.__owl__.pvnode;c4[_5_index]=pvnode;});
+ }
+ def6.then(w7._applyPatchQueue.bind(w7, patchQueue7));
+ //WIDGET
+ let def9;
+ let w10 = 10 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[10]] : false;
+ let _8_index = c4.length;
+ c4.push(null);
+ let props10 = {val:context['state'].val};
+ if (w10 && w10.__owl__.renderPromise && !w10.__owl__.vnode) {
+ if (utils.shallowEqual(props10, w10.__owl__.renderProps)) {
+ def9 = w10.__owl__.renderPromise;
+ } else {
+ w10.destroy();
+ w10 = false;
+ }
+ }
+ if (!w10) {
+ let widgetKey10 = \`AsyncChild\`;
+ let W10 = context.widgets && context.widgets[widgetKey10] || QWeb.widgets[widgetKey10];
+ if (!W10) {throw new Error('Cannot find the definition of widget \\"' + widgetKey10 + '\\"')}
+ w10 = new W10(owner, props10);
+ context.__owl__.cmap[10] = w10.__owl__.id;
+ def9 = w10._prepare();
+ def9 = def9.then(vnode=>{let pvnode=h(vnode.sel, {key: 10, hook: {insert(vn) {let nvn=w10._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w10.destroy();}}});c4[_8_index]=pvnode;w10.__owl__.pvnode = pvnode;});
+ } else {
+ def9 = def9 || w10._updateProps(props10, extra.forceUpdate, extra.patchQueue);
+ def9 = def9.then(()=>{if (w10.__owl__.isDestroyed) {return};let pvnode=w10.__owl__.pvnode;c4[_8_index]=pvnode;});
+ }
+ extra.promises.push(def9);
+ return vn1;
+}"
+`;
+
+exports[`async rendering t-widget with t-async directive: mixed re-renderings 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);
+ let c2 = [], p2 = {key:2,on:{}};
+ var vn2 = h('button', p2, c2);
+ c1.push(vn2);
+ if (!context['updateApp']) {
+ throw new Error('Missing handler \\\\'' + 'updateApp' + \`\\\\' when evaluating template 'Parent'\`)
+ }
+ extra.handlers['click' + 2] = extra.handlers['click' + 2] || context['updateApp'].bind(owner);
+ p2.on['click'] = extra.handlers['click' + 2];
+ c2.push({text: \`Update App State\`});
+ var _3 = 'children';
+ let c4 = [], p4 = {key:4,attrs:{class: _3}};
+ var vn4 = h('div', p4, c4);
+ c1.push(vn4);
+ //WIDGET
+ let def6;
+ let w7 = 7 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[7]] : false;
+ let _5_index = c4.length;
+ c4.push(null);
+ let props7 = {val:context['state'].val};
+ if (w7 && w7.__owl__.renderPromise && !w7.__owl__.vnode) {
+ if (utils.shallowEqual(props7, w7.__owl__.renderProps)) {
+ def6 = w7.__owl__.renderPromise;
+ } else {
+ w7.destroy();
+ w7 = false;
+ }
+ }
+ if (!w7) {
+ let widgetKey7 = \`Child\`;
+ 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[7] = w7.__owl__.id;
+ def6 = w7._prepare();
+ def6 = def6.then(vnode=>{let pvnode=h(vnode.sel, {key: 7, hook: {insert(vn) {let nvn=w7._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w7.destroy();}}});c4[_5_index]=pvnode;w7.__owl__.pvnode = pvnode;});
+ } else {
+ def6 = def6 || w7._updateProps(props7, extra.forceUpdate, extra.patchQueue);
+ def6 = def6.then(()=>{if (w7.__owl__.isDestroyed) {return};let pvnode=w7.__owl__.pvnode;c4[_5_index]=pvnode;});
+ }
+ extra.promises.push(def6);
+ //WIDGET
+ let def9;
+ let w10 = 10 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[10]] : false;
+ let _8_index = c4.length;
+ const patchQueue10 = [];
+ c4.push(w10 && w10.__owl__.pvnode || null);
+ let props10 = {val:context['state'].val};
+ if (w10 && w10.__owl__.renderPromise && !w10.__owl__.vnode) {
+ if (utils.shallowEqual(props10, w10.__owl__.renderProps)) {
+ def9 = w10.__owl__.renderPromise;
+ } else {
+ w10.destroy();
+ w10 = false;
+ }
+ }
+ if (!w10) {
+ let widgetKey10 = \`AsyncChild\`;
+ let W10 = context.widgets && context.widgets[widgetKey10] || QWeb.widgets[widgetKey10];
+ if (!W10) {throw new Error('Cannot find the definition of widget \\"' + widgetKey10 + '\\"')}
+ w10 = new W10(owner, props10);
+ context.__owl__.cmap[10] = w10.__owl__.id;
+ def9 = w10._prepare();
+ def9 = def9.then(vnode=>{let pvnode=h(vnode.sel, {key: 10, hook: {insert(vn) {let nvn=w10._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w10.destroy();}}});c4[_8_index]=pvnode;w10.__owl__.pvnode = pvnode;});
+ } else {
+ def9 = def9 || w10._updateProps(props10, extra.forceUpdate, patchQueue10);
+ def9 = def9.then(()=>{if (w10.__owl__.isDestroyed) {return};let pvnode=w10.__owl__.pvnode;c4[_8_index]=pvnode;});
+ }
+ def9.then(w10._applyPatchQueue.bind(w10, patchQueue10));
+ return vn1;
+}"
+`;
+
exports[`class and style attributes with t-widget dynamic t-att-style is properly added and updated on widget root el 1`] = `
"function anonymous(context,extra
) {
@@ -10,11 +253,11 @@ exports[`class and style attributes with t-widget dynamic t-att-style is properl
let c1 = [], p1 = {key:1};
var vn1 = h('div', p1, c1);
//WIDGET
- let _2_index = c1.length;
- c1.push(null);
let def3;
const _5 = context['state'].style;
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
+ let _2_index = c1.length;
+ c1.push(null);
let props4 = {};
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
@@ -51,11 +294,11 @@ exports[`class and style attributes with t-widget t-att-class is properly added/
let c1 = [], p1 = {key:1};
var vn1 = h('div', p1, c1);
//WIDGET
- let _2_index = c1.length;
- c1.push(null);
let def3;
const _5 = {a:context['state'].a,b:context['state'].b};
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
+ let _2_index = c1.length;
+ c1.push(null);
let props4 = {};
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
@@ -112,10 +355,10 @@ exports[`composition sub widgets with some state rendered in a loop 1`] = `
context.number_value = _4[i];
//WIDGET
let key8 = context['number'];
- let _5_index = c1.length;
- c1.push(null);
let def6;
let w7 = key8 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[key8]] : false;
+ let _5_index = c1.length;
+ c1.push(null);
let props7 = {};
if (w7 && w7.__owl__.renderPromise && !w7.__owl__.vnode) {
if (utils.shallowEqual(props7, w7.__owl__.renderProps)) {
@@ -153,10 +396,10 @@ exports[`composition t-widget with dynamic value 1`] = `
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 _2_index = c1.length;
+ c1.push(null);
let props4 = {};
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
@@ -193,10 +436,10 @@ exports[`composition t-widget with dynamic value 2 1`] = `
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 _2_index = c1.length;
+ c1.push(null);
let props4 = {};
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
@@ -233,10 +476,10 @@ exports[`other directives with t-widget t-on with handler bound to argument 1`]
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 _2_index = c1.length;
+ c1.push(null);
let props4 = {};
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
@@ -273,10 +516,10 @@ exports[`other directives with t-widget t-on with handler bound to empty object
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 _2_index = c1.length;
+ c1.push(null);
let props4 = {};
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
@@ -313,10 +556,10 @@ exports[`other directives with t-widget t-on with handler bound to empty object
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 _2_index = c1.length;
+ c1.push(null);
let props4 = {};
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
@@ -353,10 +596,10 @@ exports[`other directives with t-widget t-on with handler bound to object 1`] =
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 _2_index = c1.length;
+ c1.push(null);
let props4 = {};
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
@@ -393,10 +636,10 @@ exports[`other directives with t-widget t-on with prevent and self modifiers (or
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 _2_index = c1.length;
+ c1.push(null);
let props4 = {};
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
@@ -433,10 +676,10 @@ exports[`other directives with t-widget t-on with self and prevent modifiers (or
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 _2_index = c1.length;
+ c1.push(null);
let props4 = {};
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
@@ -473,10 +716,10 @@ exports[`other directives with t-widget t-on with self modifier 1`] = `
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 _2_index = c1.length;
+ c1.push(null);
let props4 = {};
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
@@ -513,10 +756,10 @@ exports[`other directives with t-widget t-on with stop and/or prevent modifiers
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 _2_index = c1.length;
+ c1.push(null);
let props4 = {};
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
@@ -554,10 +797,10 @@ exports[`random stuff/miscellaneous snapshotting compiled code 1`] = `
var vn1 = h('div', p1, c1);
//WIDGET
let key5 = 'somestring';
- let _2_index = c1.length;
- c1.push(null);
let def3;
let w4 = key5 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[key5]] : false;
+ let _2_index = c1.length;
+ c1.push(null);
let props4 = {flag:context['state'].flag};
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
@@ -594,10 +837,10 @@ exports[`t-slot directive can define and call slots 1`] = `
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 _2_index = c1.length;
+ c1.push(null);
let props4 = {};
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
diff --git a/tests/component.test.ts b/tests/component.test.ts
index c4251729..c8b825c5 100644
--- a/tests/component.test.ts
+++ b/tests/component.test.ts
@@ -2352,6 +2352,194 @@ describe("async rendering", () => {
await nextTick();
expect(destroyCount).toBe(0);
});
+
+ test("delayed t-widget with t-async directive", async () => {
+ env.qweb.addTemplates(`
+
+