/// namespace Leaf { export class Widget { // Global namespace isWidget = true static namespace = new Namespace() static events = new EventEmitter<{ create: Widget }>() // Use global namespace by default public namespace = Widget.namespace // Set custom template for this module static setTemplate(template: WidgetTemplate) { this.prototype.template = template } // VM data short cut and ViewModels public ListItemData: any public VM: TVMs // VMNest contains value or proxy can be safely used with VMTest.order.username even order doesn't exists public VMNest: TVMs public ViewModel: ViewModel // If direct parent widget that contains this widget public parent: WidgetAny = null // By default every widget has a event emitter public events: EventEmitter = new EventEmitter() // All element with data-id public UI: TUIs = {} as TUIs // root of the current element public node: WidgetElement public templates: { [index: string]: string } // Custom DOM events that this widget may trigger public providingEvents: DOMEventInfo[] = [] // Event that we should delegate (bubble or not) public interestingUnbubblingEvents: DOMEventInfo[] = Leaf.CoreLogic.UnbubbleEvents.slice(0) public interestingBubblingEvents: DOMEventInfo[] = Leaf.CoreLogic.BubbleEvents.slice(0) // Name of this widget public readonly asSlot: Leaf.WidgetSlotBehavior private _asSlot: Leaf.WidgetSlotBehavior public readonly widgetName: string public readonly core: WidgetCoreDesign = new WidgetCore(this as any) public readonly delegates: WidgetDOMEventDelegator = new WidgetDOMEventDelegator(this) public readonly initializer: WidgetInitializerDesign = new WidgetInitializer(this as any) public toString() { return `` } public before(who: WidgetAny | HTMLElement): boolean { let node: HTMLElement if (who instanceof Widget) { node = who.node } else { node = who } if (!node.parentElement) return false node.parentElement.insertBefore(this.node, node) return true } public after(who: WidgetAny | HTMLElement): boolean { let node: HTMLElement if (who instanceof Widget) { node = who.node } else { node = who } if (!node.parentElement) return false if (node.nextSibling) { node.parentElement.insertBefore(this.node, node.nextSibling) } else { node.parentElement.appendChild(this.node) } return true } public prependTo(who: WidgetAny | HTMLElement): boolean { let node: HTMLElement if (who instanceof Widget) { node = who.node } else { node = who } if (node.children.length === 0) { node.appendChild(this.node) } else { let first = node.children[0] node.insertBefore(this.node, first) } return true } public appendTo(who: WidgetAny | HTMLElement): boolean { let node: HTMLElement if (who instanceof Widget) { node = who.node } else { node = who } node.appendChild(this.node) return true } constructor(public template?: WidgetTemplate, name?: string, public _VMFields: (keyof TVMs)[] = []) { if (name) { this.widgetName = this.widgetName || ((this as any).constructor.name) || "AnonymouseWidget" } // Setup ViewModals if (!this._VMFields) { this._VMFields = [] } this.ViewModel = new ViewModel(this.widgetName, this._VMFields) Property.define(this, "VM", { get: () => { // for nont proxy if (typeof Proxy === "undefined") return this.ViewModel return this.ViewModel.tree.getNode().getProxyOrValue() } , set: (values: any) => { return this.ViewModel.sets(values) } }) Property.define(this, "VMNest", { get: () => { return this.ViewModel.tree.getNode().getProxyOrValue() } , set: (values: any) => { return this.ViewModel.sets(values) } }) // If we don't have a template, dont initialize it // Maybe we are generated widget, or maybe we gonna do it latter. Property.define(this, "asSlot", { get: () => { if (!this._asSlot) this._asSlot = new Leaf.WidgetSlotBehavior(this as any) return this._asSlot }, }) this.initializer.initTemplate(template) Widget.events.emit("create", this as any) } static makeList(el: HTMLElement) { return new Leaf.List(el) } protected emitDOMEvent(name: string, bubble: boolean, props: any = {}) { let valid = this.providingEvents.some((info) => { return info.name === name }) if (!valid) { throw new Error(`Should add events ${name}to available events at construtor`) } let e = new CustomEvent(name, { bubbles: bubble, cancelable: true }) for (let prop in props) { e[prop] = props[prop] } this.node.dispatchEvent(e) } protected emitWidgetEvent(name: string, value: any = {}) { this.events.emit(name as any, value) this.emitDOMEvent(name, false, value) } protected include(W) { this.namespace.include(W) } protected expose(name: string, remoteName?: string) { if (!remoteName) { remoteName = name } if (typeof this[name] === "function") { Object.defineProperty(this.node, remoteName, { get: () => { return this[name].bind(this) } }) return } let cap = Util.capitalize(name) let getterName = `onGet${cap}` let setterName = `onSet${cap}` Object.defineProperty(this.node, remoteName, { get: () => { if (this[getterName]) return this[getterName]() else return this[name] }, set: (value) => { if (this[setterName]) return this[setterName](value) else return this[name] } }) } protected makeList(who: (keyof TUIs) | HTMLElement) { let el: HTMLElement let name: string if (typeof who === "string") { el = this.UI[who] name = who } else { for (let prop in this.UI) { if (this.UI[prop] === who as HTMLElement) { el = who as HTMLElement name = prop break } } if (!name) { return } } let list = Leaf.Widget.makeList(el) list.events.listenBy(this, "child/add", (item) => { let fn = this[`onChildAdd${Leaf.Util.capitalize(name)}`] if (fn) { fn.call(this, item) } }) list.events.listenBy(this, "child/remove", (item) => { let fn = this[`onChildRemove${Leaf.Util.capitalize(name)}`] if (fn) { fn.call(this, item) } }) if (!this[name]) { this[name] = list } return list } behave(handler: THandlers) { // This may not work property in browser without defineProperty for (let key in handler) { (this as any)[key] = handler[key] } } } export abstract class GeneratedWidget extends Widget { constructor(private generator: Function, name: string, public _VMFields: (keyof TVMs)[] = []) { super(null, name, _VMFields) this.initGeneratedTemplate(generator.call(this)) } public GeneratedConstructor: WidgetConstructor public BindedWidgets: { path: string name: string }[] = [] public BindedLists: { name: string type: "inline" | "reference" | "void" | string reference: string }[] = [] public TestDatas: { name: string content: string json: any }[] = [] public InitialData: any = null initGeneratedTemplate(node: WidgetElement) { this.node = node this.core.bindUI(this.node, "node") if (!this.node.widget) { // in case it's a shared node by it's child // the child will own it this.node.widget = this as any } } protected _e(tagName: string, properties: ElementConfigProperties, children: WidgetElement[]): WidgetElement { return this.initializer.createElement(tagName, properties, children) } protected _t(rule: Leaf.DynamicStringConfig): Text { return this.initializer.createText(rule) } renderRecursive(data: any = {}, R: any = this["R"] || {}) { for (let widget of this.BindedWidgets) { if (!this[widget.name]) { let Cons = this.resolveWidgetByPath(R, widget.path) if (!Cons) { console.error("Invalid constructor", widget.path) continue } this[widget.name] = new Cons() } if (data[widget.name] && this[widget.name]["renderRecursive"]) { this[widget.name]["renderRecursive"](data[widget.name]) } } for (let list of this.BindedLists) { if (this[list.name] && Array.isArray(data[list.name])) { let Cons = this.resolveWidgetByPath(R, list.reference) if (!Cons) continue for (let sub of data[list.name]) { let child: WidgetAny = new Cons() this[list.name].push(child) if (child["renderRecursive"]) { child["renderRecursive"](sub) } else { child.VM = sub } } } } for (let name of this._VMFields) { if (data[name as string]) { this.VM[name as string] = data[name as string] } } } private resolveWidgetByPath(R: any, path: string): StandaloneWidgetConstructor { if (!path) return null let routes = path.split("/").filter(item => item) let cur = R for (let route of routes) { route = Leaf.Util.capitalize(route) if (!cur[route]) return null cur = cur[route] } return cur } } export interface StandaloneWidgetConstructor { new(): WidgetAny; } export interface WidgetConstructor { new(el: HTMLElement): WidgetAny; } export type WidgetAny = Widget }