March Docs

Html

Html module: HTML escaping, safe HTML helpers, and template composition.

Provides utilities for escaping HTML entities, marking strings as safe (pre-escaped) HTML, rendering lists of items into IOLists, and composing template partials and layouts.

IOList is the canonical "rendered HTML fragment" type throughout this module. Fragments can be stored in Vault for fragment caching and re-embedded into parent templates via ~H sigils without double-escaping.

Types

typeSafeSafe = Safe(String)#

Functions

fncontent_hashcontent_hash(iol : IOList) : String#

Compute a content hash over an IOList for ETag generation.

    Html.content_hash(~H"<p>Hello</p>")  -- a hex string, e.g. "a1b2c3d4e5f6..."

Delegates to `IOList.hash` (FNV-1a 64-bit over segments, no flattening).
The returned hex string is suitable for use as an HTTP ETag value or as a
Vault cache key for fragment caching.

Cost: O(total bytes across all segments).
fnescapeescape(s : String) : String#

Escape HTML entities in a string: & < > " '

    Html.escape("a & b")       -- "a &amp; b"
    Html.escape("<script>")    -- "&lt;script&gt;"
    Html.escape("he said \"hi\"") -- "he said &quot;hi&quot;"

Replaces & first to avoid double-escaping, then < > " '.

Cost: O(n) per replacement pass.
fnescape_attrescape_attr(s : String) : String#

Escape a string for use in an HTML attribute value context.

    Html.escape_attr("hello & world")  -- "hello &amp; world"
    Html.escape_attr("<script>")       -- "&lt;script&gt;"

Currently identical to `Html.escape`.  Kept as a separate function so
attribute-specific escaping rules can be tightened in the future without
breaking call sites.
fnjoinjoin(items : List(IOList), sep : String) : IOList#

Join a list of IOLists with a separator string.

    Html.join([IOList.from_string("a"), IOList.from_string("b")], ", ")
    -- IOList containing "a, b"

Cost: O(n) where n is the length of the list.
fnlayoutlayout(title : String, head_extra : IOList, body : IOList) : IOList#

Wrap content in a standard HTML document structure.

    Html.layout("My Page", IOList.empty(), page_content)

Produces:
    <!DOCTYPE html><html><head><title>My Page</title></head>
    <body>...page_content...</body></html>

- `title` is HTML-escaped automatically.
- `head_extra` is an IOList inserted after `</title>` and before `</head>`.
  Use it for `<link>`, `<meta>`, and `<style>` tags.
- `body` is an IOList (result of ~H or render_partial) inserted inside `<body>`.

Typical use:

    fn page(conn) do
      Html.layout(
        "Dashboard",
        IOList.from_string("<link rel=\"stylesheet\" href=\"/app.css\">"),
        body_content
      )
    end

Cost: O(1) for IOList construction; O(total bytes) when flushed to string.
fnlistlist(items : List(a), render_fn : a -> IOList) : IOList#

Render a list of items into an IOList using a template function.

    Html.list(["a", "b"], fn item -> IOList.from_string("<li>" ++ item ++ "</li>"))
    -- IOList containing "<li>a</li><li>b</li>"

Cost: O(n) where n is the length of the list.
fnrawraw(s : String) : Safe#

Mark a string as safe HTML (bypass escaping).

    Html.raw("<b>bold</b>")  -- Safe("<b>bold</b>")

Use this when you have pre-escaped HTML that should be inserted verbatim.
fnrender_collectionrender_collection(items : List(a), partial_fn : a -> IOList) : IOList#

Map a template partial over a list of items, returning a single IOList.

    Html.render_collection(users, fn user -> IOList.from_string("<li>" ++ user.name ++ "</li>"))
    -- IOList containing all rendered <li> items concatenated

Equivalent to `Html.list(items, partial_fn)`.  The Rails-flavoured name
makes the intent clear when building list views.

The returned IOList can be embedded in a parent ~H sigil without
double-escaping or stored in Vault as a cached fragment.

Cost: O(n) where n is the length of the list.
fnrender_partialrender_partial(partial_fn : a -> IOList, arg : a) : IOList#

Call a template partial function with a single argument, returning IOList.

    fn user_card(user) do IOList.from_string("<div>" ++ user.name ++ "</div>") end
    Html.render_partial(fn user -> user_card(user), current_user)

Partials are just functions that return IOList.  This helper makes the
intent explicit and ensures the return type is IOList so the result
can be safely embedded in a parent ~H sigil without double-escaping,
or stored in Vault as a cached fragment.

Cost: O(1) — just calls the function.
fnsafe_to_stringsafe_to_string(safe : Safe) : String#

Extract the string from a Safe value.

    Html.to_string(Html.raw("<b>bold</b>"))  -- "<b>bold</b>"
fntagtag(name : String, attrs : List((String, String)), content : IOList) : IOList#

Build an HTML tag with properly escaped attributes and an IOList body.

    Html.tag("div", [("class", "greeting")], IOList.from_string("Hi"))
    -- "<div class=\"greeting\">Hi</div>"

    Html.tag("br", [], IOList.empty())
    -- "<br></br>"  (use self-closing syntax in static HTML if needed)

Attribute keys and values are both run through `Html.escape_attr`.
The content is an IOList so it can be a composed partial or ~H result.
Passing the result of a ~H sigil as content is safe: it will not be
double-escaped.

Cost: O(k) for attribute serialisation where k = number of attributes,
plus O(1) for IOList wrapping.