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>";
vnode: VNode | null = null;
isStarted: boolean = false;
parent: Widget<T> | null = null;
children: Widget<T>[] = [];
env: T;
@@ -49,6 +50,7 @@ export class Widget<T extends WEnv> {
async mount(target?: HTMLElement): Promise<VNode> {
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<T extends WEnv> {
}
/**
* 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<T extends WEnv> {
//--------------------------------------------------------------------------
async render(): Promise<VNode> {
// localized hack to keep track of deferred list
(<any>this)._TEMP = [];
let vnode = this.env!.qweb.render(this.name, this);
await Promise.all((<any>this)._TEMP);
const promises: Promise<void>[] = [];
let vnode = this.env.qweb.render(this.name, this, { promises });
await Promise.all(promises);
if (!this.el) {
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 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(
","
@@ -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<VNode> {
@@ -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<VNode>;
if ((<Element>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) {
+3
View File
@@ -21,6 +21,9 @@ export class Counter extends Widget<Env> {
this.state.counter = props.initialState || 0;
}
mounted() {
debugger;
}
increment(delta: number) {
this.updateState({ counter: this.state.counter + delta });
}
+9 -5
View File
@@ -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 = `
<div class="o_discuss">
<span>Root Widget</span>
<div class="o_discuss" t-debug="1">
<span>DISCUSS!!</span>
<button t-on-click="resetCounter">Reset</button>
<button t-on-click="resetCounterAsync">Reset in 3s</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 t-else="1">
<t t-widget="Counter" t-ref="counter" t-props="{initialState:7}"/>
<t t-widget="Clock"/>
</t>
<div ref="target"/>
</div>
@@ -22,9 +23,12 @@ const template = `
export class Discuss extends Widget<Env> {
name = "discuss";
template = template;
widgets = { Counter };
widgets = { Clock, Counter };
state = { validcounter: true };
mounted() {
debugger;
}
resetCounter(ev: MouseEvent) {
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>`
);
});
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", () => {