mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
add t-keep-alive directive
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
Reference in New Issue
Block a user