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