@@ -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 | export type DojoNodePosition = "first" | "after" | "before" | "last" | "replace" | "only" | number; |
|
9 | export type DojoNodePosition = "first" | "after" | "before" | "last" | "replace" | "only" | number; | |
10 |
|
10 | |||
|
11 | export type DojoNodeLocation = [Node | null, DojoNodePosition]; | |||
|
12 | ||||
11 | export interface Rendition<TNode extends Node = Node> { |
|
13 | export interface Rendition<TNode extends Node = Node> { | |
12 | getDomNode(): TNode; |
|
14 | getDomNode(): TNode; | |
13 |
|
15 | |||
@@ -150,3 +152,10 export function startupWidgets(target: N | |||||
150 | target.startup(); |
|
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