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,
|
"strictPropertyInitialization": true,
|
||||||
"strictNullChecks": true
|
"strictNullChecks": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts", "tests/**/*.ts", "demo/**/*.ts"]
|
"include": ["web/static/**/*.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,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() {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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 = `
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|||||||
@@ -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,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";
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|||||||
Reference in New Issue
Block a user