fix tricky issue with sub widgets modifying el

This commit is contained in:
Géry Debongnie
2019-01-26 08:40:09 +01:00
parent a47a65d333
commit bfa76e9de5
4 changed files with 43 additions and 17 deletions
+19 -8
View File
@@ -30,8 +30,11 @@ export class Widget<T extends WEnv> {
name: string = "widget"; name: string = "widget";
template: string = "<div></div>"; template: string = "<div></div>";
get el(): HTMLElement | null {
return this._.vnode ? (<any>this)._.vnode.elm : null;
}
env: T; env: T;
el: HTMLElement | null = null;
state: Object = {}; state: Object = {};
refs: { [key: string]: Widget<T> | HTMLElement | undefined } = {}; // either HTMLElement or Widget refs: { [key: string]: Widget<T> | HTMLElement | undefined } = {}; // either HTMLElement or Widget
@@ -110,12 +113,11 @@ export class Widget<T extends WEnv> {
async render(): Promise<VNode> { async render(): Promise<VNode> {
const vnode = await this._render(); const vnode = await this._render();
if (!this.el) { this._.vnode = patch(
this.el = document.createElement(vnode.sel!); this._.vnode || document.createElement(vnode.sel!),
} vnode
patch(this._.vnode || this.el, vnode); );
this._.vnode = vnode; return this._.vnode;
return vnode;
} }
private async _start(): Promise<void> { private async _start(): Promise<void> {
@@ -130,11 +132,20 @@ export class Widget<T extends WEnv> {
} }
const promises: Promise<void>[] = []; const promises: Promise<void>[] = [];
let vnode = this.env.qweb.render(this.name, this, { promises }); let vnode = this.env.qweb.render(this.name, this, { promises });
// this part is critical for the patching process to be done correctly. The
// tricky part is that a child widget can be rerendered on its own, which
// will update its own vnode representation without the knowledge of the
// parent widget. With this, we make sure that the parent widget will be
// able to patch itself properly after
vnode.key = this._.id;
return Promise.all(promises).then(() => vnode); return Promise.all(promises).then(() => vnode);
} }
/**
* Only called by qweb t-widget directive
*/
_mount(vnode: VNode) { _mount(vnode: VNode) {
this.el = <HTMLElement>vnode.elm;
this._.vnode = vnode; this._.vnode = vnode;
if (this._.parent) { if (this._.parent) {
if (this._.parent._.isMounted) { if (this._.parent._.isMounted) {
+4 -1
View File
@@ -678,6 +678,7 @@ const widgetDirective: Directive = {
let dummyID = ctx.generateID(); let dummyID = ctx.generateID();
let defID = ctx.generateID(); let defID = ctx.generateID();
ctx.addLine(`let _${dummyID} = {} // DUMMY`); ctx.addLine(`let _${dummyID} = {} // DUMMY`);
ctx.addLine(`let _${dummyID}_index = c${ctx.parentNode}.length`);
ctx.addLine(`c${ctx.parentNode}.push(_${dummyID})`); ctx.addLine(`c${ctx.parentNode}.push(_${dummyID})`);
let props = node.getAttribute("t-props"); let props = node.getAttribute("t-props");
let widgetID = ctx.generateID(); let widgetID = ctx.generateID();
@@ -685,7 +686,9 @@ const widgetDirective: Directive = {
`let _${widgetID} = new context.widgets['${value}'](owner, ${props})` `let _${widgetID} = new context.widgets['${value}'](owner, ${props})`
); );
ctx.addLine( ctx.addLine(
`let def${defID} = _${widgetID}._start().then(() => _${widgetID}._render()).then(vnode=>{Object.assign(_${dummyID}, vnode);_${dummyID}.key="${dummyID}";_${dummyID}.data.hook = {create(_,vn){_${widgetID}._mount(vn)}}})` `let def${defID} = _${widgetID}._start().then(() => _${widgetID}._render()).then(vnode=>{c${
ctx.parentNode
}[_${dummyID}_index]=vnode;vnode.data.hook = {create(_,vn){_${widgetID}._mount(vn)}}})`
); );
ctx.addLine(`extra.promises.push(def${defID})`); ctx.addLine(`extra.promises.push(def${defID})`);
+7 -8
View File
@@ -6,12 +6,13 @@ import { Counter } from "./counter";
const template = ` const template = `
<div class="o_discuss"> <div class="o_discuss">
<span>DISCUSS!!</span> <span>DISCUSS!!</span>
<button t-on-click="resetCounter">Reset</button> <button t-on-click="resetCounter">Reset first counter</button>
<button t-on-click="resetCounterAsync">Reset in 3s</button> <button t-on-click="resetCounterAsync">Reset counter 2 in 3s</button>
<button t-on-click="toggle">Toggle Counter</button> <button t-on-click="toggle">Toggle Clock/counters</button>
<input/> <input/>
<t t-if="state.validcounter"> <t t-if="state.validcounter">
<t t-widget="Counter" t-ref="counter" t-props="{initialState:4}"/> <t t-widget="Counter" t-ref="counter" t-props="{initialState:4}"/>
<t t-widget="Counter" t-ref="counter2" t-props="{initialState:400}"/>
</t> </t>
<t t-else="1"> <t t-else="1">
<t t-widget="Clock"/> <t t-widget="Clock"/>
@@ -35,15 +36,13 @@ export class Discuss extends Widget<Env> {
resetCounterAsync(ev: MouseEvent) { resetCounterAsync(ev: MouseEvent) {
setTimeout(() => { setTimeout(() => {
if (this.refs.counter instanceof Counter) { if (this.refs.counter2 instanceof Counter) {
this.refs.counter.updateState({ counter: 3 }); this.refs.counter2.updateState({ counter: 300 });
} }
}, 3000); }, 3000);
} }
toggle() { toggle() {
if (this.refs.counter instanceof Counter) { this.updateState({ validcounter: !this.state.validcounter });
this.updateState({ validcounter: !this.state.validcounter });
}
} }
} }
+13
View File
@@ -296,4 +296,17 @@ describe("composition", () => {
"<div><div>1<button>Inc</button></div></div>" "<div><div>1<button>Inc</button></div></div>"
); );
}); });
test("parent's elm for a children === children's elm, even after rerender", async () => {
const widget = new WidgetA(env);
await widget.mount(fixture);
expect((<any>widget._.vnode!.children![1]).elm).toBe(
(<any>widget._.children[0]._.vnode).elm
);
await widget._.children[0].render();
expect((<any>widget._.vnode!.children![1]).elm).toBe(
(<any>widget._.children[0]._.vnode).elm
);
});
}); });