|
|
import { IDestroyable } from "@implab/core-amd/interfaces";
|
|
|
import { isDestroyable } from "@implab/core-amd/safe";
|
|
|
import _WidgetBase = require("dijit/_WidgetBase");
|
|
|
import registry = require("dijit/registry");
|
|
|
import { IScope } from "./Scope";
|
|
|
|
|
|
type _WidgetBaseConstructor = typeof _WidgetBase;
|
|
|
|
|
|
export type DojoNodePosition = "first" | "after" | "before" | "last" | "replace" | "only" | number;
|
|
|
|
|
|
export type DojoNodeLocation = [Node, DojoNodePosition];
|
|
|
|
|
|
export interface Rendition<TNode extends Node = Node> {
|
|
|
getDomNode(scope?: IScope): TNode;
|
|
|
|
|
|
placeAt(refNode: string | Node, position?: DojoNodePosition): void;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @deprecated use Rendition
|
|
|
*/
|
|
|
export type BuildContext<TNode extends Node = Node> = Rendition<TNode>;
|
|
|
|
|
|
export interface IRecursivelyDestroyable {
|
|
|
destroyRecursive(): void;
|
|
|
}
|
|
|
|
|
|
export const isNode = (el: unknown): el is Node => !!(el && (el as Node).nodeName && (el as Node).nodeType);
|
|
|
|
|
|
export const isElementNode = (el: unknown): el is Element => isNode(el) && el.nodeType === 1;
|
|
|
|
|
|
export const isTextNode = (el: unknown): el is Text => isNode(el) && el.nodeType === 3;
|
|
|
|
|
|
export const isProcessingInstructionNode = (el: unknown): el is ProcessingInstruction => isNode(el) && el.nodeType === 7;
|
|
|
|
|
|
export const isCommentNode = (el: unknown): el is Comment => isNode(el) && el.nodeType === 8;
|
|
|
|
|
|
export const isDocumentNode = (el: unknown): el is Document => isNode(el) && el.nodeType === 9;
|
|
|
|
|
|
export const isDocumentTypeNode = (el: unknown): el is DocumentType => isNode(el) && el.nodeType === 10;
|
|
|
|
|
|
export const isDocumentFragmentNode = (el: any): el is DocumentFragment => isNode(el) && el.nodeType === 11;
|
|
|
|
|
|
export const isWidget = (v: unknown): v is _WidgetBase => !!(v && "domNode" in (v as _WidgetBase));
|
|
|
|
|
|
export const isRendition = (v: unknown): v is Rendition => !!(v && typeof (v as Rendition).getDomNode === "function");
|
|
|
|
|
|
/**
|
|
|
* @deprecated use isRendition
|
|
|
*/
|
|
|
export const isBuildContext = isRendition;
|
|
|
|
|
|
export const isPlainObject = (v: object) => {
|
|
|
if (typeof v !== "object")
|
|
|
return false;
|
|
|
|
|
|
const vp = Object.getPrototypeOf(v);
|
|
|
return !vp || vp === Object.prototype;
|
|
|
}
|
|
|
|
|
|
export const isWidgetConstructor = (v: unknown): v is _WidgetBaseConstructor =>
|
|
|
typeof v === "function" && v.prototype && (
|
|
|
"domNode" in v.prototype ||
|
|
|
"buildRendering" in v.prototype
|
|
|
);
|
|
|
|
|
|
|
|
|
/** Tests whether the specified node is placed in visible dom.
|
|
|
* @param {Node} node The node to test
|
|
|
*/
|
|
|
export const isInPage = (node: Node) => node === document.body ? false : document.body.contains(node);
|
|
|
|
|
|
export const isRecursivelyDestroyable = (target: unknown): target is IRecursivelyDestroyable =>
|
|
|
!!(target && typeof (target as IRecursivelyDestroyable).destroyRecursive === "function");
|
|
|
|
|
|
|
|
|
|
|
|
/** Destroys DOM Node with all contained widgets.
|
|
|
* If the specified node is the root node of a widget, then the
|
|
|
* widget will be destroyed.
|
|
|
*
|
|
|
* @param target DOM Node or widget to destroy
|
|
|
*/
|
|
|
export const destroy = (target: Node | IDestroyable | IRecursivelyDestroyable) => {
|
|
|
if (isRecursivelyDestroyable(target)) {
|
|
|
target.destroyRecursive();
|
|
|
} else if (isDestroyable(target)) {
|
|
|
target.destroy();
|
|
|
} else if (isNode(target)) {
|
|
|
if (isElementNode(target)) {
|
|
|
const w = registry.byNode(target);
|
|
|
if (w) {
|
|
|
w.destroyRecursive();
|
|
|
} else {
|
|
|
emptyNode(target);
|
|
|
const parent = target.parentNode;
|
|
|
if (parent)
|
|
|
parent.removeChild(target);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/** Empties a content of the specified node and destroys all contained widgets.
|
|
|
*
|
|
|
* @param target DOM node to empty.
|
|
|
*/
|
|
|
export const emptyNode = (target: Node) => {
|
|
|
registry.findWidgets(target).forEach(destroy);
|
|
|
|
|
|
for(let c; c = target.lastChild;){ // intentional assignment
|
|
|
target.removeChild(c);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/** This function starts all widgets inside the DOM node if the target is a node
|
|
|
* or starts widget itself if the target is the widget. If the specified node
|
|
|
* associated with the widget that widget will be started.
|
|
|
*
|
|
|
* @param target DOM node to find and start widgets or the widget itself.
|
|
|
*/
|
|
|
export const startupWidgets = (target: Node | _WidgetBase, skipNode?: Node) => {
|
|
|
if (isNode(target)) {
|
|
|
if (isElementNode(target)) {
|
|
|
const w = registry.byNode(target);
|
|
|
if (w) {
|
|
|
if (w.startup)
|
|
|
w.startup();
|
|
|
} else {
|
|
|
registry.findWidgets(target, skipNode).forEach(x => x.startup());
|
|
|
}
|
|
|
}
|
|
|
} else {
|
|
|
if (target.startup)
|
|
|
target.startup();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/** Places the specified DOM node at the specified location.
|
|
|
*
|
|
|
* @param node The node which should be placed
|
|
|
* @param refNodeOrId The reference node where the created
|
|
|
* DOM should be placed.
|
|
|
* @param position Optional parameter, specifies the
|
|
|
* position relative to refNode. Default is "last" (i.e. last child).
|
|
|
*/
|
|
|
export const placeAt = (node: Node, refNodeOrId: string | Node, position: DojoNodePosition = "last") => {
|
|
|
const ref = typeof refNodeOrId == "string" ? document.getElementById(refNodeOrId) : refNodeOrId;
|
|
|
if (!ref)
|
|
|
return;
|
|
|
|
|
|
const parent = ref.parentNode;
|
|
|
|
|
|
const insertBefore = (node: Node, refNode: Node | null) => parent && parent.insertBefore(node, refNode);
|
|
|
|
|
|
if (typeof position == "number") {
|
|
|
if (ref.childNodes.length <= position) {
|
|
|
ref.appendChild(node);
|
|
|
} else {
|
|
|
ref.insertBefore(node, ref.childNodes[position]);
|
|
|
}
|
|
|
} else {
|
|
|
switch (position) {
|
|
|
case "before":
|
|
|
insertBefore(node, ref);
|
|
|
break;
|
|
|
case "after":
|
|
|
insertBefore(node, ref.nextSibling);
|
|
|
break;
|
|
|
case "first":
|
|
|
insertBefore(node, parent && parent.firstChild);
|
|
|
break;
|
|
|
case "last":
|
|
|
insertBefore(node, null);
|
|
|
break;
|
|
|
case "only":
|
|
|
emptyNode(ref);
|
|
|
ref.appendChild(node);
|
|
|
break;
|
|
|
case "replace":
|
|
|
if (parent)
|
|
|
parent.replaceChild(node, ref);
|
|
|
destroy(ref);
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/** Collects nodes from collection to an array.
|
|
|
*
|
|
|
* @param collection The collection of nodes.
|
|
|
* @returns The array of nodes.
|
|
|
*/
|
|
|
export const collectNodes = (collection: HTMLCollection) => {
|
|
|
const items = [];
|
|
|
for (let i = 0, n = collection.length; i < n; i++) {
|
|
|
items.push(collection[i]);
|
|
|
}
|
|
|
return items;
|
|
|
};
|
|
|
|
|
|
/** Starts widgets if the node contained in the document or in the started widget.
|
|
|
*
|
|
|
* @param node The node to start.
|
|
|
*/
|
|
|
export const autostartWidgets = (node: Node) => {
|
|
|
if (node.parentNode) {
|
|
|
const parentWidget = registry.getEnclosingWidget(node.parentNode);
|
|
|
if (parentWidget && parentWidget._started)
|
|
|
return startupWidgets(node);
|
|
|
}
|
|
|
if (isInPage(node))
|
|
|
startupWidgets(node);
|
|
|
};
|