diff --git a/web/static/src/ts/core/qweb_vdom.ts b/web/static/src/ts/core/qweb_vdom.ts index 9a492dde..29312877 100644 --- a/web/static/src/ts/core/qweb_vdom.ts +++ b/web/static/src/ts/core/qweb_vdom.ts @@ -8,7 +8,7 @@ import { VNode } from "../../../libs/snabbdom/src/vnode"; export type EvalContext = { [key: string]: any }; export type RawTemplate = string; export type CompiledTemplate = (context: EvalContext, extra: any) => T; -type ParsedTemplate = Document; +type ProcessedTemplate = Element; const RESERVED_WORDS = "true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,this,typeof,eval,void,Math,RegExp,Array,Object,Date".split( "," @@ -101,8 +101,7 @@ export class Context { //------------------------------------------------------------------------------ export class QWeb { - rawTemplates: { [name: string]: RawTemplate } = {}; - parsedTemplates: { [name: string]: ParsedTemplate } = {}; + processedTemplates: { [name: string]: ProcessedTemplate } = {}; templates: { [name: string]: CompiledTemplate } = {}; h = h; exprCache: { [key: string]: string } = {}; @@ -134,10 +133,9 @@ export class QWeb { * immediately compiled. */ addTemplate(name: string, template: RawTemplate) { - if (name in this.rawTemplates) { + if (name in this.processedTemplates) { return; } - this.rawTemplates[name] = template; const parser = new DOMParser(); const doc = parser.parseFromString(template, "text/xml"); if (!doc.firstChild) { @@ -146,7 +144,14 @@ export class QWeb { if (doc.getElementsByTagName("parsererror").length) { throw new Error("Invalid XML in template"); } - let tbranch = doc.querySelectorAll("[t-elif], [t-else]"); + let elem = doc.firstChild as Element; + this._processTemplate(elem); + + this.processedTemplates[name] = elem; + } + + _processTemplate(elem: Element) { + let tbranch = elem.querySelectorAll("[t-elif], [t-else]"); for (let i = 0, ilen = tbranch.length; i < ilen; i++) { let node = tbranch[i]; let prevElem = node.previousElementSibling!; @@ -185,17 +190,24 @@ export class QWeb { ); } } - - this.parsedTemplates[name] = doc; } - + loadTemplates(xmlstr: string) { + const parser = new DOMParser(); + const doc = parser.parseFromString(xmlstr, "text/xml"); + const templates = doc.getElementsByTagName("templates")[0]; + for (let elem of templates.children) { + const name = elem.getAttribute("t-name"); + this._processTemplate(elem); + this.processedTemplates[name] = elem; + } + } /** * Render a template * * @param {string} name the template should already have been added */ render(name: string, context: EvalContext = {}, extra: any = null): VNode { - if (!(name in this.rawTemplates)) { + if (!(name in this.processedTemplates)) { throw new Error(`Template ${name} does not exist`); } const template = this.templates[name] || this._compile(name); @@ -207,9 +219,8 @@ export class QWeb { return this.templates[name]; } - const doc = this.parsedTemplates[name]; + const mainNode = this.processedTemplates[name]; const ctx = new Context(); - const mainNode = doc.firstChild!; this._compileNode(mainNode, ctx); if (ctx.shouldProtectContext) { @@ -232,8 +243,9 @@ export class QWeb { ) as CompiledTemplate; if ((mainNode).attributes.hasOwnProperty("t-debug")) { console.log( - `Template: ${this.rawTemplates[name]}\nCompiled code:\n` + - template.toString() + `Template: ${ + this.processedTemplates[name].outerHTML + }\nCompiled code:\n` + template.toString() ); } template = template.bind(this); @@ -619,8 +631,11 @@ const callDirective: Directive = { throw new Error("Invalid tag for t-call directive (should be 't')"); } const subTemplate = node.getAttribute("t-call")!; - const nodeTemplate = qweb.parsedTemplates[subTemplate]; - const nodeCopy = node.cloneNode(true); + const nodeTemplate = qweb.processedTemplates[subTemplate]; + if (!nodeTemplate) { + throw new Error(`Cannot find template "${subTemplate}" (t-call)`); + } + const nodeCopy = node.cloneNode(true) as Element; nodeCopy.removeAttribute("t-call"); // extract variables from nodecopy @@ -629,7 +644,7 @@ const callDirective: Directive = { const vars = Object.assign({}, ctx.variables, tempCtx.variables); const subCtx = ctx.withCaller(nodeCopy).withVariables(vars); - qweb._compileNode(nodeTemplate.firstChild!, subCtx); + qweb._compileNode(nodeTemplate, subCtx); return true; } }; diff --git a/web/static/tests/core/qweb_vdom.test.ts b/web/static/tests/core/qweb_vdom.test.ts index 8b3b39b2..987722dd 100644 --- a/web/static/tests/core/qweb_vdom.test.ts +++ b/web/static/tests/core/qweb_vdom.test.ts @@ -679,3 +679,19 @@ describe("t-ref", () => { expect(refs.myspan.tagName).toBe("SPAN"); }); }); + +describe("loading templates", () => { + test("can load a few templates from a xml string", () => { + const data = ` + + + +
  • ok
  • foo
  • + +
    +
    `; + qweb.loadTemplates(data); + const result = renderToString(qweb, "main"); + expect(result).toBe("
    • ok
    • foo
    "); + }); +});