mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
refactoring on types
This commit is contained in:
+1
-1
@@ -17,5 +17,5 @@
|
||||
"strictPropertyInitialization": true,
|
||||
"strictNullChecks": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "tests/**/*.ts", "demo/**/*.ts"]
|
||||
"include": ["web/static/**/*.ts"]
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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,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";
|
||||
|
||||
@@ -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 };
|
||||
|
||||
Reference in New Issue
Block a user