##// END OF EJS Templates
Added tag v1.7.0 for changeset cede47727a1b
Added tag v1.7.0 for changeset cede47727a1b

File last commit:

r118:e07418577cbc v1.6.1 default
r126:57b65a506e4f default
Show More
WatchRendition.ts
98 lines | 2.9 KiB | video/mp2t | TypeScriptLexer
/ djx / src / main / ts / tsx / WatchRendition.ts
import { id as mid } from "module";
import { TraceSource } from "@implab/core-amd/log/TraceSource";
import { argumentNotNull } from "@implab/core-amd/safe";
import { getItemDom, getScope, scheduleRender } from "./render";
import { RenditionBase } from "./RenditionBase";
import { Scope } from "./Scope";
import { Subscribable } from "../observable";
import { Cancellation } from "@implab/core-amd/Cancellation";
import { collectNodes, destroy, isDocumentFragmentNode, isMounted, placeAt, startupWidgets } from "./traits";
const trace = TraceSource.get(mid);
export class WatchRendition<T> extends RenditionBase<Node> {
private readonly _component: (arg: T) => unknown;
private readonly _node: Node;
private readonly _scope = new Scope();
private readonly _subject: Subscribable<T>;
private _renderJob?: { value: T };
private _ct = Cancellation.none;
constructor(component: (arg: T) => unknown, subject: Subscribable<T>) {
super();
argumentNotNull(component, "component");
this._component = component;
this._subject = subject;
this._node = document.createComment("[Watch]");
}
protected _create() {
const scope = getScope();
scope.own(() => {
this._scope.destroy();
destroy(this._node);
});
scope.own(this._subject.subscribe({ next: this._onValue }));
this._ct = new Cancellation(cancel => scope.own(cancel));
}
private readonly _onValue = (value: T) => {
if (!this._renderJob) {
// schedule a new job
this._renderJob = { value };
this._render().catch(e => trace.error(e));
} else {
// update existing job
this._renderJob = { value };
}
};
private async _render() {
const beginRender = await scheduleRender(this._scope);
const endRender = beginRender();
try {
// don't render destroyed rendition
if (this._ct.isRequested())
return;
// remove all previous content
this._scope.clean();
// render the new node
const node = getItemDom(this._renderJob ? this._component(this._renderJob.value) : undefined);
// get actual content
const pending = isDocumentFragmentNode(node) ?
collectNodes(node.childNodes) :
[node];
placeAt(node, this._node, "after");
if (isMounted(this._node))
pending.forEach(n => startupWidgets(n));
if (pending.length)
this._scope.own(() => pending.forEach(destroy));
this._renderJob = undefined;
} finally {
endRender();
}
}
protected _getDomNode() {
if (!this._node)
throw new Error("The instance of the widget isn't created");
return this._node;
}
}