mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
@@ -36,11 +36,6 @@ Here is a short example to illustrate interactive widgets:
|
|||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
class ClickCounter extends owl.Component {
|
class ClickCounter extends owl.Component {
|
||||||
inlineTemplate = `
|
|
||||||
<button t-on-click="increment">
|
|
||||||
Click Me! [<t t-esc="state.value"/>]
|
|
||||||
</button>`;
|
|
||||||
|
|
||||||
state = { value: 0 };
|
state = { value: 0 };
|
||||||
|
|
||||||
increment() {
|
increment() {
|
||||||
@@ -48,7 +43,12 @@ class ClickCounter extends owl.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const qweb = new owl.QWeb();
|
const TEMPLATES = `
|
||||||
|
<button t-name="ClickCounter" t-on-click="increment">
|
||||||
|
Click Me! [<t t-esc="state.value"/>]
|
||||||
|
</button>`;
|
||||||
|
|
||||||
|
const qweb = new owl.QWeb(TEMPLATES);
|
||||||
const counter = new ClickCounter({ qweb });
|
const counter = new ClickCounter({ qweb });
|
||||||
counter.mount(document.body);
|
counter.mount(document.body);
|
||||||
```
|
```
|
||||||
|
|||||||
+4
-7
@@ -30,7 +30,7 @@ OWL components are the building blocks for user interface. They are designed to
|
|||||||
and follow the QWeb specification. This is a requirement for Odoo.
|
and follow the QWeb specification. This is a requirement for Odoo.
|
||||||
|
|
||||||
OWL components are defined as a subclass of Component. The rendering is
|
OWL components are defined as a subclass of Component. The rendering is
|
||||||
exclusively done by a [QWeb](qweb.md) template (either defined inline or preloaded in QWeb).
|
exclusively done by a [QWeb](qweb.md) template (which needs to be preloaded in QWeb).
|
||||||
Rendering a component generates a virtual dom representation
|
Rendering a component generates a virtual dom representation
|
||||||
of the widget, which is then patched to the DOM, in order to apply the changes in an efficient way.
|
of the widget, which is then patched to the DOM, in order to apply the changes in an efficient way.
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ 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. If no `template` key (or `inlineTemplate`), is defined, then
|
Component class. If no `template` key is defined, then
|
||||||
Owl will use the component's name as template name. Here,
|
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
|
||||||
@@ -112,7 +112,7 @@ between a valid css class added by the component, or some custom code, and a
|
|||||||
class that need to be removed. This is why we only support the explicit syntax
|
class that need to be removed. This is why we only support the explicit syntax
|
||||||
with a class object:
|
with a class object:
|
||||||
|
|
||||||
```js
|
```xml
|
||||||
<t t-widget="MyWidget" t-att-class="{a: state.flagA, b: state.flagB}" />
|
<t t-widget="MyWidget" t-att-class="{a: state.flagA, b: state.flagB}" />
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@ 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
|
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
|
not define a `template` key, then Owl will lookup in QWeb to
|
||||||
find a template with the component name (or one of its ancestor).
|
find a template with the component name (or one of its ancestor).
|
||||||
|
|
||||||
### Properties
|
### Properties
|
||||||
@@ -137,9 +137,6 @@ find a template with the component name (or one of its ancestor).
|
|||||||
- **`template`** (string, optional): if given, this 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,
|
|
||||||
this will be loaded into QWeb and used instead of the `template` property.
|
|
||||||
|
|
||||||
- **`state`** (Object): this is the location of the component's state, if there is
|
- **`state`** (Object): this is the location of the component's state, if there is
|
||||||
any. After the willStart method, the `state` property is observed, and each
|
any. After the willStart method, the `state` property is observed, and each
|
||||||
change will cause the widget to rerender itself.
|
change will cause the widget to rerender itself.
|
||||||
|
|||||||
+1
-14
@@ -43,7 +43,7 @@ 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)
|
// If a component does not define explicitely a template
|
||||||
// key, it needs to find a template with its name (or a parent's). This is
|
// 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 dependant, so we need a place to store this information indexed by
|
||||||
// qweb instances.
|
// qweb instances.
|
||||||
@@ -61,7 +61,6 @@ export class Component<
|
|||||||
> extends EventBus {
|
> extends EventBus {
|
||||||
readonly __owl__: Meta<Env, Props>;
|
readonly __owl__: Meta<Env, Props>;
|
||||||
template?: string;
|
template?: string;
|
||||||
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;
|
||||||
@@ -388,17 +387,6 @@ export class Component<
|
|||||||
|
|
||||||
const qweb = this.env.qweb;
|
const qweb = this.env.qweb;
|
||||||
if (!this.template) {
|
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];
|
let tmap = TEMPLATE_MAP[qweb.id];
|
||||||
if (!tmap) {
|
if (!tmap) {
|
||||||
tmap = {};
|
tmap = {};
|
||||||
@@ -425,7 +413,6 @@ export class Component<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
this.__owl__.render = qweb.render.bind(qweb, this.template);
|
this.__owl__.render = qweb.render.bind(qweb, this.template);
|
||||||
this._observeState();
|
this._observeState();
|
||||||
return this._render();
|
return this._render();
|
||||||
|
|||||||
+1
-8
@@ -152,14 +152,7 @@ export class QWeb {
|
|||||||
* Add a template to the internal template map. Note that it is not
|
* Add a template to the internal template map. Note that it is not
|
||||||
* immediately compiled.
|
* immediately compiled.
|
||||||
*/
|
*/
|
||||||
addTemplate(
|
addTemplate(name: string, xmlString: string) {
|
||||||
name: string,
|
|
||||||
xmlString: string,
|
|
||||||
allowDuplicates: boolean = false
|
|
||||||
) {
|
|
||||||
if (name in this.templates && allowDuplicates) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const doc = parseXML(xmlString);
|
const doc = parseXML(xmlString);
|
||||||
if (!doc.firstChild) {
|
if (!doc.firstChild) {
|
||||||
throw new Error("Invalid template (should not be empty)");
|
throw new Error("Invalid template (should not be empty)");
|
||||||
|
|||||||
+12
-2
@@ -72,7 +72,7 @@ export class Store extends EventBus {
|
|||||||
const func: (...any) => any = entry[1];
|
const func: (...any) => any = entry[1];
|
||||||
this.getters[name] = payload => {
|
this.getters[name] = payload => {
|
||||||
return func({ state: this.state, getters: this.getters }, payload);
|
return func({ state: this.state, getters: this.getters }, payload);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,6 +154,8 @@ interface EnvWithStore extends Env {
|
|||||||
store: Store;
|
store: Store;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let nextID = 1;
|
||||||
|
|
||||||
export function connect(mapStateToProps, options: any = {}) {
|
export function connect(mapStateToProps, options: any = {}) {
|
||||||
let hashFunction = options.hashFunction || null;
|
let hashFunction = options.hashFunction || null;
|
||||||
|
|
||||||
@@ -184,7 +186,7 @@ export function connect(mapStateToProps, options: any = {}) {
|
|||||||
return function<E extends EnvWithStore, P, S>(
|
return function<E extends EnvWithStore, P, S>(
|
||||||
Comp: Constructor<Component<E, P, S>>
|
Comp: Constructor<Component<E, P, S>>
|
||||||
) {
|
) {
|
||||||
return class extends Comp {
|
const Result = class extends Comp {
|
||||||
constructor(parent, props?: any) {
|
constructor(parent, props?: any) {
|
||||||
const env = parent instanceof Component ? parent.env : parent;
|
const env = parent instanceof Component ? parent.env : parent;
|
||||||
const ownProps = Object.assign({}, props || {});
|
const ownProps = Object.assign({}, props || {});
|
||||||
@@ -269,5 +271,13 @@ export function connect(mapStateToProps, options: any = {}) {
|
|||||||
return super._updateProps(mergedProps, forceUpdate, patchQueue);
|
return super._updateProps(mergedProps, forceUpdate, patchQueue);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// we assign here a unique name to the resulting anonymous class.
|
||||||
|
// this is necessary for Owl to be able to properly deduce templates.
|
||||||
|
// Otherwise, all connected components would have the same name, and then
|
||||||
|
// each component after the first will necessarily have the same template.
|
||||||
|
let name = `ConnectedComponent${nextID++}`;
|
||||||
|
Object.defineProperty(Result, "name", { value: name });
|
||||||
|
return Result;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+316
-203
File diff suppressed because it is too large
Load Diff
+111
-82
@@ -176,15 +176,15 @@ describe("basic use", () => {
|
|||||||
1: {
|
1: {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: "bertinchamps",
|
name: "bertinchamps",
|
||||||
tasterID: 1,
|
tasterID: 1
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
tasters: {
|
tasters: {
|
||||||
1: {
|
1: {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'aaron',
|
name: "aaron"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
};
|
};
|
||||||
const getters = {
|
const getters = {
|
||||||
beerTasterName({ state }, beerID) {
|
beerTasterName({ state }, beerID) {
|
||||||
@@ -207,15 +207,15 @@ describe("basic use", () => {
|
|||||||
1: {
|
1: {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: "bertinchamps",
|
name: "bertinchamps",
|
||||||
tasterID: 1,
|
tasterID: 1
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
tasters: {
|
tasters: {
|
||||||
1: {
|
1: {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'aaron',
|
name: "aaron"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
};
|
};
|
||||||
const getters = {
|
const getters = {
|
||||||
beerTasterName({ state }, beerID) {
|
beerTasterName({ state }, beerID) {
|
||||||
@@ -243,15 +243,15 @@ describe("basic use", () => {
|
|||||||
1: {
|
1: {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: "bertinchamps",
|
name: "bertinchamps",
|
||||||
tasterID: 1,
|
tasterID: 1
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
tasters: {
|
tasters: {
|
||||||
1: {
|
1: {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'aaron',
|
name: "aaron"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
};
|
};
|
||||||
const getters = {
|
const getters = {
|
||||||
beerTasterName({ state }, beerID) {
|
beerTasterName({ state }, beerID) {
|
||||||
@@ -278,15 +278,15 @@ describe("basic use", () => {
|
|||||||
return `${getters.b()}${getters.c(1)}`;
|
return `${getters.b()}${getters.c(1)}`;
|
||||||
},
|
},
|
||||||
b() {
|
b() {
|
||||||
return 'b';
|
return "b";
|
||||||
},
|
},
|
||||||
c({}, i) {
|
c({}, i) {
|
||||||
return `c${i}`;
|
return `c${i}`;
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
const store = new Store({ getters });
|
const store = new Store({ getters });
|
||||||
|
|
||||||
expect(store.getters.a()).toBe('bc1');
|
expect(store.getters.a()).toBe("bc1");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -408,20 +408,21 @@ describe("connecting a component to store", () => {
|
|||||||
fixture.remove();
|
fixture.remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
class App extends Component<any, any, any> {
|
test("connecting a component works", async () => {
|
||||||
inlineTemplate = `
|
env.qweb.addTemplate(
|
||||||
|
"App",
|
||||||
|
`
|
||||||
<div>
|
<div>
|
||||||
<t t-foreach="props.todos" t-as="todo" t-key="todo">
|
<t t-foreach="props.todos" t-as="todo" t-key="todo">
|
||||||
<t t-widget="Todo" t-props="todo"/>
|
<t t-widget="Todo" t-props="todo"/>
|
||||||
</t>
|
</t>
|
||||||
</div>`;
|
</div>`
|
||||||
|
);
|
||||||
|
env.qweb.addTemplate("Todo", `<span><t t-esc="props.msg"/></span>`);
|
||||||
|
class App extends Component<any, any, any> {
|
||||||
widgets = { Todo };
|
widgets = { Todo };
|
||||||
}
|
}
|
||||||
class Todo extends Component<any, any, any> {
|
class Todo extends Component<any, any, any> {}
|
||||||
inlineTemplate = `<span><t t-esc="props.msg"/></span>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
test("connecting a component works", async () => {
|
|
||||||
const state = { todos: [] };
|
const state = { todos: [] };
|
||||||
const mutations = {
|
const mutations = {
|
||||||
addTodo({ state }, msg) {
|
addTodo({ state }, msg) {
|
||||||
@@ -456,14 +457,16 @@ describe("connecting a component to store", () => {
|
|||||||
}
|
}
|
||||||
const store = new Store({ state, mutations });
|
const store = new Store({ state, mutations });
|
||||||
|
|
||||||
class App extends Component<any, any, any> {
|
env.qweb.addTemplate(
|
||||||
inlineTemplate = `
|
"App",
|
||||||
|
`
|
||||||
<div>
|
<div>
|
||||||
<span t-foreach="props.todos" t-as="todo" t-key="todo">
|
<span t-foreach="props.todos" t-as="todo" t-key="todo">
|
||||||
<t t-esc="todo.title"/>
|
<t t-esc="todo.title"/>
|
||||||
</span>
|
</span>
|
||||||
</div>`;
|
</div>`
|
||||||
}
|
);
|
||||||
|
class App extends Component<any, any, any> {}
|
||||||
|
|
||||||
const DeepTodoApp = connect(
|
const DeepTodoApp = connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
@@ -493,8 +496,8 @@ describe("connecting a component to store", () => {
|
|||||||
|
|
||||||
test("connected child components with custom hooks", async () => {
|
test("connected child components with custom hooks", async () => {
|
||||||
let steps: any = [];
|
let steps: any = [];
|
||||||
|
env.qweb.addTemplate("Child", `<div/>`);
|
||||||
class Child extends Component<any, any, any> {
|
class Child extends Component<any, any, any> {
|
||||||
inlineTemplate = `<div/>`;
|
|
||||||
mounted() {
|
mounted() {
|
||||||
steps.push("child:mounted");
|
steps.push("child:mounted");
|
||||||
}
|
}
|
||||||
@@ -505,11 +508,14 @@ describe("connecting a component to store", () => {
|
|||||||
|
|
||||||
const ConnectedChild = connect(s => s)(Child);
|
const ConnectedChild = connect(s => s)(Child);
|
||||||
|
|
||||||
class Parent extends Component<any, any, any> {
|
env.qweb.addTemplate(
|
||||||
inlineTemplate = `
|
"Parent",
|
||||||
|
`
|
||||||
<div>
|
<div>
|
||||||
<t t-if="state.child" t-widget="ConnectedChild"/>
|
<t t-if="state.child" t-widget="ConnectedChild"/>
|
||||||
</div>`;
|
</div>`
|
||||||
|
);
|
||||||
|
class Parent extends Component<any, any, any> {
|
||||||
widgets = { ConnectedChild };
|
widgets = { ConnectedChild };
|
||||||
|
|
||||||
constructor(env: Env) {
|
constructor(env: Env) {
|
||||||
@@ -540,20 +546,22 @@ describe("connecting a component to store", () => {
|
|||||||
};
|
};
|
||||||
const store = new Store({ state, mutations });
|
const store = new Store({ state, mutations });
|
||||||
|
|
||||||
class TodoItem extends Component<any, any, any> {
|
env.qweb.addTemplate("TodoItem", `<span><t t-esc="props.text"/></span>`);
|
||||||
inlineTemplate = `<span><t t-esc="props.text"/></span>`;
|
class TodoItem extends Component<any, any, any> {}
|
||||||
}
|
|
||||||
const ConnectedTodo = connect((state, props) => {
|
const ConnectedTodo = connect((state, props) => {
|
||||||
const todo = state.todos.find(t => t.id === props.id);
|
const todo = state.todos.find(t => t.id === props.id);
|
||||||
return todo;
|
return todo;
|
||||||
})(TodoItem);
|
})(TodoItem);
|
||||||
|
|
||||||
class TodoList extends Component<any, any, any> {
|
env.qweb.addTemplate(
|
||||||
inlineTemplate = `<div>
|
"TodoList",
|
||||||
|
`<div>
|
||||||
<t t-foreach="props.todos" t-as="todo">
|
<t t-foreach="props.todos" t-as="todo">
|
||||||
<t t-widget="ConnectedTodo" t-props="todo"/>
|
<t t-widget="ConnectedTodo" t-props="todo"/>
|
||||||
</t>
|
</t>
|
||||||
</div>`;
|
</div>`
|
||||||
|
);
|
||||||
|
class TodoList extends Component<any, any, any> {
|
||||||
widgets = { ConnectedTodo };
|
widgets = { ConnectedTodo };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -578,10 +586,7 @@ describe("connecting a component to store", () => {
|
|||||||
test("connect receives store getters as third argument", async () => {
|
test("connect receives store getters as third argument", async () => {
|
||||||
const state = {
|
const state = {
|
||||||
importantID: 1,
|
importantID: 1,
|
||||||
todos: [
|
todos: [{ id: 1, text: "jupiler" }, { id: 2, text: "bertinchamps" }]
|
||||||
{ id: 1, text: "jupiler" },
|
|
||||||
{ id: 2, text: "bertinchamps" },
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
const getters = {
|
const getters = {
|
||||||
importantTodoText({ state }) {
|
importantTodoText({ state }) {
|
||||||
@@ -589,30 +594,35 @@ describe("connecting a component to store", () => {
|
|||||||
},
|
},
|
||||||
text({ state }, id) {
|
text({ state }, id) {
|
||||||
return state.todos.find(todo => todo.id === id).text;
|
return state.todos.find(todo => todo.id === id).text;
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
const store = new Store({ state, getters });
|
const store = new Store({ state, getters });
|
||||||
|
|
||||||
class TodoItem extends Component<any, any, any> {
|
env.qweb.addTemplate(
|
||||||
inlineTemplate = `<div>
|
"TodoItem",
|
||||||
|
`<div>
|
||||||
<span><t t-esc="props.activeTodoText"/></span>
|
<span><t t-esc="props.activeTodoText"/></span>
|
||||||
<span><t t-esc="props.importantTodoText"/></span>
|
<span><t t-esc="props.importantTodoText"/></span>
|
||||||
</div>`;
|
</div>`
|
||||||
}
|
);
|
||||||
|
class TodoItem extends Component<any, any, any> {}
|
||||||
const ConnectedTodo = connect((state, props, getters) => {
|
const ConnectedTodo = connect((state, props, getters) => {
|
||||||
const todo = state.todos.find(t => t.id === props.id);
|
const todo = state.todos.find(t => t.id === props.id);
|
||||||
return {
|
return {
|
||||||
activeTodoText: getters.text(todo.id),
|
activeTodoText: getters.text(todo.id),
|
||||||
importantTodoText: getters.importantTodoText(),
|
importantTodoText: getters.importantTodoText()
|
||||||
};
|
};
|
||||||
})(TodoItem);
|
})(TodoItem);
|
||||||
|
|
||||||
class TodoList extends Component<any, any, any> {
|
env.qweb.addTemplate(
|
||||||
inlineTemplate = `<div>
|
"TodoList",
|
||||||
|
`<div>
|
||||||
<t t-foreach="props.todos" t-as="todo">
|
<t t-foreach="props.todos" t-as="todo">
|
||||||
<t t-widget="ConnectedTodo" t-props="todo"/>
|
<t t-widget="ConnectedTodo" t-props="todo"/>
|
||||||
</t>
|
</t>
|
||||||
</div>`;
|
</div>`
|
||||||
|
);
|
||||||
|
class TodoList extends Component<any, any, any> {
|
||||||
widgets = { ConnectedTodo };
|
widgets = { ConnectedTodo };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -625,21 +635,25 @@ describe("connecting a component to store", () => {
|
|||||||
const app = new ConnectedTodoList(env);
|
const app = new ConnectedTodoList(env);
|
||||||
|
|
||||||
await app.mount(fixture);
|
await app.mount(fixture);
|
||||||
expect(fixture.innerHTML).toBe("<div><div><span>jupiler</span><span>jupiler</span></div><div><span>bertinchamps</span><span>jupiler</span></div></div>");
|
expect(fixture.innerHTML).toBe(
|
||||||
|
"<div><div><span>jupiler</span><span>jupiler</span></div><div><span>bertinchamps</span><span>jupiler</span></div></div>"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("connected component is updated when props are updated", async () => {
|
test("connected component is updated when props are updated", async () => {
|
||||||
class Beer extends Component<any, any, any> {
|
env.qweb.addTemplate("Beer", `<span><t t-esc="props.name"/></span>`);
|
||||||
inlineTemplate = `<span><t t-esc="props.name"/></span>`;
|
class Beer extends Component<any, any, any> {}
|
||||||
}
|
|
||||||
const ConnectedBeer = connect((state, props) => {
|
const ConnectedBeer = connect((state, props) => {
|
||||||
return state.beers[props.id];
|
return state.beers[props.id];
|
||||||
})(Beer);
|
})(Beer);
|
||||||
|
|
||||||
class App extends Component<any, any, any> {
|
env.qweb.addTemplate(
|
||||||
inlineTemplate = `<div>
|
"App",
|
||||||
|
`<div>
|
||||||
<t t-widget="ConnectedBeer" t-props="{id: state.beerId}"/>
|
<t t-widget="ConnectedBeer" t-props="{id: state.beerId}"/>
|
||||||
</div>`;
|
</div>`
|
||||||
|
);
|
||||||
|
class App extends Component<any, any, any> {
|
||||||
widgets = { ConnectedBeer };
|
widgets = { ConnectedBeer };
|
||||||
state = { beerId: 1 };
|
state = { beerId: 1 };
|
||||||
}
|
}
|
||||||
@@ -658,12 +672,14 @@ describe("connecting a component to store", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("connected component is updated when store is changed", async () => {
|
test("connected component is updated when store is changed", async () => {
|
||||||
class App extends Component<any, any, any> {
|
env.qweb.addTemplate(
|
||||||
inlineTemplate = `
|
"App",
|
||||||
|
`
|
||||||
<div>
|
<div>
|
||||||
<span t-foreach="props.beers" t-as="beer" t-key="beer.name"><t t-esc="beer.name"/></span>
|
<span t-foreach="props.beers" t-as="beer" t-key="beer.name"><t t-esc="beer.name"/></span>
|
||||||
</div>`;
|
</div>`
|
||||||
}
|
);
|
||||||
|
class App extends Component<any, any, any> {}
|
||||||
|
|
||||||
const mutations = {
|
const mutations = {
|
||||||
addBeer({ state }, name) {
|
addBeer({ state }, name) {
|
||||||
@@ -692,13 +708,15 @@ describe("connecting a component to store", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("connected component with undefined, null and string props", async () => {
|
test("connected component with undefined, null and string props", async () => {
|
||||||
class Beer extends Component<any, any, any> {
|
env.qweb.addTemplate(
|
||||||
inlineTemplate = `<div>
|
"Beer",
|
||||||
|
`<div>
|
||||||
<span>taster:<t t-esc="props.taster"/></span>
|
<span>taster:<t t-esc="props.taster"/></span>
|
||||||
<span t-if="props.selected">selected:<t t-esc="props.selected.name"/></span>
|
<span t-if="props.selected">selected:<t t-esc="props.selected.name"/></span>
|
||||||
<span t-if="props.consumed">consumed:<t t-esc="props.consumed.name"/></span>
|
<span t-if="props.consumed">consumed:<t t-esc="props.consumed.name"/></span>
|
||||||
</div>`;
|
</div>`
|
||||||
}
|
);
|
||||||
|
class Beer extends Component<any, any, any> {}
|
||||||
const ConnectedBeer = connect((state, props) => {
|
const ConnectedBeer = connect((state, props) => {
|
||||||
return {
|
return {
|
||||||
selected: state.beers[props.id],
|
selected: state.beers[props.id],
|
||||||
@@ -707,10 +725,13 @@ describe("connecting a component to store", () => {
|
|||||||
};
|
};
|
||||||
})(Beer);
|
})(Beer);
|
||||||
|
|
||||||
class App extends Component<any, any, any> {
|
env.qweb.addTemplate(
|
||||||
inlineTemplate = `<div>
|
"App",
|
||||||
|
`<div>
|
||||||
<t t-widget="ConnectedBeer" t-props="{id: state.beerId}"/>
|
<t t-widget="ConnectedBeer" t-props="{id: state.beerId}"/>
|
||||||
</div>`;
|
</div>`
|
||||||
|
);
|
||||||
|
class App extends Component<any, any, any> {
|
||||||
widgets = { ConnectedBeer };
|
widgets = { ConnectedBeer };
|
||||||
state = { beerId: 0 };
|
state = { beerId: 0 };
|
||||||
}
|
}
|
||||||
@@ -756,13 +777,15 @@ describe("connecting a component to store", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("connected component deeply reactive with undefined, null and string props", async () => {
|
test("connected component deeply reactive with undefined, null and string props", async () => {
|
||||||
class Beer extends Component<any, any, any> {
|
env.qweb.addTemplate(
|
||||||
inlineTemplate = `<div>
|
"Beer",
|
||||||
|
`<div>
|
||||||
<span>taster:<t t-esc="props.taster"/></span>
|
<span>taster:<t t-esc="props.taster"/></span>
|
||||||
<span t-if="props.selected">selected:<t t-esc="props.selected.name"/></span>
|
<span t-if="props.selected">selected:<t t-esc="props.selected.name"/></span>
|
||||||
<span t-if="props.consumed">consumed:<t t-esc="props.consumed.name"/></span>
|
<span t-if="props.consumed">consumed:<t t-esc="props.consumed.name"/></span>
|
||||||
</div>`;
|
</div>`
|
||||||
}
|
);
|
||||||
|
class Beer extends Component<any, any, any> {}
|
||||||
const ConnectedBeer = connect((state, props) => {
|
const ConnectedBeer = connect((state, props) => {
|
||||||
return {
|
return {
|
||||||
selected: state.beers[props.id],
|
selected: state.beers[props.id],
|
||||||
@@ -771,10 +794,13 @@ describe("connecting a component to store", () => {
|
|||||||
};
|
};
|
||||||
})(Beer);
|
})(Beer);
|
||||||
|
|
||||||
class App extends Component<any, any, any> {
|
env.qweb.addTemplate(
|
||||||
inlineTemplate = `<div>
|
"App",
|
||||||
|
`<div>
|
||||||
<t t-widget="ConnectedBeer" t-props="{id: state.beerId}"/>
|
<t t-widget="ConnectedBeer" t-props="{id: state.beerId}"/>
|
||||||
</div>`;
|
</div>`
|
||||||
|
);
|
||||||
|
class App extends Component<any, any, any> {
|
||||||
widgets = { ConnectedBeer };
|
widgets = { ConnectedBeer };
|
||||||
state = { beerId: 0 };
|
state = { beerId: 0 };
|
||||||
}
|
}
|
||||||
@@ -845,12 +871,16 @@ describe("connecting a component to store", () => {
|
|||||||
|
|
||||||
test("correct update order when parent/children are connected", async () => {
|
test("correct update order when parent/children are connected", async () => {
|
||||||
const steps: string[] = [];
|
const steps: string[] = [];
|
||||||
class Parent extends Component<any, any, any> {
|
|
||||||
inlineTemplate = `
|
env.qweb.addTemplate(
|
||||||
|
"Parent",
|
||||||
|
`
|
||||||
<div>
|
<div>
|
||||||
<t t-widget="Child" t-ref="'child'" t-props="{key: props.current}"/>
|
<t t-widget="Child" t-ref="'child'" t-props="{key: props.current}"/>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`
|
||||||
|
);
|
||||||
|
class Parent extends Component<any, any, any> {
|
||||||
widgets = { Child: ConnectedChild };
|
widgets = { Child: ConnectedChild };
|
||||||
}
|
}
|
||||||
const ConnectedParent = connect(function(s) {
|
const ConnectedParent = connect(function(s) {
|
||||||
@@ -858,9 +888,8 @@ describe("connecting a component to store", () => {
|
|||||||
return { current: s.current, isvisible: s.isvisible };
|
return { current: s.current, isvisible: s.isvisible };
|
||||||
})(Parent);
|
})(Parent);
|
||||||
|
|
||||||
class Child extends Component<any, any, any> {
|
env.qweb.addTemplate("Child", `<span><t t-esc="props.msg"/></span>`);
|
||||||
inlineTemplate = `<span><t t-esc="props.msg"/></span>`;
|
class Child extends Component<any, any, any> {}
|
||||||
}
|
|
||||||
|
|
||||||
const ConnectedChild = connect(function(s, props) {
|
const ConnectedChild = connect(function(s, props) {
|
||||||
steps.push("child");
|
steps.push("child");
|
||||||
@@ -889,8 +918,8 @@ describe("connecting a component to store", () => {
|
|||||||
|
|
||||||
test("connected component willpatch/patch hooks are called on store updates", async () => {
|
test("connected component willpatch/patch hooks are called on store updates", async () => {
|
||||||
const steps: string[] = [];
|
const steps: string[] = [];
|
||||||
|
env.qweb.addTemplate("App", `<div><t t-esc="props.msg"/></div>`);
|
||||||
class App extends Component<any, any, any> {
|
class App extends Component<any, any, any> {
|
||||||
inlineTemplate = `<div><t t-esc="props.msg"/></div>`;
|
|
||||||
willPatch() {
|
willPatch() {
|
||||||
steps.push("willpatch");
|
steps.push("willpatch");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user