import { argumentNotNull } from "@implab/core-amd/safe"; import { RenditionBase } from "./RenditionBase"; import { DojoNodePosition, isElementNode, isInPage, isWidget, placeAt } from "./traits"; import registry = require("dijit/registry"); import ContentPane = require("dijit/layout/ContentPane"); import { getItemDom, refHook } from "./render"; // tslint:disable-next-line: class-name export interface _Widget { domNode: Node; containerNode?: Node; placeAt?(refNode: string | Node, position?: DojoNodePosition): void; startup?(): void; addChild?(widget: unknown, index?: number): void; } export type _WidgetCtor = new (attrs: object, srcNode?: string | Node) => _Widget; export class WidgetRendition extends RenditionBase { readonly widgetClass: _WidgetCtor; _instance: _Widget | undefined; constructor(widgetClass: _WidgetCtor) { super(); argumentNotNull(widgetClass, "widgetClass"); this.widgetClass = widgetClass; } _addChild(child: unknown): void { const instance = this._getInstance(); if (instance.addChild) { if (child instanceof WidgetRendition) { // layout containers add custom logic to addChild methods instance.addChild(child.getWidgetInstance()); } else if (isWidget(child)) { instance.addChild(child); } else { const childDom = getItemDom(child); const w = isElementNode(childDom) ? registry.byNode(childDom) : undefined; if (w) { instance.addChild(w); } else { if (!instance.containerNode) throw new Error("Failed to add DOM content. The widget doesn't have a containerNode"); // the current widget isn't started, it's children shouldn't start too placeAt(getItemDom(child), instance.containerNode, "last"); } } } else { if (!instance.containerNode) throw new Error("The widget doesn't have neither addChild nor containerNode"); // the current widget isn't started, it's children shouldn't start too placeAt(getItemDom(child), instance.containerNode, "last"); } } protected _create({ref, ...attrs}: {ref?: JSX.Ref<_Widget>}, children: unknown[]) { if (this.widgetClass.prototype instanceof ContentPane) { // a special case for the ContentPane this is for // compatibility with that heavy widget, all // regular containers could be easily manipulated // through `containerNode` property or `addChild` method. // render children to the DocumentFragment const content = document.createDocumentFragment(); children.forEach(child => content.appendChild(getItemDom(child))); // set the content property to the parameters of the widget const _attrs = { ...attrs, content }; this._instance = new this.widgetClass(_attrs); } else { this._instance = new this.widgetClass(attrs); children.forEach(x => this._addChild(x)); } if (ref) refHook(this._instance, ref); } private _getInstance() { if (!this._instance) throw new Error("The instance of the widget isn't created"); return this._instance; } protected _getDomNode() { if (!this._instance) throw new Error("The instance of the widget isn't created"); return this._instance.domNode; } /** Overrides default placeAt implementation. Calls placeAt of the * widget and then starts it. * * @param refNode A node or id of the node where the widget should be placed. * @param position A position relative to refNode. */ placeAt(refNode: string | Node, position?: DojoNodePosition) { this.ensureCreated(); const instance = this._getInstance(); if (typeof instance.placeAt === "function") { instance.placeAt(refNode, position); // fix the dojo startup behavior when the widget is placed // directly to the document and doesn't have any enclosing widgets const parentWidget = instance.domNode.parentNode ? registry.getEnclosingWidget(instance.domNode.parentNode) : null; if (!parentWidget && isInPage(instance.domNode) && typeof instance.startup === "function") instance.startup(); } else { // the widget doesn't have a placeAt method, strange but whatever super.placeAt(refNode, position); } } getWidgetInstance() { this.ensureCreated(); return this._getInstance(); } }