add t-keep-alive directive

This commit is contained in:
Géry Debongnie
2019-01-31 10:55:06 +01:00
parent c627f87463
commit 25da2b2230
4 changed files with 92 additions and 8 deletions
+12
View File
@@ -157,6 +157,16 @@ We have 3 main folders and 3 main files:
else, and make sure they are properly connected. Then, it make sure that the else, and make sure they are properly connected. Then, it make sure that the
root widget is mounted to its desired location. root widget is mounted to its desired location.
## New QWeb directives
1. t-on-\*: allow binding event handlers on any kind of event
2. t-widget: instantiate a widget at this location. It has sub directives:
a. t-props: to pass properties to the widget constructor/updateProps methods
b. t-key: useful to keep track of which widget is which, when created in a
list
c. t-keep-alive: makes sure the widget is not destroyed when removed from
the DOM (note that it will still be destroyed when parent is destroyed)
## Random Notes ## Random Notes
- Change of behaviour: The compilation of a template should have a unique root - Change of behaviour: The compilation of a template should have a unique root
@@ -187,3 +197,5 @@ We have 3 main folders and 3 main files:
nextanimationframe nextanimationframe
- when t-widget/t-on directives are compiled, the widget/eval context is actually available. Should we use that info to determine if bound methods exists on the widget? - when t-widget/t-on directives are compiled, the widget/eval context is actually available. Should we use that info to determine if bound methods exists on the widget?
- allow t-if t-widget and t-else t-widget on same t tag
+23 -5
View File
@@ -112,11 +112,27 @@ export class Widget<T extends WEnv, Props> {
if (!w.__widget__.isMounted && this.el!.contains(w.el)) { if (!w.__widget__.isMounted && this.el!.contains(w.el)) {
w.__widget__.isMounted = true; w.__widget__.isMounted = true;
w.mounted(); w.mounted();
return true;
} }
return false;
}); });
} }
} }
detach() {
if (this.el) {
this.visitSubTree(w => {
if (w.__widget__.isMounted) {
w.willUnmount();
w.__widget__.isMounted = false;
return true;
}
return false;
});
this.el.remove();
}
}
destroy() { destroy() {
if (!this.__widget__.isDestroyed) { if (!this.__widget__.isDestroyed) {
for (let id in this.__widget__.children) { for (let id in this.__widget__.children) {
@@ -221,11 +237,13 @@ export class Widget<T extends WEnv, Props> {
return this.__widget__.vnode; return this.__widget__.vnode;
} }
private visitSubTree(callback: (w: Widget<T, any>) => void) { private visitSubTree(callback: (w: Widget<T, any>) => boolean) {
callback(this); const shouldVisitChildren = callback(this);
const children = this.__widget__.children; if (shouldVisitChildren) {
for (let id in children) { const children = this.__widget__.children;
children[id].visitSubTree(callback); for (let id in children) {
children[id].visitSubTree(callback);
}
} }
} }
} }
+9 -3
View File
@@ -703,8 +703,10 @@ const widgetDirective: Directive = {
name: "widget", name: "widget",
priority: 100, priority: 100,
atNodeEncounter({ ctx, value, node, qweb }): boolean { atNodeEncounter({ ctx, value, node, qweb }): boolean {
ctx.addLine("//WIDGET");
ctx.rootContext.shouldDefineOwner = true; ctx.rootContext.shouldDefineOwner = true;
let props = node.getAttribute("t-props"); let props = node.getAttribute("t-props");
let keepAlive = node.getAttribute("t-keep-alive") ? true : false;
let key = node.getAttribute("t-key"); let key = node.getAttribute("t-key");
if (key) { if (key) {
key = qweb._formatExpression(key); key = qweb._formatExpression(key);
@@ -748,9 +750,11 @@ const widgetDirective: Directive = {
ctx.addLine(`if (w${widgetID}) {`); ctx.addLine(`if (w${widgetID}) {`);
ctx.indent(); ctx.indent();
ctx.addLine( ctx.addLine(
`def${defID} = w${widgetID}.updateProps(${props}).then(()=>{let vnode=h(w${widgetID}.__widget__.vnode.sel, {key: ${templateID}});c${ `def${defID} = w${widgetID}.updateProps(${props}).then(()=>{let vnode=h(w${widgetID}.__widget__.vnode.sel, {key: ${templateID}});vnode.elm=w${widgetID}.el;c${
ctx.parentNode ctx.parentNode
}[_${dummyID}_index]=vnode;vnode.data.hook = {remove(){w${widgetID}.destroy()}}});` }[_${dummyID}_index]=vnode;vnode.data.hook = {insert(a){a.elm.parentNode.replaceChild(w${widgetID}.el,a.elm);a.elm=w${widgetID}.el;},remove(){w${widgetID}.${
keepAlive ? "detach" : "destroy"
}()}}});`
); );
ctx.dedent(); ctx.dedent();
ctx.addLine("} else {"); ctx.addLine("} else {");
@@ -765,7 +769,9 @@ const widgetDirective: Directive = {
ctx.addLine( ctx.addLine(
`def${defID} = _${widgetID}._start().then(() => _${widgetID}._render()).then(vnode=>{let pvnode=h(vnode.sel, {key: ${templateID}});c${ `def${defID} = _${widgetID}._start().then(() => _${widgetID}._render()).then(vnode=>{let pvnode=h(vnode.sel, {key: ${templateID}});c${
ctx.parentNode ctx.parentNode
}[_${dummyID}_index]=pvnode;pvnode.data.hook = {insert(vn){let nvn=_${widgetID}._mount(vnode, vn.elm);pvnode.elm=nvn.elm},remove(){_${widgetID}.destroy()}}});` }[_${dummyID}_index]=pvnode;pvnode.data.hook = {insert(vn){let nvn=_${widgetID}._mount(vnode, vn.elm);pvnode.elm=nvn.elm},remove(){_${widgetID}.${
keepAlive ? "detach" : "destroy"
}()}}});`
); );
let ref = node.getAttribute("t-ref"); let ref = node.getAttribute("t-ref");
+48
View File
@@ -584,6 +584,54 @@ describe("composition", () => {
); );
}); });
test("sub widgets with t-keep-alive are not destroyed if no longer in dom", async () => {
class ParentWidget extends Widget<WEnv, {}> {
name = "a";
state = { ok: true };
template = `
<div><t t-if="state.ok"><t t-widget="counter" t-keep-alive="1"/></t></div>`;
widgets = { counter: Counter };
}
const widget = new ParentWidget(env);
await widget.mount(fixture);
const button = fixture.getElementsByTagName("button")[0];
await button.click();
await nextTick();
expect(fixture.innerHTML).toBe(
"<div><div>1<button>Inc</button></div></div>"
);
await widget.updateState({ ok: false });
expect(fixture.innerHTML).toBe("<div></div>");
await widget.updateState({ ok: true });
expect(fixture.innerHTML).toBe(
"<div><div>1<button>Inc</button></div></div>"
);
});
test("sub widgets dom state with t-keep-alive is preserved", async () => {
class ParentWidget extends Widget<WEnv, {}> {
name = "a";
state = { ok: true };
template = `
<div><t t-if="state.ok"><t t-widget="InputWidget" t-keep-alive="1"/></t></div>`;
widgets = { InputWidget };
}
class InputWidget extends Widget<WEnv, {}> {
template = `<input/>`;
}
const widget = new ParentWidget(env);
await widget.mount(fixture);
const input = fixture.getElementsByTagName("input")[0];
input.value = "test";
await widget.updateState({ ok: false });
expect(fixture.innerHTML).toBe("<div></div>");
await widget.updateState({ ok: true });
expect(fixture.innerHTML).toBe("<div><input></div>");
const input2 = fixture.getElementsByTagName("input")[0];
expect(input).toBe(input2);
expect(input2.value).toBe("test");
});
test("sub widgets rendered in a loop", async () => { test("sub widgets rendered in a loop", async () => {
class ChildWidget extends Widget<WEnv, { n: number }> { class ChildWidget extends Widget<WEnv, { n: number }> {
name = "c"; name = "c";