Stopwatch
A running timer ticks an elapsed signal and a text binding renders it — the display doesn't know or care that it's a timer. Compiled markup has no combinator for "every 100ms"; this is what wasmwrap.SetInterval is for, so this demo is WebAssembly-only.
How it works
State
elapsed := reactive.NewSignal(0.0)One signal, holding seconds as a float. It starts at zero and nothing about it says "timer" — it's a value like any other.
The tick
cancel := wasmwrap.SetInterval(func() {
elapsed.Update(func(v float64) float64 { return v + 0.1 })
}, 100)
// start: cancel = wasmwrap.SetInterval(...)
// pause: cancel()SetInterval runs the function every 100ms; the returned cancel stops it. The tick is the only place that knows this demo has anything to do with time.
The display
reactive.BindText(root.Find("[data-time]"), func() string {
v := elapsed.Get()
ms := int(v*1000) % 1000 / 100
sec := int(v) % 60
min := int(v) / 60
return fmt.Sprintf("%d:%02d.%d", min, sec, ms)
})BindText re-runs on every tick because it reads elapsed.Get() — the formatting is just a function of the value, so it can be as simple or as fancy as you like.
Start, pause, reset
reactive.On(start, "click", func(wasmwrap.Event) {
if running {
cancel()
start.SetText("Resume")
} else {
cancel = wasmwrap.SetInterval(func() {
elapsed.Update(func(v float64) float64 { return v + 0.1 })
}, 100)
start.SetText("Pause")
}
running = !running
})
reactive.On(reset, "click", func(wasmwrap.Event) { elapsed.Set(0) })The start button flips between starting and cancelling the interval and relabels itself. Reset just writes zero — the display updates on its own.
Full source
elapsed := reactive.NewSignal(0.0)
var cancel func()
running := false
reactive.Hydrate("#stopwatch-demo", func(root wasmwrap.Element) {
start := root.Find("[data-start]")
reactive.BindText(root.Find("[data-time]"), func() string {
v := elapsed.Get()
ms := int(v*1000) % 1000 / 100
sec := int(v) % 60
min := int(v) / 60
return fmt.Sprintf("%d:%02d.%d", min, sec, ms)
})
reactive.On(start, "click", func(wasmwrap.Event) {
if running {
cancel()
start.SetText("Resume")
} else {
cancel = wasmwrap.SetInterval(func() {
elapsed.Update(func(v float64) float64 { return v + 0.1 })
}, 100)
start.SetText("Pause")
}
running = !running
})
reactive.On(root.Find("[data-reset]"), "click", func(wasmwrap.Event) {
elapsed.Set(0)
})
})How this demo runs
scripts/demo.go is compiled to demo.wasm by CompileFrom and loaded by WasmScript — see Scripts & WebAssembly. Without WebAssembly the page still renders; only the time stays put.