mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
small improvements to widget, add clock widget
This commit is contained in:
@@ -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!);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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", () => {
|
||||
|
||||
Reference in New Issue
Block a user