[FIX] component: allow using vars with body as props

Consider this scenario:

- a variable v (with a body) is defined in a template
- it is then passed to a sub component as a prop
- and now, it is t-esc-ed.

Before this commit, the displayed value was [object object], because the
value actually passed to the sub component was a VDomArray (internal
structure used to represent nodelists)

This issue is actually quite a problem in practice, because values in a
templates are translated, but not in attributes.  Therefore, using a
t-set directive with a body text content is the proper way to have
translated values at runtime.

We override in this commit the method toString of VDomArray to make sure
it is properly displayed.

Note that we considered changing the way props were generated (by trying
to detect VDomArray, then calling vDomToString), but then the value
would not be able to be used in a t-raw.  Also, it is quite elegant to
be able to format the VDomArray only at the end.

closes #670
This commit is contained in:
Géry Debongnie
2020-09-16 14:39:20 +02:00
committed by aab-odoo
parent 7e4baf668a
commit 06d852fcf9
3 changed files with 105 additions and 14 deletions
+22 -14
View File
@@ -90,6 +90,26 @@ function isComponent(obj) {
return obj && obj.hasOwnProperty("__owl__");
}
class VDomArray extends Array {
toString() {
return vDomToString(this);
}
}
function vDomToString(vdom: VNode[]): string {
return vdom
.map((vnode) => {
if (vnode.sel) {
const node = document.createElement(vnode.sel);
const result = patch(node, vnode);
return (<HTMLElement>result.elm).outerHTML;
} else {
return vnode.text;
}
})
.join("");
}
const UTILS: Utils = {
zero: Symbol("zero"),
toObj(expr) {
@@ -111,20 +131,8 @@ const UTILS: Utils = {
addNameSpace(vnode) {
addNS(vnode.data, vnode.children, vnode.sel);
},
VDomArray: class VDomArray extends Array {},
vDomToString: function (vdom: VNode[]): string {
return vdom
.map((vnode) => {
if (vnode.sel) {
const node = document.createElement(vnode.sel);
const result = patch(node, vnode);
return (<HTMLElement>result.elm).outerHTML;
} else {
return vnode.text;
}
})
.join("");
},
VDomArray,
vDomToString,
getComponent(obj) {
while (obj && !isComponent(obj)) {
obj = obj.__proto__;
@@ -1405,6 +1405,47 @@ exports[`other directives with t-component t-set outside modified in t-foreach 1
}"
`;
exports[`props evaluation t-set with a body expression can be used as textual prop 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"__template__2\\"
let utils = this.constructor.utils;
let QWeb = this.constructor;
let parent = context;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
let c2 = new utils.VDomArray();
c2.push({text: \`42\`});
scope.abc = c2
// Component 'Child'
let w3 = '__4__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__4__']] : false;
let props3 = {val:scope.abc};
if (w3 && w3.__owl__.currentFiber && !w3.__owl__.vnode) {
w3.destroy();
w3 = false;
}
if (w3) {
w3.__updateProps(props3, extra.fiber, undefined);
let pvnode = w3.__owl__.pvnode;
c1.push(pvnode);
} else {
let componentKey3 = \`Child\`;
let W3 = context.constructor.components[componentKey3] || QWeb.components[componentKey3]|| scope['Child'];
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
w3 = new W3(parent, props3);
parent.__owl__.cmap['__4__'] = w3.__owl__.id;
let fiber = w3.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
let pvnode = h('dummy', {key: '__4__', hook: {remove() {},destroy(vn) {w3.destroy();}}});
c1.push(pvnode);
w3.__owl__.pvnode = pvnode;
}
w3.__owl__.parentLastFiberId = extra.fiber.id;
return vn1;
}"
`;
exports[`random stuff/miscellaneous can inject values in tagged templates 1`] = `
"function anonymous(context, extra
) {
+42
View File
@@ -1725,6 +1725,48 @@ describe("props evaluation ", () => {
await widget.mount(fixture);
expect(normalize(fixture.innerHTML)).toBe("<div><span>42</span></div>");
});
test("t-set with a body expression can be used as textual prop", async () => {
class Child extends Component {
static template = xml`<span t-esc="props.val"/>`;
}
class Parent extends Component {
static components = { Child };
static template = xml`
<div>
<t t-set="abc">42</t>
<Child val="abc"/>
</div>`;
}
const widget = new Parent();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>42</span></div>");
expect(env.qweb.templates[Parent.template].fn.toString()).toMatchSnapshot();
});
test("t-set with a body expression can be passed in props, and then t-raw", async () => {
class Child extends Component {
static template = xml`
<span>
<t t-esc="props.val"/>
<t t-raw="props.val"/>
</span>`;
}
class Parent extends Component {
static components = { Child };
static template = xml`
<div>
<t t-set="abc"><p>43</p></t>
<Child val="abc"/>
</div>`;
}
const widget = new Parent();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>&lt;p&gt;4343&lt;/p&gt;<p>43</p></span></div>");
});
});
describe("other directives with t-component", () => {