type Html =
| Element(String, List>, List>)
| Text(String)
| Empty
type Attr =
| Class(String)
| Id(String)
| OnClick(M)
fn div(attrs: List>, children: List>): Html = Element("div", attrs, children)
fn span(attrs: List>, children: List>): Html = Element("span", attrs, children)
fn h1(attrs: List>, children: List>): Html = Element("h1", attrs, children)
fn button(attrs: List>, children: List>): Html = Element("button", attrs, children)
fn text(content: String): Html = Text(content)
fn class(name: String): Attr = Class(name)
fn onClick(msg: M): Attr = OnClick(msg)
type Model =
| Counter(Int)
fn getCount(model: Model): Int =
match model {
Counter(n) => n,
}
fn init(): Model = Counter(0)
type Msg =
| Increment
| Decrement
| Reset
fn update(model: Model, msg: Msg): Model =
match msg {
Increment => Counter(getCount(model) + 1),
Decrement => Counter(getCount(model) - 1),
Reset => Counter(0),
}
fn viewCounter(count: Int): Html = {
let countText = text(toString(count))
let countSpan = span([class("count")], [countText])
let displayDiv = div([class("counter-display")], [countSpan])
let minusBtn = button([onClick(Decrement), class("btn")], [text("-")])
let resetBtn = button([onClick(Reset), class("btn btn-reset")], [text("Reset")])
let plusBtn = button([onClick(Increment), class("btn")], [text("+")])
let buttonsDiv = div([class("counter-buttons")], [minusBtn, resetBtn, plusBtn])
let title = h1([], [text("Counter")])
div([class("counter-app")], [title, displayDiv, buttonsDiv])
}
fn view(model: Model): Html = viewCounter(getCount(model))
fn showAttr(attr: Attr): String =
match attr {
Class(s) => "class=\"" + s + "\"",
Id(s) => "id=\"" + s + "\"",
OnClick(msg) => match msg {
Increment => "onclick=\"Increment\"",
Decrement => "onclick=\"Decrement\"",
Reset => "onclick=\"Reset\"",
},
}
fn showAttrs(attrs: List>): String =
match List.head(attrs) {
None => "",
Some(a) => match List.tail(attrs) {
None => showAttr(a),
Some(rest) => showAttr(a) + " " + showAttrs(rest),
},
}
fn showChildren(children: List>, indent: Int): String =
match List.head(children) {
None => "",
Some(c) => match List.tail(children) {
None => showHtml(c, indent),
Some(rest) => showHtml(c, indent) + showChildren(rest, indent),
},
}
fn showHtml(html: Html, indent: Int): String =
match html {
Empty => "",
Text(s) => s,
Element(tag, attrs, children) => {
let attrStr = showAttrs(attrs)
let attrPart = if String.length(attrStr) > 0 then " " + attrStr else ""
let childStr = showChildren(children, indent + 2)
"<" + tag + attrPart + ">" + childStr + "" + tag + ">"
},
}
fn main(): Unit with {Console} = {
let model = init()
Console.print("=== Counter App (TEA Pattern) ===")
Console.print("")
Console.print("Initial count: " + toString(getCount(model)))
Console.print("")
let m1 = update(model, Increment)
Console.print("After Increment: " + toString(getCount(m1)))
let m2 = update(m1, Increment)
Console.print("After Increment: " + toString(getCount(m2)))
let m3 = update(m2, Increment)
Console.print("After Increment: " + toString(getCount(m3)))
let m4 = update(m3, Decrement)
Console.print("After Decrement: " + toString(getCount(m4)))
let m5 = update(m4, Reset)
Console.print("After Reset: " + toString(getCount(m5)))
Console.print("")
Console.print("=== View (HTML Structure) ===")
Console.print(showHtml(view(m2), 0))
}
let output = run main() with {}