##// END OF EJS Templates
Corrected Scope.own() to cleanup the supplied object immediately when the scope is disposed already
Corrected Scope.own() to cleanup the supplied object immediately when the scope is disposed already

File last commit:

r118:e07418577cbc v1.6.1 default
r131:c7d9ad82b374 v1.8.1 default
Show More
WatchForRendition.ts
208 lines | 6.5 KiB | video/mp2t | TypeScriptLexer
/ djx / src / main / ts / tsx / WatchForRendition.ts
cin
Working on WatchForRendition
r107 import { id as mid } from "module";
import { TraceSource } from "@implab/core-amd/log/TraceSource";
import { argumentNotNull } from "@implab/core-amd/safe";
cin
added whenRendered() method to wait for pending oprations to complete
r118 import { getScope, render, scheduleRender } from "./render";
cin
Working on WatchForRendition
r107 import { RenditionBase } from "./RenditionBase";
import { Scope } from "./Scope";
import { Cancellation } from "@implab/core-amd/Cancellation";
import { collectNodes, destroy as safeDestroy, isDocumentFragmentNode, isElementNode, isMounted, placeAt, startupWidgets } from "./traits";
import { IDestroyable } from "@implab/core-amd/interfaces";
import { play } from "../play";
import * as fx from "dojo/fx";
cin
added whenRendered() method to wait for pending oprations to complete
r118 import { isSubscribable, Subscribable } from "../observable";
import { isDjObservableResults, OrderedUpdate } from "../store";
cin
Working on WatchForRendition
r107
const trace = TraceSource.get(mid);
interface ItemRendition {
nodes: Node[];
scope: IDestroyable;
destroy(): void;
}
cin
added reduce() and next() methods to observable...
r116 interface RenderTask<T> extends OrderedUpdate<T> {
cin
Working on WatchForRendition
r107 animate: boolean;
}
export interface AnimationAttrs {
animate?: boolean;
animateIn?: (nodes: Node[]) => Promise<void>;
animateOut?: (nodes: Node[]) => Promise<void>;
}
export interface WatchForRenditionAttrs<T> extends AnimationAttrs {
cin
added whenRendered() method to wait for pending oprations to complete
r118 subject: T[] | Subscribable<OrderedUpdate<T>> | undefined | null;
cin
Working on WatchForRendition
r107
component: (arg: T, index: number) => unknown;
}
cin
linting
r109 const noop = () => { };
cin
Working on WatchForRendition
r107
const fadeIn = (nodes: Node[]) => Promise.all(nodes
.filter(isElementNode)
.map(el => play(fx.fadeIn({ node: el as HTMLElement })))
).then(noop);
const fadeOut = (nodes: Node[]) => Promise.all(nodes
.filter(isElementNode)
.map(el => play(fx.fadeOut({ node: el as HTMLElement })))
).then(noop);
export class WatchForRendition<T> extends RenditionBase<Node> {
private readonly _component: (arg: T, index: number) => unknown;
private readonly _node: Node;
private readonly _itemRenditions: ItemRendition[] = [];
cin
added reduce() and next() methods to observable...
r116 private readonly _subject: T[] | Subscribable<OrderedUpdate<T>>;
cin
Working on WatchForRendition
r107
private readonly _renderTasks: RenderTask<T>[] = [];
private readonly _animate: boolean;
private readonly _animateIn: (nodes: Node[]) => Promise<void>;
private readonly _animateOut: (nodes: Node[]) => Promise<void>;
private _ct = Cancellation.none;
constructor({ subject, component, animate, animateIn, animateOut }: WatchForRenditionAttrs<T>) {
super();
argumentNotNull(component, "component");
this._component = component;
cin
added whenRendered() method to wait for pending oprations to complete
r118 this._subject = subject ?? [];
cin
Working on WatchForRendition
r107
this._node = document.createComment("[WatchFor]");
this._animate = !!animate;
this._animateIn = animateIn ?? fadeIn;
this._animateOut = animateOut ?? fadeOut;
}
protected _create() {
const scope = getScope();
scope.own(() => {
this._itemRenditions.forEach(safeDestroy);
safeDestroy(this._node);
});
const result = this._subject;
if (result) {
cin
added whenRendered() method to wait for pending oprations to complete
r118 if (isSubscribable<OrderedUpdate<T>>(result)) {
cin
Working on WatchForRendition
r107 let animate = false;
const subscription = result.subscribe({
cin
linting
r109 next: ({ item, prevIndex, newIndex }) => this._onItemUpdated({ item, prevIndex, newIndex, animate })
cin
Working on WatchForRendition
r107 });
scope.own(subscription);
animate = this._animate;
} else {
cin
added whenRendered() method to wait for pending oprations to complete
r118 if (isDjObservableResults<T>(result))
cin
Working on WatchForRendition
r107 scope.own(result.observe((item, prevIndex, newIndex) => this._onItemUpdated({ item, prevIndex, newIndex, animate: false }), true));
for (let i = 0, n = result.length; i < n; i++)
this._onItemUpdated({ item: result[i], prevIndex: -1, newIndex: i, animate: this._animate });
}
}
this._ct = new Cancellation(cancel => scope.own(cancel));
}
cin
linting
r109 private readonly _onItemUpdated = (item: RenderTask<T>) => {
cin
Working on WatchForRendition
r107 if (!this._renderTasks.length) {
// schedule a new job
this._renderTasks.push(item);
this._render().catch(e => trace.error(e));
} else {
// update existing job
this._renderTasks.push(item);
}
cin
linting
r109 };
cin
Working on WatchForRendition
r107
private async _render() {
// fork
cin
added whenRendered() method to wait for pending oprations to complete
r118 const beginRender = await scheduleRender();
const endRender = beginRender();
try {
// don't render destroyed rendition
if (this._ct.isRequested())
return;
cin
Working on WatchForRendition
r107
cin
added whenRendered() method to wait for pending oprations to complete
r118 this._renderTasks.forEach(this._onRenderItem);
this._renderTasks.length = 0;
} finally {
endRender();
}
cin
Working on WatchForRendition
r107 }
cin
linting
r109 private readonly _onRenderItem = ({ item, newIndex, prevIndex, animate: _animate }: RenderTask<T>) => {
cin
Working on WatchForRendition
r107 const animate = _animate && prevIndex !== newIndex;
if (prevIndex > -1) {
// if we need to delete previous rendition
const [{ nodes, destroy }] = this._itemRenditions.splice(prevIndex, 1);
if (animate) {
this._animateOut(nodes)
.then(destroy)
.catch(e => trace.error(e));
} else {
destroy();
}
}
if (newIndex > -1) {
// if we need to create the new rendition
// 1. create a new scope for rendering a content
const scope = new Scope();
// 2. render the content
const itemNode = render(this._component(item, newIndex), scope);
// 3. track nodes
const nodes = isDocumentFragmentNode(itemNode) ?
collectNodes(itemNode.childNodes) :
[itemNode];
// 5. insert node at the correct position
const { nodes: [beforeNode] } = this._itemRenditions[newIndex] ?? { nodes: [] };
if (beforeNode)
placeAt(itemNode, beforeNode, "before");
else
placeAt(itemNode, this._node, "before");
// 6. store information about rendition
this._itemRenditions.splice(newIndex, 0, {
scope,
nodes,
destroy: () => {
scope.destroy();
nodes.forEach(safeDestroy);
}
});
// 7. startup widgets if needed
if (isMounted(this._node))
nodes.forEach(n => startupWidgets(n));
// 8. optionally play the animation
if (animate)
this._animateIn(nodes).catch(e => trace.error(e));
}
cin
linting
r109 };
cin
Working on WatchForRendition
r107
protected _getDomNode() {
if (!this._node)
cin
added whenRendered() method to wait for pending oprations to complete
r118 throw new Error("The instance of the rendition isn't created");
cin
Working on WatchForRendition
r107 return this._node;
}
}