diff --git a/doc/qweb.md b/doc/qweb.md
index 984aae89..eb4b6dd9 100644
--- a/doc/qweb.md
+++ b/doc/qweb.md
@@ -285,6 +285,11 @@ rendered with the value `value` set to `foo` in the rendering conte
foo
```
+Note that since the content of the expression is not known beforehand, the `t-raw`
+directive has to parse the html (and convert it to a virtual dom structure) for
+each rendering. So, it will be much slower than a regular template. It is
+therefore advised to limit the use of `t-raw` whenever possible.
+
### Setting Variables
QWeb allows creating variables from within the template, to memoize a computation (to use it multiple times), give a piece of data a clearer name, ...
diff --git a/src/qweb/base_directives.ts b/src/qweb/base_directives.ts
index 5bdaddb8..cf96424f 100644
--- a/src/qweb/base_directives.ts
+++ b/src/qweb/base_directives.ts
@@ -1,6 +1,7 @@
import { Context } from "./context";
import { QWebExprVar } from "./expression_parser";
import { QWeb } from "./qweb";
+import { htmlToVDOM } from "../vdom/html_to_vdom";
/**
* Owl QWeb Directives
@@ -25,6 +26,8 @@ QWeb.utils.getFragment = function(str: string): DocumentFragment {
return temp.content;
};
+QWeb.utils.htmlToVDOM = htmlToVDOM;
+
function compileValueNode(value: any, node: Element, qweb: QWeb, ctx: Context) {
if (value === "0" && ctx.caller) {
qweb._compileNode(ctx.caller, ctx);
@@ -60,15 +63,8 @@ function compileValueNode(value: any, node: Element, qweb: QWeb, ctx: Context) {
}
}
} else {
- let fragID = ctx.generateID();
ctx.rootContext.shouldDefineUtils = true;
- ctx.addLine(`var frag${fragID} = utils.getFragment(${exprID})`);
- let tempNodeID = ctx.generateID();
- ctx.addLine(`var p${tempNodeID} = {hook: {`);
- ctx.addLine(` insert: n => n.elm.parentNode.replaceChild(frag${fragID}, n.elm),`);
- ctx.addLine(`}};`);
- ctx.addLine(`var vn${tempNodeID} = h('div', p${tempNodeID})`);
- ctx.addLine(`c${ctx.parentNode}.push(vn${tempNodeID});`);
+ ctx.addLine(`c${ctx.parentNode}.push(...utils.htmlToVDOM(${exprID}));`);
}
if (node.childNodes.length) {
ctx.addElse();
diff --git a/src/qweb/extensions.ts b/src/qweb/extensions.ts
index 3054ee8d..97dbdb8c 100644
--- a/src/qweb/extensions.ts
+++ b/src/qweb/extensions.ts
@@ -207,9 +207,7 @@ QWeb.addDirective({
);
ctx.addIf(`slot${slotKey}`);
ctx.addLine(
- `slot${slotKey}.call(this, context.__owl__.parent, Object.assign({}, extra, {parentNode: c${
- ctx.parentNode
- }, vars: extra.vars, parent: owner}));`
+ `slot${slotKey}.call(this, context.__owl__.parent, Object.assign({}, extra, {parentNode: c${ctx.parentNode}, vars: extra.vars, parent: owner}));`
);
ctx.closeIf();
return true;
diff --git a/src/qweb/qweb.ts b/src/qweb/qweb.ts
index 992ff18a..b5d27fcc 100644
--- a/src/qweb/qweb.ts
+++ b/src/qweb/qweb.ts
@@ -458,8 +458,8 @@ 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-raw')) {
- const tNode = document.createElement('t');
+ if (node.tagName !== "t" && (attrName === "t-esc" || attrName === "t-raw")) {
+ const tNode = document.createElement("t");
tNode.setAttribute(attrName, node.getAttribute(attrName)!);
for (let child of Array.from(node.childNodes)) {
tNode.appendChild(child);
diff --git a/src/vdom/html_to_vdom.ts b/src/vdom/html_to_vdom.ts
new file mode 100644
index 00000000..a7cb434c
--- /dev/null
+++ b/src/vdom/html_to_vdom.ts
@@ -0,0 +1,29 @@
+import { VNode, h } from "./vdom";
+
+const parser = new DOMParser();
+
+export function htmlToVDOM(html: string): VNode[] {
+ const doc = parser.parseFromString(html, "text/html");
+ const result: VNode[] = [];
+ for (let child of doc.body.childNodes) {
+ result.push(htmlToVNode(child));
+ }
+ return result;
+}
+
+function htmlToVNode(node: ChildNode): VNode {
+ if (!(node instanceof Element)) {
+ return { text: node.textContent! } as VNode;
+ }
+ const attrs = {};
+ for (let attr of node.attributes) {
+ attrs[attr.name] = attr.textContent;
+ }
+ const children: VNode[] = [];
+ if (node.hasChildNodes) {
+ for (let c of node.childNodes) {
+ children.push(htmlToVNode(c));
+ }
+ }
+ return h((node as Element).tagName, { attrs }, children);
+}
diff --git a/tests/Context.test.ts b/tests/Context.test.ts
index 706d7000..c5de29a9 100644
--- a/tests/Context.test.ts
+++ b/tests/Context.test.ts
@@ -96,10 +96,10 @@ describe("Context", () => {
const parent = new Parent(env);
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("
12
");
- expect(steps).toEqual(['child']);
+ expect(steps).toEqual(["child"]);
testContext.state.a = 3;
await nextTick();
- expect(steps).toEqual(['child', 'child']);
+ expect(steps).toEqual(["child", "child"]);
});
test("parent and children subscribed to same context", async () => {
diff --git a/tests/component/component.test.ts b/tests/component/component.test.ts
index 26eb78ee..a2df7280 100644
--- a/tests/component/component.test.ts
+++ b/tests/component/component.test.ts
@@ -4293,3 +4293,39 @@ describe("support svg components", () => {
);
});
});
+
+describe("t-raw in components", () => {
+ test("update properly on state changes", async () => {
+ class TestW extends Widget {
+ static template = xml`