///
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
}