hyperion_html

Package Version Hex Docs

Function-based HTML templates for Hypermedia-Driven Applications.

Usage

import hyperion_html
import hyperion_html/node
import hyperion_html/tag

pub fn main() {
  let page = [
    tag.h1([], [node.text("Welcome")]),
    tag.p([node.attr("class", "intro")], [
      node.text("Rendered by hyperion_html."),
    ]),
  ]

  assert hyperion_html.to_html(page, hyperion_html.options())
    == "<h1>Welcome</h1><p class=\"intro\">Rendered by hyperion_html.</p>"
}

Fragments

Declare fragments within your templates to render either full pages or partial updates for any hypermedia frontend like htmx.

One tree can be used in both cases. In a full render, every node is rendered normally. In a partial render (when a list of IDs is given to with_only), only the requested fragments are rendered.

With fragment_envelope, a fragment renders its children as they are in a full render, and wraps them in an element carrying its own placement instructions in a partial render such as htmx 4’s <hx-partial>:

import hyperion_html
import hyperion_html/node
import hyperion_html/tag

fn cart_item(id: String, label: String) {
  node.fragment_envelope(
    id: "cart_item_" <> id,
    envelope: "hx-partial",
    attrs: [node.attr("hx-target", "#cart"), node.attr("hx-swap", "beforeend")],
    children: [
      tag.span([], [node.text(label)]),
      tag.button([node.attr("hx-post", "/cart/" <> id <> "/remove")], [
        node.text("Remove"),
      ]),
    ],
  )
}

pub fn main() {
  let page = [cart_item("1", "Coffee mug")]

  // In a full render, the children are rendered as they are:
  assert hyperion_html.to_html(page, hyperion_html.options())
    == "<span>Coffee mug</span><button hx-post=\"/cart/1/remove\">Remove</button>"

  // In a partial render, the fragment is wrapped with its placement instructions:
  assert hyperion_html.to_html(
      page,
      hyperion_html.options() |> hyperion_html.with_only(["cart_item_1"]),
    )
    == "<hx-partial hx-target=\"#cart\" hx-swap=\"beforeend\"><span>Coffee mug</span><button hx-post=\"/cart/1/remove\">Remove</button></hx-partial>"
}

When a fragment renders the same in both modes, use fragment. For full control over how a fragment renders itself in each mode, use fragment_lazy.

Typed context

The context is a value of your own type, attached with with_context and read at render time by node.lazy callbacks, wherever they sit in the tree:

import hyperion_html
import hyperion_html/node
import hyperion_html/tag

type Ctx {
  Ctx(username: String)
}

fn greeting() {
  [
    tag.p([], [node.lazy(fn(c: Ctx) { [node.text("Hello " <> c.username)] })]),
  ]
}

pub fn main() {
  let options =
    hyperion_html.options()
    |> hyperion_html.with_context(Ctx(username: "Quentin"))

  assert hyperion_html.to_html(greeting(), options) == "<p>Hello Quentin</p>"
}

In most cases you should favor using explicit parameters, since it makes the code clearer, but the context option can be useful to avoid “prop drilling”, which refers to passing data through multiple layers of components.

Installation

gleam add hyperion_html@1

Further documentation can be found at https://hexdocs.pm/hyperion_html.

Development

gleam test  # Run the tests
✨ Search Document