diff --git a/src/qweb/base_directives.ts b/src/qweb/base_directives.ts index 90a9b258..a1bc2293 100644 --- a/src/qweb/base_directives.ts +++ b/src/qweb/base_directives.ts @@ -204,8 +204,50 @@ QWeb.addDirective({ const vars = Object.assign({}, ctx.variables, tempCtx.variables); ctx.rootContext.nextID = tempCtx.nextID; + const templateMap = Object.create(ctx.templates); // open new scope, if necessary 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) { ctx.addLine("{"); ctx.indent(); @@ -218,10 +260,6 @@ QWeb.addDirective({ // todo: handle XML variables... } } - - // compile sub template - const subCtx = ctx.subContext("caller", nodeCopy).subContext("variables", Object.create(vars)); - qweb._compileNode(nodeTemplate.elem, subCtx); // close new scope diff --git a/src/qweb/context.ts b/src/qweb/context.ts index 94d61ab7..b4370af3 100644 --- a/src/qweb/context.ts +++ b/src/qweb/context.ts @@ -30,10 +30,12 @@ export class Context { scopeVars: any[] = []; currentKey: string = ""; lastNodeKey: string = ""; // temp variable to communicate to previous caller + templates: { [key: string]: boolean } = {}; constructor(name?: string) { this.rootContext = this; this.templateName = name || "noname"; + this.templates[this.templateName] = true; this.addLine("var h = this.h;"); } diff --git a/src/qweb/qweb.ts b/src/qweb/qweb.ts index 3cf8e5b3..c6ae525f 100644 --- a/src/qweb/qweb.ts +++ b/src/qweb/qweb.ts @@ -157,6 +157,11 @@ export class QWeb extends EventBus { slots = {}; 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; constructor(data?: string) { @@ -322,6 +327,7 @@ export class QWeb extends EventBus { const isDebug = elem.attributes.hasOwnProperty("t-debug"); const ctx = new Context(name); if (parentContext) { + ctx.templates = Object.create(parentContext.templates); ctx.variables = Object.create(parentContext.variables); ctx.nextID = parentContext.parentNode! + 1; ctx.parentNode = parentContext.parentNode!; diff --git a/tests/qweb/__snapshots__/qweb.test.ts.snap b/tests/qweb/__snapshots__/qweb.test.ts.snap index 7a11fdfe..31cc678a 100644 --- a/tests/qweb/__snapshots__/qweb.test.ts.snap +++ b/tests/qweb/__snapshots__/qweb.test.ts.snap @@ -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`] = ` "function anonymous(context,extra ) { diff --git a/tests/qweb/qweb.test.ts b/tests/qweb/qweb.test.ts index 459e181f..c8c4d398 100644 --- a/tests/qweb/qweb.test.ts +++ b/tests/qweb/qweb.test.ts @@ -573,6 +573,76 @@ describe("t-call (template calling", () => { const expected = "
a
b
c
a
b
d
c