mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
+14
-26
@@ -11,7 +11,8 @@ import "./props_validation";
|
||||
* contains:
|
||||
*
|
||||
* - the Env interface (generic type for the environment)
|
||||
* - the Meta interface (the owl specific metadata attached to a component)
|
||||
* - the Fiber interface (owl metadata attached to a rendering)
|
||||
* - the Internal interface (the owl specific metadata attached to a component)
|
||||
* - the Component class
|
||||
*/
|
||||
|
||||
@@ -46,7 +47,7 @@ export interface Fiber<Props> {
|
||||
scope: any;
|
||||
vars: any;
|
||||
patchQueue: Fiber<any>[];
|
||||
component: Component<any, any, any>;
|
||||
component: Component<any, any>;
|
||||
vnode: VNode | null;
|
||||
willPatchResult: any;
|
||||
props: Props;
|
||||
@@ -70,8 +71,8 @@ interface Internal<T extends Env, Props> {
|
||||
|
||||
// parent and children keys are obviously useful to setup the parent-children
|
||||
// relationship.
|
||||
parent: Component<T, any, any> | null;
|
||||
children: { [key: number]: Component<T, any, any> };
|
||||
parent: Component<T, any> | null;
|
||||
children: { [key: number]: Component<T, any> };
|
||||
// children mapping: from templateID to componentID. templateID identifies a
|
||||
// place in a template. The t-component directive needs it to be able to get
|
||||
// the component instance back whenever the template is rerendered.
|
||||
@@ -91,10 +92,11 @@ interface Internal<T extends Env, Props> {
|
||||
//------------------------------------------------------------------------------
|
||||
let nextId = 1;
|
||||
|
||||
export class Component<T extends Env, Props extends {}, State extends {}> {
|
||||
export class Component<T extends Env, Props extends {}> {
|
||||
readonly __owl__: Internal<Env, Props>;
|
||||
static template?: string | null = null;
|
||||
static _template?: string | null = null;
|
||||
static _current?: any | null = null;
|
||||
|
||||
/**
|
||||
* The `el` is the root element of the component. Note that it could be null:
|
||||
@@ -106,7 +108,6 @@ export class Component<T extends Env, Props extends {}, State extends {}> {
|
||||
static components = {};
|
||||
|
||||
env: T;
|
||||
state?: State;
|
||||
props: Props;
|
||||
|
||||
// type of props is not easily representable in typescript...
|
||||
@@ -114,7 +115,7 @@ export class Component<T extends Env, Props extends {}, State extends {}> {
|
||||
static defaultProps?: any;
|
||||
|
||||
refs: {
|
||||
[key: string]: Component<T, any, any> | HTMLElement | undefined;
|
||||
[key: string]: Component<T, any> | HTMLElement | undefined;
|
||||
} = {};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -140,8 +141,9 @@ export class Component<T extends Env, Props extends {}, State extends {}> {
|
||||
* hand. Other components should be created automatically by the framework (with
|
||||
* the t-component directive in a template)
|
||||
*/
|
||||
constructor(parent: Component<T, any, any> | T, props?: Props) {
|
||||
constructor(parent: Component<T, any> | T, props?: Props) {
|
||||
const defaultProps = (<any>this.constructor).defaultProps;
|
||||
Component._current = this;
|
||||
if (defaultProps) {
|
||||
props = this.__applyDefaultProps(props, defaultProps);
|
||||
}
|
||||
@@ -151,7 +153,7 @@ export class Component<T extends Env, Props extends {}, State extends {}> {
|
||||
// Pro: but creating component (by a template) is always unsafe anyway
|
||||
this.props = <Props>props || <Props>{};
|
||||
let id: number = nextId++;
|
||||
let p: Component<T, any, any> | null = null;
|
||||
let p: Component<T, any> | null = null;
|
||||
if (parent instanceof Component) {
|
||||
p = parent;
|
||||
this.env = parent.env;
|
||||
@@ -452,7 +454,7 @@ export class Component<T extends Env, Props extends {}, State extends {}> {
|
||||
* Note that it does not call the __callWillUnmount method to avoid visiting
|
||||
* all children many times.
|
||||
*/
|
||||
__destroy(parent: Component<any, any, any> | null) {
|
||||
__destroy(parent: Component<any, any> | null) {
|
||||
const __owl__ = this.__owl__;
|
||||
const isMounted = __owl__.isMounted;
|
||||
if (isMounted) {
|
||||
@@ -586,7 +588,6 @@ export class Component<T extends Env, Props extends {}, State extends {}> {
|
||||
}
|
||||
}
|
||||
__owl__.render = qweb.render.bind(qweb, p._template);
|
||||
this.__observeState();
|
||||
return this.__render(fiber);
|
||||
}
|
||||
|
||||
@@ -656,19 +657,6 @@ export class Component<T extends Env, Props extends {}, State extends {}> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the observe feature on the state. We only create an observer if
|
||||
* there is some state to be observed.
|
||||
*/
|
||||
__observeState() {
|
||||
if (this.state) {
|
||||
const __owl__ = this.__owl__;
|
||||
__owl__.observer = new Observer();
|
||||
this.state = __owl__.observer.observe(this.state);
|
||||
__owl__.observer.notifyCB = this.render.bind(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply default props (only top level).
|
||||
*
|
||||
@@ -693,7 +681,7 @@ export class Component<T extends Env, Props extends {}, State extends {}> {
|
||||
*/
|
||||
__applyPatchQueue(fiber: Fiber<Props>) {
|
||||
const patchQueue = fiber.patchQueue;
|
||||
let component: Component<any, any, any> = this;
|
||||
let component: Component<any, any> = this;
|
||||
try {
|
||||
const patchLen = patchQueue.length;
|
||||
for (let i = 0; i < patchLen; i++) {
|
||||
@@ -729,7 +717,7 @@ export class Component<T extends Env, Props extends {}, State extends {}> {
|
||||
* If there are no such component, we destroy everything. This is better than
|
||||
* being in a corrupted state.
|
||||
*/
|
||||
function errorHandler(error: Error, component: Component<any, any, any>) {
|
||||
function errorHandler(error: Error, component: Component<any, any>) {
|
||||
let canCatch = false;
|
||||
let qweb = component.env.qweb;
|
||||
let root = component;
|
||||
|
||||
@@ -33,7 +33,7 @@ export class Observer {
|
||||
}
|
||||
}
|
||||
|
||||
observe(value: any, parent?: any): any {
|
||||
observe<T>(value: T, parent?: any): T {
|
||||
if (value === null || typeof value !== "object" || value instanceof Date) {
|
||||
// fun fact: typeof null === 'object'
|
||||
return value;
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Component } from "./component/component";
|
||||
import { Observer } from "./core/observer";
|
||||
|
||||
/**
|
||||
* Owl Hook System
|
||||
*
|
||||
* This file introduces the concept of hooks, similar to React or Vue hooks.
|
||||
* We have currently an implementation of:
|
||||
* - useState (reactive state)
|
||||
* - onMounted
|
||||
* - onWillUnmount
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* useState hook
|
||||
*
|
||||
* This is the main way a component can be made reactive. The useState hook
|
||||
* will return an observed object (or array). Changes to that value will then
|
||||
* trigger a rerendering of the current component.
|
||||
*/
|
||||
export function useState<T>(state: T): T {
|
||||
const component: Component<any,any> = Component._current;
|
||||
const __owl__ = component.__owl__;
|
||||
if (!__owl__.observer) {
|
||||
__owl__.observer = new Observer();
|
||||
__owl__.observer.notifyCB = component.render.bind(component);
|
||||
}
|
||||
return __owl__.observer.observe(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mounted hook. The callback will be called when the current component is
|
||||
* mounted. Note that the component mounted method is called first.
|
||||
*/
|
||||
export function onMounted(cb) {
|
||||
const component = Component._current;
|
||||
const current = component.mounted;
|
||||
component.mounted = function() {
|
||||
current.call(component);
|
||||
cb();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* willUnmount hook. The callback will be called when the current component is
|
||||
* willUnmounted. Note that the component mounted method is called last.
|
||||
*/
|
||||
export function onWillUnmount(cb) {
|
||||
const component = Component._current;
|
||||
const current = component.willUnmount;
|
||||
component.willUnmount = function() {
|
||||
cb();
|
||||
current.call(component);
|
||||
};
|
||||
}
|
||||
@@ -11,17 +11,21 @@ import { ConnectedComponent } from "./store/connected_component";
|
||||
import { Store } from "./store/store";
|
||||
import * as _utils from "./utils";
|
||||
import * as _tags from "./tags";
|
||||
import * as _hooks from "./hooks";
|
||||
import { Link } from "./router/Link";
|
||||
import { RouteComponent } from "./router/RouteComponent";
|
||||
import { Router } from "./router/Router";
|
||||
|
||||
export { Component } from "./component/component";
|
||||
export { QWeb };
|
||||
|
||||
export const useState = _hooks.useState;
|
||||
export const core = { EventBus, Observer };
|
||||
export const router = { Router, RouteComponent, Link };
|
||||
export const store = { Store, ConnectedComponent };
|
||||
export const utils = _utils;
|
||||
export const tags = _tags;
|
||||
export const hooks = _hooks;
|
||||
export const __info__ = {};
|
||||
|
||||
Object.defineProperty(__info__, "mode", {
|
||||
|
||||
@@ -254,7 +254,7 @@ QWeb.addDirective({
|
||||
const type = node.getAttribute("type");
|
||||
let handler;
|
||||
let event = fullName.includes(".lazy") ? "change" : "input";
|
||||
const expr = ctx.formatExpression(`state.${value}`);
|
||||
const expr = ctx.formatExpression(value);
|
||||
if (node.tagName === "select") {
|
||||
ctx.addLine(`p${nodeID}.props = {value: ${expr}};`);
|
||||
addNodeHook("create", `n.elm.value=${expr};`);
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ import { Destination, RouterEnv } from "./Router";
|
||||
|
||||
type Props = Destination;
|
||||
|
||||
export class Link<Env extends RouterEnv> extends Component<Env, Props, {}> {
|
||||
export class Link<Env extends RouterEnv> extends Component<Env, Props> {
|
||||
static template = xml`
|
||||
<a t-att-class="{'router-link-active': isActive }"
|
||||
t-att-href="href"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component } from "../component/component";
|
||||
import { xml } from "../tags";
|
||||
|
||||
export class RouteComponent extends Component<any, {}, {}> {
|
||||
export class RouteComponent extends Component<any, {}> {
|
||||
static template = xml`
|
||||
<t>
|
||||
<t
|
||||
|
||||
@@ -7,7 +7,7 @@ import { VNode } from "../vdom/index";
|
||||
|
||||
type HashFunction = (a: any, b: any) => number;
|
||||
|
||||
export class ConnectedComponent<T extends Env, P, S> extends Component<T, P, S> {
|
||||
export class ConnectedComponent<T extends Env, P> extends Component<T, P> {
|
||||
deep: boolean = true;
|
||||
getStore(env) {
|
||||
return env.store;
|
||||
|
||||
Reference in New Issue
Block a user