mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
+10
-8
@@ -43,7 +43,6 @@ Let us have a look at a simple component:
|
|||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
class ClickCounter extends owl.Component {
|
class ClickCounter extends owl.Component {
|
||||||
template = "click-counter";
|
|
||||||
state = { value: 0 };
|
state = { value: 0 };
|
||||||
|
|
||||||
increment() {
|
increment() {
|
||||||
@@ -53,7 +52,7 @@ class ClickCounter extends owl.Component {
|
|||||||
```
|
```
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<button t-name="click-counter" t-on-click="increment">
|
<button t-name="ClickCounter" t-on-click="increment">
|
||||||
Click Me! [<t t-esc="state.value"/>]
|
Click Me! [<t t-esc="state.value"/>]
|
||||||
</button>
|
</button>
|
||||||
```
|
```
|
||||||
@@ -62,7 +61,8 @@ Note that this code is written in ESNext style, so it will only run on the
|
|||||||
latest browsers without a transpilation step.
|
latest browsers without a transpilation step.
|
||||||
|
|
||||||
This example show how a component should be defined: it simply subclasses the
|
This example show how a component should be defined: it simply subclasses the
|
||||||
Component class. It needs to define a template (`click-counter` here). Also,
|
Component class. If no `template` key (or `inlineTemplate`), is defined, then
|
||||||
|
Owl will use the component's name as template name. Here,
|
||||||
a state object is defined. It is not mandatory to use the state object, but it
|
a state object is defined. It is not mandatory to use the state object, but it
|
||||||
is certainly encouraged. The state object is [observed](observer.md), and any
|
is certainly encouraged. The state object is [observed](observer.md), and any
|
||||||
change to it will cause a rerendering.
|
change to it will cause a rerendering.
|
||||||
@@ -75,7 +75,7 @@ templates are standard [QWeb](qweb.md) templates, but with an extra directive:
|
|||||||
widgets:
|
widgets:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<div t-name="parent">
|
<div t-name="ParentWidget">
|
||||||
<span>some text</span>
|
<span>some text</span>
|
||||||
<t t-widget="MyWidget" t-props="{info: 13}"/>
|
<t t-widget="MyWidget" t-props="{info: 13}"/>
|
||||||
</div>
|
</div>
|
||||||
@@ -83,7 +83,6 @@ widgets:
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
class ParentWidget extends owl.Component {
|
class ParentWidget extends owl.Component {
|
||||||
template = 'parent';
|
|
||||||
widgets = { MyWidget: MyWidget};
|
widgets = { MyWidget: MyWidget};
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
@@ -101,7 +100,7 @@ additional css classes or style for the sub widget: css declared in `class`, `st
|
|||||||
root widget element.
|
root widget element.
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<div t-name="parent">
|
<div t-name="ParentWidget">
|
||||||
<t t-widget="MyWidget" class="someClass" style="font-weight:bold;" t-props="{info: 13}"/>
|
<t t-widget="MyWidget" class="someClass" style="font-weight:bold;" t-props="{info: 13}"/>
|
||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
@@ -124,6 +123,10 @@ It exists in the context of an environment (`env`), which is propagated from a
|
|||||||
parent to its children. The environment needs to have a QWeb instance, which
|
parent to its children. The environment needs to have a QWeb instance, which
|
||||||
will be used to render the component template.
|
will be used to render the component template.
|
||||||
|
|
||||||
|
Be aware that the name of the component may be significant: if a component does
|
||||||
|
not define a `template` or `inlineTemplate` key, then Owl will lookup in QWeb to
|
||||||
|
find a template with the component name (or one of its ancestor).
|
||||||
|
|
||||||
### Properties
|
### Properties
|
||||||
|
|
||||||
- **`el`** (HTMLElement | null): reference to the DOM root node of the element. It is `null` when the
|
- **`el`** (HTMLElement | null): reference to the DOM root node of the element. It is `null` when the
|
||||||
@@ -131,7 +134,7 @@ will be used to render the component template.
|
|||||||
|
|
||||||
- **`env`** (Object): the component environment, which contains a QWeb instance.
|
- **`env`** (Object): the component environment, which contains a QWeb instance.
|
||||||
|
|
||||||
- **`template`** (string): a string, which is the name of the QWeb template that will render
|
- **`template`** (string, optional): if given, this is the name of the QWeb template that will render
|
||||||
the component.
|
the component.
|
||||||
|
|
||||||
- **`inlineTemplate`** (string, optional): a string that represents a xml template. If set,
|
- **`inlineTemplate`** (string, optional): a string that represents a xml template. If set,
|
||||||
@@ -236,7 +239,6 @@ implemented in most cases:
|
|||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
class ClickCounter extends owl.Component {
|
class ClickCounter extends owl.Component {
|
||||||
template = "click-counter";
|
|
||||||
state = { value: 0 };
|
state = { value: 0 };
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|||||||
+49
-8
@@ -43,6 +43,12 @@ export interface Meta<T extends Env, Props> {
|
|||||||
mountedHandlers: { [key: number]: Function };
|
mountedHandlers: { [key: number]: Function };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If a component does not define explicitely a template (or inlineTemplate)
|
||||||
|
// key, it needs to find a template with its name (or a parent's). This is
|
||||||
|
// qweb dependant, so we need a place to store this information indexed by
|
||||||
|
// qweb instances.
|
||||||
|
const TEMPLATE_MAP: { [key: number]: { [name: string]: string } } = {};
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Widget
|
// Widget
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -54,8 +60,8 @@ export class Component<
|
|||||||
State extends {}
|
State extends {}
|
||||||
> extends EventBus {
|
> extends EventBus {
|
||||||
readonly __owl__: Meta<Env, Props>;
|
readonly __owl__: Meta<Env, Props>;
|
||||||
template: string = "default";
|
template?: string;
|
||||||
inlineTemplate: string | null = null;
|
inlineTemplate?: string;
|
||||||
|
|
||||||
get el(): HTMLElement | null {
|
get el(): HTMLElement | null {
|
||||||
return this.__owl__.vnode ? (<any>this).__owl__.vnode.elm : null;
|
return this.__owl__.vnode ? (<any>this).__owl__.vnode.elm : null;
|
||||||
@@ -379,13 +385,48 @@ export class Component<
|
|||||||
return Promise.resolve(h("div"));
|
return Promise.resolve(h("div"));
|
||||||
}
|
}
|
||||||
this.__owl__.isStarted = true;
|
this.__owl__.isStarted = true;
|
||||||
if (this.inlineTemplate) {
|
|
||||||
this.env.qweb.addTemplate(this.inlineTemplate, this.inlineTemplate, true);
|
const qweb = this.env.qweb;
|
||||||
|
if (!this.template) {
|
||||||
|
if (this.inlineTemplate) {
|
||||||
|
this.env.qweb.addTemplate(
|
||||||
|
this.inlineTemplate,
|
||||||
|
this.inlineTemplate,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
// we write on the proto, so any new component of this class will get
|
||||||
|
// automatically the template key properly setup.
|
||||||
|
(<any>this).__proto__.template = this.inlineTemplate;
|
||||||
|
} else {
|
||||||
|
let tmap = TEMPLATE_MAP[qweb.id];
|
||||||
|
if (!tmap) {
|
||||||
|
tmap = {};
|
||||||
|
TEMPLATE_MAP[qweb.id] = tmap;
|
||||||
|
}
|
||||||
|
let p = (<any>this).constructor;
|
||||||
|
let name: string = p.name;
|
||||||
|
let template = tmap[name];
|
||||||
|
if (template) {
|
||||||
|
this.template = template;
|
||||||
|
} else {
|
||||||
|
while (
|
||||||
|
(template = p.name) &&
|
||||||
|
!(template in qweb.templates) &&
|
||||||
|
p !== Component
|
||||||
|
) {
|
||||||
|
p = p.__proto__;
|
||||||
|
}
|
||||||
|
if (p === Component) {
|
||||||
|
this.template = "default";
|
||||||
|
} else {
|
||||||
|
tmap[name] = template;
|
||||||
|
this.template = template;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.__owl__.render = this.env.qweb.render.bind(
|
this.__owl__.render = qweb.render.bind(qweb, this.template);
|
||||||
this.env.qweb,
|
|
||||||
this.inlineTemplate || this.template
|
|
||||||
);
|
|
||||||
this._observeState();
|
this._observeState();
|
||||||
return this._render();
|
return this._render();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,6 +118,8 @@ function parseXML(xml: string): Document {
|
|||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let nextID = 1;
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// QWeb rendering engine
|
// QWeb rendering engine
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -125,6 +127,11 @@ export class QWeb {
|
|||||||
templates: { [name: string]: Template } = {};
|
templates: { [name: string]: Template } = {};
|
||||||
utils = UTILS;
|
utils = UTILS;
|
||||||
|
|
||||||
|
// the id field is useful to be able to hash qweb instances. The current
|
||||||
|
// use case is that component's templates are qweb dependant, and need to be
|
||||||
|
// able to map a qweb instance to a template name.
|
||||||
|
id = nextID++;
|
||||||
|
|
||||||
constructor(data?: string) {
|
constructor(data?: string) {
|
||||||
if (data) {
|
if (data) {
|
||||||
this.loadTemplates(data);
|
this.loadTemplates(data);
|
||||||
|
|||||||
@@ -2117,3 +2117,57 @@ describe("t-mounted directive", () => {
|
|||||||
expect(widget.f).toHaveBeenCalledTimes(1);
|
expect(widget.f).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("can deduce template from name", () => {
|
||||||
|
test("can find template if name of component", async () => {
|
||||||
|
class ABC extends Widget {}
|
||||||
|
env.qweb.addTemplate("ABC", "<span>Orval</span>");
|
||||||
|
const abc = new ABC(env);
|
||||||
|
await abc.mount(fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("<span>Orval</span>");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("can find template of parent component", async () => {
|
||||||
|
class ABC extends Widget {}
|
||||||
|
class DEF extends ABC {}
|
||||||
|
env.qweb.addTemplate("ABC", "<span>Orval</span>");
|
||||||
|
const def = new DEF(env);
|
||||||
|
await def.mount(fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("<span>Orval</span>");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("can find template of parent component, defined by template key", async () => {
|
||||||
|
class ABC extends Widget {
|
||||||
|
template = "Achel";
|
||||||
|
}
|
||||||
|
class DEF extends ABC {}
|
||||||
|
env.qweb.addTemplate("Achel", "<span>Orval</span>");
|
||||||
|
const def = new DEF(env);
|
||||||
|
await def.mount(fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("<span>Orval</span>");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("can find template of parent component, defined by inlinetemplate key", async () => {
|
||||||
|
class ABC extends Widget {
|
||||||
|
inlineTemplate = "<span>Orval</span>";
|
||||||
|
}
|
||||||
|
class DEF extends ABC {}
|
||||||
|
const def = new DEF(env);
|
||||||
|
await def.mount(fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("<span>Orval</span>");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("templates are found in proper qweb instance", async () => {
|
||||||
|
const env2 = makeTestWEnv();
|
||||||
|
env.qweb.addTemplate("ABC", "<span>Rochefort 8</span>");
|
||||||
|
env2.qweb.addTemplate("ABC", "<span>Rochefort 10</span>");
|
||||||
|
class ABC extends Widget {}
|
||||||
|
const abc = new ABC(env);
|
||||||
|
await abc.mount(fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("<span>Rochefort 8</span>");
|
||||||
|
abc.destroy();
|
||||||
|
const abc2 = new ABC(env2);
|
||||||
|
await abc2.mount(fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("<span>Rochefort 10</span>");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user