diff --git a/djx/src/main/ts/tsx/Renderer.ts b/djx/src/main/ts/tsx/Renderer.ts new file mode 100644 --- /dev/null +++ b/djx/src/main/ts/tsx/Renderer.ts @@ -0,0 +1,26 @@ +import { Scope } from "./Scope"; +import { destroy, Rendition } from "./traits"; + +let _scope = Scope.dummy; + +const beginRender = async () => { +} + +const endRender = () => { +} + +export const getScope = () => _scope; + +export const render = async (rendition: () => Rendition, scope = Scope.dummy) => { + await beginRender(); + const prev = _scope; + _scope = scope; + try { + const node = rendition().getDomNode(); + scope.own(() => destroy(node)); + return node; + } finally { + _scope = prev; + endRender(); + } +} diff --git a/djx/src/main/ts/tsx/Scope.ts b/djx/src/main/ts/tsx/Scope.ts new file mode 100644 --- /dev/null +++ b/djx/src/main/ts/tsx/Scope.ts @@ -0,0 +1,40 @@ +import { IDestroyable, IRemovable } from "@implab/core-amd/interfaces"; +import { isDestroyable, isRemovable } from "@implab/core-amd/safe"; + +export interface IScope { + own(target: (() => void) | IDestroyable | IRemovable): void; +} + +export class Scope implements IDestroyable, IScope { + private readonly _cleanup: (() => void)[] = []; + + static readonly dummy: IScope = { own() { } }; + + own(target: (() => void) | IDestroyable | IRemovable) { + if (target instanceof Function) { + this._cleanup.push(target); + } else if (isDestroyable(target)) { + this._cleanup.push(() => target.destroy()); + } else if (isRemovable(target)) { + this._cleanup.push(() => target.remove()); + } + } + + clean() { + const guard = (cb: () => void) => { + try { + cb(); + } catch { + // guard + } + } + + this._cleanup.forEach(guard); + this._cleanup.length = 0; + } + + destroy() { + this.clean(); + } + +} \ No newline at end of file diff --git a/djx/src/main/ts/tsx/WatchRendition.ts b/djx/src/main/ts/tsx/WatchRendition.ts new file mode 100644 --- /dev/null +++ b/djx/src/main/ts/tsx/WatchRendition.ts @@ -0,0 +1,58 @@ +import { id as mid } from "module"; +import { TraceSource } from "@implab/core-amd/log/TraceSource"; +import { argumentNotNull } from "@implab/core-amd/safe"; +import { place } from "dojo/dom-construct"; +import { getScope, render } from "./Renderer"; +import { RenditionBase } from "./RenditionBase"; +import { Scope } from "./Scope"; +import { locateNode } from "./traits"; + +const trace = TraceSource.get(mid); + +export class WatchRendition extends RenditionBase { + private readonly _factory: (arg: T) => any; + + private _node: Node; + + private readonly _scope = new Scope(); + + constructor(component: (arg: T) => any, subject: any) { + super(); + argumentNotNull(component, "component"); + + this._factory = component; + + this._node = document.createComment("WatchRendition placeholder"); + } + + protected _create(attrs: object, children: any[]) { + const _attrs: any = attrs || {}; + const _children = children.map(x => this.getItemDom(x)); + this._node = this.getItemDom( + this._factory.call(null, { ..._attrs, children: _children }) + ); + + const scope = getScope(); + scope.own(this._scope); + + // если отрендерили текст? или DocumentFragment + } + + private async _render(value: T) { + const [refNode, position] = locateNode(this._node); + this._scope.clean(); + + this._node = await render(() => this._factory(value), this._scope); + + if (refNode) + place(this._node, refNode, position); + } + + protected _getDomNode() { + if (!this._node) + throw new Error("The instance of the widget isn't created"); + return this._node; + } + + +} diff --git a/djx/src/main/ts/tsx/traits.ts b/djx/src/main/ts/tsx/traits.ts --- a/djx/src/main/ts/tsx/traits.ts +++ b/djx/src/main/ts/tsx/traits.ts @@ -8,6 +8,8 @@ type _WidgetBaseConstructor = typeof _Wi export type DojoNodePosition = "first" | "after" | "before" | "last" | "replace" | "only" | number; +export type DojoNodeLocation = [Node | null, DojoNodePosition]; + export interface Rendition { getDomNode(): TNode; @@ -150,3 +152,10 @@ export function startupWidgets(target: N target.startup(); } } + +export function locateNode(node: Node): DojoNodeLocation { + const next = node.nextSibling; + return next ? + [next, "before"] : + [node.parentNode, "last"]; +} \ No newline at end of file