mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[REF] qweb, component: small code refactoring
This commit is contained in:
+21
-20
@@ -1,6 +1,6 @@
|
||||
import { EventBus } from "./event_bus";
|
||||
import { Observer } from "./observer";
|
||||
import { QWeb } from "./qweb";
|
||||
import { QWeb, CompiledTemplate } from "./qweb";
|
||||
import { h, patch, VNode } from "./vdom";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -28,6 +28,7 @@ interface Meta<T extends Env, Props> {
|
||||
renderPromise: Promise<VNode> | null;
|
||||
boundHandlers: { [key: number]: any };
|
||||
observer?: Observer;
|
||||
render?: CompiledTemplate;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -319,7 +320,6 @@ export class Component<
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
set(target: any, key: string | number, value: any) {
|
||||
this.__owl__.observer!.set(target, key, value);
|
||||
}
|
||||
@@ -349,26 +349,28 @@ export class Component<
|
||||
this.__owl__.vnode = patch(document.createElement(vnode.sel!), vnode);
|
||||
}
|
||||
}
|
||||
async _prepare(): Promise<VNode> {
|
||||
_prepare(): Promise<VNode> {
|
||||
this.__owl__.renderProps = this.props;
|
||||
this.__owl__.renderPromise = this.willStart().then(() => {
|
||||
if (this.__owl__.isDestroyed) {
|
||||
return Promise.resolve(h("div"));
|
||||
}
|
||||
this.__owl__.isStarted = true;
|
||||
if (this.inlineTemplate) {
|
||||
this.env.qweb.addTemplate(
|
||||
this.inlineTemplate,
|
||||
this.inlineTemplate,
|
||||
true
|
||||
);
|
||||
}
|
||||
this._observeState();
|
||||
return this._render();
|
||||
});
|
||||
this.__owl__.renderPromise = this._prepareAndRender();
|
||||
return this.__owl__.renderPromise;
|
||||
}
|
||||
|
||||
async _prepareAndRender(): Promise<VNode> {
|
||||
await this.willStart();
|
||||
if (this.__owl__.isDestroyed) {
|
||||
return Promise.resolve(h("div"));
|
||||
}
|
||||
this.__owl__.isStarted = true;
|
||||
if (this.inlineTemplate) {
|
||||
this.env.qweb.addTemplate(this.inlineTemplate, this.inlineTemplate, true);
|
||||
}
|
||||
this.__owl__.render = this.env.qweb.render.bind(
|
||||
this.env.qweb,
|
||||
this.inlineTemplate || this.template
|
||||
);
|
||||
this._observeState();
|
||||
return this._render();
|
||||
}
|
||||
async _render(
|
||||
force: boolean = false,
|
||||
patchQueue: any[] = []
|
||||
@@ -378,11 +380,10 @@ export class Component<
|
||||
}
|
||||
this.__owl__.renderId++;
|
||||
const promises: Promise<void>[] = [];
|
||||
const template = this.inlineTemplate || this.template;
|
||||
if (this.__owl__.observer) {
|
||||
this.__owl__.observer.allowMutations = false;
|
||||
}
|
||||
let vnode = this.env.qweb.render(template, this, {
|
||||
let vnode = this.__owl__.render!(this, {
|
||||
promises,
|
||||
handlers: this.__owl__.boundHandlers,
|
||||
forceUpdate: force,
|
||||
|
||||
+67
-64
@@ -5,9 +5,12 @@ import { VNode, h } from "./vdom";
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
export type EvalContext = { [key: string]: any };
|
||||
export type RawTemplate = string;
|
||||
export type CompiledTemplate<T> = (context: EvalContext, extra: any) => T;
|
||||
type ProcessedTemplate = Element;
|
||||
export type CompiledTemplate = (context: EvalContext, extra: any) => VNode;
|
||||
|
||||
interface Template {
|
||||
elem: Element;
|
||||
fn: CompiledTemplate;
|
||||
}
|
||||
|
||||
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(
|
||||
","
|
||||
@@ -34,6 +37,32 @@ const DISABLED_TAGS = [
|
||||
const lineBreakRE = /[\r\n]/;
|
||||
const whitespaceRE = /\s+/g;
|
||||
|
||||
function parseXML(xml: string): Document {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(xml, "text/xml");
|
||||
if (doc.getElementsByTagName("parsererror").length) {
|
||||
throw new Error("Invalid XML in template");
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
|
||||
const UTILS = {
|
||||
h: h,
|
||||
getFragment(str: string): DocumentFragment {
|
||||
const temp = document.createElement("template");
|
||||
temp.innerHTML = str;
|
||||
return temp.content;
|
||||
},
|
||||
objectToAttrString(obj: Object): string {
|
||||
let classes: string[] = [];
|
||||
for (let k in obj) {
|
||||
if (obj[k]) {
|
||||
classes.push(k);
|
||||
}
|
||||
}
|
||||
return classes.join(" ");
|
||||
}
|
||||
};
|
||||
//------------------------------------------------------------------------------
|
||||
// Compilation Context
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -181,12 +210,11 @@ export class Context {
|
||||
//------------------------------------------------------------------------------
|
||||
// QWeb rendering engine
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
export class QWeb {
|
||||
processedTemplates: { [name: string]: ProcessedTemplate } = {};
|
||||
templates: { [name: string]: CompiledTemplate<VNode> } = {};
|
||||
templates: { [name: string]: Template } = {};
|
||||
directives: Directive[] = [];
|
||||
directiveNames: { [key: string]: 1 };
|
||||
utils = UTILS;
|
||||
|
||||
constructor(data?: string) {
|
||||
this.directiveNames = {
|
||||
@@ -221,24 +249,6 @@ export class QWeb {
|
||||
}
|
||||
}
|
||||
|
||||
utils = {
|
||||
h: h,
|
||||
getFragment(str: string): DocumentFragment {
|
||||
const temp = document.createElement("template");
|
||||
temp.innerHTML = str;
|
||||
return temp.content;
|
||||
},
|
||||
objectToAttrString(obj: Object): string {
|
||||
let classes: string[] = [];
|
||||
for (let k in obj) {
|
||||
if (obj[k]) {
|
||||
classes.push(k);
|
||||
}
|
||||
}
|
||||
return classes.join(" ");
|
||||
}
|
||||
};
|
||||
|
||||
addDirective(dir: Directive) {
|
||||
this.directives.push(dir);
|
||||
this.directiveNames[dir.name] = 1;
|
||||
@@ -251,28 +261,33 @@ export class QWeb {
|
||||
*/
|
||||
addTemplate(
|
||||
name: string,
|
||||
template: RawTemplate,
|
||||
xmlString: string,
|
||||
allowDuplicates: boolean = false
|
||||
) {
|
||||
if (name in this.processedTemplates) {
|
||||
if (allowDuplicates) {
|
||||
return;
|
||||
} else {
|
||||
throw new Error(`Template ${name} already defined`);
|
||||
}
|
||||
if (name in this.templates && allowDuplicates) {
|
||||
return;
|
||||
}
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(template, "text/xml");
|
||||
const doc = parseXML(xmlString);
|
||||
if (!doc.firstChild) {
|
||||
throw new Error("Invalid template (should not be empty)");
|
||||
}
|
||||
if (doc.getElementsByTagName("parsererror").length) {
|
||||
throw new Error("Invalid XML in template");
|
||||
}
|
||||
let elem = doc.firstChild as Element;
|
||||
this._processTemplate(elem);
|
||||
this._addTemplate(name, <Element>doc.firstChild);
|
||||
}
|
||||
|
||||
this.processedTemplates[name] = elem;
|
||||
_addTemplate(name: string, elem: Element) {
|
||||
if (name in this.templates) {
|
||||
throw new Error(`Template ${name} already defined`);
|
||||
}
|
||||
this._processTemplate(elem);
|
||||
const template = {
|
||||
elem,
|
||||
fn: (context, extra) => {
|
||||
const compiledFunction = this._compile(name, elem);
|
||||
template.fn = compiledFunction;
|
||||
return compiledFunction.call(this, context, extra);
|
||||
}
|
||||
};
|
||||
this.templates[name] = template;
|
||||
}
|
||||
|
||||
_processTemplate(elem: Element) {
|
||||
@@ -322,19 +337,14 @@ export class QWeb {
|
||||
* the name given by the t-name attribute.
|
||||
*/
|
||||
loadTemplates(xmlstr: string) {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(xmlstr, "text/xml");
|
||||
if (doc.getElementsByTagName("parsererror").length) {
|
||||
throw new Error("Invalid XML in template");
|
||||
}
|
||||
const doc = parseXML(xmlstr);
|
||||
const templates = doc.getElementsByTagName("templates")[0];
|
||||
if (!templates) {
|
||||
return;
|
||||
}
|
||||
for (let elem of <any>templates.children) {
|
||||
const name = elem.getAttribute("t-name");
|
||||
this._processTemplate(elem);
|
||||
this.processedTemplates[name] = elem;
|
||||
this._addTemplate(name, elem);
|
||||
}
|
||||
}
|
||||
/**
|
||||
@@ -343,22 +353,17 @@ export class QWeb {
|
||||
* @param {string} name the template should already have been added
|
||||
*/
|
||||
render(name: string, context: EvalContext = {}, extra: any = null): VNode {
|
||||
if (!(name in this.processedTemplates)) {
|
||||
const template = this.templates[name];
|
||||
if (!template) {
|
||||
throw new Error(`Template ${name} does not exist`);
|
||||
}
|
||||
const template = this.templates[name] || this._compile(name);
|
||||
return template.call(this, context, extra);
|
||||
return template.fn.call(this, context, extra);
|
||||
}
|
||||
|
||||
_compile(name: string): CompiledTemplate<VNode> {
|
||||
if (name in this.templates) {
|
||||
return this.templates[name];
|
||||
}
|
||||
|
||||
const mainNode = this.processedTemplates[name];
|
||||
const isDebug = (<Element>mainNode).attributes.hasOwnProperty("t-debug");
|
||||
_compile(name: string, elem: Element): CompiledTemplate {
|
||||
const isDebug = elem.attributes.hasOwnProperty("t-debug");
|
||||
const ctx = new Context(name);
|
||||
this._compileNode(mainNode, ctx);
|
||||
this._compileNode(elem, ctx);
|
||||
|
||||
if (ctx.shouldProtectContext) {
|
||||
ctx.code.unshift(" context = Object.create(context);");
|
||||
@@ -379,7 +384,7 @@ export class QWeb {
|
||||
"context",
|
||||
"extra",
|
||||
ctx.code.join("\n")
|
||||
) as CompiledTemplate<VNode>;
|
||||
) as CompiledTemplate;
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Invalid generated code while compiling template '${ctx.templateName.replace(
|
||||
@@ -390,12 +395,10 @@ export class QWeb {
|
||||
}
|
||||
if (isDebug) {
|
||||
console.log(
|
||||
`Template: ${
|
||||
this.processedTemplates[name].outerHTML
|
||||
}\nCompiled code:\n` + template.toString()
|
||||
`Template: ${this.templates[name].elem.outerHTML}\nCompiled code:\n` +
|
||||
template.toString()
|
||||
);
|
||||
}
|
||||
this.templates[name] = template;
|
||||
return template;
|
||||
}
|
||||
|
||||
@@ -845,7 +848,7 @@ const callDirective: Directive = {
|
||||
throw new Error("Invalid tag for t-call directive (should be 't')");
|
||||
}
|
||||
const subTemplate = node.getAttribute("t-call")!;
|
||||
const nodeTemplate = qweb.processedTemplates[subTemplate];
|
||||
const nodeTemplate = qweb.templates[subTemplate];
|
||||
if (!nodeTemplate) {
|
||||
throw new Error(`Cannot find template "${subTemplate}" (t-call)`);
|
||||
}
|
||||
@@ -882,7 +885,7 @@ const callDirective: Directive = {
|
||||
.subContext("variables", Object.create(vars))
|
||||
.subContext("definedVariables", Object.create(definedVariables));
|
||||
|
||||
qweb._compileNode(nodeTemplate, subCtx);
|
||||
qweb._compileNode(nodeTemplate.elem, subCtx);
|
||||
|
||||
// close new scope
|
||||
if (hasNewVariables) {
|
||||
|
||||
@@ -1088,7 +1088,7 @@ describe("composition", () => {
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
expect(env.qweb.templates.parent.toString()).toMatchSnapshot();
|
||||
expect(env.qweb.templates.parent.fn.toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("sub widgets between t-ifs", async () => {
|
||||
@@ -1358,7 +1358,7 @@ describe("random stuff/miscellaneous", () => {
|
||||
|
||||
const widget = new Parent(env);
|
||||
await widget.mount(fixture);
|
||||
expect(env.qweb.templates.parent.toString()).toMatchSnapshot();
|
||||
expect(env.qweb.templates.parent.fn.toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("t-props should not be undefined (snapshotting)", async () => {
|
||||
@@ -1374,7 +1374,7 @@ describe("random stuff/miscellaneous", () => {
|
||||
|
||||
const widget = new Parent(env);
|
||||
await widget.mount(fixture);
|
||||
expect(env.qweb.templates.parent.toString()).toMatchSnapshot();
|
||||
expect(env.qweb.templates.parent.fn.toString()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+4
-4
@@ -29,7 +29,7 @@ function renderToDOM(
|
||||
|
||||
// we snapshot here the compiled code. This is useful to prevent unwanted code
|
||||
// change.
|
||||
expect(qweb.templates[template].toString()).toMatchSnapshot();
|
||||
expect(qweb.templates[template].fn.toString()).toMatchSnapshot();
|
||||
|
||||
if (vnode.sel === undefined) {
|
||||
return document.createTextNode(vnode.text!);
|
||||
@@ -991,7 +991,7 @@ describe("loading templates", () => {
|
||||
test("does not crash if string does not have templates", () => {
|
||||
const data = "";
|
||||
qweb.loadTemplates(data);
|
||||
expect(qweb.processedTemplates).toEqual({});
|
||||
expect(qweb.templates).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1104,7 +1104,7 @@ describe("debugging", () => {
|
||||
`<div t-debug="1"><t t-if="true"><span t-debug="1">hey</span></t></div>`
|
||||
);
|
||||
qweb.render('test');
|
||||
expect(qweb.templates.test.toString()).toMatchSnapshot();
|
||||
expect(qweb.templates.test.fn.toString()).toMatchSnapshot();
|
||||
|
||||
expect(console.log).toHaveBeenCalledTimes(1);
|
||||
console.log = consoleLog;
|
||||
@@ -1122,7 +1122,7 @@ describe("debugging", () => {
|
||||
</div>`
|
||||
);
|
||||
qweb.render('test');
|
||||
expect(qweb.templates.test.toString()).toMatchSnapshot();
|
||||
expect(qweb.templates.test.fn.toString()).toMatchSnapshot();
|
||||
|
||||
expect(console.log).toHaveBeenCalledWith(45);
|
||||
console.log = consoleLog;
|
||||
|
||||
Reference in New Issue
Block a user