[REF] qweb: use native Node and Element instead of custom Dom types

This commit is contained in:
Samuel Degueldre
2021-10-14 13:05:18 +02:00
committed by Aaron Bohy
parent e746574a1d
commit efa147fdab
2 changed files with 28 additions and 87 deletions
+28 -13
View File
@@ -1,5 +1,4 @@
import { BDom } from "../blockdom"; import { BDom } from "../blockdom";
import { Dom, DomNode, domToString, DomType } from "./dom_helpers";
import { compileExpr, compileExprToArray, interpolate, INTERP_REGEXP } from "./inline_expressions"; import { compileExpr, compileExprToArray, interpolate, INTERP_REGEXP } from "./inline_expressions";
import { import {
AST, AST,
@@ -33,6 +32,10 @@ export function compileTemplate(template: string, name?: string): TemplateFuncti
return compiler.compile(); return compiler.compile();
} }
// using a non-html document so that <inner/outer>HTML serializes as XML instead
// of HTML (as we will parse it as xml later)
const xmlDoc = document.implementation.createDocument(null, null, null);
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// BlockDescription // BlockDescription
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@@ -47,8 +50,8 @@ class BlockDescription {
hasDynamicChildren: boolean = false; hasDynamicChildren: boolean = false;
children: BlockDescription[] = []; children: BlockDescription[] = [];
data: string[] = []; data: string[] = [];
dom?: Dom; dom?: Node;
currentDom?: DomNode; currentDom?: Element;
childNumber: number = 0; childNumber: number = 0;
target: CodeTarget; target: CodeTarget;
type: BlockType; type: BlockType;
@@ -69,9 +72,9 @@ class BlockDescription {
return this.data.push(id) - 1; return this.data.push(id) - 1;
} }
insert(dom: Dom) { insert(dom: Node) {
if (this.currentDom) { if (this.currentDom) {
this.currentDom.content.push(dom); this.currentDom.appendChild(dom);
} else { } else {
this.dom = dom; this.dom = dom;
} }
@@ -90,6 +93,14 @@ class BlockDescription {
} }
return expr; return expr;
} }
asXmlString() {
// Can't use outerHTML on text/comment nodes
// append dom to any element and use innerHTML instead
const t = xmlDoc.createElement('t');
t.appendChild(this.dom!);
return t.innerHTML;
}
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@@ -186,7 +197,7 @@ export class QWebCompiler {
insertAnchor(block: BlockDescription) { insertAnchor(block: BlockDescription) {
const tag = `block-child-${block.children.length}`; const tag = `block-child-${block.children.length}`;
const anchor: Dom = { type: DomType.Node, tag, attrs: {}, content: [] }; const anchor = xmlDoc.createElement(tag);
block.insert(anchor); block.insert(anchor);
} }
@@ -247,7 +258,7 @@ export class QWebCompiler {
this.addLine(``); this.addLine(``);
for (let block of this.blocks) { for (let block of this.blocks) {
if (block.dom) { if (block.dom) {
this.addLine(`let ${block.blockName} = createBlock(\`${domToString(block.dom)}\`);`); this.addLine(`let ${block.blockName} = createBlock(\`${block.asXmlString()}\`);`);
} }
} }
} }
@@ -395,7 +406,7 @@ export class QWebCompiler {
block = this.createBlock(block, "block", ctx); block = this.createBlock(block, "block", ctx);
this.blocks.push(block); this.blocks.push(block);
} }
const text: Dom = { type: DomType.Comment, value: ast.value }; const text = xmlDoc.createComment(ast.value);
block!.insert(text); block!.insert(text);
if (isNewBlock) { if (isNewBlock) {
this.insertBlock("", block!, ctx); this.insertBlock("", block!, ctx);
@@ -411,9 +422,8 @@ export class QWebCompiler {
forceNewBlock: forceNewBlock && !block, forceNewBlock: forceNewBlock && !block,
}); });
} else { } else {
const type = ast.type === ASTType.Text ? DomType.Text : DomType.Comment; const createFn = ast.type === ASTType.Text ? xmlDoc.createTextNode : xmlDoc.createComment;
const text: Dom = { type, value: ast.value }; block.insert(createFn.call(xmlDoc, ast.value));
block.insert(text);
} }
} }
@@ -499,7 +509,12 @@ export class QWebCompiler {
} }
} }
const dom: Dom = { type: DomType.Node, tag: ast.tag, attrs: attrs, content: [] }; const dom = xmlDoc.createElement(ast.tag);
for (const [attr, val] of Object.entries(attrs)) {
if (!(attr === "class" && val === "")) {
dom.setAttribute(attr, val);
}
}
block!.insert(dom); block!.insert(dom);
if (ast.content.length) { if (ast.content.length) {
const initialDom = block!.currentDom; const initialDom = block!.currentDom;
@@ -553,7 +568,7 @@ export class QWebCompiler {
this.insertBlock(`text(${expr})`, block, { ...ctx, forceNewBlock: forceNewBlock && !block }); this.insertBlock(`text(${expr})`, block, { ...ctx, forceNewBlock: forceNewBlock && !block });
} else { } else {
const idx = block.insertData(expr); const idx = block.insertData(expr);
const text: Dom = { type: DomType.Node, tag: `block-text-${idx}`, attrs: {}, content: [] }; const text = xmlDoc.createElement(`block-text-${idx}`);
block.insert(text); block.insert(text);
} }
} }
-74
View File
@@ -1,74 +0,0 @@
export const enum DomType {
Text,
Comment,
Node,
}
export interface DomText {
type: DomType.Text;
value: string;
}
export interface DomComment {
type: DomType.Comment;
value: string;
}
export interface DomNode {
type: DomType.Node;
tag: string;
attrs: { [key: string]: string };
content: Dom[];
}
export type Dom = DomText | DomComment | DomNode;
function escape(str: string): string {
const p = document.createElement("p");
p.textContent = str;
return p.innerHTML;
}
export function domToString(dom: Dom): string {
switch (dom.type) {
case DomType.Text:
return escape(dom.value);
case DomType.Comment:
return `<!--${dom.value}-->`;
case DomType.Node:
const content = dom.content.map(domToString).join("");
const attrs: string[] = [];
for (let [key, value] of Object.entries(dom.attrs)) {
if (!(key === "class" && value === "")) {
attrs.push(`${key}="${escape(value)}"`);
}
}
if (content) {
return `<${dom.tag}${attrs.length ? " " + attrs.join(" ") : ""}>${content}</${dom.tag}>`;
} else {
return `<${dom.tag}${attrs.length ? " " + attrs.join(" ") : ""}/>`;
}
}
}
export function isProp(tag: string, key: string): boolean {
switch (tag) {
case "input":
return (
key === "checked" ||
key === "indeterminate" ||
key === "value" ||
key === "readonly" ||
key === "disabled"
);
case "option":
return key === "selected" || key === "disabled";
case "textarea":
return key === "readonly" || key === "disabled";
break;
case "button":
case "select":
case "optgroup":
return key === "disabled";
}
return false;
}