WidgetRendition.ts
        
        
            
                    134 lines
            
             | 4.9 KiB
            
                | video/mp2t
            
             |
                TypeScriptLexer
            
          
        |  | r65 | import { argumentNotNull } from "@implab/core-amd/safe"; | ||
|  | r97 | import { RenditionBase } from "./RenditionBase"; | ||
|  | r96 | import { DojoNodePosition, isElementNode, isInPage, isWidget, placeAt } from "./traits"; | ||
|  | r65 | import registry = require("dijit/registry"); | ||
| import ContentPane = require("dijit/layout/ContentPane"); | ||||
|  | r96 | import { IScope } from "./Scope"; | ||
|  | r101 | import { getItemDom, getScope, renderHook } from "./render"; | ||
|  | r65 | |||
| // tslint:disable-next-line: class-name | ||||
| export interface _Widget { | ||||
| domNode: Node; | ||||
| containerNode?: Node; | ||||
| placeAt?(refNode: string | Node, position?: DojoNodePosition): void; | ||||
| startup?(): void; | ||||
|  | r101 | addChild?(widget: unknown, index?: number): void; | ||
|  | r65 | } | ||
|  | r101 | export type _WidgetCtor = new (attrs: {}, srcNode?: string | Node) => _Widget; | ||
|  | r65 | |||
| export class WidgetRendition extends RenditionBase<Node> { | ||||
| readonly widgetClass: _WidgetCtor; | ||||
| _instance: _Widget | undefined; | ||||
| constructor(widgetClass: _WidgetCtor) { | ||||
| super(); | ||||
| argumentNotNull(widgetClass, "widgetClass"); | ||||
| this.widgetClass = widgetClass; | ||||
| } | ||||
|  | r101 | _addChild(child: unknown, scope: IScope): void { | ||
|  | r65 | const instance = this._getInstance(); | ||
| if (instance.addChild) { | ||||
| if (child instanceof WidgetRendition) { | ||||
| // layout containers add custom logic to addChild methods | ||||
|  | r96 | instance.addChild(child.getWidgetInstance(scope)); | ||
|  | r65 | } else if (isWidget(child)) { | ||
| instance.addChild(child); | ||||
| } else { | ||||
|  | r101 | const childDom = getItemDom(child); | ||
|  | r91 | const w = isElementNode(childDom) ? registry.byNode(childDom) : undefined; | ||
|  | r65 | |||
|  | r91 | 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 | ||||
|  | r101 | placeAt(getItemDom(child), instance.containerNode, "last"); | ||
|  | r91 | } | ||
|  | r65 | } | ||
| } 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 | ||||
|  | r101 | placeAt(getItemDom(child), instance.containerNode, "last"); | ||
|  | r65 | } | ||
| } | ||||
|  | r101 | protected _create({ref, ...attrs}: {ref?: JSX.Ref<_Widget>}, children: unknown[], scope: IScope) { | ||
|  | r65 | if (this.widgetClass.prototype instanceof ContentPane) { | ||
| // a special case for the ContentPane this is for | ||||
|  | r101 | // compatibility with that heavy widget, all | ||
|  | r65 | // regular containers could be easily manipulated | ||
| // through `containerNode` property or `addChild` method. | ||||
| // render children to the DocumentFragment | ||||
| const content = document.createDocumentFragment(); | ||||
|  | r101 | children.forEach(child => content.appendChild(getItemDom(child))); | ||
|  | r65 | |||
| // 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); | ||||
|  | r96 | children.forEach(x => this._addChild(x, scope)); | ||
|  | r65 | } | ||
|  | r101 | if (ref) { | ||
| const instance = this._instance; | ||||
| renderHook(() => ref(instance)); | ||||
| } | ||||
|  | r65 | } | ||
| 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) { | ||||
|  | r96 | this.ensureCreated(getScope()); | ||
|  | r65 | 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 ? | ||||
|  | r79 | registry.getEnclosingWidget(instance.domNode.parentNode) : null; | ||
|  | r65 | 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); | ||||
| } | ||||
| } | ||||
|  | r96 | getWidgetInstance(scope?: IScope) { | ||
| this.ensureCreated(scope || getScope()); | ||||
|  | r65 | return this._getInstance(); | ||
| } | ||||
| } | ||||
