mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] portal: compile t-portal in an internal Component Portal
This commit also clean-up the deepRemove for the Portal that is not needed any more.
This commit is contained in:
committed by
Aaron Bohy
parent
42a140a8e3
commit
41ad5db2e3
@@ -2,8 +2,6 @@ import { BDom, multi, text, toggler } from "../blockdom";
|
||||
import { validateProps } from "../component/props_validation";
|
||||
import { Markup } from "../utils";
|
||||
import { html } from "../blockdom/index";
|
||||
import { VPortal } from "../portal";
|
||||
|
||||
/**
|
||||
* This file contains utility functions that will be injected in each template,
|
||||
* to perform various useful tasks in the compiled code.
|
||||
@@ -13,16 +11,6 @@ function withDefault(value: any, defaultValue: any): any {
|
||||
return value === undefined || value === null || value === false ? defaultValue : value;
|
||||
}
|
||||
|
||||
function callPortal(
|
||||
ctx: any,
|
||||
parent: any,
|
||||
key: string,
|
||||
target: string,
|
||||
content: (ctx: any, node: any, key: string) => BDom
|
||||
): BDom {
|
||||
return new VPortal(target, content(ctx, parent, key)) as any;
|
||||
}
|
||||
|
||||
function callSlot(
|
||||
ctx: any,
|
||||
parent: any,
|
||||
@@ -198,7 +186,6 @@ export const UTILS = {
|
||||
zero: Symbol("zero"),
|
||||
isBoundary,
|
||||
callSlot,
|
||||
callPortal,
|
||||
capture,
|
||||
withKey,
|
||||
prepareList,
|
||||
|
||||
@@ -47,7 +47,7 @@ const cache: { [key: string]: BlockType } = {};
|
||||
* @param str
|
||||
* @returns a new block type, that can build concrete blocks
|
||||
*/
|
||||
export function createBlock(str: string, deepRemove: boolean = false): BlockType {
|
||||
export function createBlock(str: string): BlockType {
|
||||
if (str in cache) {
|
||||
return cache[str];
|
||||
}
|
||||
@@ -67,7 +67,7 @@ export function createBlock(str: string, deepRemove: boolean = false): BlockType
|
||||
|
||||
// step 3: build the final block class
|
||||
const template = tree.el as HTMLElement;
|
||||
const Block = buildBlock(template, context, deepRemove);
|
||||
const Block = buildBlock(template, context);
|
||||
cache[str] = Block;
|
||||
return Block;
|
||||
}
|
||||
@@ -422,7 +422,7 @@ function updateCtx(ctx: BlockCtx, tree: IntermediateTree) {
|
||||
// building the concrete block class
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
function buildBlock(template: HTMLElement, ctx: BlockCtx, deepRemove: boolean): BlockType {
|
||||
function buildBlock(template: HTMLElement, ctx: BlockCtx): BlockType {
|
||||
let B = createBlockClass(template, ctx);
|
||||
|
||||
if (ctx.cbRefs.length) {
|
||||
@@ -447,14 +447,6 @@ function buildBlock(template: HTMLElement, ctx: BlockCtx, deepRemove: boolean):
|
||||
}
|
||||
};
|
||||
B.prototype.beforeRemove = VMulti.prototype.beforeRemove;
|
||||
if (deepRemove) {
|
||||
const blockRemove = B.prototype.remove;
|
||||
const vMultiRemove = VMulti.prototype.remove;
|
||||
B.prototype.remove = function () {
|
||||
blockRemove.call(this);
|
||||
vMultiRemove.call(this);
|
||||
};
|
||||
}
|
||||
return (data?: any[], children: (VNode | undefined)[] = []) => new B(data, children);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,11 +17,9 @@ class VList {
|
||||
anchor: Node | undefined;
|
||||
parentEl?: HTMLElement | undefined;
|
||||
isOnlyChild?: boolean | undefined;
|
||||
deepRemove: boolean;
|
||||
|
||||
constructor(children: VNode[], deepRemove: boolean) {
|
||||
constructor(children: VNode[]) {
|
||||
this.children = children;
|
||||
this.deepRemove = deepRemove;
|
||||
}
|
||||
|
||||
mount(parent: HTMLElement, afterNode: Node | null) {
|
||||
@@ -77,7 +75,7 @@ class VList {
|
||||
const parent = this.parentEl!;
|
||||
|
||||
// fast path: no new child => only remove
|
||||
if (ch2.length === 0 && isOnlyChild && !this.deepRemove) {
|
||||
if (ch2.length === 0 && isOnlyChild) {
|
||||
if (withBeforeRemove) {
|
||||
for (let i = 0, l = ch1.length; i < l; i++) {
|
||||
beforeRemove.call(ch1[i]);
|
||||
@@ -203,7 +201,7 @@ class VList {
|
||||
|
||||
remove() {
|
||||
const { parentEl, anchor } = this;
|
||||
if (this.isOnlyChild && !this.deepRemove) {
|
||||
if (this.isOnlyChild) {
|
||||
nodeSetTextContent.call(parentEl, "");
|
||||
} else {
|
||||
const children = this.children;
|
||||
@@ -228,8 +226,8 @@ class VList {
|
||||
}
|
||||
}
|
||||
|
||||
export function list(children: VNode[], deepRemove = false): VNode<VList> {
|
||||
return new VList(children, deepRemove);
|
||||
export function list(children: VNode[]): VNode<VList> {
|
||||
return new VList(children);
|
||||
}
|
||||
|
||||
function createMapping(ch1: any[], startIdx1: number, endIdx2: number): { [key: string]: any } {
|
||||
|
||||
@@ -15,11 +15,9 @@ export class VMulti {
|
||||
anchors?: Node[] | undefined;
|
||||
parentEl?: HTMLElement | undefined;
|
||||
isOnlyChild?: boolean | undefined;
|
||||
deepRemove: boolean;
|
||||
|
||||
constructor(children: (VNode | undefined)[], deepRemove: boolean) {
|
||||
constructor(children: (VNode | undefined)[]) {
|
||||
this.children = children;
|
||||
this.deepRemove = deepRemove;
|
||||
}
|
||||
|
||||
mount(parent: HTMLElement, afterNode: Node | null) {
|
||||
@@ -105,7 +103,7 @@ export class VMulti {
|
||||
|
||||
remove() {
|
||||
const parentEl = this.parentEl;
|
||||
if (this.isOnlyChild && !this.deepRemove) {
|
||||
if (this.isOnlyChild) {
|
||||
nodeSetTextContent.call(parentEl, "");
|
||||
} else {
|
||||
const children = this.children;
|
||||
@@ -131,6 +129,6 @@ export class VMulti {
|
||||
}
|
||||
}
|
||||
|
||||
export function multi(children: (VNode | undefined)[], deepRemove = false): VNode<VMulti> {
|
||||
return new VMulti(children, deepRemove);
|
||||
export function multi(children: (VNode | undefined)[]): VNode<VMulti> {
|
||||
return new VMulti(children);
|
||||
}
|
||||
|
||||
@@ -65,15 +65,13 @@ class BlockDescription {
|
||||
type: BlockType;
|
||||
parentVar: string = "";
|
||||
id: number;
|
||||
deepRemove: boolean;
|
||||
|
||||
constructor(target: CodeTarget, type: BlockType, deepRemove: boolean = false) {
|
||||
constructor(target: CodeTarget, type: BlockType) {
|
||||
this.id = BlockDescription.nextBlockId++;
|
||||
this.varName = "b" + this.id;
|
||||
this.blockName = "block" + this.id;
|
||||
this.target = target;
|
||||
this.type = type;
|
||||
this.deepRemove = deepRemove;
|
||||
}
|
||||
|
||||
insertData(str: string, prefix: string = "d"): number {
|
||||
@@ -102,7 +100,7 @@ class BlockDescription {
|
||||
}
|
||||
return `${this.blockName}(${params})`;
|
||||
} else if (this.type === "list") {
|
||||
return `list(c_block${this.id}${this.deepRemove ? ", true" : ""})`;
|
||||
return `list(c_block${this.id})`;
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
@@ -253,8 +251,6 @@ export class CodeGenerator {
|
||||
mainCode.push(`const ${id} = getTemplate(${template});`);
|
||||
}
|
||||
|
||||
const deepRemove = "deepRemove" in this.ast ? this.ast.deepRemove : false;
|
||||
|
||||
// define all blocks
|
||||
if (this.blocks.length) {
|
||||
mainCode.push(``);
|
||||
@@ -264,15 +260,9 @@ export class CodeGenerator {
|
||||
if (block.dynamicTagName) {
|
||||
xmlString = xmlString.replace(/^<\w+/, `<\${tag || '${block.dom.nodeName}'}`);
|
||||
xmlString = xmlString.replace(/\w+>$/, `\${tag || '${block.dom.nodeName}'}>`);
|
||||
mainCode.push(
|
||||
`let ${block.blockName} = tag => createBlock(\`${xmlString}\`${
|
||||
deepRemove ? ", true" : ""
|
||||
});`
|
||||
);
|
||||
mainCode.push(`let ${block.blockName} = tag => createBlock(\`${xmlString}\`);`);
|
||||
} else {
|
||||
mainCode.push(
|
||||
`let ${block.blockName} = createBlock(\`${xmlString}\`${deepRemove ? ", true" : ""});`
|
||||
);
|
||||
mainCode.push(`let ${block.blockName} = createBlock(\`${xmlString}\`);`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -328,11 +318,10 @@ export class CodeGenerator {
|
||||
createBlock(
|
||||
parentBlock: BlockDescription | null,
|
||||
type: BlockType,
|
||||
ctx: Context,
|
||||
deepRemove: boolean = false
|
||||
ctx: Context
|
||||
): BlockDescription {
|
||||
const hasRoot = this.target.hasRoot;
|
||||
const block = new BlockDescription(this.target, type, deepRemove);
|
||||
const block = new BlockDescription(this.target, type);
|
||||
if (!hasRoot && !ctx.preventRoot) {
|
||||
this.target.hasRoot = true;
|
||||
block.isRoot = true;
|
||||
@@ -718,7 +707,7 @@ export class CodeGenerator {
|
||||
if (ast.body) {
|
||||
const nextId = BlockDescription.nextBlockId;
|
||||
const subCtx: Context = createContext(ctx);
|
||||
this.compileAST({ type: ASTType.Multi, content: ast.body, deepRemove: false }, subCtx);
|
||||
this.compileAST({ type: ASTType.Multi, content: ast.body }, subCtx);
|
||||
this.helpers.add("withDefault");
|
||||
expr = `withDefault(${expr}, b${nextId})`;
|
||||
}
|
||||
@@ -779,7 +768,7 @@ export class CodeGenerator {
|
||||
|
||||
// note: this part is duplicated from end of compilemulti:
|
||||
const args = block!.children.map((c) => c.varName).join(", ");
|
||||
this.insertBlock(`multi([${args}]${ast.deepRemove ? ", true" : ""})`, block!, ctx)!;
|
||||
this.insertBlock(`multi([${args}])`, block!, ctx)!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -788,7 +777,7 @@ export class CodeGenerator {
|
||||
if (block) {
|
||||
this.insertAnchor(block);
|
||||
}
|
||||
block = this.createBlock(block, "list", ctx, ast.deepRemove);
|
||||
block = this.createBlock(block, "list", ctx);
|
||||
this.target.loopLevel++;
|
||||
const loopVar = `i${this.target.loopLevel}`;
|
||||
this.addLine(`ctx = Object.create(ctx);`);
|
||||
@@ -923,7 +912,7 @@ export class CodeGenerator {
|
||||
}
|
||||
|
||||
const args = block!.children.map((c) => c.varName).join(", ");
|
||||
this.insertBlock(`multi([${args}]${ast.deepRemove ? ", true" : ""})`, block!, ctx)!;
|
||||
this.insertBlock(`multi([${args}])`, block!, ctx)!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -935,7 +924,7 @@ export class CodeGenerator {
|
||||
this.helpers.add("isBoundary");
|
||||
const nextId = BlockDescription.nextBlockId;
|
||||
const subCtx: Context = createContext(ctx, { preventRoot: true });
|
||||
this.compileAST({ type: ASTType.Multi, content: ast.body, deepRemove: false }, subCtx);
|
||||
this.compileAST({ type: ASTType.Multi, content: ast.body }, subCtx);
|
||||
if (nextId !== BlockDescription.nextBlockId) {
|
||||
this.helpers.add("zero");
|
||||
this.addLine(`ctx[zero] = b${nextId};`);
|
||||
@@ -990,7 +979,7 @@ export class CodeGenerator {
|
||||
const expr = ast.value ? compileExpr(ast.value || "") : "null";
|
||||
if (ast.body) {
|
||||
this.helpers.add("LazyValue");
|
||||
const bodyAst: AST = { type: ASTType.Multi, content: ast.body, deepRemove: false };
|
||||
const bodyAst: AST = { type: ASTType.Multi, content: ast.body };
|
||||
const name = this.compileInNewTarget("value", bodyAst, ctx);
|
||||
let value = `new LazyValue(${name}, ctx, node)`;
|
||||
value = ast.value ? (value ? `withDefault(${expr}, ${value})` : expr) : value;
|
||||
@@ -1177,10 +1166,18 @@ export class CodeGenerator {
|
||||
}
|
||||
}
|
||||
compileTPortal(ast: ASTTPortal, ctx: Context) {
|
||||
this.helpers.add("callPortal");
|
||||
this.helpers.add("Portal");
|
||||
|
||||
let { block } = ctx;
|
||||
const name = this.compileInNewTarget("portalContent", ast.content, ctx);
|
||||
const blockString = `callPortal(ctx, node, key, ${ast.target}, ${name})`;
|
||||
const name = this.compileInNewTarget("slot", ast.content, ctx);
|
||||
const key = this.generateComponentKey();
|
||||
let ctxStr = "ctx";
|
||||
if (this.target.loopLevel || !this.hasSafeContext) {
|
||||
ctxStr = this.generateId("ctx");
|
||||
this.helpers.add("capture");
|
||||
this.addLine(`const ${ctxStr} = capture(ctx);`);
|
||||
}
|
||||
const blockString = `component(Portal, {target: ${ast.target},slots: {'default': {__render: ${name}, __ctx: ${ctxStr}}}}, key + \`${key}\`, node, ctx)`;
|
||||
if (block) {
|
||||
this.insertAnchor(block);
|
||||
}
|
||||
|
||||
+1
-61
@@ -56,7 +56,6 @@ export interface ASTDomNode {
|
||||
export interface ASTMulti {
|
||||
type: ASTType.Multi;
|
||||
content: AST[];
|
||||
deepRemove: boolean;
|
||||
}
|
||||
|
||||
export interface ASTTEsc {
|
||||
@@ -77,7 +76,6 @@ export interface ASTTif {
|
||||
content: AST;
|
||||
tElif: { condition: string; content: AST }[] | null;
|
||||
tElse: AST | null;
|
||||
deepRemove: boolean;
|
||||
}
|
||||
|
||||
export interface ASTTSet {
|
||||
@@ -95,7 +93,6 @@ export interface ASTTForEach {
|
||||
key: string | null;
|
||||
body: AST;
|
||||
memo: string;
|
||||
deepRemove: boolean;
|
||||
hasNoFirst: boolean;
|
||||
hasNoLast: boolean;
|
||||
hasNoIndex: boolean;
|
||||
@@ -484,7 +481,6 @@ function parseTForEach(node: Element, ctx: ParsingContext): AST | null {
|
||||
elem,
|
||||
body,
|
||||
memo,
|
||||
deepRemove: needDeepRemove(body),
|
||||
key,
|
||||
hasNoFirst,
|
||||
hasNoLast,
|
||||
@@ -493,44 +489,6 @@ function parseTForEach(node: Element, ctx: ParsingContext): AST | null {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns true if we are sure that a deep remove (without optimisation) is needed, for exemple
|
||||
* if there is a portal.
|
||||
*/
|
||||
function needDeepRemove(ast: AST): boolean {
|
||||
switch (ast.type) {
|
||||
case ASTType.Multi:
|
||||
case ASTType.TForEach:
|
||||
case ASTType.TIf:
|
||||
return ast.deepRemove;
|
||||
|
||||
case ASTType.TPortal:
|
||||
return true;
|
||||
|
||||
case ASTType.TComponent:
|
||||
case ASTType.TOut:
|
||||
case ASTType.TCall:
|
||||
case ASTType.TCallBlock:
|
||||
case ASTType.TSlot:
|
||||
case ASTType.Text:
|
||||
case ASTType.Comment:
|
||||
case ASTType.TEsc:
|
||||
return false;
|
||||
|
||||
case ASTType.TKey:
|
||||
return needDeepRemove(ast.content);
|
||||
case ASTType.TDebug:
|
||||
case ASTType.TLog:
|
||||
case ASTType.TTranslation:
|
||||
return ast.content ? needDeepRemove(ast.content) : false;
|
||||
case ASTType.TSet:
|
||||
return ast.body ? ast.body.some((ast) => needDeepRemove(ast)) : false;
|
||||
|
||||
case ASTType.DomNode:
|
||||
return ast.content.some((ast) => needDeepRemove(ast));
|
||||
}
|
||||
}
|
||||
|
||||
function parseTKey(node: Element, ctx: ParsingContext): AST | null {
|
||||
if (!node.hasAttribute("t-key")) {
|
||||
return null;
|
||||
@@ -628,21 +586,12 @@ function parseTIf(node: Element, ctx: ParsingContext): AST | null {
|
||||
nextElement.remove();
|
||||
}
|
||||
|
||||
let deepRemove = needDeepRemove(content);
|
||||
if (tElifs) {
|
||||
deepRemove = deepRemove || tElifs.some((ast) => needDeepRemove(ast));
|
||||
}
|
||||
if (tElse) {
|
||||
deepRemove = deepRemove || needDeepRemove(tElse);
|
||||
}
|
||||
|
||||
return {
|
||||
type: ASTType.TIf,
|
||||
condition,
|
||||
content,
|
||||
tElif: tElifs.length ? tElifs : null,
|
||||
tElse,
|
||||
deepRemove,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -814,11 +763,6 @@ function parseTPortal(node: Element, ctx: ParsingContext): AST | null {
|
||||
if (!node.hasAttribute("t-portal")) {
|
||||
return null;
|
||||
}
|
||||
if (node.tagName !== "t") {
|
||||
throw new Error(
|
||||
`Directive 't-portal' can only be used on <t> nodes (used on a <${node.tagName}>)`
|
||||
);
|
||||
}
|
||||
const target = node.getAttribute("t-portal")!;
|
||||
node.removeAttribute("t-portal");
|
||||
const content = parseNode(node, ctx);
|
||||
@@ -869,11 +813,7 @@ function parseChildNodes(node: Node, ctx: ParsingContext): AST | null {
|
||||
case 1:
|
||||
return children[0];
|
||||
default:
|
||||
return {
|
||||
type: ASTType.Multi,
|
||||
content: children,
|
||||
deepRemove: children.some((ast) => needDeepRemove(ast)),
|
||||
};
|
||||
return { type: ASTType.Multi, content: children };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { UTILS } from "./app/template_helpers";
|
||||
import {
|
||||
config,
|
||||
createBlock,
|
||||
@@ -12,9 +13,11 @@ import {
|
||||
comment,
|
||||
} from "./blockdom";
|
||||
import { mainEventHandler } from "./component/handler";
|
||||
import { Portal } from "./portal";
|
||||
|
||||
config.shouldNormalizeDom = false;
|
||||
config.mainEventHandler = mainEventHandler;
|
||||
(UTILS as any).Portal = Portal;
|
||||
|
||||
export const blockDom = {
|
||||
config,
|
||||
|
||||
+26
-1
@@ -1,8 +1,11 @@
|
||||
import { onWillUnmount } from ".";
|
||||
import { xml } from "./app/template_set";
|
||||
import { BDom, text, VNode } from "./blockdom";
|
||||
import { Component } from "./component/component";
|
||||
|
||||
const VText: any = text("").constructor;
|
||||
|
||||
export class VPortal extends VText implements Partial<VNode<VPortal>> {
|
||||
class VPortal extends VText implements Partial<VNode<VPortal>> {
|
||||
// selector: string;
|
||||
realBDom: BDom | null;
|
||||
target: HTMLElement | null = null;
|
||||
@@ -47,3 +50,25 @@ export class VPortal extends VText implements Partial<VNode<VPortal>> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Portal extends Component {
|
||||
static template = xml`<t t-slot="default"/>`;
|
||||
static props = {
|
||||
target: {
|
||||
type: String,
|
||||
},
|
||||
slots: true,
|
||||
};
|
||||
|
||||
setup() {
|
||||
const node = this.__owl__;
|
||||
const renderFn = node.renderFn;
|
||||
node.renderFn = () => new VPortal(this.props.target, renderFn());
|
||||
onWillUnmount(async () => {
|
||||
await Promise.resolve();
|
||||
if (node.bdom && (node.bdom as any).realBDom) {
|
||||
(node.bdom as any).realBDom.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user