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>";
|
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!);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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 });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>`
|
`<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", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user