|
|
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<TNode extends Node = Node> {
|
|
|
getDomNode(): 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 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 typeof v === "object" && 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 self = registry.byNode(target);
|
|
|
if (self) {
|
|
|
self.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> = T extends Stateful<infer A> ? 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<W extends _WidgetBase, K extends keyof W>(
|
|
|
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<T extends Stateful, K extends keyof StatefulProps<T>>(
|
|
|
target: T,
|
|
|
prop: K,
|
|
|
render: (model: StatefulProps<T>[K]) => any,
|
|
|
cleanupOrOwner?: { own: CleanFn } | CleanFn
|
|
|
): Rendition;
|
|
|
export function watch<T extends Stateful, K extends keyof StatefulProps<T> & string>(
|
|
|
target: T,
|
|
|
prop: K,
|
|
|
render: (model: StatefulProps<T>[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;
|
|
|
}
|
|
|
|
|
|
export type WidgetEvents<W> = W extends DjxWidgetBase<any, infer EM> ? EM : never;
|
|
|
|
|
|
export type HandlerType<W, E extends keyof any> = W extends {
|
|
|
on(eventName: E, handler: infer H): any;
|
|
|
} ? H : never;
|
|
|
|
|
|
export const on = <E extends keyof any>(eventName: E) =>
|
|
|
<K extends keyof T,
|
|
|
T extends DjxWidgetBase
|
|
|
>(
|
|
|
target: T,
|
|
|
key: K,
|
|
|
descriptor: PropertyDescriptor
|
|
|
): any => {
|
|
|
target.
|
|
|
};
|
|
|
|