UNPKG

@v4fire/client

Version:

V4Fire client core library

281 lines (232 loc) • 6.01 kB
/*! * V4Fire Client Core * https://github.com/V4Fire/Client * * Released under the MIT license * https://github.com/V4Fire/Client/blob/master/LICENSE */ /** * [[include:super/i-block/modules/vdom/README.md]] * @packageDocumentation */ import { execRenderObject, RenderObject, VNode, ScopedSlot } from 'core/component'; import type iBlock from 'super/i-block/i-block'; import Friend from 'super/i-block/modules/friend'; import Opt from 'super/i-block/modules/opt'; import Field from 'super/i-block/modules/field'; import Provide from 'super/i-block/modules/provide'; import { tplCache } from 'super/i-block/modules/vdom/const'; import type { RenderFn, RenderPath, RenderContext } from 'super/i-block/modules/vdom/interface'; export * from 'super/i-block/modules/vdom/interface'; /** * Class provides API to work with a VDOM tree */ export default class VDOM extends Friend { /** * Renders the specified data * @param data * * @example * ```js * this.render(this.$createElement('b-example', { * attrs: { * prop1: 'foo' * } * })); * ``` */ render(data: VNode): Node; render(data: VNode[]): Node[]; render(data: CanArray<VNode>): CanArray<Node> { return this.ctx.$renderEngine.renderVNode(Object.cast(data), this.ctx); } /** * Returns a render object by the specified path * @param path - path to a template * * @example * ```js * // Returns the main render object of bExample * this.getRenderObject('bExample/'); * this.getRenderObject('bExample.index'); * * this.getRenderObject('bExample.subTemplate'); * ``` */ getRenderObject(path: string): CanUndef<RenderObject> { const chunks = path.split('.'); if (path.endsWith('/')) { const l = chunks.length - 1; chunks[l] = chunks[l].slice(0, -1); chunks.push('index'); } if (chunks.length === 1) { chunks.unshift(this.ctx.componentName); } else { chunks[0] = chunks[0].dasherize(); } const key = chunks.join('.'), cache = tplCache[key]; if (cache) { return cache; } const tpl = TPLS[chunks[0]]; if (!tpl) { return; } const fn = Object.get(tpl, chunks.slice(1)); if (Object.isFunction(fn)) { return tplCache[key] = fn(); } } /** * Returns a function that executes the specified render object * * @param objOrPath - render object or path to a template * @param [ctx] - render context * * @example * ```js * this.bindRenderObject(this.getRenderObject('bExample/')); * this.bindRenderObject('bExample.subTemplate'); * ``` */ bindRenderObject(objOrPath: RenderPath, ctx?: RenderContext): RenderFn { const renderObj = Object.isString(objOrPath) ? this.getRenderObject(objOrPath) : objOrPath; if (!renderObj) { return () => this.ctx.$createElement('span'); } let instanceCtx, renderCtx; if (ctx && Object.isArray(ctx)) { instanceCtx = ctx[0] ?? this.ctx; renderCtx = ctx[1]; if (instanceCtx !== instanceCtx.provide.component) { instanceCtx.field = new Field(instanceCtx); instanceCtx.provide = new Provide(instanceCtx); instanceCtx.opts = new Opt(instanceCtx); } } else { instanceCtx = this.ctx; renderCtx = ctx; } return (p) => { instanceCtx = Object.create(instanceCtx); instanceCtx.isVirtualTpl = true; if (p) { for (let keys = Object.keys(p), i = 0; i < keys.length; i++) { const key = keys[i], value = p[key]; if (key in instanceCtx) { Object.defineProperty(instanceCtx, key, { configurable: true, enumerable: true, writable: true, value }); } else { instanceCtx[key] = value; } } } const vnode = execRenderObject(renderObj, instanceCtx); if (renderCtx != null) { this.ctx.$renderEngine.patchVNode(vnode, instanceCtx, renderCtx); } return vnode; }; } /** * Executes the specified render object * * @param objOrPath - render object or path to a template * @param [ctx] - render context * * @example * ```js * this.execRenderObject(this.getRenderObject('bExample/')); * this.execRenderObject('bExample.subTemplate'); * ``` */ execRenderObject(objOrPath: RenderPath, ctx?: RenderContext): VNode { return this.bindRenderObject(objOrPath, ctx)(); } /** * Returns a link to the closest parent component from the current * @param component - component name or a link to the component constructor */ closest<T extends iBlock = iBlock>(component: string | ClassConstructor<any[], T> | Function): CanUndef<T> { const nm = Object.isString(component) ? component.dasherize() : undefined; let el = <CanUndef<T>>this.ctx.$parent; while (el) { if ((Object.isFunction(component) && el.instance instanceof component) || el.componentName === nm) { return el; } el = <CanUndef<T>>el.$parent; } return undefined; } /** * Searches an element by the specified name from a virtual node * * @param vnode * @param elName * @param [ctx] - component context */ findElFromVNode( vnode: VNode, elName: string, ctx: iBlock = this.component ): CanUndef<VNode> { const selector = ctx.provide.fullElName(elName); const search = (vnode) => { const data = vnode.data ?? {}; const classes = Object.fromArray( Array.concat([], (data.staticClass ?? '').split(' '), data.class) ); if (classes[selector] != null) { return vnode; } if (vnode.children != null) { for (let i = 0; i < vnode.children.length; i++) { const res = search(vnode.children[i]); if (res != null) { return res; } } } return undefined; }; return search(vnode); } /** * Returns a slot by the specified name * * @param name * @param [ctx] - component context to get the slot */ getSlot( name: string, ctx: iBlock = this.component ): CanUndef<VNode | ScopedSlot> { return Object.get(ctx, `$slots.${name}`) ?? Object.get(ctx, `$scopedSlots.${name}`); } }