WatchRendition.ts
93 lines
| 2.6 KiB
| video/mp2t
|
TypeScriptLexer
|
|
r94 | import { argumentNotNull } from "@implab/core-amd/safe"; | ||
|
|
r146 | import { queueRenderTask, getItemDom, getPriority, getScope } from "./render"; | ||
|
|
r94 | import { RenditionBase } from "./RenditionBase"; | ||
|
|
r101 | import { Scope } from "./Scope"; | ||
|
|
r102 | import { Subscribable } from "../observable"; | ||
| import { Cancellation } from "@implab/core-amd/Cancellation"; | ||||
| import { collectNodes, destroy, isDocumentFragmentNode, isMounted, placeAt, startupWidgets } from "./traits"; | ||||
|
|
r94 | |||
| export class WatchRendition<T> extends RenditionBase<Node> { | ||||
|
|
r98 | private readonly _component: (arg: T) => unknown; | ||
|
|
r94 | |||
|
|
r102 | private readonly _node: Node; | ||
|
|
r94 | |||
| private readonly _scope = new Scope(); | ||||
|
|
r102 | private readonly _subject: Subscribable<T>; | ||
| private _renderJob?: { value: T }; | ||||
|
|
r96 | |||
|
|
r102 | private _ct = Cancellation.none; | ||
|
|
r146 | private _priority = 0; | ||
|
|
r102 | constructor(component: (arg: T) => unknown, subject: Subscribable<T>) { | ||
|
|
r94 | super(); | ||
| argumentNotNull(component, "component"); | ||||
|
|
r98 | this._component = component; | ||
|
|
r94 | |||
|
|
r96 | this._subject = subject; | ||
|
|
r102 | this._node = document.createComment("[Watch]"); | ||
|
|
r94 | } | ||
|
|
r102 | protected _create() { | ||
|
|
r101 | const scope = getScope(); | ||
|
|
r146 | this._priority = getPriority() + 1; | ||
|
|
r102 | scope.own(() => { | ||
| this._scope.destroy(); | ||||
| destroy(this._node); | ||||
| }); | ||||
| scope.own(this._subject.subscribe({ next: this._onValue })); | ||||
| this._ct = new Cancellation(cancel => scope.own(cancel)); | ||||
| } | ||||
|
|
r107 | private readonly _onValue = (value: T) => { | ||
|
|
r102 | if (!this._renderJob) { | ||
| // schedule a new job | ||||
| this._renderJob = { value }; | ||||
|
|
r146 | queueRenderTask(this._render, this._scope, this._priority); | ||
|
|
r102 | } else { | ||
| // update existing job | ||||
| this._renderJob = { value }; | ||||
| } | ||||
|
|
r107 | }; | ||
|
|
r94 | |||
|
|
r146 | private readonly _render = () => { | ||
| // don't render destroyed rendition | ||||
| if (this._ct.isRequested()) | ||||
| return; | ||||
|
|
r102 | |||
|
|
r146 | // remove all previous content | ||
| this._scope.clean(); | ||||
|
|
r96 | |||
|
|
r146 | // render the new node | ||
| const node = getItemDom(this._renderJob ? this._component(this._renderJob.value) : undefined); | ||||
|
|
r102 | |||
|
|
r146 | // get actual content | ||
| const pending = isDocumentFragmentNode(node) ? | ||||
| collectNodes(node.childNodes) : | ||||
| [node]; | ||||
|
|
r102 | |||
|
|
r146 | placeAt(node, this._node, "after"); | ||
|
|
r102 | |||
|
|
r146 | if (isMounted(this._node)) | ||
| pending.forEach(n => startupWidgets(n)); | ||||
|
|
r102 | |||
|
|
r146 | if (pending.length) | ||
| this._scope.own(() => pending.forEach(destroy)); | ||||
| this._renderJob = undefined; | ||||
| }; | ||||
|
|
r94 | |||
| protected _getDomNode() { | ||||
| if (!this._node) | ||||
| throw new Error("The instance of the widget isn't created"); | ||||
| return this._node; | ||||
| } | ||||
| } | ||||
