make widget class generic on props

This commit is contained in:
Géry Debongnie
2019-01-28 21:32:21 +01:00
parent d80421d647
commit 2ea569f7aa
10 changed files with 58 additions and 54 deletions
+10 -10
View File
@@ -23,8 +23,8 @@ interface Meta<T extends WEnv> {
isStarted: boolean; isStarted: boolean;
isMounted: boolean; isMounted: boolean;
isDestroyed: boolean; isDestroyed: boolean;
parent: Widget<T> | null; parent: Widget<T, {}> | null;
children: { [key: number]: Widget<T> }; children: { [key: number]: Widget<T, {}> };
// children mapping: from templateID to widgetID // children mapping: from templateID to widgetID
// should it be a map number => Widget? // should it be a map number => Widget?
cmap: { [key: number]: number }; cmap: { [key: number]: number };
@@ -35,7 +35,7 @@ const patch = init([sdListeners, sdAttrs]);
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Widget // Widget
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
export class Widget<T extends WEnv> { export class Widget<T extends WEnv, Props> {
__widget__: Meta<WEnv>; __widget__: Meta<WEnv>;
name: string = "widget"; name: string = "widget";
template: string = "<div></div>"; template: string = "<div></div>";
@@ -46,18 +46,18 @@ export class Widget<T extends WEnv> {
env: T; env: T;
state: Object = {}; state: Object = {};
props: any; props: Props | undefined;
refs: { [key: string]: Widget<T> | HTMLElement | undefined } = {}; refs: { [key: string]: Widget<T, {}> | HTMLElement | undefined } = {};
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Lifecycle // Lifecycle
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
constructor(parent: Widget<T> | T, props?: any) { constructor(parent: Widget<T, {}> | T, props?: Props) {
wl.push(this); wl.push(this);
this.props = props; this.props = props;
let id: number; let id: number;
let p: Widget<T> | null = null; let p: Widget<T, any> | null = null;
if (parent instanceof Widget) { if (parent instanceof Widget) {
p = parent; p = parent;
this.env = parent.env; this.env = parent.env;
@@ -83,7 +83,7 @@ export class Widget<T extends WEnv> {
mounted() {} mounted() {}
shouldUpdate(nextProps: any): boolean { shouldUpdate(nextProps: Props): boolean {
return true; return true;
} }
@@ -147,7 +147,7 @@ export class Widget<T extends WEnv> {
} }
} }
updateProps(nextProps?: any): Promise<void> { updateProps(nextProps: Props): Promise<void> {
const shouldUpdate = this.shouldUpdate(nextProps); const shouldUpdate = this.shouldUpdate(nextProps);
this.props = nextProps; this.props = nextProps;
return shouldUpdate ? this.render() : Promise.resolve(); return shouldUpdate ? this.render() : Promise.resolve();
@@ -205,7 +205,7 @@ export class Widget<T extends WEnv> {
} }
} }
private visitSubTree(callback: (w: Widget<T>) => void) { private visitSubTree(callback: (w: Widget<T, any>) => void) {
callback(this); callback(this);
const children = this.__widget__.children; const children = this.__widget__.children;
for (let id in children) { for (let id in children) {
+1 -1
View File
@@ -6,7 +6,7 @@ import { Type } from "./types";
// Types // Types
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
type ActionWidget = Type<Widget<Env>>; type ActionWidget = Type<Widget<Env, {}>>;
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Registry code // Registry code
+1 -1
View File
@@ -33,7 +33,7 @@ export type Action = ClientAction | ActWindowAction;
export interface ActionWidget { export interface ActionWidget {
id: number; id: number;
Widget: Type<Widget<Env>>; Widget: Type<Widget<Env, {}>>;
props: any; props: any;
} }
+1 -1
View File
@@ -7,7 +7,7 @@ const template = `
</div> </div>
`; `;
export class CRM extends Widget<Env> { export class CRM extends Widget<Env, {}> {
name = "crm"; name = "crm";
template = template; template = template;
} }
+7 -3
View File
@@ -9,15 +9,19 @@ const template = `
</div> </div>
`; `;
export class Counter extends Widget<Env> { interface Props {
initialState?: number;
}
export class Counter extends Widget<Env, Props> {
name = "counter"; name = "counter";
template = template; template = template;
state = { state = {
counter: 0 counter: 0
}; };
constructor(parent: Widget<Env>, props: { initialState?: number }) { constructor(parent: Widget<Env, {}>, props: Props) {
super(parent); super(parent, props);
this.state.counter = props.initialState || 0; this.state.counter = props.initialState || 0;
} }
+2 -2
View File
@@ -23,7 +23,7 @@ const template = `
</div> </div>
`; `;
export class Discuss extends Widget<Env> { export class Discuss extends Widget<Env, {}> {
name = "discuss"; name = "discuss";
template = template; template = template;
widgets = { Clock, Counter, ColorWidget }; widgets = { Clock, Counter, ColorWidget };
@@ -54,7 +54,7 @@ export class Discuss extends Widget<Env> {
} }
} }
class ColorWidget extends Widget<Env> { class ColorWidget extends Widget<Env, { color: "red" | "blue" }> {
name = "colorwidget"; name = "colorwidget";
template = `<div>Current Color: <t t-esc="props.color"/></div>`; template = `<div>Current Color: <t t-esc="props.color"/></div>`;
} }
+1 -1
View File
@@ -14,7 +14,7 @@ const template = `
</div> </div>
`; `;
export class Navbar extends Widget<Env> { export class Navbar extends Widget<Env, {}> {
name = "navbar"; name = "navbar";
template = template; template = template;
+1 -1
View File
@@ -3,7 +3,7 @@ import { Env } from "../env";
const template = `<div class="o_clock"><t t-esc="state.currentTime"/></div>`; const template = `<div class="o_clock"><t t-esc="state.currentTime"/></div>`;
export class Clock extends Widget<Env> { export class Clock extends Widget<Env, {}> {
name = "clock"; name = "clock";
template = template; template = template;
interval: any | undefined; interval: any | undefined;
+2 -2
View File
@@ -11,11 +11,11 @@ const template = `
</div> </div>
`; `;
export class RootWidget extends Widget<Env> { export class RootWidget extends Widget<Env, {}> {
name = "root"; name = "root";
template = template; template = template;
widgets = { Navbar }; widgets = { Navbar };
content: Widget<Env> | null = null; content: Widget<Env, {}> | null = null;
mounted() { mounted() {
this.env.actionManager.on("action_ready", this, this.setContentWidget); this.env.actionManager.on("action_ready", this, this.setContentWidget);
+32 -32
View File
@@ -31,13 +31,13 @@ function nextTick(): Promise<void> {
return Promise.resolve(); return Promise.resolve();
} }
function children(w: Widget<WEnv>): Widget<WEnv>[] { function children(w: Widget<WEnv, {}>): Widget<WEnv, {}>[] {
const childrenMap = w.__widget__.children; const childrenMap = w.__widget__.children;
return Object.keys(childrenMap).map(id => childrenMap[id]); return Object.keys(childrenMap).map(id => childrenMap[id]);
} }
// Test widgets // Test widgets
class Counter extends Widget<WEnv> { class Counter extends Widget<WEnv, {}> {
name = "counter"; name = "counter";
template = `<div><t t-esc="state.counter"/><button t-on-click="inc">Inc</button></div>`; template = `<div><t t-esc="state.counter"/><button t-on-click="inc">Inc</button></div>`;
state = { state = {
@@ -49,13 +49,13 @@ class Counter extends Widget<WEnv> {
} }
} }
class WidgetA extends Widget<WEnv> { class WidgetA extends Widget<WEnv, {}> {
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<WEnv> { class WidgetB extends Widget<WEnv, {}> {
template = `<div>world</div>`; template = `<div>world</div>`;
} }
@@ -87,7 +87,7 @@ describe("basic widget properties", () => {
}); });
test("widget style and classname", async () => { test("widget style and classname", async () => {
class StyledWidget extends Widget<WEnv> { class StyledWidget extends Widget<WEnv, {}> {
template = `<div style="font-weight:bold;" class="some-class">world</div>`; template = `<div style="font-weight:bold;" class="some-class">world</div>`;
} }
const widget = new StyledWidget(env); const widget = new StyledWidget(env);
@@ -99,7 +99,7 @@ describe("basic widget properties", () => {
test("updateState before first render does not trigger a render", async () => { test("updateState before first render does not trigger a render", async () => {
let renderCalls = 0; let renderCalls = 0;
class TestW extends Widget<WEnv> { class TestW extends Widget<WEnv, {}> {
async willStart() { async willStart() {
this.updateState({}); this.updateState({});
} }
@@ -132,7 +132,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<WEnv> { class HookWidget extends Widget<WEnv, {}> {
async willStart() { async willStart() {
willstart = true; willstart = true;
} }
@@ -144,7 +144,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<WEnv> { class HookWidget extends Widget<WEnv, {}> {
async mounted() { async mounted() {
mounted = true; mounted = true;
} }
@@ -157,7 +157,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<WEnv> { class HookWidget extends Widget<WEnv, {}> {
async mounted() { async mounted() {
mounted = true; mounted = true;
} }
@@ -169,12 +169,12 @@ describe("lifecycle hooks", () => {
test("willStart hook is called on subwidget", async () => { test("willStart hook is called on subwidget", async () => {
let ok = false; let ok = false;
class ParentWidget extends Widget<WEnv> { class ParentWidget extends Widget<WEnv, {}> {
name = "a"; name = "a";
template = `<div><t t-widget="child"/></div>`; template = `<div><t t-widget="child"/></div>`;
widgets = { child: ChildWidget }; widgets = { child: ChildWidget };
} }
class ChildWidget extends Widget<WEnv> { class ChildWidget extends Widget<WEnv, {}> {
async willStart() { async willStart() {
ok = true; ok = true;
} }
@@ -188,7 +188,7 @@ describe("lifecycle hooks", () => {
expect.assertions(4); expect.assertions(4);
let parentMounted = false; let parentMounted = false;
let childMounted = false; let childMounted = false;
class ParentWidget extends Widget<WEnv> { class ParentWidget extends Widget<WEnv, {}> {
name = "a"; name = "a";
template = `<div><t t-widget="child"/></div>`; template = `<div><t t-widget="child"/></div>`;
widgets = { child: ChildWidget }; widgets = { child: ChildWidget };
@@ -197,7 +197,7 @@ describe("lifecycle hooks", () => {
parentMounted = true; parentMounted = true;
} }
} }
class ChildWidget extends Widget<WEnv> { class ChildWidget extends Widget<WEnv, {}> {
mounted() { mounted() {
expect(document.body.contains(this.el)).toBe(true); expect(document.body.contains(this.el)).toBe(true);
expect(parentMounted).toBe(true); expect(parentMounted).toBe(true);
@@ -212,7 +212,7 @@ describe("lifecycle hooks", () => {
test("willStart, mounted on subwidget rendered after main is mounted in some other position", async () => { test("willStart, mounted on subwidget rendered after main is mounted in some other position", async () => {
expect.assertions(3); expect.assertions(3);
let hookCounter = 0; let hookCounter = 0;
class ParentWidget extends Widget<WEnv> { class ParentWidget extends Widget<WEnv, {}> {
name = "a"; name = "a";
state = { ok: false }; state = { ok: false };
template = ` template = `
@@ -228,7 +228,7 @@ describe("lifecycle hooks", () => {
// patching algorithm // patching algorithm
widgets = { child: ChildWidget }; widgets = { child: ChildWidget };
} }
class ChildWidget extends Widget<WEnv> { class ChildWidget extends Widget<WEnv, {}> {
async willStart() { async willStart() {
hookCounter++; hookCounter++;
} }
@@ -253,14 +253,14 @@ describe("lifecycle hooks", () => {
expect.assertions(1); expect.assertions(1);
const target = document.createElement("div"); const target = document.createElement("div");
document.body.appendChild(target); document.body.appendChild(target);
class ParentWidget extends Widget<WEnv> { class ParentWidget extends Widget<WEnv, {}> {
name = "a"; name = "a";
mounted() { mounted() {
const child = new ChildWidget(this); const child = new ChildWidget(this);
child.mount(this.el!); child.mount(this.el!);
} }
} }
class ChildWidget extends Widget<WEnv> { class ChildWidget extends Widget<WEnv, {}> {
mounted() { mounted() {
expect(this.el).toBeTruthy(); expect(this.el).toBeTruthy();
done(); done();
@@ -272,7 +272,7 @@ describe("lifecycle hooks", () => {
test("widgets are unmounted and destroyed if no longer in DOM", async () => { test("widgets are unmounted and destroyed if no longer in DOM", async () => {
let steps: string[] = []; let steps: string[] = [];
class ParentWidget extends Widget<WEnv> { class ParentWidget extends Widget<WEnv, {}> {
name = "a"; name = "a";
state = { ok: true }; state = { ok: true };
template = ` template = `
@@ -282,7 +282,7 @@ describe("lifecycle hooks", () => {
widgets = { child: ChildWidget }; widgets = { child: ChildWidget };
} }
class ChildWidget extends Widget<WEnv> { class ChildWidget extends Widget<WEnv, {}> {
constructor(parent) { constructor(parent) {
super(parent); super(parent);
steps.push("init"); steps.push("init");
@@ -315,7 +315,7 @@ describe("lifecycle hooks", () => {
test("hooks are called in proper order in widget creation/destruction", async () => { test("hooks are called in proper order in widget creation/destruction", async () => {
let steps: string[] = []; let steps: string[] = [];
class ParentWidget extends Widget<WEnv> { class ParentWidget extends Widget<WEnv, {}> {
name = "a"; name = "a";
template = ` template = `
<div><t t-widget="child"/></div>`; <div><t t-widget="child"/></div>`;
@@ -338,7 +338,7 @@ describe("lifecycle hooks", () => {
} }
} }
class ChildWidget extends Widget<WEnv> { class ChildWidget extends Widget<WEnv, {}> {
constructor(parent) { constructor(parent) {
super(parent); super(parent);
steps.push("c init"); steps.push("c init");
@@ -375,7 +375,7 @@ describe("lifecycle hooks", () => {
test("shouldUpdate hook prevent rerendering", async () => { test("shouldUpdate hook prevent rerendering", async () => {
let shouldUpdate = false; let shouldUpdate = false;
class TestWidget extends Widget<WEnv> { class TestWidget extends Widget<WEnv, {}> {
name = "a"; name = "a";
template = `<div><t t-esc="props.val"/></div>`; template = `<div><t t-esc="props.val"/></div>`;
shouldUpdate() { shouldUpdate() {
@@ -406,7 +406,7 @@ describe("destroy method", () => {
test("destroying a widget twice only call destroyed once", async () => { test("destroying a widget twice only call destroyed once", async () => {
let count = 0; let count = 0;
class TestWidget extends Widget<WEnv> { class TestWidget extends Widget<WEnv, {}> {
destroyed() { destroyed() {
count++; count++;
} }
@@ -448,7 +448,7 @@ describe("destroy method", () => {
let p: Promise<void> = new Promise(function(r) { let p: Promise<void> = new Promise(function(r) {
resolve = r; resolve = r;
}); });
class DelayedWidget extends Widget<WEnv> { class DelayedWidget extends Widget<WEnv, {}> {
willStart() { willStart() {
return p; return p;
} }
@@ -488,7 +488,7 @@ describe("composition", () => {
}); });
test("t-refs on widget are widgets", async () => { test("t-refs on widget are widgets", async () => {
class WidgetC extends Widget<WEnv> { class WidgetC extends Widget<WEnv, {}> {
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 };
@@ -499,7 +499,7 @@ describe("composition", () => {
}); });
test("modifying a sub widget", async () => { test("modifying a sub widget", async () => {
class ParentWidget extends Widget<WEnv> { class ParentWidget extends Widget<WEnv, {}> {
template = `<div><t t-widget="Counter"/></div>`; template = `<div><t t-widget="Counter"/></div>`;
widgets = { Counter }; widgets = { Counter };
} }
@@ -537,7 +537,7 @@ describe("composition", () => {
}); });
test("rerendering a widget with a sub widget", async () => { test("rerendering a widget with a sub widget", async () => {
class ParentWidget extends Widget<WEnv> { class ParentWidget extends Widget<WEnv, {}> {
template = `<div><t t-widget="Counter"/></div>`; template = `<div><t t-widget="Counter"/></div>`;
widgets = { Counter }; widgets = { Counter };
} }
@@ -556,7 +556,7 @@ describe("composition", () => {
}); });
test("sub widgets are destroyed if no longer in dom, then recreated", async () => { test("sub widgets are destroyed if no longer in dom, then recreated", async () => {
class ParentWidget extends Widget<WEnv> { class ParentWidget extends Widget<WEnv, {}> {
name = "a"; name = "a";
state = { ok: true }; state = { ok: true };
template = ` template = `
@@ -582,14 +582,14 @@ describe("composition", () => {
describe("props evaluation (with t-props directive)", () => { describe("props evaluation (with t-props directive)", () => {
test("explicit object prop", async () => { test("explicit object prop", async () => {
class Parent extends Widget<WEnv> { class Parent extends Widget<WEnv, {}> {
name = "a"; name = "a";
template = `<div><t t-widget="child" t-props="{value: state.val}"/></div>`; template = `<div><t t-widget="child" t-props="{value: state.val}"/></div>`;
widgets = { child: Child }; widgets = { child: Child };
state = { val: 42 }; state = { val: 42 };
} }
class Child extends Widget<WEnv> { class Child extends Widget<WEnv, {}> {
template = `<span><t t-esc="state.someval"/></span>`; template = `<span><t t-esc="state.someval"/></span>`;
state: { someval: number }; state: { someval: number };
constructor(parent: Parent, props: { value: number }) { constructor(parent: Parent, props: { value: number }) {
@@ -604,14 +604,14 @@ describe("props evaluation (with t-props directive)", () => {
}); });
test("object prop value", async () => { test("object prop value", async () => {
class Parent extends Widget<WEnv> { class Parent extends Widget<WEnv, {}> {
name = "a"; name = "a";
template = `<div><t t-widget="child" t-props="state"/></div>`; template = `<div><t t-widget="child" t-props="state"/></div>`;
widgets = { child: Child }; widgets = { child: Child };
state = { val: 42 }; state = { val: 42 };
} }
class Child extends Widget<WEnv> { class Child extends Widget<WEnv, {}> {
template = `<span><t t-esc="state.someval"/></span>`; template = `<span><t t-esc="state.someval"/></span>`;
state: { someval: number }; state: { someval: number };
constructor(parent: Parent, props: { val: number }) { constructor(parent: Parent, props: { val: number }) {