@winged/core
Version:
Morden webapp framekwork made only for ts developers. (UNDER DEVELOPMENT, PLEASE DO NOT USE)
156 lines (146 loc) • 5.23 kB
text/typescript
import { StateDependencies, ViewState } from '../types'
import { utils } from '../utils'
import { Renderable } from './Renderable'
import { AtAttr } from './vAttribute/AtAttr'
import { AtRef } from './vAttribute/AtRef'
import { AtValue } from './vAttribute/AtValue'
import { DataAttr } from './vAttribute/DataAttr'
import { NormalDomAttr } from './vAttribute/NormalDomAttr'
import { OnEventAttr } from './vAttribute/OnEventAttr'
import { VAttribute } from './vAttribute/VAttribute'
import { AttrDict, ContentStructList, PrevSibling, vDomFactory, VDomStruct, VNodeRegister } from './vdom'
import { VAttrVerifyError } from './vdomErrors'
import { vdomUtils } from './vdomUtils'
export class VElement extends Renderable {
public static vAttributeClasses = [
AtAttr, AtRef, AtValue,
DataAttr, OnEventAttr,
NormalDomAttr
]
public tagName: string
public element?: HTMLElement
public vAttributes: { [key: string]: VAttribute } = {}
private children: Renderable[] = []
constructor(tagName: string, attrDict: AttrDict, children: ContentStructList, register: VNodeRegister) {
super()
this.tagName = tagName
const copiedAttrDict = utils.clone(attrDict, false)
for (const VAttrClass of VElement.vAttributeClasses) {
for (const attrName in copiedAttrDict) {
const attr = copiedAttrDict[attrName]
let vAttr: VAttribute | undefined
if (typeof VAttrClass.matcher === 'string' && VAttrClass.matcher === attrName) {
vAttr = new VAttrClass(attrName, attr, this, register)
} else if (VAttrClass.matcher instanceof RegExp && VAttrClass.matcher.test(attrName)) {
vAttr = new VAttrClass(attrName, attr, this, register)
}
if (vAttr) {
if (vAttr.created) {
this.vAttributes[attrName] = vAttr
delete copiedAttrDict[attrName]
} else {
vAttr.destroy()
}
}
}
}
// handling unresolved attrs
for (const attrName in copiedAttrDict) {
throw new VAttrVerifyError(
attrName,
`Unsupported vAttribute: '${attrName}' in VElement '${tagName}' of ${register.viewClassName}, please check!`
)
}
for (const struct of children as Array<string | VDomStruct>) {
this.children.push(vDomFactory.createVChildren(struct, 'common', register))
}
this.stateDependencies = this.initStateDependencies()
}
public destroyDomElement() {
super.destroyDomElement()
if (!this.element) {
return
}
for (const key in this.vAttributes) {
this.vAttributes[key].onElementDestroy(this.element)
}
if (this.element.parentElement) {
this.element.parentElement.removeChild(this.element)
}
this.element = undefined
}
public render(state: ViewState, modifiedState: ViewState, container: HTMLElement, prevSibling: PrevSibling) {
if (!this.stateDependencies) {
console.error(this)
throw new Error('Can\'t render VElement: stateDependencies is null')
}
// console.log("do render");
if (!this.element) {
this.createDomElement(container, prevSibling)
this.doRender(state, modifiedState, prevSibling)
} else {
if (vdomUtils.checkStateDependencies(modifiedState, this.stateDependencies)) {
this.doRender(state, modifiedState, prevSibling)
}
}
// if (prevSibling.node === this.element) {
// prevSibling.node = this.element.nextSibling;
// }
prevSibling.node = this.element
}
public skipRender(prevSibling: PrevSibling) {
if (!this.element) {
return
}
super.skipRender(prevSibling)
prevSibling.node = this.element
}
public destroy() {
this.destroyDomElement()
for (const key in this.vAttributes) {
this.vAttributes[key].destroy()
}
for (const child of this.children) {
child.destroy()
}
delete this.vAttributes
delete this.children
super.destroy()
}
public forEachChildren(fn: Renderable.ChildrenIter) {
for (const child of this.children) {
if (fn(child) === true) {
return
}
}
}
protected initStateDependencies() {
const deps: StateDependencies = {}
for (const key in this.vAttributes) {
const vAttr = this.vAttributes[key]
vdomUtils.mergeStateDependenciesN(deps, vAttr.stateDependencies)
}
vdomUtils.mergeStateDependenciesWithChildren(deps, this.children)
return deps
}
private createDomElement(container: HTMLElement, prevSibling: PrevSibling) {
this.element = document.createElement(this.tagName)
if (prevSibling.node) {
container.insertBefore(this.element, prevSibling.node.nextSibling)
} else {
container.insertBefore(this.element, container.firstChild)
}
for (const key in this.vAttributes) {
this.vAttributes[key].onElementCreate(this.element)
}
}
private doRender(state: ViewState, modifiedState: ViewState, prevSibling: PrevSibling) {
for (const key in this.vAttributes) {
this.vAttributes[key].onRender(state, modifiedState)
}
prevSibling.node = undefined
for (const child of this.children) {
child.render(state, modifiedState, this.element!, prevSibling)
}
}
}