Files
owl/web/static/src/ts/core/Widget.ts
T

165 lines
4.2 KiB
TypeScript
Raw Normal View History

2019-01-22 19:25:12 +01:00
import sdAttrs from "../../../libs/snabbdom/src/modules/attributes";
2019-01-23 14:28:41 +01:00
import sdListeners from "../../../libs/snabbdom/src/modules/eventlisteners";
import { init } from "../../../libs/snabbdom/src/snabbdom";
2019-01-22 19:25:12 +01:00
import { VNode } from "../../../libs/snabbdom/src/vnode";
2019-01-24 10:33:11 +01:00
import { QWeb } from "./qweb_vdom";
2019-01-17 18:08:39 +01:00
2019-01-21 14:38:44 +01:00
const patch = init([sdListeners, sdAttrs]);
2019-01-16 13:19:58 +01:00
2019-01-23 14:28:41 +01:00
export interface WEnv {
2019-01-16 13:19:58 +01:00
qweb: QWeb;
getID(): number;
}
let wl: any[] = [];
(<any>window).wl = wl;
interface Meta<T extends WEnv> {
id: number;
// name: string;
// template: string;
vnode: VNode | null;
isStarted: boolean;
isMounted: boolean;
parent: Widget<T> | null;
children: Widget<T>[];
2019-01-16 13:19:58 +01:00
}
2019-01-24 10:33:11 +01:00
export class Widget<T extends WEnv> {
_: Meta<WEnv>;
2019-01-16 13:19:58 +01:00
name: string = "widget";
template: string = "<div></div>";
get el(): HTMLElement | null {
return this._.vnode ? (<any>this)._.vnode.elm : null;
}
2019-01-23 11:12:54 +01:00
env: T;
2019-01-16 13:19:58 +01:00
state: Object = {};
2019-01-25 10:25:42 +01:00
refs: { [key: string]: Widget<T> | HTMLElement | undefined } = {}; // either HTMLElement or Widget
2019-01-16 13:19:58 +01:00
//--------------------------------------------------------------------------
// Lifecycle
//--------------------------------------------------------------------------
2019-01-23 11:12:54 +01:00
constructor(parent: Widget<T> | T, props?: any) {
wl.push(this);
let p: Widget<T> | null = null;
if (parent instanceof Widget) {
p = parent;
parent._.children.push(this);
this.env = Object.create(parent.env);
} else {
this.env = parent;
2019-01-16 13:19:58 +01:00
}
this._ = {
id: this.env.getID(),
vnode: null,
isStarted: false,
isMounted: false,
parent: p,
children: []
};
2019-01-16 13:19:58 +01:00
}
async willStart() {}
mounted() {}
willUnmount() {}
destroyed() {}
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
async mount(target: HTMLElement): Promise<VNode> {
await this._start();
2019-01-20 14:46:36 +01:00
const vnode = await this.render();
target.appendChild(this.el!);
if (document.body.contains(target)) {
this.visitSubTree(w => {
if (!w._.isMounted && this.el!.contains(w.el)) {
w._.isMounted = true;
w.mounted();
}
});
2019-01-20 14:46:36 +01:00
}
// }
2019-01-20 14:46:36 +01:00
return vnode;
2019-01-16 13:19:58 +01:00
}
2019-01-16 23:03:51 +01:00
destroy() {
if (this.el) {
this.el.remove();
2019-01-16 13:19:58 +01:00
}
}
2019-01-17 18:08:39 +01:00
/**
* 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
2019-01-17 18:08:39 +01:00
*/
2019-01-16 13:19:58 +01:00
async updateState(newState: Object) {
Object.assign(this.state, newState);
if (this._.isStarted) {
await this.render();
}
2019-01-16 13:19:58 +01:00
}
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
2019-01-20 14:46:36 +01:00
async render(): Promise<VNode> {
2019-01-25 10:25:42 +01:00
const vnode = await this._render();
this._.vnode = patch(
this._.vnode || document.createElement(vnode.sel!),
vnode
);
return this._.vnode;
2019-01-16 13:19:58 +01:00
}
2019-01-21 16:26:51 +01:00
private async _start(): Promise<void> {
await this.willStart();
this._.isStarted = true;
}
2019-01-25 10:25:42 +01:00
private async _render(): Promise<VNode> {
2019-01-25 11:01:43 +01:00
if (this.template) {
this.env.qweb.addTemplate(this.name, this.template);
delete this.template;
}
2019-01-25 10:25:42 +01:00
const promises: Promise<void>[] = [];
let vnode = this.env.qweb.render(this.name, this, { promises });
// this part is critical for the patching process to be done correctly. The
// tricky part is that a child widget can be rerendered on its own, which
// will update its own vnode representation without the knowledge of the
// parent widget. With this, we make sure that the parent widget will be
// able to patch itself properly after
vnode.key = this._.id;
2019-01-25 10:25:42 +01:00
return Promise.all(promises).then(() => vnode);
}
/**
* Only called by qweb t-widget directive
*/
_mount(vnode: VNode) {
this._.vnode = vnode;
if (this._.parent) {
if (this._.parent._.isMounted) {
this._.isMounted = true;
this.mounted();
}
}
}
2019-01-23 11:12:54 +01:00
private visitSubTree(callback: (w: Widget<T>) => void) {
2019-01-21 16:26:51 +01:00
callback(this);
for (let child of this._.children) {
2019-01-21 16:26:51 +01:00
child.visitSubTree(callback);
}
}
2019-01-16 13:19:58 +01:00
}