Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
const $state = Symbol('state')
const $shadow = Symbol('shadow')
// StateElement is a deterministic web component,
// inspired loosely by Elm's App Architecture pattern.
// To use, extend this class and override the static methods.
export class StateElement extends HTMLElement {
static flags(el) {
const flags = el.getAttribute('flags')
if (flags) {
return JSON.parse(flags)
} else {
return {}
}
}
static init(flags) {
throw new Error('Method not implemented')
}
static update(model, msg) {
throw new Error('Method not implemented')
}
static setup(shadowRoot, model, handle) {
throw new Error('Method not implemented')
}
static render(shadowRoot, model, handle) {
throw new Error('Method not implemented')
}
static event(event) {
return event
}
constructor() {
super()
this.send = this.send.bind(this)
this.handleEvent = this.handleEvent.bind(this)
// Attach *closed* shadow. We keep the insides of the component closed
// so that we can be sure no one is messing with the DOM, and DOM
// writes are deterministic functions of state.
this[$shadow] = this.attachShadow({mode: 'closed'})
const flags = this.constructor.flags(this)
const [state, fx] = this.constructor.init(flags)
this[$state] = state
this.constructor.setup(this[$shadow], this[$state], this.handleEvent)
this.effect(fx)
}
send(msg) {
const [state, fx] = this.constructor.update(this[$state], msg)
if (this[$state] !== state) {
this[$state] = state
this.constructor.render(this[$shadow], this[$state], this.handleEvent)
}
if (fx) {
this.effect(fx)
}
}
async effect(fx) {
const message = await fx
if (message) {
this.send(message)
}
}
handleEvent(event) {
const msg = this.constructor.event(event)
if (msg) {
this.send(msg)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment