small improvements to widget, add clock widget

This commit is contained in:
Géry Debongnie
2019-01-24 13:48:31 +01:00
parent 3415d830c7
commit 299a6b4977
6 changed files with 78 additions and 26 deletions
+10 -9
View File
@@ -15,6 +15,7 @@ export class Widget<T extends WEnv> {
template: string = "<div></div>"; template: string = "<div></div>";
vnode: VNode | null = null; vnode: VNode | null = null;
isStarted: boolean = false;
parent: Widget<T> | null = null; parent: Widget<T> | null = null;
children: Widget<T>[] = []; children: Widget<T>[] = [];
env: T; env: T;
@@ -49,6 +50,7 @@ export class Widget<T extends WEnv> {
async mount(target?: HTMLElement): Promise<VNode> { async mount(target?: HTMLElement): Promise<VNode> {
await this.willStart(); await this.willStart();
this.isStarted = true;
this.env.qweb.addTemplate(this.name, this.template); this.env.qweb.addTemplate(this.name, this.template);
delete this.template; delete this.template;
const vnode = await this.render(); const vnode = await this.render();
@@ -69,14 +71,14 @@ export class Widget<T extends WEnv> {
} }
/** /**
* DOCSTRIGN * 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
* @param {Object} newState
* @memberof Widget
*/ */
async updateState(newState: Object) { async updateState(newState: Object) {
Object.assign(this.state, newState); Object.assign(this.state, newState);
await this.render(); if (this.isStarted) {
await this.render();
}
} }
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
@@ -84,10 +86,9 @@ export class Widget<T extends WEnv> {
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
async render(): Promise<VNode> { async render(): Promise<VNode> {
// localized hack to keep track of deferred list const promises: Promise<void>[] = [];
(<any>this)._TEMP = []; let vnode = this.env.qweb.render(this.name, this, { promises });
let vnode = this.env!.qweb.render(this.name, this); await Promise.all(promises);
await Promise.all((<any>this)._TEMP);
if (!this.el) { if (!this.el) {
this.el = document.createElement(vnode.sel!); this.el = document.createElement(vnode.sel!);
} }
+13 -12
View File
@@ -3,7 +3,7 @@ import h from "../../../libs/snabbdom/src/h";
export type EvalContext = { [key: string]: any }; export type EvalContext = { [key: string]: any };
export type RawTemplate = string; export type RawTemplate = string;
export type CompiledTemplate<T> = (context: EvalContext) => T; export type CompiledTemplate<T> = (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( 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 * @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)) { if (!(name in this.rawTemplates)) {
throw new Error(`Template ${name} does not exist`); throw new Error(`Template ${name} does not exist`);
} }
const template = this.templates[name] || this._compile(name); const template = this.templates[name] || this._compile(name);
return template(context); return template(context, extra);
} }
_compile(name: string): CompiledTemplate<VNode> { _compile(name: string): CompiledTemplate<VNode> {
@@ -191,9 +191,7 @@ export class QWeb {
} }
const doc = this.parsedTemplates[name]; const doc = this.parsedTemplates[name];
const ctx = new Context();
let ctx = new Context();
const mainNode = doc.firstChild!; const mainNode = doc.firstChild!;
this._compileNode(mainNode, ctx); this._compileNode(mainNode, ctx);
@@ -201,15 +199,18 @@ export class QWeb {
throw new Error("A template should have one root node"); throw new Error("A template should have one root node");
} }
ctx.addLine(`return vn${ctx.rootNode}`); ctx.addLine(`return vn${ctx.rootNode}`);
const functionCode = ctx.code.join("\n"); let template = new Function(
"context",
"extra",
ctx.code.join("\n")
) as CompiledTemplate<VNode>;
if ((<Element>mainNode).attributes.hasOwnProperty("t-debug")) { if ((<Element>mainNode).attributes.hasOwnProperty("t-debug")) {
console.log( 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< template = template.bind(this);
VNode
>).bind(this);
this.templates[name] = template; this.templates[name] = template;
return template; return template;
} }
@@ -682,7 +683,7 @@ const widgetDirective: Directive = {
ctx.addLine( ctx.addLine(
`let def${defID} = _${widgetID}.mount().then(vnode=>Object.assign(_${dummyID}, vnode))` `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"); let ref = node.getAttribute("t-ref");
if (ref) { if (ref) {
+3
View File
@@ -21,6 +21,9 @@ export class Counter extends Widget<Env> {
this.state.counter = props.initialState || 0; this.state.counter = props.initialState || 0;
} }
mounted() {
debugger;
}
increment(delta: number) { increment(delta: number) {
this.updateState({ counter: this.state.counter + delta }); this.updateState({ counter: this.state.counter + delta });
} }
+9 -5
View File
@@ -1,10 +1,11 @@
import { Widget } from "../core/widget"; import { Widget } from "../core/widget";
import { Counter } from "./counter";
import { Env } from "../env"; import { Env } from "../env";
import { Clock } from "./clock";
import { Counter } from "./counter";
const template = ` const template = `
<div class="o_discuss"> <div class="o_discuss" t-debug="1">
<span>Root Widget</span> <span>DISCUSS!!</span>
<button t-on-click="resetCounter">Reset</button> <button t-on-click="resetCounter">Reset</button>
<button t-on-click="resetCounterAsync">Reset in 3s</button> <button t-on-click="resetCounterAsync">Reset in 3s</button>
<button t-on-click="toggle">Toggle Counter</button> <button t-on-click="toggle">Toggle Counter</button>
@@ -13,7 +14,7 @@ const template = `
<t t-widget="Counter" t-ref="counter" t-props="{initialState:4}"/> <t t-widget="Counter" t-ref="counter" t-props="{initialState:4}"/>
</t> </t>
<t t-else="1"> <t t-else="1">
<t t-widget="Counter" t-ref="counter" t-props="{initialState:7}"/> <t t-widget="Clock"/>
</t> </t>
<div ref="target"/> <div ref="target"/>
</div> </div>
@@ -22,9 +23,12 @@ const template = `
export class Discuss extends Widget<Env> { export class Discuss extends Widget<Env> {
name = "discuss"; name = "discuss";
template = template; template = template;
widgets = { Counter }; widgets = { Clock, Counter };
state = { validcounter: true }; state = { validcounter: true };
mounted() {
debugger;
}
resetCounter(ev: MouseEvent) { resetCounter(ev: MouseEvent) {
this.refs.counter.updateState({ counter: 3 }); this.refs.counter.updateState({ counter: 3 });
} }
+27
View File
@@ -0,0 +1,27 @@
import { Widget } from "../core/widget";
import { Env } from "../env";
const template = `<div class="o_clock"><t t-esc="state.currentTime"/></div>`;
export class Clock extends Widget<Env> {
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()
});
}
}
+16
View File
@@ -70,6 +70,22 @@ describe("basic widget properties", () => {
`<div style="font-weight:bold;" class="some-class">world</div>` `<div style="font-weight:bold;" class="some-class">world</div>`
); );
}); });
test("updateState before first render does not trigger a render", async () => {
let renderCalls = 0;
class TestW extends Widget<TestEnv> {
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", () => { describe("lifecycle hooks", () => {