##// END OF EJS Templates
Testing nested watch, release candidate
Testing nested watch, release candidate

File last commit:

r101:bb6b1db1b430 v1.3
r101:bb6b1db1b430 v1.3
Show More
render.ts
104 lines | 3.1 KiB | video/mp2t | TypeScriptLexer
import { TraceSource } from "@implab/core-amd/log/TraceSource";
import { isPromise } from "@implab/core-amd/safe";
import { id as mid } from "module";
import { Scope } from "./Scope";
import { autostartWidgets, collectNodes, DojoNodePosition, isDocumentFragmentNode, isNode, isRendition, isWidget, placeAt } from "./traits";
const trace = TraceSource.get(mid);
let _scope = Scope.dummy;
let renderCount = 0;
const hooks: (() => void)[] = [];
const guard = (cb: () => unknown) => {
try {
const result = cb()
if (isPromise(result)) {
const warn = (ret: unknown) => trace.error("The callback {0} competed asynchronously. result = {1}", cb, ret);
result.then(warn, warn);
}
} catch (e) {
trace.error(e);
}
}
/**
* Schedules rendering micro task
* @returns Promise
*/
const beginRender = () => {
renderCount++;
return Promise.resolve();
}
/**
* Completes render operation
*/
const endRender = () => {
if (!--renderCount) {
hooks.forEach(guard);
hooks.length = 0;
}
}
export const renderHook = (hook: () => void) => {
if (renderCount)
hooks.push(hook);
else
guard(hook);
}
/** Returns the current scope */
export const getScope = () => _scope;
/** Schedules the rendition to be rendered to the DOM Node
* @param rendition The rendition to be rendered
* @param scope The scope
*/
export const render = async (rendition: unknown, refNode: Node, position: DojoNodePosition = "last", scope = Scope.dummy) => {
await beginRender();
const prev = _scope;
_scope = scope;
try {
const domNode = getItemDom(rendition);
const startupPending = isDocumentFragmentNode(domNode) ? collectNodes(domNode.children) : [domNode];
placeAt(domNode, refNode, position);
startupPending.forEach(autostartWidgets);
return startupPending;
} finally {
_scope = prev;
endRender();
}
}
/** Renders DOM element for different types of the argument. */
export const getItemDom = (v: unknown) => {
if (typeof v === "string" || typeof v === "number" || v instanceof RegExp || v instanceof Date) {
// primitive types converted to the text nodes
return document.createTextNode(v.toString());
} else if (isNode(v)) {
// nodes are kept as is
return v;
} else if (isRendition(v)) {
// renditions are instantiated
return v.getDomNode();
} else if (isWidget(v)) {
// widgets are converted to it's markup
return v.domNode;
} else if (typeof v === "boolean" || v === null || v === undefined) {
// null | undefined | boolean are removed, converted to comments
return document.createComment(`[${typeof v} ${String(v)}]`);
} else if (v instanceof Array) {
// arrays will be translated to document fragments
const fragment = document.createDocumentFragment();
v.map(item => getItemDom(item))
.forEach(node => fragment.appendChild(node));
return fragment;
} else {
// bug: explicit error otherwise
throw new Error("Invalid parameter: " + v);
}
}