diff --git a/src/qweb/base_directives.ts b/src/qweb/base_directives.ts
index 0684bd29..da9dbe5a 100644
--- a/src/qweb/base_directives.ts
+++ b/src/qweb/base_directives.ts
@@ -44,50 +44,70 @@ function compileValueNode(value: any, node: Element, qweb: QWeb, ctx: Compilatio
} else {
exprID = `scope.${value.id}`;
}
- ctx.addIf(`${exprID} != null`);
- if (ctx.escaping) {
- let protectID;
- if (value.hasBody) {
- ctx.rootContext.shouldDefineUtils = true;
- protectID = ctx.startProtectScope();
- ctx.addLine(
- `${exprID} = ${exprID} instanceof utils.VDomArray ? utils.vDomToString(${exprID}) : ${exprID};`
- );
- }
- if (ctx.parentTextNode) {
- ctx.addLine(`vn${ctx.parentTextNode}.text += ${exprID};`);
- } else if (ctx.parentNode) {
- ctx.addLine(`c${ctx.parentNode}.push({text: ${exprID}});`);
- } else {
- let nodeID = ctx.generateID();
- ctx.rootContext.rootNode = nodeID;
- ctx.rootContext.parentTextNode = nodeID;
- ctx.addLine(`let vn${nodeID} = {text: ${exprID}};`);
- if (ctx.rootContext.shouldDefineResult) {
- ctx.addLine(`result = vn${nodeID}`);
- }
- }
- if (value.hasBody) {
- ctx.stopProtectScope(protectID);
- }
- } else {
+ if (ctx.parentTextNode) {
+ ctx.addIf(`${exprID} != null`);
+ ctx.addLine(`vn${ctx.parentTextNode}.text += ${exprID};`);
+ ctx.closeIf();
+ } else if (ctx.parentNode) {
ctx.rootContext.shouldDefineUtils = true;
- if (value.hasBody) {
- ctx.addLine(
- `const vnodeArray = ${exprID} instanceof utils.VDomArray ? ${exprID} : utils.htmlToVDOM(${exprID});`
- );
- ctx.addLine(`c${ctx.parentNode}.push(...vnodeArray);`);
- } else {
- ctx.addLine(`c${ctx.parentNode}.push(...utils.htmlToVDOM(${exprID}));`);
- }
- }
- if (node.childNodes.length) {
- ctx.addElse();
- qweb._compileChildren(node, ctx);
- }
+ ctx.addLine(`insertValue(c${ctx.parentNode}, ${exprID})`);
+ } else {
+ ctx.addIf(`${exprID} != null`);
- ctx.closeIf();
+ let nodeID = ctx.generateID();
+ ctx.rootContext.rootNode = nodeID;
+ ctx.rootContext.parentTextNode = nodeID;
+ ctx.addLine(`let vn${nodeID} = {text: ${exprID}};`);
+ if (ctx.rootContext.shouldDefineResult) {
+ ctx.addLine(`result = vn${nodeID}`);
+ }
+ ctx.closeIf();
+ }
+ // ctx.addIf(`${exprID} != null`);
+
+ // if (ctx.escaping) {
+ // let protectID;
+ // if (value.hasBody) {
+ // ctx.rootContext.shouldDefineUtils = true;
+ // protectID = ctx.startProtectScope();
+ // ctx.addLine(
+ // `${exprID} = ${exprID} instanceof utils.VDomArray ? utils.vDomToString(${exprID}) : ${exprID};`
+ // );
+ // }
+ // if (ctx.parentTextNode) {
+ // ctx.addLine(`vn${ctx.parentTextNode}.text += ${exprID};`);
+ // } else if (ctx.parentNode) {
+ // ctx.addLine(`c${ctx.parentNode}.push({text: ${exprID}});`);
+ // } else {
+ // let nodeID = ctx.generateID();
+ // ctx.rootContext.rootNode = nodeID;
+ // ctx.rootContext.parentTextNode = nodeID;
+ // ctx.addLine(`let vn${nodeID} = {text: ${exprID}};`);
+ // if (ctx.rootContext.shouldDefineResult) {
+ // ctx.addLine(`result = vn${nodeID}`);
+ // }
+ // }
+ // if (value.hasBody) {
+ // ctx.stopProtectScope(protectID);
+ // }
+ // } else {
+ // ctx.rootContext.shouldDefineUtils = true;
+ // if (value.hasBody) {
+ // ctx.addLine(
+ // `const vnodeArray = ${exprID} instanceof utils.VDomArray ? ${exprID} : utils.htmlToVDOM(${exprID});`
+ // );
+ // ctx.addLine(`c${ctx.parentNode}.push(...vnodeArray);`);
+ // } else {
+ // ctx.addLine(`c${ctx.parentNode}.push(...utils.htmlToVDOM(${exprID}));`);
+ // }
+ // }
+ // if (node.childNodes.length) {
+ // ctx.addElse();
+ // qweb._compileChildren(node, ctx);
+ // }
+
+ // ctx.closeIf();
}
QWeb.addDirective({
diff --git a/src/qweb/compilation_context.ts b/src/qweb/compilation_context.ts
index 38f0c46b..f321c758 100644
--- a/src/qweb/compilation_context.ts
+++ b/src/qweb/compilation_context.ts
@@ -84,6 +84,7 @@ export class CompilationContext {
this.code.unshift(" let QWeb = this.constructor;");
}
if (this.shouldDefineUtils) {
+ this.code.unshift(" let insertValue = utils.insertValue;");
this.code.unshift(" let utils = this.constructor.utils;");
}
return this.code;
diff --git a/src/qweb/qweb.ts b/src/qweb/qweb.ts
index f01e2691..065c5e48 100644
--- a/src/qweb/qweb.ts
+++ b/src/qweb/qweb.ts
@@ -1,8 +1,9 @@
import { EventBus } from "../core/event_bus";
import { h, patch, VNode } from "../vdom/index";
import { CompilationContext } from "./compilation_context";
-import { shallowEqual, escape } from "../utils";
+import { shallowEqual, escape, _Markup } from "../utils";
import { addNS } from "../vdom/vdom";
+import { htmlToVDOM } from "../vdom/html_to_vdom";
/**
* Owl QWeb Engine
@@ -89,6 +90,16 @@ function isComponent(obj): boolean {
return obj && obj.hasOwnProperty("__owl__");
}
+function insertValue(children: any[], value: any) {
+ if (value != null) {
+ if (value instanceof _Markup) {
+ children.push(...htmlToVDOM(value as any));
+ } else {
+ children.push({ text: value });
+ }
+ }
+}
+
class VDomArray extends Array {
toString() {
return vDomToString(this);
@@ -111,6 +122,7 @@ function vDomToString(vdom: VNode[]): string {
const UTILS: Utils = {
zero: Symbol("zero"),
+ insertValue,
toClassObj(expr) {
const result = {};
if (typeof expr === "string") {
@@ -599,7 +611,10 @@ export class QWeb extends EventBus {
if (!(dName in QWeb.DIRECTIVE_NAMES)) {
throw new Error(`Unknown QWeb directive: '${attrName}'`);
}
- if (node.tagName !== "t" && (attrName === "t-esc" || attrName === "t-out" || attrName === "t-raw")) {
+ if (
+ node.tagName !== "t" &&
+ (attrName === "t-esc" || attrName === "t-out" || attrName === "t-raw")
+ ) {
const tNode = document.implementation.createDocument(
"http://www.w3.org/1999/xhtml",
"t",
diff --git a/src/utils.ts b/src/utils.ts
index 3f222623..7c846bd6 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -102,68 +102,63 @@ export function shallowEqual(p1, p2): boolean {
return true;
}
-const escapeMethod = Symbol('html')
-
// notable issues:
// * objects can't be negative in JS, so !!"" -> false but
// !!(new String) -> true, likewise markup
// TODO (?)
// * Markup.join / Markup#join => escapes items and returns a Markup
// * Markup#replace => automatically escapes the replacements (difficult impl)
-class _Markup extends String {
- [escapeMethod]() {
- return this;
- }
-}
+export class _Markup extends String {}
/**
-* Returns a markup object, which acts like a String but is considered safe by
-* `_.escape`, and will therefore be injected as-is (without additional
-* escaping) in templates. Can be used to inject dynamic HTML in templates
-* (where the template itself can't), see first example.
-*
-* Can also be used as a *template tag*, in which case the literal content
-* won't be escaped but the substitutions which are not already markup objects
-* will be.
-*
-* ## WARNINGS:
-* * A markup object is a `String` (boxed) but not a `string` (primitive), they
-* typecheck differently which can be relevant.
-* * To strip out the "markupness", just call `String(markup)`.
-* * Most string operations (e.g. concatenation, `String#replace`, ...) will
-* also strip out markupness
-* * If the input is empty, returns a regular string (that way boolean tests
-* work as expected).
-*
-* @returns a markup object
-*
-* @example regular function
-* let h;
-* if (someTest) {
-* h = Markup(_t("This is a success"));
-* } else {
-* h = Markup(_t("Things did not work out"));
-* }
-* qweb.render("some_template", { message: h });
-*
-* @example template tag
-* const escaped = " text";
-* const asis = Markup`some text`;
-* const h = Markup`Regular strings get ${escaped} but markup is injected ${asis}`;
-*/
+ * Returns a markup object, which acts like a String but is considered safe by
+ * `_.escape`, and will therefore be injected as-is (without additional
+ * escaping) in templates. Can be used to inject dynamic HTML in templates
+ * (where the template itself can't), see first example.
+ *
+ * Can also be used as a *template tag*, in which case the literal content
+ * won't be escaped but the substitutions which are not already markup objects
+ * will be.
+ *
+ * ## WARNINGS:
+ * * A markup object is a `String` (boxed) but not a `string` (primitive), they
+ * typecheck differently which can be relevant.
+ * * To strip out the "markupness", just call `String(markup)`.
+ * * Most string operations (e.g. concatenation, `String#replace`, ...) will
+ * also strip out markupness
+ * * If the input is empty, returns a regular string (that way boolean tests
+ * work as expected).
+ *
+ * @returns a markup object
+ *
+ * @example regular function
+ * let h;
+ * if (someTest) {
+ * h = Markup(_t("This is a success"));
+ * } else {
+ * h = Markup(_t("Things did not work out"));
+ * }
+ * qweb.render("some_template", { message: h });
+ *
+ * @example template tag
+ * const escaped = " text";
+ * const asis = Markup`some text`;
+ * const h = Markup`Regular strings get ${escaped} but markup is injected ${asis}`;
+ */
export function Markup(v, ...exprs) {
if (!(v instanceof Array)) {
- return v ? new _Markup(v) : '';
+ return v ? new _Markup(v) : "";
}
const elements = [];
let i = 0;
- for(; i < exprs.length; ++i) {
- elements.push(v[i], escape(exprs[i]));
+ for (; i < exprs.length; ++i) {
+ elements.push(v[i], escape(exprs[i]));
}
elements.push(v[i]);
- const s = elements.join('');
- if (!s) { return '' }
+ const s = elements.join("");
+ if (!s) {
+ return "";
+ }
return new _Markup(s);
}
-
diff --git a/tests/qweb/__snapshots__/qweb.test.ts.snap b/tests/qweb/__snapshots__/qweb.test.ts.snap
index 5ebaa6ce..9c258883 100644
--- a/tests/qweb/__snapshots__/qweb.test.ts.snap
+++ b/tests/qweb/__snapshots__/qweb.test.ts.snap
@@ -3064,14 +3064,14 @@ exports[`t-out (formerly t-raw tests) not escaping 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
+ let utils = this.constructor.utils;
+ let insertValue = utils.insertValue;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
let _2 = scope['var'];
- if (_2 != null) {
- c1.push({text: _2});
- }
+ insertValue(c1, _2)
return vn1;
}"
`;
@@ -3181,14 +3181,14 @@ exports[`t-out escaping 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
+ let utils = this.constructor.utils;
+ let insertValue = utils.insertValue;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('span', p1, c1);
let _2 = scope['var'];
- if (_2 != null) {
- c1.push({text: _2});
- }
+ insertValue(c1, _2)
return vn1;
}"
`;
@@ -3197,14 +3197,14 @@ exports[`t-out escaping on a node 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
+ let utils = this.constructor.utils;
+ let insertValue = utils.insertValue;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('span', p1, c1);
let _2 = 'ok';
- if (_2 != null) {
- c1.push({text: _2});
- }
+ insertValue(c1, _2)
return vn1;
}"
`;
@@ -3213,16 +3213,14 @@ exports[`t-out escaping on a node with a body 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
+ let utils = this.constructor.utils;
+ let insertValue = utils.insertValue;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('span', p1, c1);
let _2 = 'ok';
- if (_2 != null) {
- c1.push({text: _2});
- } else {
- c1.push({text: \`nope\`});
- }
+ insertValue(c1, _2)
return vn1;
}"
`;
@@ -3231,16 +3229,14 @@ exports[`t-out escaping on a node with a body, as a default 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
+ let utils = this.constructor.utils;
+ let insertValue = utils.insertValue;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('span', p1, c1);
let _2 = scope['var'];
- if (_2 != null) {
- c1.push({text: _2});
- } else {
- c1.push({text: \`nope\`});
- }
+ insertValue(c1, _2)
return vn1;
}"
`;
@@ -3249,14 +3245,14 @@ exports[`t-out literal 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
+ let utils = this.constructor.utils;
+ let insertValue = utils.insertValue;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('span', p1, c1);
let _2 = 'ok';
- if (_2 != null) {
- c1.push({text: _2});
- }
+ insertValue(c1, _2)
return vn1;
}"
`;
@@ -3357,14 +3353,14 @@ exports[`t-out variable 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
+ let utils = this.constructor.utils;
+ let insertValue = utils.insertValue;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('span', p1, c1);
let _2 = scope['var'];
- if (_2 != null) {
- c1.push({text: _2});
- }
+ insertValue(c1, _2)
return vn1;
}"
`;
diff --git a/tests/qweb/qweb.test.ts b/tests/qweb/qweb.test.ts
index 8cc5b5f5..d04a089a 100644
--- a/tests/qweb/qweb.test.ts
+++ b/tests/qweb/qweb.test.ts
@@ -150,7 +150,7 @@ describe("error handling", () => {
});
});
-describe("t-out", () => {
+describe.only("t-out", () => {
test("literal", () => {
qweb.addTemplate("test", ``);
expect(renderToString(qweb, "test")).toBe("ok");
@@ -178,7 +178,7 @@ describe("t-out", () => {
expect(renderToString(qweb, "test")).toBe("ok");
});
- test("escaping on a node with a body, as a default", () => {
+ test.only("escaping on a node with a body, as a default", () => {
qweb.addTemplate("test", `nope`);
expect(renderToString(qweb, "test")).toBe("nope");
});
diff --git a/tests/qweb/qweb_t_esc_t_raw.test.ts b/tests/qweb/qweb_t_esc_t_raw.test.ts
index b770511c..ff273a7f 100644
--- a/tests/qweb/qweb_t_esc_t_raw.test.ts
+++ b/tests/qweb/qweb_t_esc_t_raw.test.ts
@@ -32,36 +32,35 @@ describe("old t-esc directive", () => {
"<ok>abc</ok>"
);
});
-
});
describe("old t-raw directive", () => {
- test("literal", () => {
- qweb.addTemplate("test", ``);
- expect(renderToString(qweb, "test")).toBe("ok");
- });
-
- test("variable", () => {
- qweb.addTemplate("test", ``);
- expect(renderToString(qweb, "test", { var: "ok" })).toBe("ok");
- });
-
- test("not escaping", () => {
- qweb.addTemplate("test", `
`);
- expect(renderToString(qweb, "test", { var: "" })).toBe("
");
- });
-
- test("t-raw and another sibling node", () => {
- qweb.addTemplate("test", `hello`);
- expect(renderToString(qweb, "test", { var: "world" })).toBe(
- "helloworld"
- );
- });
-
- test("t-raw with comment", () => {
- qweb.addTemplate("test", ``);
- expect(renderToString(qweb, "test", { var: "text
" })).toBe(
- "text
"
- );
- });
- });
\ No newline at end of file
+ test("literal", () => {
+ qweb.addTemplate("test", ``);
+ expect(renderToString(qweb, "test")).toBe("ok");
+ });
+
+ test("variable", () => {
+ qweb.addTemplate("test", ``);
+ expect(renderToString(qweb, "test", { var: "ok" })).toBe("ok");
+ });
+
+ test("not escaping", () => {
+ qweb.addTemplate("test", `
`);
+ expect(renderToString(qweb, "test", { var: "" })).toBe("
");
+ });
+
+ test("t-raw and another sibling node", () => {
+ qweb.addTemplate("test", `hello`);
+ expect(renderToString(qweb, "test", { var: "world" })).toBe(
+ "helloworld"
+ );
+ });
+
+ test("t-raw with comment", () => {
+ qweb.addTemplate("test", ``);
+ expect(renderToString(qweb, "test", { var: "text
" })).toBe(
+ "text
"
+ );
+ });
+});