import { isNull, mixin } from "@implab/core-amd/safe"; import { isPlainObject, isNode, isRendition, DojoNodePosition, Rendition, isInPage, isWidget, isDocumentFragmentNode, startupWidgets } from "./traits"; import dom = require("dojo/dom-construct"); import registry = require("dijit/registry"); export abstract class RenditionBase implements Rendition { private _attrs = {}; private _children = new Array(); private _created: boolean = false; visitNext(v: any) { if (this._created) throw new Error("The Element is already created"); if (isNull(v) || typeof v === "boolean") // skip null, undefined, booleans ( this will work: {value && {value}} ) return; if (isPlainObject(v)) { mixin(this._attrs, v); } else if (v instanceof Array) { v.forEach(x => this.visitNext(x)); } else { this._children.push(v); } } /** Renders DOM element for different types of the argument. */ protected getItemDom(v: any) { const tv = typeof v; if (tv === "string" || tv === "number" || v instanceof RegExp || v instanceof Date) { // primitive types converted to the text nodes return document.createTextNode(v.toString()); } else if (isNode(v)) { // nodes are kept as is return v; } else if (isRendition(v)) { // renditions are instantiated return v.getDomNode(); } else if (isWidget(v)) { // widgets are converted to it's markup return v.domNode; } else if (tv === "boolean" || v === null || v === undefined) { // null | undefined | boolean are removed, converted to comments return document.createComment(`[${tv} ${String(v)}]`); } else { // bug: explicit error otherwise throw new Error("Invalid parameter: " + v); } } ensureCreated() { if (!this._created) { this._create(this._attrs, this._children); this._children = []; this._attrs = {}; this._created = true; } } /** Is rendition was instantiated to the DOM node */ isCreated() { return this._created; } /** Creates DOM node if not created. No additional actions are taken. */ getDomNode() { this.ensureCreated(); return this._getDomNode(); } /** Creates DOM node if not created, places it to the specified position * and calls startup() method for all widgets contained by this node. * * @param {string | Node} refNode The reference node where the created * DOM should be placed. * @param {DojoNodePosition} position Optional parameter, specifies the * position relative to refNode. Default is "last" (i.e. last child). */ placeAt(refNode: string | Node, position?: DojoNodePosition) { const domNode = this.getDomNode(); const collect = (collection: HTMLCollection) => { const items = []; for (let i = 0, n = collection.length; i < n; i++) { items.push(collection[i]); } return items; }; const startup = (node: Node) => { if (node.parentNode) { const parentWidget = registry.getEnclosingWidget(node.parentNode); if (parentWidget && parentWidget._started) return startupWidgets(node); } if (isInPage(node)) startupWidgets(node); }; const startupPending = isDocumentFragmentNode(domNode) ? collect(domNode.children) : [domNode]; dom.place(domNode, refNode, position); startupPending.forEach(startup); } protected abstract _create(attrs: object, children: any[]): void; protected abstract _getDomNode(): TNode; }