mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] app: improve API, small refactoring
This commit is contained in:
committed by
Mathieu Duckerts-Antoine
parent
f4da50d350
commit
0b1c4dd4ef
+17
-28
@@ -2,7 +2,7 @@ import { Component } from "../component/component";
|
|||||||
import { ComponentNode } from "../component/component_node";
|
import { ComponentNode } from "../component/component_node";
|
||||||
import { MountOptions } from "../component/fibers";
|
import { MountOptions } from "../component/fibers";
|
||||||
import { Scheduler } from "../component/scheduler";
|
import { Scheduler } from "../component/scheduler";
|
||||||
import { TemplateSet } from "./template_set";
|
import { TemplateSet, TemplateSetConfig } from "./template_set";
|
||||||
import { nodeErrorHandlers } from "../component/error_handling";
|
import { nodeErrorHandlers } from "../component/error_handling";
|
||||||
|
|
||||||
// reimplement dev mode stuff see last change in 0f7a8289a6fb8387c3c1af41c6664b2a8448758f
|
// reimplement dev mode stuff see last change in 0f7a8289a6fb8387c3c1af41c6664b2a8448758f
|
||||||
@@ -11,12 +11,9 @@ export interface Env {
|
|||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AppConfig {
|
export interface AppConfig extends TemplateSetConfig {
|
||||||
dev?: boolean;
|
|
||||||
env?: Env;
|
env?: Env;
|
||||||
translatableAttributes?: string[];
|
props?: any;
|
||||||
translateFn?: (s: string) => string;
|
|
||||||
templates?: string | Document;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEV_MSG = `Owl is running in 'dev' mode.
|
export const DEV_MSG = `Owl is running in 'dev' mode.
|
||||||
@@ -27,35 +24,19 @@ See https://github.com/odoo/owl/blob/master/doc/reference/config.md#mode for mor
|
|||||||
export class App<T extends typeof Component = any> extends TemplateSet {
|
export class App<T extends typeof Component = any> extends TemplateSet {
|
||||||
Root: T;
|
Root: T;
|
||||||
props: any;
|
props: any;
|
||||||
env: Env = Object.freeze({});
|
env: Env;
|
||||||
scheduler = new Scheduler(window.requestAnimationFrame.bind(window));
|
scheduler = new Scheduler(window.requestAnimationFrame.bind(window));
|
||||||
root: ComponentNode | null = null;
|
root: ComponentNode | null = null;
|
||||||
|
|
||||||
constructor(Root: T, props?: any) {
|
constructor(Root: T, config: AppConfig = {}) {
|
||||||
super();
|
super(config);
|
||||||
this.Root = Root;
|
this.Root = Root;
|
||||||
this.props = props;
|
|
||||||
}
|
|
||||||
|
|
||||||
configure(config: AppConfig): App<T> {
|
|
||||||
if (config.dev) {
|
if (config.dev) {
|
||||||
this.dev = config.dev;
|
|
||||||
console.info(DEV_MSG);
|
console.info(DEV_MSG);
|
||||||
}
|
}
|
||||||
if (config.env) {
|
const descrs = Object.getOwnPropertyDescriptors(config.env || {});
|
||||||
const descrs = Object.getOwnPropertyDescriptors(config.env);
|
this.env = Object.freeze(Object.defineProperties({}, descrs));
|
||||||
this.env = Object.freeze(Object.defineProperties({}, descrs));
|
this.props = config.props || {};
|
||||||
}
|
|
||||||
if (config.translateFn) {
|
|
||||||
this.translateFn = config.translateFn;
|
|
||||||
}
|
|
||||||
if (config.translatableAttributes) {
|
|
||||||
this.translatableAttributes = config.translatableAttributes;
|
|
||||||
}
|
|
||||||
if (config.templates) {
|
|
||||||
this.addTemplates(config.templates);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mount(target: HTMLElement, options?: MountOptions): Promise<InstanceType<T>> {
|
mount(target: HTMLElement, options?: MountOptions): Promise<InstanceType<T>> {
|
||||||
@@ -114,3 +95,11 @@ export class App<T extends typeof Component = any> extends TemplateSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function mount<T extends typeof Component>(
|
||||||
|
C: T,
|
||||||
|
target: HTMLElement,
|
||||||
|
config: AppConfig & MountOptions = {}
|
||||||
|
): Promise<InstanceType<T>> {
|
||||||
|
return new App(C, config).mount(target, config);
|
||||||
|
}
|
||||||
|
|||||||
+20
-8
@@ -37,22 +37,34 @@ function parseXML(xml: string): Document {
|
|||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TemplateSetConfig {
|
||||||
|
dev?: boolean;
|
||||||
|
translatableAttributes?: string[];
|
||||||
|
translateFn?: (s: string) => string;
|
||||||
|
templates?: string | Document;
|
||||||
|
}
|
||||||
|
|
||||||
export class TemplateSet {
|
export class TemplateSet {
|
||||||
|
dev: boolean;
|
||||||
rawTemplates: typeof globalTemplates = Object.create(globalTemplates);
|
rawTemplates: typeof globalTemplates = Object.create(globalTemplates);
|
||||||
templates: { [name: string]: Template } = {};
|
templates: { [name: string]: Template } = {};
|
||||||
translateFn?: (s: string) => string;
|
translateFn?: (s: string) => string;
|
||||||
translatableAttributes?: string[];
|
translatableAttributes?: string[];
|
||||||
utils: typeof UTILS;
|
utils: typeof UTILS = Object.assign({}, UTILS, {
|
||||||
dev?: boolean;
|
call: (owner: any, subTemplate: string, ctx: any, parent: any) => {
|
||||||
|
|
||||||
constructor() {
|
|
||||||
const call = (owner: any, subTemplate: string, ctx: any, parent: any) => {
|
|
||||||
const template = this.getTemplate(subTemplate);
|
const template = this.getTemplate(subTemplate);
|
||||||
return toggler(subTemplate, template.call(owner, ctx, parent));
|
return toggler(subTemplate, template.call(owner, ctx, parent));
|
||||||
};
|
},
|
||||||
|
getTemplate: (name: string) => this.getTemplate(name),
|
||||||
|
});
|
||||||
|
|
||||||
const getTemplate = (name: string) => this.getTemplate(name);
|
constructor(config: TemplateSetConfig = {}) {
|
||||||
this.utils = Object.assign({}, UTILS, { getTemplate, call });
|
this.dev = config.dev || false;
|
||||||
|
this.translateFn = config.translateFn;
|
||||||
|
this.translatableAttributes = config.translatableAttributes;
|
||||||
|
if (config.templates) {
|
||||||
|
this.addTemplates(config.templates);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addTemplate(name: string, template: string | Node, options: { allowDuplicate?: boolean } = {}) {
|
addTemplate(name: string, template: string | Node, options: { allowDuplicate?: boolean } = {}) {
|
||||||
|
|||||||
@@ -13,6 +13,16 @@ import { handleError, fibersInError } from "./error_handling";
|
|||||||
import { applyDefaultProps } from "./props_validation";
|
import { applyDefaultProps } from "./props_validation";
|
||||||
import { STATUS } from "./status";
|
import { STATUS } from "./status";
|
||||||
|
|
||||||
|
let currentNode: ComponentNode | null = null;
|
||||||
|
|
||||||
|
export function getCurrent(): ComponentNode | null {
|
||||||
|
return currentNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useComponent(): Component {
|
||||||
|
return currentNode!.component;
|
||||||
|
}
|
||||||
|
|
||||||
export function component(
|
export function component(
|
||||||
name: string | typeof Component,
|
name: string | typeof Component,
|
||||||
props: any,
|
props: any,
|
||||||
@@ -62,12 +72,6 @@ export function component(
|
|||||||
// Component VNode
|
// Component VNode
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
let currentNode: ComponentNode | null = null;
|
|
||||||
|
|
||||||
export function getCurrent(): ComponentNode | null {
|
|
||||||
return currentNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
type LifecycleHook = Function;
|
type LifecycleHook = Function;
|
||||||
|
|
||||||
export class ComponentNode<T extends typeof Component = typeof Component>
|
export class ComponentNode<T extends typeof Component = typeof Component>
|
||||||
|
|||||||
+3
-22
@@ -32,27 +32,9 @@ export const blockDom = {
|
|||||||
comment,
|
comment,
|
||||||
};
|
};
|
||||||
|
|
||||||
import type { AppConfig } from "./app/app";
|
export { App, mount } from "./app/app";
|
||||||
import { App } from "./app/app";
|
export { Component } from "./component/component";
|
||||||
import { Component } from "./component/component";
|
export { useComponent } from "./component/component_node";
|
||||||
import { getCurrent } from "./component/component_node";
|
|
||||||
|
|
||||||
export { App, Component };
|
|
||||||
|
|
||||||
export async function mount<T extends typeof Component>(
|
|
||||||
C: T,
|
|
||||||
target: HTMLElement,
|
|
||||||
config: AppConfig = {}
|
|
||||||
): Promise<InstanceType<T>> {
|
|
||||||
const app = new App(C);
|
|
||||||
return app.configure(config).mount(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useComponent(): Component {
|
|
||||||
const current = getCurrent();
|
|
||||||
return current!.component;
|
|
||||||
}
|
|
||||||
|
|
||||||
export { status } from "./component/status";
|
export { status } from "./component/status";
|
||||||
export { Portal } from "./portal";
|
export { Portal } from "./portal";
|
||||||
export { Memo } from "./memo";
|
export { Memo } from "./memo";
|
||||||
@@ -60,7 +42,6 @@ export { xml } from "./app/template_set";
|
|||||||
export { useState, reactive } from "./reactivity";
|
export { useState, reactive } from "./reactivity";
|
||||||
export { useEffect, useEnv, useExternalListener, useRef, useSubEnv } from "./hooks";
|
export { useEffect, useEnv, useExternalListener, useRef, useSubEnv } from "./hooks";
|
||||||
export { EventBus, whenReady, loadFile, markup } from "./utils";
|
export { EventBus, whenReady, loadFile, markup } from "./utils";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
onWillStart,
|
onWillStart,
|
||||||
onMounted,
|
onMounted,
|
||||||
|
|||||||
+14
@@ -15,6 +15,20 @@ exports[`app App supports env with getters/setters 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`app can configure an app with props 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div><block-text-0/></div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let d1 = ctx['props'].value;
|
||||||
|
return block1([d1]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`app destroy remove the widget from the DOM 1`] = `
|
exports[`app destroy remove the widget from the DOM 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -40,8 +40,7 @@ describe("app", () => {
|
|||||||
static template = xml`<div><t t-esc="env.someVal" /> <t t-esc="Object.keys(env.services)" /></div>`;
|
static template = xml`<div><t t-esc="env.someVal" /> <t t-esc="Object.keys(env.services)" /></div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = new App(SomeComponent);
|
const app = new App(SomeComponent, { env });
|
||||||
app.configure({ env });
|
|
||||||
const comp = await app.mount(fixture);
|
const comp = await app.mount(fixture);
|
||||||
expect(fixture.innerHTML).toBe("<div>maggot serv1</div>");
|
expect(fixture.innerHTML).toBe("<div>maggot serv1</div>");
|
||||||
someVal = "brain";
|
someVal = "brain";
|
||||||
@@ -50,4 +49,14 @@ describe("app", () => {
|
|||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe("<div>brain serv1,serv2</div>");
|
expect(fixture.innerHTML).toBe("<div>brain serv1,serv2</div>");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("can configure an app with props", async () => {
|
||||||
|
class SomeComponent extends Component {
|
||||||
|
static template = xml`<div t-esc="props.value"/>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = new App(SomeComponent, { props: { value: 333 } });
|
||||||
|
await app.mount(fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("<div>333</div>");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -1060,6 +1060,19 @@ exports[`mount targets default mount option is 'last-child' 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`mount targets mount function: can mount a component (with default position='last-child') 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div>app</div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return block1();
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`support svg components add proper namespace to svg 1`] = `
|
exports[`support svg components add proper namespace to svg 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ describe("basics", () => {
|
|||||||
static template = xml`<span><t t-esc="props.value"/></span>`;
|
static template = xml`<span><t t-esc="props.value"/></span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = new App(Test, { value: 3 });
|
const app = new App(Test, { props: { value: 3 } });
|
||||||
const component = await app.mount(fixture);
|
const component = await app.mount(fixture);
|
||||||
|
|
||||||
expect(fixture.innerHTML).toBe("<span>3</span>");
|
expect(fixture.innerHTML).toBe("<span>3</span>");
|
||||||
@@ -125,7 +125,7 @@ describe("basics", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = new App(Test, p);
|
const app = new App(Test, { props: p });
|
||||||
await app.mount(fixture);
|
await app.mount(fixture);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -819,6 +819,16 @@ describe("mount targets", () => {
|
|||||||
expect(fixture.innerHTML).toBe("<span></span><div>app</div>");
|
expect(fixture.innerHTML).toBe("<span></span><div>app</div>");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("mount function: can mount a component (with default position='last-child')", async () => {
|
||||||
|
class Root extends Component {
|
||||||
|
static template = xml`<div>app</div>`;
|
||||||
|
}
|
||||||
|
const span = document.createElement("span");
|
||||||
|
fixture.appendChild(span);
|
||||||
|
await mount(Root, fixture, { position: "last-child" });
|
||||||
|
expect(fixture.innerHTML).toBe("<span></span><div>app</div>");
|
||||||
|
});
|
||||||
|
|
||||||
test("default mount option is 'last-child'", async () => {
|
test("default mount option is 'last-child'", async () => {
|
||||||
class Root extends Component {
|
class Root extends Component {
|
||||||
static template = xml`<div>app</div>`;
|
static template = xml`<div>app</div>`;
|
||||||
|
|||||||
@@ -623,7 +623,7 @@ test("rendering component again in next microtick", async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const env = { config: { flag: false } };
|
const env = { config: { flag: false } };
|
||||||
await new App(Parent).configure({ env }).mount(fixture);
|
await mount(Parent, fixture, { env });
|
||||||
expect(fixture.innerHTML).toBe("<div><button>Click</button></div>");
|
expect(fixture.innerHTML).toBe("<div><button>Click</button></div>");
|
||||||
expect([
|
expect([
|
||||||
"Parent:setup",
|
"Parent:setup",
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ describe("env handling", () => {
|
|||||||
class Test extends Component {
|
class Test extends Component {
|
||||||
static template = xml`<div/>`;
|
static template = xml`<div/>`;
|
||||||
}
|
}
|
||||||
const component = await new App(Test).configure({ env }).mount(fixture);
|
const component = await new App(Test, { env }).mount(fixture);
|
||||||
expect(Object.isFrozen(component.env)).toBeTruthy();
|
expect(Object.isFrozen(component.env)).toBeTruthy();
|
||||||
expect(component.env).toEqual({ foo: 42, bar: { value: 42 } });
|
expect(component.env).toEqual({ foo: 42, bar: { value: 42 } });
|
||||||
expect(() => {
|
expect(() => {
|
||||||
@@ -47,7 +47,7 @@ describe("env handling", () => {
|
|||||||
static components = { Child };
|
static components = { Child };
|
||||||
}
|
}
|
||||||
|
|
||||||
await new App(Test).configure({ env }).mount(fixture);
|
await new App(Test, { env }).mount(fixture);
|
||||||
expect(child.env).toEqual(env);
|
expect(child.env).toEqual(env);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { App, Component, mount, useState, xml } from "../../src";
|
import { Component, mount, useState, xml } from "../../src";
|
||||||
import { makeTestFixture, nextTick, snapshotEverything } from "../helpers";
|
import { makeTestFixture, nextTick, snapshotEverything } from "../helpers";
|
||||||
|
|
||||||
let fixture: HTMLElement;
|
let fixture: HTMLElement;
|
||||||
@@ -64,7 +64,7 @@ describe("basics", () => {
|
|||||||
static components = { Child, OtherChild };
|
static components = { Child, OtherChild };
|
||||||
}
|
}
|
||||||
const env = { options: { flag: true } };
|
const env = { options: { flag: true } };
|
||||||
const parent = await new App(Parent).configure({ env }).mount(fixture);
|
const parent = await mount(Parent, fixture, { env });
|
||||||
expect(fixture.innerHTML).toBe("<span>CHILD 1</span>");
|
expect(fixture.innerHTML).toBe("<span>CHILD 1</span>");
|
||||||
|
|
||||||
env.options.flag = false;
|
env.options.flag = false;
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ describe("hooks", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const env = { val: 1 };
|
const env = { val: 1 };
|
||||||
await new App(Test).configure({ env }).mount(fixture);
|
await mount(Test, fixture, { env });
|
||||||
expect(fixture.innerHTML).toBe("<div>1</div>");
|
expect(fixture.innerHTML).toBe("<div>1</div>");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -188,7 +188,7 @@ describe("hooks", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const env = { val: 3 };
|
const env = { val: 3 };
|
||||||
const component = await new App(Test).configure({ env }).mount(fixture);
|
const component = await mount(Test, fixture, { env });
|
||||||
expect(fixture.innerHTML).toBe("<div>3</div>");
|
expect(fixture.innerHTML).toBe("<div>3</div>");
|
||||||
expect(component.env).not.toHaveProperty("val2");
|
expect(component.env).not.toHaveProperty("val2");
|
||||||
expect(component.env).toHaveProperty("val");
|
expect(component.env).toHaveProperty("val");
|
||||||
@@ -219,7 +219,7 @@ describe("hooks", () => {
|
|||||||
return someVal;
|
return someVal;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const component = await new App(Test).configure({ env }).mount(fixture);
|
const component = await mount(Test, fixture, { env });
|
||||||
expect(fixture.innerHTML).toBe("<div>maggot brain</div>");
|
expect(fixture.innerHTML).toBe("<div>maggot brain</div>");
|
||||||
someVal = "brain";
|
someVal = "brain";
|
||||||
someVal2 = "maggot";
|
someVal2 = "maggot";
|
||||||
@@ -252,7 +252,7 @@ describe("hooks", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const env = { val: 3 };
|
const env = { val: 3 };
|
||||||
await new App(Parent).configure({ env }).mount(fixture);
|
await mount(Parent, fixture, { env });
|
||||||
expect(fixture.innerHTML).toBe("3<div>5</div>");
|
expect(fixture.innerHTML).toBe("3<div>5</div>");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
App,
|
|
||||||
Component,
|
Component,
|
||||||
mount,
|
mount,
|
||||||
onError,
|
onError,
|
||||||
@@ -432,10 +431,8 @@ describe("Portal", () => {
|
|||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
const env = {};
|
const env = {};
|
||||||
const app = new App(Parent);
|
|
||||||
app.configure({ env });
|
|
||||||
addOutsideDiv(fixture);
|
addOutsideDiv(fixture);
|
||||||
const parent = await mount(Parent, fixture);
|
const parent = await mount(Parent, fixture, { env });
|
||||||
expect(parent.env).toStrictEqual({});
|
expect(parent.env).toStrictEqual({});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -529,10 +526,8 @@ describe("Portal: Props validation", () => {
|
|||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
let error: Error;
|
let error: Error;
|
||||||
let app = new App(Parent);
|
|
||||||
app.configure({ dev: true });
|
|
||||||
try {
|
try {
|
||||||
await app.mount(fixture);
|
await mount(Parent, fixture, { dev: true });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = e as Error;
|
error = e as Error;
|
||||||
}
|
}
|
||||||
@@ -555,10 +550,8 @@ describe("Portal: Props validation", () => {
|
|||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
let error: Error;
|
let error: Error;
|
||||||
let app = new App(Parent);
|
|
||||||
app.configure({ dev: true });
|
|
||||||
try {
|
try {
|
||||||
await app.mount(fixture);
|
await mount(Parent, fixture, { dev: true });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = e as Error;
|
error = e as Error;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user