@@ -0,0 +1,26 | |||
|
1 | import { Scope } from "./Scope"; | |
|
2 | import { destroy, Rendition } from "./traits"; | |
|
3 | ||
|
4 | let _scope = Scope.dummy; | |
|
5 | ||
|
6 | const beginRender = async () => { | |
|
7 | } | |
|
8 | ||
|
9 | const endRender = () => { | |
|
10 | } | |
|
11 | ||
|
12 | export const getScope = () => _scope; | |
|
13 | ||
|
14 | export const render = async (rendition: () => Rendition, scope = Scope.dummy) => { | |
|
15 | await beginRender(); | |
|
16 | const prev = _scope; | |
|
17 | _scope = scope; | |
|
18 | try { | |
|
19 | const node = rendition().getDomNode(); | |
|
20 | scope.own(() => destroy(node)); | |
|
21 | return node; | |
|
22 | } finally { | |
|
23 | _scope = prev; | |
|
24 | endRender(); | |
|
25 | } | |
|
26 | } |
@@ -0,0 +1,40 | |||
|
1 | import { IDestroyable, IRemovable } from "@implab/core-amd/interfaces"; | |
|
2 | import { isDestroyable, isRemovable } from "@implab/core-amd/safe"; | |
|
3 | ||
|
4 | export interface IScope { | |
|
5 | own(target: (() => void) | IDestroyable | IRemovable): void; | |
|
6 | } | |
|
7 | ||
|
8 | export class Scope implements IDestroyable, IScope { | |
|
9 | private readonly _cleanup: (() => void)[] = []; | |
|
10 | ||
|
11 | static readonly dummy: IScope = { own() { } }; | |
|
12 | ||
|
13 | own(target: (() => void) | IDestroyable | IRemovable) { | |
|
14 | if (target instanceof Function) { | |
|
15 | this._cleanup.push(target); | |
|
16 | } else if (isDestroyable(target)) { | |
|
17 | this._cleanup.push(() => target.destroy()); | |
|
18 | } else if (isRemovable(target)) { | |
|
19 | this._cleanup.push(() => target.remove()); | |
|
20 | } | |
|
21 | } | |
|
22 | ||
|
23 | clean() { | |
|
24 | const guard = (cb: () => void) => { | |
|
25 | try { | |
|
26 | cb(); | |
|
27 | } catch { | |
|
28 | // guard | |
|
29 | } | |
|
30 | } | |
|
31 | ||
|
32 | this._cleanup.forEach(guard); | |
|
33 | this._cleanup.length = 0; | |
|
34 | } | |
|
35 | ||
|
36 | destroy() { | |
|
37 | this.clean(); | |
|
38 | } | |
|
39 | ||
|
40 | } No newline at end of file |
@@ -0,0 +1,58 | |||
|
1 | import { id as mid } from "module"; | |
|
2 | import { TraceSource } from "@implab/core-amd/log/TraceSource"; | |
|
3 | import { argumentNotNull } from "@implab/core-amd/safe"; | |
|
4 | import { place } from "dojo/dom-construct"; | |
|
5 | import { getScope, render } from "./Renderer"; | |
|
6 | import { RenditionBase } from "./RenditionBase"; | |
|
7 | import { Scope } from "./Scope"; | |
|
8 | import { locateNode } from "./traits"; | |
|
9 | ||
|
10 | const trace = TraceSource.get(mid); | |
|
11 | ||
|
12 | export class WatchRendition<T> extends RenditionBase<Node> { | |
|
13 | private readonly _factory: (arg: T) => any; | |
|
14 | ||
|
15 | private _node: Node; | |
|
16 | ||
|
17 | private readonly _scope = new Scope(); | |
|
18 | ||
|
19 | constructor(component: (arg: T) => any, subject: any) { | |
|
20 | super(); | |
|
21 | argumentNotNull(component, "component"); | |
|
22 | ||
|
23 | this._factory = component; | |
|
24 | ||
|
25 | this._node = document.createComment("WatchRendition placeholder"); | |
|
26 | } | |
|
27 | ||
|
28 | protected _create(attrs: object, children: any[]) { | |
|
29 | const _attrs: any = attrs || {}; | |
|
30 | const _children = children.map(x => this.getItemDom(x)); | |
|
31 | this._node = this.getItemDom( | |
|
32 | this._factory.call(null, { ..._attrs, children: _children }) | |
|
33 | ); | |
|
34 | ||
|
35 | const scope = getScope(); | |
|
36 | scope.own(this._scope); | |
|
37 | ||
|
38 | // если отрендерили текст? или DocumentFragment | |
|
39 | } | |
|
40 | ||
|
41 | private async _render(value: T) { | |
|
42 | const [refNode, position] = locateNode(this._node); | |
|
43 | this._scope.clean(); | |
|
44 | ||
|
45 | this._node = await render(() => this._factory(value), this._scope); | |
|
46 | ||
|
47 | if (refNode) | |
|
48 | place(this._node, refNode, position); | |
|
49 | } | |
|
50 | ||
|
51 | protected _getDomNode() { | |
|
52 | if (!this._node) | |
|
53 | throw new Error("The instance of the widget isn't created"); | |
|
54 | return this._node; | |
|
55 | } | |
|
56 | ||
|
57 | ||
|
58 | } |
@@ -8,6 +8,8 type _WidgetBaseConstructor = typeof _Wi | |||
|
8 | 8 | |
|
9 | 9 | export type DojoNodePosition = "first" | "after" | "before" | "last" | "replace" | "only" | number; |
|
10 | 10 | |
|
11 | export type DojoNodeLocation = [Node | null, DojoNodePosition]; | |
|
12 | ||
|
11 | 13 | export interface Rendition<TNode extends Node = Node> { |
|
12 | 14 | getDomNode(): TNode; |
|
13 | 15 | |
@@ -150,3 +152,10 export function startupWidgets(target: N | |||
|
150 | 152 | target.startup(); |
|
151 | 153 | } |
|
152 | 154 | } |
|
155 | ||
|
156 | export function locateNode(node: Node): DojoNodeLocation { | |
|
157 | const next = node.nextSibling; | |
|
158 | return next ? | |
|
159 | [next, "before"] : | |
|
160 | [node.parentNode, "last"]; | |
|
161 | } No newline at end of file |
General Comments 0
You need to be logged in to leave comments.
Login now