Files
lux/examples/counter.lux
Brandon Lucas 634f665b1b feat: add stdlib and browser examples
- stdlib/html.lux: Type-safe HTML construction
- stdlib/browser.lux: Browser utilities
- examples/web/: Counter app with DOM manipulation
- examples/counter.lux: Simple counter example

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-15 03:54:17 -05:00

174 lines
5.5 KiB
Plaintext

// Counter Example - A simple interactive counter using TEA pattern
//
// This example demonstrates:
// - Model-View-Update architecture (TEA)
// - Html DSL for describing UI (inline version)
// - Message-based state updates
// ============================================================================
// Html Types (subset of stdlib/html)
// ============================================================================
type Html<M> =
| Element(String, List<Attr<M>>, List<Html<M>>)
| Text(String)
| Empty
type Attr<M> =
| Class(String)
| Id(String)
| OnClick(M)
// Html builder helpers
fn div<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("div", attrs, children)
fn span<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("span", attrs, children)
fn h1<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("h1", attrs, children)
fn button<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("button", attrs, children)
fn text<M>(content: String): Html<M> =
Text(content)
fn class<M>(name: String): Attr<M> =
Class(name)
fn onClick<M>(msg: M): Attr<M> =
OnClick(msg)
// ============================================================================
// Model - The application state (using ADT wrapper)
// ============================================================================
type Model =
| Counter(Int)
fn getCount(model: Model): Int =
match model {
Counter(n) => n
}
fn init(): Model = Counter(0)
// ============================================================================
// Messages - Events that can occur
// ============================================================================
type Msg =
| Increment
| Decrement
| Reset
// ============================================================================
// Update - State transitions
// ============================================================================
fn update(model: Model, msg: Msg): Model =
match msg {
Increment => Counter(getCount(model) + 1),
Decrement => Counter(getCount(model) - 1),
Reset => Counter(0)
}
// ============================================================================
// View - Render the UI
// ============================================================================
fn viewCounter(count: Int): Html<Msg> = {
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<Msg> = viewCounter(getCount(model))
// ============================================================================
// Debug: Print Html structure
// ============================================================================
fn showAttr(attr: Attr<Msg>): 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<Attr<Msg>>): 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<Html<Msg>>, 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<Msg>, 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 + ">"
}
}
// ============================================================================
// Entry point
// ============================================================================
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 {}