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;
|
2019-01-25 14:27:16 +01:00
|
|
|
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> {
|
2019-01-25 14:27:16 +01:00
|
|
|
_: Meta<WEnv>;
|
2019-01-16 13:19:58 +01:00
|
|
|
name: string = "widget";
|
|
|
|
|
template: string = "<div></div>";
|
|
|
|
|
|
2019-01-23 11:12:54 +01:00
|
|
|
env: T;
|
2019-01-20 09:25:41 +01:00
|
|
|
el: HTMLElement | null = null;
|
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) {
|
2019-01-25 14:27:16 +01:00
|
|
|
wl.push(this);
|
|
|
|
|
let p: Widget<T> | null = null;
|
2019-01-22 10:38:44 +01:00
|
|
|
if (parent instanceof Widget) {
|
2019-01-25 14:27:16 +01:00
|
|
|
p = parent;
|
|
|
|
|
parent._.children.push(this);
|
2019-01-22 10:38:44 +01:00
|
|
|
this.env = Object.create(parent.env);
|
|
|
|
|
} else {
|
|
|
|
|
this.env = parent;
|
2019-01-16 13:19:58 +01:00
|
|
|
}
|
2019-01-25 14:27:16 +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
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
|
2019-01-25 11:12:20 +01:00
|
|
|
async mount(target: HTMLElement): Promise<VNode> {
|
|
|
|
|
await this._start();
|
2019-01-20 14:46:36 +01:00
|
|
|
const vnode = await this.render();
|
2019-01-25 11:12:20 +01:00
|
|
|
target.appendChild(this.el!);
|
|
|
|
|
|
|
|
|
|
if (document.body.contains(target)) {
|
|
|
|
|
this.visitSubTree(w => {
|
2019-01-25 14:27:16 +01:00
|
|
|
if (!w._.isMounted && this.el!.contains(w.el)) {
|
|
|
|
|
w._.isMounted = true;
|
2019-01-25 11:12:20 +01:00
|
|
|
w.mounted();
|
|
|
|
|
}
|
|
|
|
|
});
|
2019-01-20 14:46:36 +01:00
|
|
|
}
|
2019-01-25 11:12:20 +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
|
|
|
/**
|
2019-01-24 13:48:31 +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);
|
2019-01-25 14:27:16 +01:00
|
|
|
if (this._.isStarted) {
|
2019-01-24 13:48:31 +01:00
|
|
|
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();
|
2019-01-20 09:25:41 +01:00
|
|
|
if (!this.el) {
|
|
|
|
|
this.el = document.createElement(vnode.sel!);
|
|
|
|
|
}
|
2019-01-25 14:27:16 +01:00
|
|
|
patch(this._.vnode || this.el, vnode);
|
|
|
|
|
this._.vnode = vnode;
|
2019-01-20 14:46:36 +01:00
|
|
|
return vnode;
|
2019-01-16 13:19:58 +01:00
|
|
|
}
|
2019-01-21 16:26:51 +01:00
|
|
|
|
2019-01-25 11:12:20 +01:00
|
|
|
private async _start(): Promise<void> {
|
|
|
|
|
await this.willStart();
|
2019-01-25 14:27:16 +01:00
|
|
|
this._.isStarted = true;
|
2019-01-25 11:12:20 +01:00
|
|
|
}
|
|
|
|
|
|
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 });
|
|
|
|
|
return Promise.all(promises).then(() => vnode);
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-25 11:12:20 +01:00
|
|
|
_mount(el: HTMLElement) {
|
|
|
|
|
this.el = el;
|
2019-01-25 14:27:16 +01:00
|
|
|
if (this._.parent) {
|
|
|
|
|
if (this._.parent._.isMounted) {
|
|
|
|
|
this._.isMounted = true;
|
2019-01-25 11:12:20 +01:00
|
|
|
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);
|
2019-01-25 14:27:16 +01:00
|
|
|
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
|
|
|
}
|