tea_dom_counter

The whole application: a model, three messages, update and view, plus the text form of the model the wire carries between turns. No io and no host; main.dawn is the one call that connects it to one.

Running it

What CI runs and the output it checks, byte for byte.

sh

dawn run examples/projects/tea_dom_counter

with this on stdin:

{"op":"init"}
{"op":"event","model":"0","path":[2,1],"event":"click"}
{"op":"event","model":"1","path":[2,0],"event":"click"}
{"op":"event","model":"0","path":[2,0],"event":"click"}
{"op":"event","model":"-1","path":[2,2],"event":"click"}
{"op":"event","model":"0","path":[1],"event":"click"}
not json at all
{"ok":true,"model":"0","patches":[{"path":[],"op":"replace","node":{"t":"elem","tag":"div","props":[["class","counter"]],"on":[],"kids":[{"t":"elem","tag":"h1","props":[],"on":[],"kids":[{"t":"text","s":"dawn counter"}]},{"t":"elem","tag":"p","props":[["class","count"]],"on":[],"kids":[{"t":"text","s":"0"}]},{"t":"elem","tag":"div","props":[["class","row"]],"on":[],"kids":[{"t":"elem","tag":"button","props":[],"on":["click"],"kids":[{"t":"text","s":"-"}]},{"t":"elem","tag":"button","props":[],"on":["click"],"kids":[{"t":"text","s":"+"}]},{"t":"elem","tag":"button","props":[],"on":["click"],"kids":[{"t":"text","s":"reset"}]}]}]}}]}
{"ok":true,"model":"1","patches":[{"path":[1,0],"op":"replace","node":{"t":"text","s":"1"}}]}
{"ok":true,"model":"0","patches":[{"path":[1,0],"op":"replace","node":{"t":"text","s":"0"}}]}
{"ok":true,"model":"-1","patches":[{"path":[1,0],"op":"replace","node":{"t":"text","s":"-1"}}]}
{"ok":true,"model":"0","patches":[{"path":[1,0],"op":"replace","node":{"t":"text","s":"0"}}]}
{"ok":false,"kind":"no-handler","error":"nothing at [1] listens for `click`"}
{"ok":false,"kind":"bad-request","error":"not JSON: invalid literal at offset 0"}

examples/projects/tea_dom_counter/src/main.dawn

main.dawn
# The whole io surface of the demo: one call. The same binary answers a
# browser (built with `dawnc build --target wasm --reactor`) and a shell:
#
#   $ echo '{"op":"init"}' | dawn run examples/projects/tea_dom_counter

use tea_core/app.{update, view}
use tea_dom/reactor.{serve}
use counter.{init, encode, decode}

pub fn main() -> Unit !io = serve(init(), encode, decode, update, view)

examples/projects/tea_dom_counter/src/counter.dawn

counter.dawn
# The whole application: a model, three messages, `update` and `view`, plus
# the text form of the model the wire carries between turns. No io and no
# host; `main.dawn` is the one call that connects it to one.
#
# `update` and `view` are an `impl App`, and `main` hands the trait's two
# methods to `serve` by name. The gates' fixtures live in
# examples/projects/tea_dom_counter_contract, not here.

use tea_core/app.{App, view}
use tea_core/cmd.{Cmd, NoCmd}
use tea_dom/node.{Node}
use tea_dom/dsl.{button, div, el, text}

## The whole model: how many times the user has pressed something, net.
pub type Counter = { n: Int }

## What the three buttons mean.
pub type Msg =
  | Inc
  | Dec
  | Reset

## The model a fresh page starts from.
pub fn init() -> Counter = Counter { n: 0 }

impl App[Counter] {
  type Msg = Msg
  type View = Node[Msg]

  fn update(m: Counter, msg: Msg) -> (Counter, Cmd[Msg]) =
    match msg {
      Inc -> (Counter { n: m.n + 1 }, NoCmd)
      Dec -> (Counter { n: m.n - 1 }, NoCmd)
      Reset -> (init(), NoCmd)
    }

  fn view(m: Counter) -> Node[Msg] =
    div(class: "counter", kids: [
      el("h1", kids: [text("dawn counter")]),
      el("p", class: "count", kids: [text(to_string(m.n))]),
      div(class: "row", kids: [button("-", Dec), button("+", Inc), button("reset", Reset)]),
    ])
}

## A model as text, and back. The host carries it untouched between turns.
pub fn encode(m: Counter) -> String = to_string(m.n)

## Total, because a host may hand back anything: unreadable text reads as the
## initial model rather than as a panic.
pub fn decode(s: String) -> Counter =
  match parse_int(s) {
    Some(n) -> Counter { n: n }
    None -> init()
  }

test "the view is a value, so a frame is one assertion" {
  let want: Node[Msg] =
    div(class: "counter", kids: [
      el("h1", kids: [text("dawn counter")]),
      el("p", class: "count", kids: [text("0")]),
      div(class: "row", kids: [button("-", Dec), button("+", Inc), button("reset", Reset)]),
    ])
  assert init().view() == want
}