@winged/core
Version:
Morden webapp framekwork made only for ts developers. (UNDER DEVELOPMENT, PLEASE DO NOT USE)
150 lines (139 loc) • 4.73 kB
text/typescript
import { BasicType, NonFuncPropNames, NonFuncProps, RSDList, RSDObject } from '../types'
import { Observable } from './Observable'
import { ViewModelList } from './ViewModelList'
interface IViewModel {
[key: string]: any
}
/** ViewModel is only for internal use of the framework */
export class ViewModel extends Observable {
private static bultinPropDict: { [key: string]: boolean } = {
_stateStore: true,
_stateDescriber: true
}
// [key: string]: any;
private _stateStore: { [key: string]: BasicType | ViewModel | ViewModelList | null } = {}
private _stateDescriber: RSDObject
constructor(data: object, stateDescriber: RSDObject) {
super()
this._stateDescriber = stateDescriber
for (const field in this._stateDescriber) {
// for (const key in data) {
// this[key as keyof this] = data[key as keyof typeof data];
// }
Object.defineProperty(this, field, {
get() {
return (this as ViewModel)._getRenderableState(field)
},
set(value) {
(this as ViewModel)._setRenderableState(field, value)
}
})
}
// if a field is described in describer, it will be set via this.setValue
// otherwise, it will be set directly on context like plain objects
for (const field in data) {
this[field as keyof this] = data[field as keyof typeof data]
}
}
public _destory() {
for (const id in this._watchedTargets) {
this._watchedTargets[id].target._stopWatchBy(this)
}
delete this._watchedTargets
}
public _get(field: string) {
if (this._stateDescriber[field]) {
return this._getRenderableState(field)
} else {
return (this as IViewModel)[field]
}
}
public _getFields(): string[] {
const fields: string[] = []
for (const field in this) {
if (Object.hasOwnProperty(field) && !ViewModel.bultinPropDict[field]) {
fields.push(field)
}
}
return fields.concat(Object.keys(this._stateStore))
}
public _set(field: string, value: any) {
if (this._stateDescriber[field]) {
this._setRenderableState(field, value)
} else {
(this as IViewModel)[field] = value
}
}
private _getRenderableState(field: string) {
return this._stateStore[field]
}
private _setRenderableState(field: string, value: BasicType | ViewModel | ViewModelList) {
const describer = this._stateDescriber[field]
const oldValue = this._stateStore[field]
if (value === oldValue) {
return
}
if (oldValue && oldValue instanceof Observable) {
this._unregisterChild(oldValue)
}
if (value === null || value === undefined) {
// TODO: is setting _state[stateName] to 'null' or 'undefined' to be ok?
this._stateStore[field] = value
} else if (describer === true) {
// basicType state
this._stateStore[field] = value as BasicType
} else if (describer instanceof Array) {
// ViewModelList
if (value instanceof Array) {
// replace this with monky-patched array
// to watch every change of the list
value = new ViewModelList(value, describer[0] as RSDList)
}
if (!(value instanceof ViewModelList)) {
console.error(value)
throw new TypeError(`Can't set state ${field}: Expecting an array`)
}
const watchInfo = this._watchedTargets[value._observableId]
if (watchInfo) {
watchInfo.watcherCount++
} else {
this._watchedTargets[value._observableId] = {
target: value,
watcherCount: 1
}
value._watchBy(this, (modificationTree) => {
this._emitValueChange({ [field]: modificationTree })
})
}
this._stateStore[field] = value
} else {
// ViewModel
if (typeof value === 'object') {
value = new ViewModel(value, describer as RSDObject)
}
if (!(value instanceof ViewModel)) {
console.error(value)
throw new TypeError(`Can't set state ${field}: Expecting an object`)
}
const watchInfo = this._watchedTargets[value._observableId]
if (watchInfo) {
watchInfo.watcherCount++
} else {
this._watchedTargets[value._observableId] = {
target: value,
watcherCount: 1
}
value._watchBy(this, (modificationTree) => {
this._emitValueChange({ [field]: modificationTree })
})
}
this._stateStore[field] = value
}
// console.log(`setting ${field} value of ${this.getModelId()} to ${value}`);
this._emitValueChange({ [field]: {} })
}
}
export namespace Model {
export type MField<M> = NonFuncPropNames<M>
export type MData<M> = NonFuncProps<M>
}