import { IDestroyable, IRemovable } from "@implab/core-amd/interfaces"; import { isDestroyable } from "@implab/core-amd/safe"; import _WidgetBase = require("dijit/_WidgetBase"); import registry = require("dijit/registry"); import dom = require("dojo/dom-construct"); import Stateful = require("dojo/Stateful"); import { FunctionRendition } from "./FunctionRendition"; import { DjxWidgetBase } from "./DjxWidgetBase"; type _WidgetBaseConstructor = typeof _WidgetBase; export type DojoNodePosition = "first" | "after" | "before" | "last" | "replace" | "only" | number; export interface Rendition { getDomNode(): TNode; placeAt(refNode: string | Node, position?: DojoNodePosition): void; } /** * @deprecated use Rendition */ export type BuildContext = Rendition; export interface IRecursivelyDestroyable { destroyRecursive(): void; } export function isNode(el: any): el is Node { return el && el.nodeName && el.nodeType; } export function isElementNode(el: any): el is Element { return isNode(el) && el.nodeType === 1; } export function isTextNode(el: any): el is Text { return isNode(el) && el.nodeType === 3; } export function isProcessingInstructionNode(el: any): el is ProcessingInstruction { return isNode(el) && el.nodeType === 7; } export function isCommentNode(el: any): el is Comment { return isNode(el) && el.nodeType === 8; } export function isDocumentNode(el: any): el is Document { return isNode(el) && el.nodeType === 9; } export function isDocumentTypeNode(el: any): el is DocumentType { return isNode(el) && el.nodeType === 10; } export function isDocumentFragmentNode(el: any): el is DocumentFragment { return isNode(el) && el.nodeType === 11; } export function isWidget(v: any): v is _WidgetBase { return v && "domNode" in v; } export function isRendition(v: any): v is Rendition { return v && typeof v.getDomElement === "function"; } /** * @deprecated use isRendition */ export const isBuildContext = isRendition; export function isPlainObject(v: object) { if (typeof v !== "object") return false; const vp = Object.getPrototypeOf(v); return !vp || vp === Object.prototype; } export function isWidgetConstructor(v: any): v is _WidgetBaseConstructor { return 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 function isInPage(node: Node) { return (node === document.body) ? false : document.body.contains(node); } export function isRecursivelyDestroyable(target: any): target is IRecursivelyDestroyable { return target && typeof target.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 function destroy(target: Node | IDestroyable | IRecursivelyDestroyable) { if (isRecursivelyDestroyable(target)) { target.destroyRecursive(); } else if (isDestroyable(target)) { target.destroy(); } else if (isNode(target)) { const w = isElementNode(target) ? registry.byNode(target) : undefined; if (w) { w.destroyRecursive(); } else { registry.findWidgets(target).forEach(destroy); dom.destroy(target); } } } /** Empties a content of the specified node and destroys all contained widgets. * * @param target DOM node to . */ export function emptyNode(target: Node) { registry.findWidgets(target).forEach(destroy); dom.empty(target); } /** 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 function startupWidgets(target: Node | _WidgetBase, skipNode?: Node) { if (isNode(target)) { const w = isElementNode(target) ? registry.byNode(target) : undefined; if (w) { if (w.startup) w.startup(); } else { registry.findWidgets(target, skipNode).forEach(x => x.startup()); } } else { if (target.startup) target.startup(); } } type StatefulProps = T extends Stateful ? A : never; type CleanFn = (instance: IRemovable | IDestroyable) => void; /** * Observers the property and calls render callback each change. * * @param target The target object which property will be observed. * @param prop The name of the property. * @param render The callback which will be called every time the value is changed * @param cleanupOrOwner The object with method `own` or an callback to register lifecycle for the observer. * @returns Rendition which is created instantly */ export function watch( target: W, prop: K, render: (model: W[K]) => any, cleanupOrOwner?: { own: CleanFn } | CleanFn ): Rendition; /** * Observers the property and calls render callback each change. * * @param target The target object which property will be observed. * @param prop The name of the property. * @param render The callback which will be called every time the value is changed * @param cleanupOrOwner The object with method `own` or an callback to register lifecycle for the observer. * @returns Rendition which is created instantly */ export function watch>( target: T, prop: K, render: (model: StatefulProps[K]) => any, cleanupOrOwner?: { own: CleanFn } | CleanFn ): Rendition; export function watch & string>( target: T, prop: K, render: (model: StatefulProps[K]) => any, cleanupOrOwner: { own: CleanFn } | CleanFn = () => { } ) { let rendition = new FunctionRendition(() => render(target.get(prop))); const _own = cleanupOrOwner instanceof Function ? cleanupOrOwner : (x: IRemovable) => cleanupOrOwner.own(x); _own(target.watch(prop, (_name, oldValue, newValue) => { if (oldValue !== newValue) { const newRendition = new FunctionRendition(() => render(newValue)); newRendition.placeAt(rendition.getDomNode(), "replace"); destroy(rendition.getDomNode()); rendition = newRendition; } })); return rendition; } /** Decorates the method which will be registered as the handle for the specified event. * This decorator can be applied to DjxWidgetBase subclass methods. * * ``` * @on("click") * _onClick(eventObj: MouseEvent) { * // ... * } * ``` */ export const on = (...eventNames: E[]) => , EV extends Event >( target: T, key: K, _descriptor: TypedPropertyDescriptor<(eventObj: EV) => void> | TypedPropertyDescriptor<() => void> ): any => { const handlers = eventNames.map(eventName => ({ eventName, handlerMethod: key })) target._eventHandlers = target._eventHandlers ? target._eventHandlers.concat(handlers) : handlers; };