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