UNPKG

@winged/core

Version:

Morden webapp framekwork made only for ts developers. (UNDER DEVELOPMENT, PLEASE DO NOT USE)

150 lines (139 loc) 4.73 kB
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> }