refactoring on types

This commit is contained in:
Géry Debongnie
2019-01-23 11:12:54 +01:00
parent 3766fbc7b8
commit 08938d2334
15 changed files with 85 additions and 65 deletions
+1 -1
View File
@@ -17,5 +17,5 @@
"strictPropertyInitialization": true, "strictPropertyInitialization": true,
"strictNullChecks": true "strictNullChecks": true
}, },
"include": ["src/**/*.ts", "tests/**/*.ts", "demo/**/*.ts"] "include": ["web/static/**/*.ts"]
} }
+1
View File
@@ -5,6 +5,7 @@
<title>Odoo Web Core Demo</title> <title>Odoo Web Core Demo</title>
<script src="/web/static/libs/almond/almond.js"></script> <script src="/web/static/libs/almond/almond.js"></script>
<script src="/main.js"></script> <script src="/main.js"></script>
<link rel="icon" href="data:,">
<link rel="stylesheet" href="/app.css"> <link rel="stylesheet" href="/app.css">
<script> <script>
require('main'); require('main');
+9 -12
View File
@@ -1,35 +1,32 @@
import QWeb from "./qweb_vdom";
import { init } from "../../../libs/snabbdom/src/snabbdom"; import { init } from "../../../libs/snabbdom/src/snabbdom";
import sdListeners from "../../../libs/snabbdom/src/modules/eventlisteners"; import sdListeners from "../../../libs/snabbdom/src/modules/eventlisteners";
import sdAttrs from "../../../libs/snabbdom/src/modules/attributes"; import sdAttrs from "../../../libs/snabbdom/src/modules/attributes";
import { VNode } from "../../../libs/snabbdom/src/vnode"; import { VNode } from "../../../libs/snabbdom/src/vnode";
import QWeb from "./qweb_vdom";
const patch = init([sdListeners, sdAttrs]); const patch = init([sdListeners, sdAttrs]);
export interface Env { export interface WidgetEnv {
qweb: QWeb; qweb: QWeb;
services: { [key: string]: any };
[key: string]: any;
} }
export default class Widget { export default class Widget<T extends WidgetEnv> {
name: string = "widget"; name: string = "widget";
template: string = "<div></div>"; template: string = "<div></div>";
vnode: VNode | null = null; vnode: VNode | null = null;
parent: Widget | null = null; parent: Widget<T> | null = null;
children: Widget[] = []; children: Widget<T>[] = [];
env: Env; env: T;
el: HTMLElement | null = null; el: HTMLElement | null = null;
state: Object = {}; state: Object = {};
refs: { [key: string]: Widget } = {}; refs: { [key: string]: Widget<T> } = {};
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Lifecycle // Lifecycle
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
constructor(parent: Widget | Env, props?: any) { constructor(parent: Widget<T> | T, props?: any) {
if (parent instanceof Widget) { if (parent instanceof Widget) {
this.parent = parent; this.parent = parent;
parent.children.push(this); parent.children.push(this);
@@ -99,7 +96,7 @@ export default class Widget {
return vnode; return vnode;
} }
private visitSubTree(callback: (w: Widget) => void) { private visitSubTree(callback: (w: Widget<T>) => void) {
callback(this); callback(this);
for (let child of this.children) { for (let child of this.children) {
child.visitSubTree(callback); child.visitSubTree(callback);
+10 -9
View File
@@ -2,13 +2,15 @@ import { VNode } from "../../../libs/snabbdom/src/vnode";
import h from "../../../libs/snabbdom/src/h"; import h from "../../../libs/snabbdom/src/h";
export type EvalContext = { [key: string]: any }; export type EvalContext = { [key: string]: any };
type RawTemplate = string; export type RawTemplate = string;
type ParsedTemplate = Document; export type CompiledTemplate<T> = (context: EvalContext) => T;
type CompiledTemplate = (context: EvalContext) => VNode;
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(
"," ","
); );
type ParsedTemplate = Document;
// Compilation Context // Compilation Context
export class Context { export class Context {
nextID: number = 1; nextID: number = 1;
@@ -84,7 +86,7 @@ export class Context {
export default class QWeb { export default class QWeb {
rawTemplates: { [name: string]: RawTemplate } = {}; rawTemplates: { [name: string]: RawTemplate } = {};
parsedTemplates: { [name: string]: ParsedTemplate } = {}; parsedTemplates: { [name: string]: ParsedTemplate } = {};
templates: { [name: string]: CompiledTemplate } = {}; templates: { [name: string]: CompiledTemplate<VNode> } = {};
h = h; h = h;
exprCache: { [key: string]: string } = {}; exprCache: { [key: string]: string } = {};
directives: Directive[] = []; directives: Directive[] = [];
@@ -183,7 +185,7 @@ export default class QWeb {
return template(context); return template(context);
} }
_compile(name: string): CompiledTemplate { _compile(name: string): CompiledTemplate<VNode> {
if (name in this.templates) { if (name in this.templates) {
return this.templates[name]; return this.templates[name];
} }
@@ -205,10 +207,9 @@ export default class QWeb {
`Template: ${this.rawTemplates[name]}\nCompiled code:\n` + functionCode `Template: ${this.rawTemplates[name]}\nCompiled code:\n` + functionCode
); );
} }
const template: CompiledTemplate = (new Function( const template = (new Function("context", functionCode) as CompiledTemplate<
"context", VNode
functionCode >).bind(this);
) as CompiledTemplate).bind(this);
this.templates[name] = template; this.templates[name] = template;
return template; return template;
} }
+4 -6
View File
@@ -1,14 +1,12 @@
import { Env } from "./core/Widget";
import QWeb from "./core/qweb_vdom"; import QWeb from "./core/qweb_vdom";
import Router from "./services/router"; import Router from "./services/router";
import actions from "./services/actions"; import actions from "./services/actions";
import { Env } from "./types";
const qweb = new QWeb();
const router = new Router();
const env: Env = { const env: Env = {
qweb: qweb, qweb: new QWeb(),
services: { router, actions } router: new Router(),
services: { actions }
}; };
export default env; export default env;
+1 -1
View File
@@ -1,6 +1,6 @@
///<amd-module name="main" /> ///<amd-module name="main" />
import RootWidget from "./widgets/RootWidget"; import RootWidget from "./widgets/root_widget";
import env from "./env"; import env from "./env";
document.addEventListener("DOMContentLoaded", async function() { document.addEventListener("DOMContentLoaded", async function() {
+8 -4
View File
@@ -1,11 +1,14 @@
import CRM from "../widgets/CRM"; import CRM from "../widgets/crm";
import Discuss from "../widgets/Discuss"; import Discuss from "../widgets/discuss";
import Widget from "../core/Widget"; import Widget from "../core/widget";
import { Env, Type } from "../types";
export interface Action { export interface Action {
id: number; id: number;
title: string; title: string;
Widget: typeof Widget; Widget: Type<Widget<Env>>;
default?: boolean; default?: boolean;
} }
@@ -15,3 +18,4 @@ const actions: Action[] = [
]; ];
export default actions; export default actions;
+11
View File
@@ -0,0 +1,11 @@
import { WidgetEnv } from "./core/widget";
import Router from "./services/router";
export interface Env extends WidgetEnv {
router: Router;
services: { [key: string]: any };
}
export interface Type<T> extends Function {
new (...args: any[]): T;
}
+3 -2
View File
@@ -1,4 +1,5 @@
import Widget from "../core/Widget"; import Widget from "../core/widget";
import { Env } from "../types";
const template = ` const template = `
<div class="o_crm"> <div class="o_crm">
@@ -6,7 +7,7 @@ const template = `
</div> </div>
`; `;
export default class Discuss extends Widget { export default class Discuss extends Widget<Env> {
name = "crm"; name = "crm";
template = template; template = template;
} }
+4 -3
View File
@@ -1,4 +1,5 @@
import Widget from "../core/Widget"; import Widget from "../core/widget";
import { Env } from "../types";
const template = ` const template = `
<div> <div>
@@ -8,14 +9,14 @@ const template = `
</div> </div>
`; `;
export default class Counter extends Widget { export default class Counter extends Widget<Env> {
name = "counter"; name = "counter";
template = template; template = template;
state = { state = {
counter: 0 counter: 0
}; };
constructor(parent: Widget | null, props: {initialState?: number}) { constructor(parent: Widget<Env>, props: {initialState?: number}) {
super(parent); super(parent);
this.state.counter = props.initialState || 0; this.state.counter = props.initialState || 0;
} }
+4 -3
View File
@@ -1,5 +1,6 @@
import Widget from "../core/Widget"; import Widget from "../core/widget";
import Counter from "./Counter"; import Counter from "./counter";
import { Env } from "../types";
const template = ` const template = `
<div class="o_discuss"> <div class="o_discuss">
@@ -18,7 +19,7 @@ const template = `
</div> </div>
`; `;
export default class Discuss extends Widget { export default class Discuss extends Widget<Env> {
name = "discuss"; name = "discuss";
template = template; template = template;
widgets = { Counter }; widgets = { Counter };
+5 -4
View File
@@ -1,5 +1,6 @@
import Widget from "../core/Widget"; import Widget from "../core/widget";
import { Action } from "../services/actions"; import { Action } from "../services/actions";
import { Env } from "../types";
const template = ` const template = `
<div class="o_navbar"> <div class="o_navbar">
@@ -12,12 +13,12 @@ const template = `
</div> </div>
`; `;
export default class Navbar extends Widget { export default class Navbar extends Widget<Env> {
name = "navbar"; name = "navbar";
template = template; template = template;
getUrl(action: Action) { getUrl(action: Action) {
const action_id = action.id; const action_id = String(action.id);
return this.env.services.router.formatURL("web", { action_id }); return this.env.router.formatURL("web", { action_id });
} }
} }
@@ -1,6 +1,7 @@
import Widget, { Env } from "../core/Widget"; import Widget from "../core/widget";
import Navbar from "./Navbar"; import Navbar from "./navbar";
import { Action } from "../services/actions"; import { Action } from "../services/actions";
import { Env } from "../types";
const template = ` const template = `
<div class="o_web_client"> <div class="o_web_client">
@@ -11,11 +12,10 @@ const template = `
</div> </div>
`; `;
export default class RootWidget extends Widget { export default class RootWidget extends Widget<Env> {
name = "root"; name = "root";
template = template; template = template;
widgets = { Navbar }; widgets = { Navbar };
state = { validcounter: true };
constructor(env: Env) { constructor(env: Env) {
super(env); super(env);
@@ -23,7 +23,7 @@ export default class RootWidget extends Widget {
} }
mounted() { mounted() {
this.env.services.router.register(this, this.onUrlChange); this.env.router.register(this, this.onUrlChange);
} }
setMainWidget() { setMainWidget() {
@@ -39,7 +39,7 @@ export default class RootWidget extends Widget {
} }
getAction(): Action { getAction(): Action {
const routeInfo = this.env.services.router.getRouteInfo(); const routeInfo = this.env.router.getRouteInfo();
const actionID = parseInt(routeInfo.query.action_id); const actionID = parseInt(routeInfo.query.action_id);
let actions: Action[] = this.env.services.actions; let actions: Action[] = this.env.services.actions;
let action = actions.find(a => a.id === actionID); let action = actions.find(a => a.id === actionID);
+1 -1
View File
@@ -1,4 +1,4 @@
import QWeb, { EvalContext } from "../src/ts/core/qweb_vdom"; import QWeb, {EvalContext} from "../src/ts/core/qweb_vdom";
import { init } from "../libs/snabbdom/src/snabbdom"; import { init } from "../libs/snabbdom/src/snabbdom";
import sdAttributes from "../libs/snabbdom/src/modules/attributes"; import sdAttributes from "../libs/snabbdom/src/modules/attributes";
import sdListeners from "../libs/snabbdom/src/modules/eventlisteners"; import sdListeners from "../libs/snabbdom/src/modules/eventlisteners";
+17 -13
View File
@@ -1,10 +1,14 @@
import Widget from "../src/ts/core/Widget"; import Widget, {WidgetEnv} from "../src/ts/core/widget";
import QWeb from "../src/ts/core/qweb_vdom"; import QWeb from "../src/ts/core/qweb_vdom";
import { Type } from "../src/ts/types";
function makeWidget(W: typeof Widget): Widget {
type TestEnv = WidgetEnv;
type TestWidget = Widget<TestEnv>
function makeWidget(W: Type<TestWidget>): TestWidget {
const env = { const env = {
qweb: new QWeb(), qweb: new QWeb(),
services: {}
}; };
const w = new W(env); const w = new W(env);
return w; return w;
@@ -19,7 +23,7 @@ const template = `
<div><t t-esc="state.counter"/><button t-on-click="inc">Inc</button></div> <div><t t-esc="state.counter"/><button t-on-click="inc">Inc</button></div>
`; `;
export default class Counter extends Widget { export default class Counter extends Widget<TestEnv> {
name = "counter"; name = "counter";
template = template; template = template;
state = { state = {
@@ -54,7 +58,7 @@ describe("basic widget properties", () => {
}); });
test("widget style and classname", async () => { test("widget style and classname", async () => {
class StyledWidget extends Widget { class StyledWidget extends Widget<TestEnv> {
template = `<div style="font-weight:bold;" class="some-class">world</div>`; template = `<div style="font-weight:bold;" class="some-class">world</div>`;
} }
const widget = makeWidget(StyledWidget); const widget = makeWidget(StyledWidget);
@@ -69,7 +73,7 @@ describe("basic widget properties", () => {
describe("lifecycle hooks", () => { describe("lifecycle hooks", () => {
test("willStart hook is called", async () => { test("willStart hook is called", async () => {
let willstart = false; let willstart = false;
class HookWidget extends Widget { class HookWidget extends Widget<TestEnv> {
async willStart() { async willStart() {
willstart = true; willstart = true;
} }
@@ -82,7 +86,7 @@ describe("lifecycle hooks", () => {
test("mounted hook is not called if not in DOM", async () => { test("mounted hook is not called if not in DOM", async () => {
let mounted = false; let mounted = false;
class HookWidget extends Widget { class HookWidget extends Widget<TestEnv> {
async mounted() { async mounted() {
mounted = true; mounted = true;
} }
@@ -95,7 +99,7 @@ describe("lifecycle hooks", () => {
test("mounted hook is called if mounted in DOM", async () => { test("mounted hook is called if mounted in DOM", async () => {
let mounted = false; let mounted = false;
class HookWidget extends Widget { class HookWidget extends Widget<TestEnv> {
async mounted() { async mounted() {
mounted = true; mounted = true;
} }
@@ -112,7 +116,7 @@ describe("lifecycle hooks", () => {
expect.assertions(2); expect.assertions(2);
let parentMounted = false; let parentMounted = false;
let childMounted = false; let childMounted = false;
class ParentWidget extends Widget { class ParentWidget extends Widget<TestEnv> {
name="a"; name="a";
template = `<div>Hello<t t-widget="child"/></div>`; template = `<div>Hello<t t-widget="child"/></div>`;
widgets = { child: ChildWidget }; widgets = { child: ChildWidget };
@@ -121,7 +125,7 @@ describe("lifecycle hooks", () => {
parentMounted = true; parentMounted = true;
} }
} }
class ChildWidget extends Widget { class ChildWidget extends Widget<TestEnv> {
async mounted() { async mounted() {
expect(parentMounted).toBe(true); expect(parentMounted).toBe(true);
childMounted = true; childMounted = true;
@@ -147,13 +151,13 @@ describe("destroy method", () => {
}); });
describe("composition", () => { describe("composition", () => {
class WidgetA extends Widget { class WidgetA extends Widget<TestEnv> {
name = "a"; name = "a";
template = `<div>Hello<t t-widget="b"/></div>`; template = `<div>Hello<t t-widget="b"/></div>`;
widgets = { b: WidgetB }; widgets = { b: WidgetB };
} }
class WidgetB extends Widget { class WidgetB extends Widget<TestEnv> {
template = `<div>world</div>`; template = `<div>world</div>`;
} }
@@ -165,7 +169,7 @@ describe("composition", () => {
}); });
test("t-refs on widget are widgets", async () => { test("t-refs on widget are widgets", async () => {
class WidgetC extends Widget { class WidgetC extends Widget<TestEnv> {
name = "a"; name = "a";
template = `<div>Hello<t t-ref="mywidgetb" t-widget="b"/></div>`; template = `<div>Hello<t t-ref="mywidgetb" t-widget="b"/></div>`;
widgets = { b: WidgetB }; widgets = { b: WidgetB };