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 { getDomNode(scope?: IScope): TNode; placeAt(refNode: string | Node, position?: DojoNodePosition): void; } /** * @deprecated use Rendition */ export type BuildContext = Rendition; 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); };