From 299a6b497735e3d4aaa8924ab413b8cfbbbc9a64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Thu, 24 Jan 2019 13:48:31 +0100 Subject: [PATCH] small improvements to widget, add clock widget --- web/static/src/ts/core/Widget.ts | 19 ++++++++++--------- web/static/src/ts/core/qweb_vdom.ts | 25 +++++++++++++------------ web/static/src/ts/widgets/Counter.ts | 3 +++ web/static/src/ts/widgets/Discuss.ts | 14 +++++++++----- web/static/src/ts/widgets/clock.ts | 27 +++++++++++++++++++++++++++ web/static/tests/widget.test.ts | 16 ++++++++++++++++ 6 files changed, 78 insertions(+), 26 deletions(-) create mode 100644 web/static/src/ts/widgets/clock.ts diff --git a/web/static/src/ts/core/Widget.ts b/web/static/src/ts/core/Widget.ts index c07997c4..602df3d5 100644 --- a/web/static/src/ts/core/Widget.ts +++ b/web/static/src/ts/core/Widget.ts @@ -15,6 +15,7 @@ export class Widget { template: string = "
"; vnode: VNode | null = null; + isStarted: boolean = false; parent: Widget | null = null; children: Widget[] = []; env: T; @@ -49,6 +50,7 @@ export class Widget { async mount(target?: HTMLElement): Promise { await this.willStart(); + this.isStarted = true; this.env.qweb.addTemplate(this.name, this.template); delete this.template; const vnode = await this.render(); @@ -69,14 +71,14 @@ export class Widget { } /** - * DOCSTRIGN - * - * @param {Object} newState - * @memberof Widget + * Note: it is ok to call updateState before the widget is started. In that + * case, it will simply update the state and will not rerender */ async updateState(newState: Object) { Object.assign(this.state, newState); - await this.render(); + if (this.isStarted) { + await this.render(); + } } //-------------------------------------------------------------------------- @@ -84,10 +86,9 @@ export class Widget { //-------------------------------------------------------------------------- async render(): Promise { - // localized hack to keep track of deferred list - (this)._TEMP = []; - let vnode = this.env!.qweb.render(this.name, this); - await Promise.all((this)._TEMP); + const promises: Promise[] = []; + let vnode = this.env.qweb.render(this.name, this, { promises }); + await Promise.all(promises); if (!this.el) { this.el = document.createElement(vnode.sel!); } diff --git a/web/static/src/ts/core/qweb_vdom.ts b/web/static/src/ts/core/qweb_vdom.ts index fbcdaa01..eb5f985d 100644 --- a/web/static/src/ts/core/qweb_vdom.ts +++ b/web/static/src/ts/core/qweb_vdom.ts @@ -3,7 +3,7 @@ import h from "../../../libs/snabbdom/src/h"; export type EvalContext = { [key: string]: any }; export type RawTemplate = string; -export type CompiledTemplate = (context: EvalContext) => T; +export type CompiledTemplate = (context: EvalContext, extra: any) => T; const RESERVED_WORDS = "true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,this,typeof,eval,void,Math,RegExp,Array,Object,Date".split( "," @@ -177,12 +177,12 @@ export class QWeb { * * @param {string} name the template should already have been added */ - render(name: string, context: EvalContext = {}): VNode { + render(name: string, context: EvalContext = {}, extra: any = null): VNode { if (!(name in this.rawTemplates)) { throw new Error(`Template ${name} does not exist`); } const template = this.templates[name] || this._compile(name); - return template(context); + return template(context, extra); } _compile(name: string): CompiledTemplate { @@ -191,9 +191,7 @@ export class QWeb { } const doc = this.parsedTemplates[name]; - - let ctx = new Context(); - + const ctx = new Context(); const mainNode = doc.firstChild!; this._compileNode(mainNode, ctx); @@ -201,15 +199,18 @@ export class QWeb { throw new Error("A template should have one root node"); } ctx.addLine(`return vn${ctx.rootNode}`); - const functionCode = ctx.code.join("\n"); + let template = new Function( + "context", + "extra", + ctx.code.join("\n") + ) as CompiledTemplate; if ((mainNode).attributes.hasOwnProperty("t-debug")) { console.log( - `Template: ${this.rawTemplates[name]}\nCompiled code:\n` + functionCode + `Template: ${this.rawTemplates[name]}\nCompiled code:\n` + + template.toString() ); } - const template = (new Function("context", functionCode) as CompiledTemplate< - VNode - >).bind(this); + template = template.bind(this); this.templates[name] = template; return template; } @@ -682,7 +683,7 @@ const widgetDirective: Directive = { ctx.addLine( `let def${defID} = _${widgetID}.mount().then(vnode=>Object.assign(_${dummyID}, vnode))` ); - ctx.addLine(`context._TEMP.push(def${defID})`); + ctx.addLine(`extra.promises.push(def${defID})`); let ref = node.getAttribute("t-ref"); if (ref) { diff --git a/web/static/src/ts/widgets/Counter.ts b/web/static/src/ts/widgets/Counter.ts index c8c5de0b..6a426887 100644 --- a/web/static/src/ts/widgets/Counter.ts +++ b/web/static/src/ts/widgets/Counter.ts @@ -21,6 +21,9 @@ export class Counter extends Widget { this.state.counter = props.initialState || 0; } + mounted() { + debugger; + } increment(delta: number) { this.updateState({ counter: this.state.counter + delta }); } diff --git a/web/static/src/ts/widgets/Discuss.ts b/web/static/src/ts/widgets/Discuss.ts index a7a7e8f2..ea70004c 100644 --- a/web/static/src/ts/widgets/Discuss.ts +++ b/web/static/src/ts/widgets/Discuss.ts @@ -1,10 +1,11 @@ import { Widget } from "../core/widget"; -import { Counter } from "./counter"; import { Env } from "../env"; +import { Clock } from "./clock"; +import { Counter } from "./counter"; const template = ` -
- Root Widget +
+ DISCUSS!! @@ -13,7 +14,7 @@ const template = ` - +
@@ -22,9 +23,12 @@ const template = ` export class Discuss extends Widget { name = "discuss"; template = template; - widgets = { Counter }; + widgets = { Clock, Counter }; state = { validcounter: true }; + mounted() { + debugger; + } resetCounter(ev: MouseEvent) { this.refs.counter.updateState({ counter: 3 }); } diff --git a/web/static/src/ts/widgets/clock.ts b/web/static/src/ts/widgets/clock.ts new file mode 100644 index 00000000..d159c71d --- /dev/null +++ b/web/static/src/ts/widgets/clock.ts @@ -0,0 +1,27 @@ +import { Widget } from "../core/widget"; +import { Env } from "../env"; + +const template = `
`; + +export class Clock extends Widget { + name = "clock"; + template = template; + state = { + currentTime: "" + }; + + async willStart() { + this.updateTime(); + } + + mounted() { + setInterval(this.updateTime.bind(this), 500); + } + + updateTime() { + debugger; + this.updateState({ + currentTime: new Date().toLocaleTimeString() + }); + } +} diff --git a/web/static/tests/widget.test.ts b/web/static/tests/widget.test.ts index d1738086..6de86f9a 100644 --- a/web/static/tests/widget.test.ts +++ b/web/static/tests/widget.test.ts @@ -70,6 +70,22 @@ describe("basic widget properties", () => { `
world
` ); }); + + test("updateState before first render does not trigger a render", async () => { + let renderCalls = 0; + class TestW extends Widget { + async willStart() { + this.updateState({}); + } + async render() { + renderCalls++; + return super.render(); + } + } + const widget = makeWidget(TestW); + await widget.mount(document.createElement("div")); + expect(renderCalls).toBe(1); + }); }); describe("lifecycle hooks", () => {