import { djbase, djclass } from "../declare"; import _WidgetBase = require("dijit/_WidgetBase"); import _AttachMixin = require("dijit/_AttachMixin"); import { isNode, isElementNode } from "./traits"; import registry = require("dijit/registry"); import on = require("dojo/on"); import { Scope } from "./Scope"; import { queueRenderTask, getPriority, render } from "./render"; import { isNull } from "@implab/core-amd/safe"; // type Handle = dojo.Handle; export interface EventArgs { bubbles?: boolean; cancelable?: boolean; composed?: boolean; } // eslint-disable-next-line @typescript-eslint/no-unused-vars export interface DjxWidgetBase extends _WidgetBase { /** This property is declared only for type inference to work, it is never assigned * and should not be used. */ readonly _eventMap: Events & GlobalEventHandlersEventMap; /** The list of pairs of event and method names. When the widget is created all methods from * this list will be connected to corresponding events. * * This property is maintained in the prototype */ _eventHandlers: Array<{ eventName: string, handlerMethod: string; }>; } type _super = { startup(): void; destroy(preserveDom?: boolean): void; }; @djclass // eslint-disable-next-line @typescript-eslint/no-unused-vars export abstract class DjxWidgetBase extends djbase<_super, _AttachMixin>(_WidgetBase, _AttachMixin) { private readonly _scope = new Scope(); private readonly _priority = getPriority() + 1; buildRendering() { const node = render(this.render(), this._scope); if (!isElementNode(node)) throw new Error("The render method must return a single DOM element"); this.domNode = node as HTMLElement; super.buildRendering(); // now we should get assigned data-dojo-attach-points // place the contents of the original srcNode to the containerNode const src = this.srcNodeRef; const dest = this.containerNode; // the donNode is constructed now we need to connect event handlers this._connectEventHandlers(); if (src && dest) { while (src.firstChild) dest.appendChild(src.firstChild); } } /** Schedules a new deferred rendition within the scope of the widget */ scheduleRender(task: () => void) { return queueRenderTask(task, this._scope, this._priority); } abstract render(): JSX.Element; private _connectEventHandlers() { if (this._eventHandlers) this._eventHandlers.forEach(({ eventName, handlerMethod }) => { const handler = this[handlerMethod as keyof this]; if (typeof handler === "function") on(this.domNode, eventName, handler.bind(this) as (...args: unknown[]) => unknown); }); } _processTemplateNode( baseNode: T, getAttrFunc: (baseNode: T, attr: string) => string | undefined, // tslint:disable-next-line: ban-types attachFunc: (node: T, type: string, func?: (...args: unknown[]) => unknown) => dojo.Handle ): boolean { if (isNode(baseNode)) { const w = registry.byNode(baseNode); if (w) { // from dijit/_WidgetsInTemplateMixin this._processTemplateNode(w, (n, p) => { const v = n.get(p as keyof typeof n); return isNull(v) ? undefined : String(v); }, // callback to get a property of a widget (widget, type, callback) => { if (!callback) throw new Error("The callback must be specified"); // callback to do data-dojo-attach-event to a widget if (type in widget) { // back-compat, remove for 2.0 return widget.connect(widget, type, callback as EventListener); } else { // 1.x may never hit this branch, but it's the default for 2.0 return widget.on(type as keyof GlobalEventHandlersEventMap, callback); } }); // don't process widgets internals return false; } } // eslint-disable-next-line @typescript-eslint/ban-types return super._processTemplateNode(baseNode, getAttrFunc as (baseNode: T, attr: string) => string, attachFunc as (node: T, type: string, func?: Function) => dojo.Handle); } /** Starts current widget and all its supporting widgets (placed outside * `containerNode`) and child widgets (placed inside `containerNode`) */ startup() { // startup supporting widgets registry.findWidgets(this.domNode, this.containerNode).forEach(w => w.startup()); super.startup(); } destroy(preserveDom?: boolean) { this._scope.destroy(); super.destroy(preserveDom); } }