/// namespace Leaf { export class DataSource { public events = new EventEmitter<{ [key: string]: any change: Partial }>() public readonly fields: (keyof TAttribute)[] // instance id public id = Date.now().toString() + Math.random().toString() protected _data: TAttribute = {} as TAttribute public readonly data: TAttribute [index: string]: any constructor(public readonly _name: string, protected _fields: (keyof TAttribute)[] = []) { for (let field of this._fields) { this.define(field) } Object.defineProperty(this, "fields", { get: () => { return this._fields.slice(0) } }) Object.defineProperty(this, "data", { get: () => { return this as any as TAttribute }, set: (values: TAttribute) => { this.sets(values) } }) } protected define(name: keyof TAttribute, defaultValue: any = null) { Object.defineProperty(this, name, { get: () => { return this.get(name) }, set: (value) => { return this.set(name, value) } }) } public hasKey(key: string) { return this._fields.indexOf(key as keyof TAttribute) >= 0 } public sets(obj: any): boolean { if (!obj) { return } if (obj instanceof DataSource) { obj = obj.toJSON() } let changed = false let diff = {} for (let key in obj) { if (!this.hasKey(key)) { continue } let old = this._data[key] let value = obj[key] if (this.set(key as keyof TAttribute, value, true)) { changed = true this.events.emit(`change/${key}`, value, old) diff[key as string] = old } } if (changed) { this.events.emit("change", diff as any) } } public set(key: keyof TAttribute, value: any, silent?: boolean): boolean { let old = this._data[key as string] if (!this.hasKey(key as string)) return false; if (Leaf.Util.deepEqual(old, value)) { return false } this._data[key as string] = value if (silent) return true this.events.emit(`change/${key}`, value, old) let diff: { [index: string]: any } = {} diff[key as string] = old this.events.emit("change", diff as any) return true } public get(key: keyof TAttribute) { return this._data[key as string] || null } public toJSON() { return JSON.parse(JSON.stringify(this._data)) } } export class Model extends DataSource{ } export class DataTree { constructor(public readonly source: DataSource) { } getNode(path?: string) { if (!path) return new DataNode(this) path = path.trim() let routes = path.split(".") let parent = this.source.data as any let valid = false for (let route of routes) { if (!this.valueCanBeObject(parent[route])) { return null } if (parent[route]) { parent = parent[route] } else if (this.valueCanBeObject(parent[route])) { parent = {} continue } else { return null } } // Point at something can possibly be a data node return new DataNode(this, path) } valueCanBeObject(v: any) { if (typeof v === "string") return false if (typeof v === "boolean") return false if (typeof v === "number") return false if (typeof v === "undefined") return false if (typeof v === "object") return true return false } getValueByPath(path: string) { if (!path) return this.source["_data"] path = path.trim() let routes = path.split(".") let parent = this.source["_data"] as any let valid = false let last = routes.pop() for (let route of routes) { if (!this.valueCanBeObject(parent[route])) { return undefined } if (parent[route]) { parent = parent[route] } else { return undefined } } // Point at something can possibly be a data node let v = parent[last] return v } setValueByPath(path: string, v: any): boolean { if (!path) { this.source.sets(v) return true } path = path.trim() let routes = path.split(".") let root = routes[0] let last = routes.pop() let parent = this.source["_data"] as any let valid = false let passed = [] for (let route of routes) { passed.push(route) if (!this.valueCanBeObject(parent[route])) { throw new Error(`Typeof ${typeof parent} at ${passed.join(".")} in path ${path} is not a nestable`) } if (parent[route]) { parent = parent[route] } else if (this.valueCanBeObject(parent[route])) { parent[route] = {} parent = parent[route] } } // Point at something can possibly be a data node let old = parent[last] parent[last] = v this.source.events.emit(`change/${root}`, v, old) routes.push(last) this.source.events.emit(`change`, { [routes.join(".")]: old } as any) return true } } export class DataNode { constructor(public readonly tree: DataTree, public readonly path: string = "") { Object.defineProperty(this, "v", { get: () => this.get(), set: (v) => this.set(v) }) } getProxyOrValue() { let v = this.get() if (this.tree.valueCanBeObject(v)) { return new Proxy((v) as Object, { get: (obj, k) => { if (typeof k !== "string") return let node = this.tree.getNode(this.path + `${this.path && "."}${k}`) if (node) return node.getProxyOrValue() return obj[k] }, set: (obj, k, v) => { if (typeof k !== "string") return this.tree.setValueByPath(this.path + `${this.path && "."}${k}`, v) return true } }) } return v } set(v: T) { return this.tree.setValueByPath(this.path, v) } get(): T { return this.tree.getValueByPath(this.path) } toString() { return `` } } }