mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[FIX] component: scope issue with slots
Slot templates need to be able to access variables from the parent scope. closes #228
This commit is contained in:
+14
-10
@@ -274,7 +274,7 @@ export class Component<T extends Env, Props extends {}, State extends {}> {
|
||||
}
|
||||
}
|
||||
|
||||
async render(force: boolean = false, patchQueue?: any[]): Promise<void> {
|
||||
async render(force: boolean = false, patchQueue?: any[], scope?: any, vars?: any): Promise<void> {
|
||||
const __owl__ = this.__owl__;
|
||||
if (!__owl__.isMounted) {
|
||||
return;
|
||||
@@ -283,7 +283,7 @@ export class Component<T extends Env, Props extends {}, State extends {}> {
|
||||
if (shouldPatch) {
|
||||
patchQueue = [];
|
||||
}
|
||||
const renderVDom = this.__render(force, patchQueue);
|
||||
const renderVDom = this.__render(force, patchQueue, scope, vars);
|
||||
const renderId = __owl__.renderId;
|
||||
await renderVDom;
|
||||
|
||||
@@ -417,7 +417,9 @@ export class Component<T extends Env, Props extends {}, State extends {}> {
|
||||
async __updateProps(
|
||||
nextProps: Props,
|
||||
forceUpdate: boolean = false,
|
||||
patchQueue?: any[]
|
||||
patchQueue?: any[],
|
||||
scope?: any,
|
||||
vars?: any,
|
||||
): Promise<void> {
|
||||
const shouldUpdate = forceUpdate || this.shouldUpdate(nextProps);
|
||||
if (shouldUpdate) {
|
||||
@@ -427,7 +429,7 @@ export class Component<T extends Env, Props extends {}, State extends {}> {
|
||||
}
|
||||
await this.willUpdateProps(nextProps);
|
||||
this.props = nextProps;
|
||||
await this.render(forceUpdate, patchQueue);
|
||||
await this.render(forceUpdate, patchQueue, scope, vars);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -441,14 +443,14 @@ export class Component<T extends Env, Props extends {}, State extends {}> {
|
||||
__owl__.vnode = patch(target, vnode);
|
||||
}
|
||||
|
||||
__prepare(): Promise<VNode> {
|
||||
__prepare(scope?: Object, vars?: any): Promise<VNode> {
|
||||
const __owl__ = this.__owl__;
|
||||
__owl__.renderProps = this.props;
|
||||
__owl__.renderPromise = this.__prepareAndRender();
|
||||
__owl__.renderPromise = this.__prepareAndRender(scope, vars);
|
||||
return __owl__.renderPromise;
|
||||
}
|
||||
|
||||
async __prepareAndRender(): Promise<VNode> {
|
||||
async __prepareAndRender(scope?: Object, vars?: any): Promise<VNode> {
|
||||
await this.willStart();
|
||||
const __owl__ = this.__owl__;
|
||||
if (__owl__.isDestroyed) {
|
||||
@@ -480,10 +482,10 @@ export class Component<T extends Env, Props extends {}, State extends {}> {
|
||||
}
|
||||
__owl__.render = qweb.render.bind(qweb, this.template);
|
||||
this.__observeState();
|
||||
return this.__render();
|
||||
return this.__render(false, [], scope, vars);
|
||||
}
|
||||
|
||||
async __render(force: boolean = false, patchQueue: any[] = []): Promise<VNode> {
|
||||
async __render(force: boolean = false, patchQueue: any[] = [], scope?: Object, vars?: any): Promise<VNode> {
|
||||
const __owl__ = this.__owl__;
|
||||
__owl__.renderId++;
|
||||
const promises: Promise<void>[] = [];
|
||||
@@ -499,7 +501,9 @@ export class Component<T extends Env, Props extends {}, State extends {}> {
|
||||
handlers: __owl__.boundHandlers,
|
||||
mountedHandlers: __owl__.mountedHandlers,
|
||||
forceUpdate: force,
|
||||
patchQueue
|
||||
patchQueue,
|
||||
scope,
|
||||
vars
|
||||
});
|
||||
patch.push(vnode);
|
||||
if (__owl__.observer) {
|
||||
|
||||
+59
-24
@@ -288,45 +288,44 @@ export class QWeb extends EventBus {
|
||||
return template.fn.call(this, context, extra);
|
||||
}
|
||||
|
||||
_compile(name: string, elem: Element, parentNode?: number): CompiledTemplate {
|
||||
_compile(name: string, elem: Element, parentContext?: Context): CompiledTemplate {
|
||||
const isDebug = elem.attributes.hasOwnProperty("t-debug");
|
||||
const ctx = new Context(name);
|
||||
if (parentNode) {
|
||||
ctx.nextID = parentNode + 1;
|
||||
ctx.parentNode = parentNode;
|
||||
if (parentContext) {
|
||||
ctx.variables = Object.create(parentContext.variables);
|
||||
ctx.nextID = parentContext.parentNode! + 1;
|
||||
ctx.parentNode = parentContext.parentNode!;
|
||||
ctx.allowMultipleRoots = true;
|
||||
ctx.addLine(`let c${parentNode} = extra.parentNode;`);
|
||||
ctx.addLine(`let c${ctx.parentNode} = extra.parentNode;`);
|
||||
|
||||
for (let v in parentContext.variables) {
|
||||
let variable = <any>parentContext.variables[v];
|
||||
if (variable.id) {
|
||||
ctx.addLine(`let ${variable.id} = extra.vars.${variable.id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (parentContext) {
|
||||
ctx.addLine(" Object.assign(context, extra.scope);");
|
||||
}
|
||||
this._compileNode(elem, ctx);
|
||||
|
||||
if (ctx.shouldProtectContext) {
|
||||
ctx.code.unshift(" context = Object.create(context);");
|
||||
}
|
||||
if (ctx.shouldDefineOwner) {
|
||||
// this is necessary to prevent some directives (t-forach for ex) to
|
||||
// pollute the rendering context by adding some keys in it.
|
||||
ctx.code.unshift(" let owner = context;");
|
||||
}
|
||||
if (ctx.shouldDefineQWeb) {
|
||||
ctx.code.unshift(" let QWeb = this.constructor;");
|
||||
}
|
||||
if (ctx.shouldDefineUtils) {
|
||||
ctx.code.unshift(" let utils = this.utils;");
|
||||
}
|
||||
|
||||
if (!parentNode) {
|
||||
if (!parentContext) {
|
||||
if (!ctx.rootNode) {
|
||||
throw new Error("A template should have one root node");
|
||||
}
|
||||
ctx.addLine(`return vn${ctx.rootNode};`);
|
||||
}
|
||||
|
||||
let code = ctx.generateCode();
|
||||
|
||||
let template;
|
||||
try {
|
||||
template = new Function("context", "extra", ctx.code.join("\n")) as CompiledTemplate;
|
||||
template = new Function("context", "extra", code.join("\n")) as CompiledTemplate;
|
||||
} catch (e) {
|
||||
const templateName = ctx.templateName.replace(/`/g, "'");
|
||||
console.groupCollapsed(`Invalid Code generated by ${templateName}`);
|
||||
console.warn(ctx.code.join("\n"));
|
||||
console.warn(code.join("\n"));
|
||||
console.groupEnd();
|
||||
throw new Error(
|
||||
`Invalid generated code while compiling template '${templateName}': ${e.message}`
|
||||
@@ -672,10 +671,12 @@ export class Context {
|
||||
shouldDefineQWeb: boolean = false;
|
||||
shouldDefineUtils: boolean = false;
|
||||
shouldProtectContext: boolean = false;
|
||||
shouldTrackScope: boolean = false;
|
||||
inLoop: boolean = false;
|
||||
inPreTag: boolean = false;
|
||||
templateName: string;
|
||||
allowMultipleRoots: boolean = false;
|
||||
scopeVars: any[] = [];
|
||||
|
||||
constructor(name?: string) {
|
||||
this.rootContext = this;
|
||||
@@ -688,6 +689,34 @@ export class Context {
|
||||
return id;
|
||||
}
|
||||
|
||||
generateCode(): string[] {
|
||||
const shouldTrackScope = this.shouldTrackScope && this.scopeVars.length;
|
||||
if (shouldTrackScope) {
|
||||
// add some vars to scope if needed
|
||||
for (let scopeVar of this.scopeVars.reverse()) {
|
||||
let { index, key, indent } = scopeVar;
|
||||
const prefix = new Array(indent + 2).join(" ");
|
||||
this.code.splice(index + 1, 0, prefix + `scope.${key} = context.${key};`);
|
||||
}
|
||||
this.code.unshift(" const scope = Object.create(null);");
|
||||
}
|
||||
if (this.shouldProtectContext) {
|
||||
this.code.unshift(" context = Object.create(context);");
|
||||
}
|
||||
if (this.shouldDefineOwner) {
|
||||
// this is necessary to prevent some directives (t-forach for ex) to
|
||||
// pollute the rendering context by adding some keys in it.
|
||||
this.code.unshift(" let owner = context;");
|
||||
}
|
||||
if (this.shouldDefineQWeb) {
|
||||
this.code.unshift(" let QWeb = this.constructor;");
|
||||
}
|
||||
if (this.shouldDefineUtils) {
|
||||
this.code.unshift(" let utils = this.utils;");
|
||||
}
|
||||
return this.code;
|
||||
}
|
||||
|
||||
withParent(node: number): Context {
|
||||
if (
|
||||
!this.allowMultipleRoots &&
|
||||
@@ -716,9 +745,15 @@ export class Context {
|
||||
this.indentLevel--;
|
||||
}
|
||||
|
||||
addLine(line: string) {
|
||||
addLine(line: string): number {
|
||||
const prefix = new Array(this.indentLevel + 2).join(" ");
|
||||
this.code.push(prefix + line);
|
||||
return this.code.length - 1;
|
||||
}
|
||||
|
||||
addToScope(key: string, expr: string) {
|
||||
const index = this.addLine(`context.${key} = ${expr};`);
|
||||
this.rootContext.scopeVars.push({ index, key, indent: this.indentLevel });
|
||||
}
|
||||
|
||||
addIf(condition: string) {
|
||||
|
||||
@@ -255,11 +255,11 @@ QWeb.addDirective({
|
||||
ctx.addLine(`var _length${keysID} = _${keysID}.length;`);
|
||||
ctx.addLine(`for (let i = 0; i < _length${keysID}; i++) {`);
|
||||
ctx.indent();
|
||||
ctx.addLine(`context.${name}_first = i === 0;`);
|
||||
ctx.addLine(`context.${name}_last = i === _length${keysID} - 1;`);
|
||||
ctx.addLine(`context.${name}_index = i;`);
|
||||
ctx.addLine(`context.${name} = _${keysID}[i];`);
|
||||
ctx.addLine(`context.${name}_value = _${valuesID}[i];`);
|
||||
ctx.addToScope(name + '_first', 'i === 0');
|
||||
ctx.addToScope(name + '_last', `i === _length${keysID} - 1`);
|
||||
ctx.addToScope(name + '_index', 'i');
|
||||
ctx.addToScope(name, `_${keysID}[i]`);
|
||||
ctx.addToScope(name + '_value', `_${valuesID}[i]`);
|
||||
const nodeCopy = <Element>node.cloneNode(true);
|
||||
let shouldWarn = nodeCopy.tagName !== "t" && !nodeCopy.hasAttribute("t-key");
|
||||
if (!shouldWarn && node.tagName === "t") {
|
||||
|
||||
+18
-6
@@ -556,7 +556,16 @@ QWeb.addDirective({
|
||||
ctx.addLine(`context.__owl__.cmap[${templateID}] = w${componentID}.__owl__.id;`);
|
||||
|
||||
// SLOTS
|
||||
if (node.childNodes.length) {
|
||||
const varDefs: string[] = [];
|
||||
const hasSlots = node.childNodes.length;
|
||||
if (hasSlots) {
|
||||
ctx.rootContext.shouldTrackScope = true;
|
||||
for (let v of Object.values(ctx.variables)) {
|
||||
if (v["id"]) {
|
||||
varDefs.push(v["id"]);
|
||||
}
|
||||
}
|
||||
|
||||
const clone = <Element>node.cloneNode(true);
|
||||
const slotNodes = clone.querySelectorAll("[t-set]");
|
||||
const slotId = qweb.nextSlotId++;
|
||||
@@ -567,7 +576,7 @@ QWeb.addDirective({
|
||||
slotNode.parentElement!.removeChild(slotNode);
|
||||
const key = slotNode.getAttribute("t-set")!;
|
||||
slotNode.removeAttribute("t-set");
|
||||
const slotFn = qweb._compile(`slot_${key}_template`, slotNode, ctx.parentNode!);
|
||||
const slotFn = qweb._compile(`slot_${key}_template`, slotNode, ctx);
|
||||
qweb.slots[`${slotId}_${key}`] = slotFn.bind(qweb);
|
||||
}
|
||||
}
|
||||
@@ -576,12 +585,14 @@ QWeb.addDirective({
|
||||
for (let child of Object.values(clone.childNodes)) {
|
||||
t.appendChild(child);
|
||||
}
|
||||
const slotFn = qweb._compile(`slot_default_template`, t, ctx.parentNode!);
|
||||
const slotFn = qweb._compile(`slot_default_template`, t, ctx);
|
||||
qweb.slots[`${slotId}_default`] = slotFn.bind(qweb);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.addLine(`def${defID} = w${componentID}.__prepare();`);
|
||||
const scopeVars =
|
||||
hasSlots && ctx.scopeVars.length ? `Object.assign({}, scope), {${varDefs.join(",")}}` : "";
|
||||
ctx.addLine(`def${defID} = w${componentID}.__prepare(${scopeVars});`);
|
||||
// hack: specify empty remove hook to prevent the node from being removed from the DOM
|
||||
ctx.addLine(
|
||||
`def${defID} = def${defID}.then(vnode=>{${createHook}let pvnode=h(vnode.sel, {key: ${templateID}, hook: {insert(vn) {let nvn=w${componentID}.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;${refExpr}${transitionsInsertCode}},remove() {},destroy(vn) {${finalizeComponentCode}}}});c${
|
||||
@@ -596,7 +607,8 @@ QWeb.addDirective({
|
||||
ctx.addLine(`utils.validateProps(w${componentID}.constructor, props${componentID})`);
|
||||
}
|
||||
ctx.addLine(
|
||||
`def${defID} = def${defID} || w${componentID}.__updateProps(props${componentID}, extra.forceUpdate, ${patchQueueCode});`
|
||||
`def${defID} = def${defID} || w${componentID}.__updateProps(props${componentID}, extra.forceUpdate, ${patchQueueCode}${scopeVars &&
|
||||
", " + scopeVars});`
|
||||
);
|
||||
let keepAliveCode = "";
|
||||
if (keepAlive) {
|
||||
@@ -774,7 +786,7 @@ QWeb.addDirective({
|
||||
ctx.addLine(
|
||||
`slot${slotKey}(context.__owl__.parent, Object.assign({}, extra, {parentNode: c${
|
||||
ctx.parentNode
|
||||
}}));`
|
||||
}, vars: extra.vars}));`
|
||||
);
|
||||
ctx.closeIf();
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user