Add model and add event listeners

This commit is contained in:
Misode
2020-05-25 03:37:39 +02:00
parent 92161c39f1
commit 1bef36a713
8 changed files with 128 additions and 33 deletions

View File

@@ -2,6 +2,8 @@ import { RootNode } from '../nodes/RootNode'
import { EnumNode } from '../nodes/EnumNode'
import { ObjectNode } from '../nodes/ObjectNode'
import { StringNode } from '../nodes/StringNode'
import { DataModel } from '../model/DataModel'
const EntityCollection = ['sheep', 'pig']
@@ -13,14 +15,12 @@ const predicateTree = new RootNode('predicate', {
type: new EnumNode(EntityCollection, {}),
nbt: new StringNode()
})
}, {
default: () => ({ condition: 'foo', predicate: { nbt: 'hi' } })
});
let dummyData = {
condition: 'foo',
predicate: {
nbt: 'hi'
}
}
const model = new DataModel(predicateTree)
document!.getElementById('view')!.innerHTML = predicateTree.render('', dummyData)
document!.getElementById('source')!.textContent = predicateTree.transform(dummyData)
model.render(document!.getElementById('view')!)
// document!.getElementById('source')!.textContent = predicateTree.transform(dummyData)

38
src/model/DataModel.ts Normal file
View File

@@ -0,0 +1,38 @@
import { RootNode } from "../nodes/RootNode"
import { Path } from "./Path"
type Registry = {
[id: string]: (el: Element) => void
}
const registryIdLength = 12
const dec2hex = (dec: number) => ('0' + dec.toString(16)).substr(-2)
export function getId() {
var arr = new Uint8Array((registryIdLength || 40) / 2)
window.crypto.getRandomValues(arr)
return Array.from(arr, dec2hex).join('')
}
export class DataModel {
private data: any
private schema: RootNode
private registry: Registry = {}
constructor(schema: RootNode) {
this.schema = schema
this.data = schema.default
}
register(id: string, callback: (el: Element) => void) {
this.registry[id] = callback
}
render(target: HTMLElement) {
target.innerHTML = this.schema.render(new Path(), this.data, this)
for (const id in this.registry) {
const element = target.querySelector(`[data-id="${id}"]`)
if (element !== null) this.registry[id](element)
}
}
}

26
src/model/Path.ts Normal file
View File

@@ -0,0 +1,26 @@
type PathElement = (string | number)
export class Path {
private arr: PathElement[]
constructor(arr?: PathElement[]) {
this.arr = arr || []
}
last(): PathElement {
return this.arr[this.arr.length - 1]
}
pop(): Path {
return new Path(this.arr.slice(0, -1))
}
push(element: PathElement): Path {
return new Path([...this.arr, element])
}
copy(): Path {
return new Path([...this.arr])
}
}

View File

@@ -1,23 +1,28 @@
import { DataModel, getId } from "../model/DataModel"
import { Path } from "../model/Path"
export interface INode<T> {
setParent: (parent: INode<any>) => void
transform: (value: T) => any
render: (field: string, value: T) => string
render: (path: Path, value: T, model: DataModel) => string
}
export interface NodeChildren {
export type NodeChildren = {
[name: string]: INode<any>
}
export interface NodeMods<T> {
export type NodeMods<T> = {
default?: () => T
transform?: (value: T) => any
}
export abstract class AbstractNode<T> implements INode<T> {
private transformMod = (v: T) => v
protected parent?: INode<any>
parent?: INode<any>
default?: T
transformMod = (v: T) => v
constructor(mods?: NodeMods<T>) {
if (mods?.default) this.default = mods.default()
if (mods?.transform) this.transformMod = mods.transform
}
@@ -25,9 +30,24 @@ export abstract class AbstractNode<T> implements INode<T> {
this.parent = parent
}
wrap(path: Path, model: DataModel, renderResult: string): string {
const id = getId()
model.register(id, el => {
console.log(`Callback ${id} -> ${path.last()}`)
this.mounted(el, path, model)
})
return `<div data-id="${id}">${renderResult}</div>`
}
mounted(el: Element, path: Path, model: DataModel) {
el.addEventListener('change', evt => this.updateModel(el, path, model))
}
updateModel(el: Element, path: Path, model: DataModel) {}
transform(value: T) {
return this.transformMod(value)
}
abstract render(field: string, value: T): string
abstract render(path: Path, value: T, model: DataModel): string
}

View File

@@ -1,4 +1,6 @@
import { AbstractNode, NodeMods } from './AbstractNode'
import { DataModel } from '../model/DataModel'
import { Path } from '../model/Path'
export class EnumNode extends AbstractNode<string> {
protected options: string[]
@@ -8,10 +10,13 @@ export class EnumNode extends AbstractNode<string> {
this.options = options
}
render (field: string, value: string) {
return `<span>${field}</span>
updateModel(el: Element, path: Path, model: DataModel) {
}
render(path: Path, value: string, model: DataModel) {
return this.wrap(path, model, `<span>${path.last()}</span>
<select value="${value}">
${this.options.map(o => `<option>${o}</option>`)}
</select>`
</select>`)
}
}

View File

@@ -1,4 +1,6 @@
import { AbstractNode, NodeChildren, NodeMods } from './AbstractNode'
import { DataModel } from '../model/DataModel'
import { Path } from '../model/Path'
export interface IObject {
[name: string]: any
@@ -15,10 +17,6 @@ export class ObjectNode extends AbstractNode<IObject> {
})
}
getFields() {
return this.fields
}
transform(value: IObject) {
if (value === undefined) return undefined
value = value || {}
@@ -29,12 +27,12 @@ export class ObjectNode extends AbstractNode<IObject> {
return res;
}
render(field: string, value: IObject) {
render(path: Path, value: IObject, model: DataModel) {
if (value === undefined) return ``
return `<span>${field}:</span><div style="padding-left:8px">
return this.wrap(path, model, `<span>${path.last()}:</span><div style="padding-left:8px">
${Object.keys(this.fields).map(f => {
return this.fields[f].render(f, value[f])
}).join('<br>')}
</div>`
return this.fields[f].render(path.push(f), value[f], model)
}).join('')}
</div>`)
}
}

View File

@@ -1,10 +1,12 @@
import { NodeChildren, NodeMods } from './AbstractNode'
import { ObjectNode, IObject } from './ObjectNode'
import { Path } from '../model/Path'
import { DataModel } from '../model/DataModel'
export class RootNode extends ObjectNode {
private id: string
id: string
constructor(id: string, fields: NodeChildren, mods?: NodeMods<any>) {
constructor(id: string, fields: NodeChildren, mods?: NodeMods<IObject>) {
super(fields, mods)
this.id = id
}
@@ -13,12 +15,12 @@ export class RootNode extends ObjectNode {
return JSON.stringify(super.transform(value))
}
render(field: string, value: IObject) {
render(path: Path, value: IObject, model: DataModel) {
value = value || {}
return `<div>
${Object.keys(this.fields).map(f => {
return this.fields[f].render(f, value[f])
}).join('<br>')}
return this.fields[f].render(path.push(f), value[f], model)
}).join('')}
</div>`
}
}

View File

@@ -1,11 +1,17 @@
import { AbstractNode, NodeMods } from './AbstractNode'
import { Path } from '../model/Path'
import { DataModel } from '../model/DataModel'
export class StringNode extends AbstractNode<string> {
constructor(mods?: NodeMods<string>) {
super(mods)
}
render(field: string, value: string) {
return `<span>${field}</span> <span>${value || ''}</span>`
updateModel(el: Element, path: Path, model: DataModel) {
}
render(path: Path, value: string, model: DataModel) {
return this.wrap(path, model,
`<span>${path.last()}</span> <input value="${value || ''}"></input>`)
}
}