mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
@@ -204,8 +204,50 @@ QWeb.addDirective({
|
|||||||
const vars = Object.assign({}, ctx.variables, tempCtx.variables);
|
const vars = Object.assign({}, ctx.variables, tempCtx.variables);
|
||||||
ctx.rootContext.nextID = tempCtx.nextID;
|
ctx.rootContext.nextID = tempCtx.nextID;
|
||||||
|
|
||||||
|
const templateMap = Object.create(ctx.templates);
|
||||||
// open new scope, if necessary
|
// open new scope, if necessary
|
||||||
const hasNewVariables = Object.keys(tempCtx.variables).length > 0;
|
const hasNewVariables = Object.keys(tempCtx.variables).length > 0;
|
||||||
|
|
||||||
|
// compile sub template
|
||||||
|
let subCtx = ctx.subContext("caller", nodeCopy).subContext("variables", Object.create(vars));
|
||||||
|
subCtx = subCtx.subContext("templates", templateMap);
|
||||||
|
|
||||||
|
if (templateMap[subTemplate]) {
|
||||||
|
// OUCH, IT IS A RECURSIVE TEMPLATE SITUATION...
|
||||||
|
// This is a tricky situation... We obviously cannot inline the compiled
|
||||||
|
// template. So, what we need to do is to compile it, and make sure we
|
||||||
|
// properly transfer everything from the current scope to the sub template.
|
||||||
|
ctx.rootContext.shouldTrackScope = true;
|
||||||
|
ctx.rootContext.shouldDefineOwner = true;
|
||||||
|
let subTemplateName;
|
||||||
|
if (ctx.hasParentWidget) {
|
||||||
|
subTemplateName = ctx.templateName;
|
||||||
|
} else {
|
||||||
|
subTemplateName = `__${ctx.generateID()}`;
|
||||||
|
subCtx.variables = {};
|
||||||
|
let id = 0;
|
||||||
|
for (let v in vars) {
|
||||||
|
subCtx.variables[v] = vars[v];
|
||||||
|
(vars[v] as any).id = `_v${id++}`;
|
||||||
|
}
|
||||||
|
const subTemplateFn = qweb._compile(subTemplateName, nodeTemplate.elem, subCtx);
|
||||||
|
qweb.recursiveFns[subTemplateName] = subTemplateFn;
|
||||||
|
}
|
||||||
|
let varCode = `{}`;
|
||||||
|
if (Object.keys(vars).length) {
|
||||||
|
let id = 0;
|
||||||
|
const content = Object.values(vars)
|
||||||
|
.map((v: any) => `_v${id++}: ${v.expr}`)
|
||||||
|
.join(",");
|
||||||
|
varCode = `{${content}}`;
|
||||||
|
}
|
||||||
|
ctx.addLine(
|
||||||
|
`this.recursiveFns['${subTemplateName}'].call(this, context, Object.assign({}, extra, {parentNode: c${ctx.parentNode}, vars: ${varCode}, scope}));`
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
templateMap[subTemplate] = true;
|
||||||
|
|
||||||
if (hasNewVariables) {
|
if (hasNewVariables) {
|
||||||
ctx.addLine("{");
|
ctx.addLine("{");
|
||||||
ctx.indent();
|
ctx.indent();
|
||||||
@@ -218,10 +260,6 @@ QWeb.addDirective({
|
|||||||
// todo: handle XML variables...
|
// todo: handle XML variables...
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// compile sub template
|
|
||||||
const subCtx = ctx.subContext("caller", nodeCopy).subContext("variables", Object.create(vars));
|
|
||||||
|
|
||||||
qweb._compileNode(nodeTemplate.elem, subCtx);
|
qweb._compileNode(nodeTemplate.elem, subCtx);
|
||||||
|
|
||||||
// close new scope
|
// close new scope
|
||||||
|
|||||||
@@ -30,10 +30,12 @@ export class Context {
|
|||||||
scopeVars: any[] = [];
|
scopeVars: any[] = [];
|
||||||
currentKey: string = "";
|
currentKey: string = "";
|
||||||
lastNodeKey: string = ""; // temp variable to communicate to previous caller
|
lastNodeKey: string = ""; // temp variable to communicate to previous caller
|
||||||
|
templates: { [key: string]: boolean } = {};
|
||||||
|
|
||||||
constructor(name?: string) {
|
constructor(name?: string) {
|
||||||
this.rootContext = this;
|
this.rootContext = this;
|
||||||
this.templateName = name || "noname";
|
this.templateName = name || "noname";
|
||||||
|
this.templates[this.templateName] = true;
|
||||||
this.addLine("var h = this.h;");
|
this.addLine("var h = this.h;");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -157,6 +157,11 @@ export class QWeb extends EventBus {
|
|||||||
slots = {};
|
slots = {};
|
||||||
nextSlotId = 1;
|
nextSlotId = 1;
|
||||||
|
|
||||||
|
// recursiveTemplates contains sub templates called with t-call, but which
|
||||||
|
// ends up in recursive situations. This is very similar to the slot situation,
|
||||||
|
// as in we need to propagate the scope.
|
||||||
|
recursiveFns = {};
|
||||||
|
|
||||||
isUpdating: boolean = false;
|
isUpdating: boolean = false;
|
||||||
|
|
||||||
constructor(data?: string) {
|
constructor(data?: string) {
|
||||||
@@ -322,6 +327,7 @@ export class QWeb extends EventBus {
|
|||||||
const isDebug = elem.attributes.hasOwnProperty("t-debug");
|
const isDebug = elem.attributes.hasOwnProperty("t-debug");
|
||||||
const ctx = new Context(name);
|
const ctx = new Context(name);
|
||||||
if (parentContext) {
|
if (parentContext) {
|
||||||
|
ctx.templates = Object.create(parentContext.templates);
|
||||||
ctx.variables = Object.create(parentContext.variables);
|
ctx.variables = Object.create(parentContext.variables);
|
||||||
ctx.nextID = parentContext.parentNode! + 1;
|
ctx.nextID = parentContext.parentNode! + 1;
|
||||||
ctx.parentNode = parentContext.parentNode!;
|
ctx.parentNode = parentContext.parentNode!;
|
||||||
|
|||||||
@@ -789,6 +789,226 @@ exports[`t-call (template calling inherit context 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`t-call (template calling recursive template, part 1 1`] = `
|
||||||
|
"function anonymous(context,extra
|
||||||
|
) {
|
||||||
|
let owner = context;
|
||||||
|
var h = this.h;
|
||||||
|
let c1 = [], p1 = {key:1};
|
||||||
|
var vn1 = h('div', p1, c1);
|
||||||
|
let c2 = [], p2 = {key:2};
|
||||||
|
var vn2 = h('span', p2, c2);
|
||||||
|
c1.push(vn2);
|
||||||
|
c2.push({text: \`hey\`});
|
||||||
|
if (false) {
|
||||||
|
this.recursiveFns['__3'].call(this, context, Object.assign({}, extra, {parentNode: c1, vars: {}, scope}));
|
||||||
|
}
|
||||||
|
return vn1;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-call (template calling recursive template, part 1 2`] = `
|
||||||
|
"function anonymous(context,extra
|
||||||
|
) {
|
||||||
|
let owner = context;
|
||||||
|
var h = this.h;
|
||||||
|
let c1 = extra.parentNode;
|
||||||
|
Object.assign(context, extra.scope);
|
||||||
|
let c2 = [], p2 = {key:2};
|
||||||
|
var vn2 = h('div', p2, c2);
|
||||||
|
c1.push(vn2);
|
||||||
|
let c3 = [], p3 = {key:3};
|
||||||
|
var vn3 = h('span', p3, c3);
|
||||||
|
c2.push(vn3);
|
||||||
|
c3.push({text: \`hey\`});
|
||||||
|
if (false) {
|
||||||
|
this.recursiveFns['__3'].call(this, context, Object.assign({}, extra, {parentNode: c2, vars: {}, scope}));
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-call (template calling recursive template, part 2 1`] = `
|
||||||
|
"function anonymous(context,extra
|
||||||
|
) {
|
||||||
|
let owner = context;
|
||||||
|
context = Object.create(context);
|
||||||
|
const scope = Object.create(null);
|
||||||
|
var h = this.h;
|
||||||
|
let c1 = [], p1 = {key:1};
|
||||||
|
var vn1 = h('div', p1, c1);
|
||||||
|
{
|
||||||
|
let _2 = context['root'];
|
||||||
|
let c3 = [], p3 = {key:3};
|
||||||
|
var vn3 = h('div', p3, c3);
|
||||||
|
c1.push(vn3);
|
||||||
|
let c4 = [], p4 = {key:4};
|
||||||
|
var vn4 = h('p', p4, c4);
|
||||||
|
c3.push(vn4);
|
||||||
|
var _5 = _2.val;
|
||||||
|
if (_5 || _5 === 0) {
|
||||||
|
c4.push({text: _5});
|
||||||
|
}
|
||||||
|
var _6 = _2.children||[];
|
||||||
|
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
|
var _7 = _8 = _6;
|
||||||
|
if (!(_6 instanceof Array)) {
|
||||||
|
_7 = Object.keys(_6);
|
||||||
|
_8 = Object.values(_6);
|
||||||
|
}
|
||||||
|
var _length7 = _7.length;
|
||||||
|
for (let i = 0; i < _length7; i++) {
|
||||||
|
context.subtree_first = i === 0;
|
||||||
|
scope.subtree_first = context.subtree_first;
|
||||||
|
context.subtree_last = i === _length7 - 1;
|
||||||
|
scope.subtree_last = context.subtree_last;
|
||||||
|
context.subtree_index = i;
|
||||||
|
scope.subtree_index = context.subtree_index;
|
||||||
|
context.subtree = _7[i];
|
||||||
|
scope.subtree = context.subtree;
|
||||||
|
context.subtree_value = _8[i];
|
||||||
|
scope.subtree_value = context.subtree_value;
|
||||||
|
this.recursiveFns['__10'].call(this, context, Object.assign({}, extra, {parentNode: c3, vars: {_v0: context['subtree']}, scope}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vn1;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-call (template calling recursive template, part 2 2`] = `
|
||||||
|
"function anonymous(context,extra
|
||||||
|
) {
|
||||||
|
let owner = context;
|
||||||
|
context = Object.create(context);
|
||||||
|
const scope = Object.create(null);
|
||||||
|
var h = this.h;
|
||||||
|
let c3 = extra.parentNode;
|
||||||
|
let _v0 = extra.vars._v0
|
||||||
|
Object.assign(context, extra.scope);
|
||||||
|
let c4 = [], p4 = {key:4};
|
||||||
|
var vn4 = h('div', p4, c4);
|
||||||
|
c3.push(vn4);
|
||||||
|
let c5 = [], p5 = {key:5};
|
||||||
|
var vn5 = h('p', p5, c5);
|
||||||
|
c4.push(vn5);
|
||||||
|
var _6 = _v0.val;
|
||||||
|
if (_6 || _6 === 0) {
|
||||||
|
c5.push({text: _6});
|
||||||
|
}
|
||||||
|
var _7 = _v0.children||[];
|
||||||
|
if (!_7) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
|
var _8 = _9 = _7;
|
||||||
|
if (!(_7 instanceof Array)) {
|
||||||
|
_8 = Object.keys(_7);
|
||||||
|
_9 = Object.values(_7);
|
||||||
|
}
|
||||||
|
var _length8 = _8.length;
|
||||||
|
for (let i = 0; i < _length8; i++) {
|
||||||
|
context.subtree_first = i === 0;
|
||||||
|
scope.subtree_first = context.subtree_first;
|
||||||
|
context.subtree_last = i === _length8 - 1;
|
||||||
|
scope.subtree_last = context.subtree_last;
|
||||||
|
context.subtree_index = i;
|
||||||
|
scope.subtree_index = context.subtree_index;
|
||||||
|
context.subtree = _8[i];
|
||||||
|
scope.subtree = context.subtree;
|
||||||
|
context.subtree_value = _9[i];
|
||||||
|
scope.subtree_value = context.subtree_value;
|
||||||
|
this.recursiveFns['__10'].call(this, context, Object.assign({}, extra, {parentNode: c4, vars: {_v0: context['subtree']}, scope}));
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-call (template calling recursive template, part 3 1`] = `
|
||||||
|
"function anonymous(context,extra
|
||||||
|
) {
|
||||||
|
let owner = context;
|
||||||
|
context = Object.create(context);
|
||||||
|
const scope = Object.create(null);
|
||||||
|
var h = this.h;
|
||||||
|
let c1 = [], p1 = {key:1};
|
||||||
|
var vn1 = h('div', p1, c1);
|
||||||
|
{
|
||||||
|
let _2 = context['root'];
|
||||||
|
let c3 = [], p3 = {key:3};
|
||||||
|
var vn3 = h('div', p3, c3);
|
||||||
|
c1.push(vn3);
|
||||||
|
let c4 = [], p4 = {key:4};
|
||||||
|
var vn4 = h('p', p4, c4);
|
||||||
|
c3.push(vn4);
|
||||||
|
var _5 = _2.val;
|
||||||
|
if (_5 || _5 === 0) {
|
||||||
|
c4.push({text: _5});
|
||||||
|
}
|
||||||
|
var _6 = _2.children||[];
|
||||||
|
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
|
var _7 = _8 = _6;
|
||||||
|
if (!(_6 instanceof Array)) {
|
||||||
|
_7 = Object.keys(_6);
|
||||||
|
_8 = Object.values(_6);
|
||||||
|
}
|
||||||
|
var _length7 = _7.length;
|
||||||
|
for (let i = 0; i < _length7; i++) {
|
||||||
|
context.subtree_first = i === 0;
|
||||||
|
scope.subtree_first = context.subtree_first;
|
||||||
|
context.subtree_last = i === _length7 - 1;
|
||||||
|
scope.subtree_last = context.subtree_last;
|
||||||
|
context.subtree_index = i;
|
||||||
|
scope.subtree_index = context.subtree_index;
|
||||||
|
context.subtree = _7[i];
|
||||||
|
scope.subtree = context.subtree;
|
||||||
|
context.subtree_value = _8[i];
|
||||||
|
scope.subtree_value = context.subtree_value;
|
||||||
|
this.recursiveFns['__10'].call(this, context, Object.assign({}, extra, {parentNode: c3, vars: {_v0: context['subtree']}, scope}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vn1;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-call (template calling recursive template, part 3 2`] = `
|
||||||
|
"function anonymous(context,extra
|
||||||
|
) {
|
||||||
|
let owner = context;
|
||||||
|
context = Object.create(context);
|
||||||
|
const scope = Object.create(null);
|
||||||
|
var h = this.h;
|
||||||
|
let c3 = extra.parentNode;
|
||||||
|
let _v0 = extra.vars._v0
|
||||||
|
Object.assign(context, extra.scope);
|
||||||
|
let c4 = [], p4 = {key:4};
|
||||||
|
var vn4 = h('div', p4, c4);
|
||||||
|
c3.push(vn4);
|
||||||
|
let c5 = [], p5 = {key:5};
|
||||||
|
var vn5 = h('p', p5, c5);
|
||||||
|
c4.push(vn5);
|
||||||
|
var _6 = _v0.val;
|
||||||
|
if (_6 || _6 === 0) {
|
||||||
|
c5.push({text: _6});
|
||||||
|
}
|
||||||
|
var _7 = _v0.children||[];
|
||||||
|
if (!_7) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
|
var _8 = _9 = _7;
|
||||||
|
if (!(_7 instanceof Array)) {
|
||||||
|
_8 = Object.keys(_7);
|
||||||
|
_9 = Object.values(_7);
|
||||||
|
}
|
||||||
|
var _length8 = _8.length;
|
||||||
|
for (let i = 0; i < _length8; i++) {
|
||||||
|
context.subtree_first = i === 0;
|
||||||
|
scope.subtree_first = context.subtree_first;
|
||||||
|
context.subtree_last = i === _length8 - 1;
|
||||||
|
scope.subtree_last = context.subtree_last;
|
||||||
|
context.subtree_index = i;
|
||||||
|
scope.subtree_index = context.subtree_index;
|
||||||
|
context.subtree = _8[i];
|
||||||
|
scope.subtree = context.subtree;
|
||||||
|
context.subtree_value = _9[i];
|
||||||
|
scope.subtree_value = context.subtree_value;
|
||||||
|
this.recursiveFns['__10'].call(this, context, Object.assign({}, extra, {parentNode: c4, vars: {_v0: context['subtree']}, scope}));
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`t-call (template calling scoped parameters 1`] = `
|
exports[`t-call (template calling scoped parameters 1`] = `
|
||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -573,6 +573,76 @@ describe("t-call (template calling", () => {
|
|||||||
const expected = "<div>ok</div>";
|
const expected = "<div>ok</div>";
|
||||||
expect(trim(renderToString(qweb, "caller"))).toBe(expected);
|
expect(trim(renderToString(qweb, "caller"))).toBe(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("recursive template, part 1", () => {
|
||||||
|
qweb.addTemplates(`
|
||||||
|
<templates>
|
||||||
|
<div t-name="recursive">
|
||||||
|
<span>hey</span>
|
||||||
|
<t t-if="false">
|
||||||
|
<t t-call="recursive"/>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
</templates>
|
||||||
|
`);
|
||||||
|
const expected = "<div><span>hey</span></div>";
|
||||||
|
expect(renderToString(qweb, "recursive")).toBe(expected);
|
||||||
|
const recursiveFn = Object.values(qweb.recursiveFns)[0];
|
||||||
|
expect(recursiveFn.toString()).toMatchSnapshot();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test("recursive template, part 2", () => {
|
||||||
|
qweb.addTemplates(`
|
||||||
|
<templates>
|
||||||
|
<div t-name="Parent">
|
||||||
|
<t t-call="nodeTemplate">
|
||||||
|
<t t-set="node" t-value="root"/>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
<div t-name="nodeTemplate">
|
||||||
|
<p><t t-esc="node.val"/></p>
|
||||||
|
<t t-foreach="node.children or []" t-as="subtree">
|
||||||
|
<t t-call="nodeTemplate">
|
||||||
|
<t t-set="node" t-value="subtree"/>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</templates>
|
||||||
|
`);
|
||||||
|
const root = { val: "a", children: [{val: "b"}, {val: "c"}]};
|
||||||
|
const expected = "<div><div><p>a</p><div><p>b</p></div><div><p>c</p></div></div></div>";
|
||||||
|
expect(renderToString(qweb, "Parent", {root })).toBe(expected);
|
||||||
|
const recursiveFn = Object.values(qweb.recursiveFns)[0];
|
||||||
|
expect(recursiveFn.toString()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("recursive template, part 3", () => {
|
||||||
|
qweb.addTemplates(`
|
||||||
|
<templates>
|
||||||
|
<div t-name="Parent">
|
||||||
|
<t t-call="nodeTemplate">
|
||||||
|
<t t-set="node" t-value="root"/>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
<div t-name="nodeTemplate">
|
||||||
|
<p><t t-esc="node.val"/></p>
|
||||||
|
<t t-foreach="node.children or []" t-as="subtree">
|
||||||
|
<t t-call="nodeTemplate">
|
||||||
|
<t t-set="node" t-value="subtree"/>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</templates>
|
||||||
|
`);
|
||||||
|
const root = { val: "a", children: [{val: "b", children: [{val: "d"}]}, {val: "c"}]};
|
||||||
|
const expected = "<div><div><p>a</p><div><p>b</p><div><p>d</p></div></div><div><p>c</p></div></div></div>";
|
||||||
|
expect(renderToString(qweb, "Parent", {root })).toBe(expected);
|
||||||
|
const recursiveFn = Object.values(qweb.recursiveFns)[0];
|
||||||
|
expect(recursiveFn.toString()).toMatchSnapshot();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("foreach", () => {
|
describe("foreach", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user