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