JSFiddle - React, Tailwind, and code Playground

by mcsf

HTML

<meta charset=utf8>
<title>Counter</title>

<body>
  <p data-setup="counter"></p>
  <button data-setup="countIncrementer">count++</button>
  <button data-setup="multiplierIncrementer">multiplier++</button>

  <script>
    console.log('out')
    window.addEventListener('framework-ready', () => {
      console.log('in')
      const count = new Datum(2)
      const multiplier = new Datum(5)
      const product = new DerivedDatum([count, multiplier], (a, b) => a * b)

      const setup = {
        counter(element) {
          element.append(...tpl `${count} Ă— ${multiplier} = ${product}`)
        },
        countIncrementer(element) {
          element.onclick = () => count.set(count.get() + 1)
        },
        multiplierIncrementer(element) {
          element.onclick = () => multiplier.set(multiplier.get() + 1)
        },
      }
    })

  </script>
</body>

JavaScript

class Datum {
	constructor(value) {
		this.value = value
		this.listeners = []
	}
	subscribe(callback) {
		this.listeners.push(callback)
	}
	get() {
		return this.value
	}
	set(value) {
		this.value = value
		this.listeners.forEach(l => l(value))
	}
}

class DerivedDatum extends Datum {
	constructor(deps, valueFn) {
		function getValue() {
			return valueFn(...deps.map(d => d.get()))
		}
		super(getValue())
		deps.forEach(d => d.subscribe(() => {
			const value = getValue()
			this.value = value
			this.listeners.forEach(l => l(value))
		}))
	}
	set(value) {
		throw new TypeError('Unsupported method')
	}
}

const tpl = (() => {
	const T = value => document.createTextNode(value)
	const D = datum => {
		const node = document.createTextNode(datum.get())
		datum.subscribe(value => node.textContent = value)
		return node
	}
	return function tpl(strings, ...datums) {
		return strings.flatMap((string, i) => [
			T(string),
			i < strings.length - 1 ? D(datums[i]) : undefined,
		].filter(Boolean))
	}
})()

window.addEventListener('load', () => {
	for (const element of document.querySelectorAll('[data-setup]')) {
		const methodName = element.dataset.setup
		setup[methodName](element)
	}
})