hyperion_html/node
This module holds all the AST node types needed to build an HTML tree.
Types
pub type Attribute =
#(String, AttributeValue)
pub opaque type AttributeValue
pub type Attributes =
List(#(String, AttributeValue))
Values
pub fn attr(
key key: String,
value value: String,
) -> #(String, AttributeValue)
An attribute with a string value.
pub fn attr_values(
key key: String,
values values: List(String),
) -> #(String, AttributeValue)
Pass a list of values for an attribute, joined with spaces. Empty strings are ignored, so lists can be built unconditionally.
pub fn bool_attr(
key key: String,
rendered value: Bool,
) -> #(String, AttributeValue)
A boolean attribute, rendered bare when True (e.g., <input disabled>),
omitted entirely when False.
pub fn comment(content: String) -> Node(ctx)
An HTML comment, rendered verbatim between <!-- and -->.
pub fn element(
tag tag: String,
attrs attrs: List(#(String, AttributeValue)),
children children: List(Node(ctx)),
) -> Node(ctx)
A generic element with an opening and closing tag around children.
Renders as <tag attrs>children</tag>. For standard HTML tags, prefer
the typed helpers in hyperion_html/tag; use this one for tags they
don’t cover, such as custom elements or Web Components.
Example
node.element(
"my-widget",
[node.attr("variant", "card")],
[node.text("hello")]
)
The element above would render as
<my-widget variant="card">hello</my-widget>
pub fn float_attr(
key key: String,
value value: Float,
) -> #(String, AttributeValue)
An attribute with a float value, rendered as its string form.
pub fn fragment(
id id: String,
children children: List(Node(ctx)),
) -> Node(ctx)
A named fragment of the page that can also be rendered on its own.
Fragments are the unit of partial rendering: one tree can be used for both full page and the partial responses of hypermedia frontends such as htmx.
In a full render the children are rendered as they are.
In a partial render, when the fragment’s id is listed in
hyperion_html.with_only, only the requested fragments are rendered, and
the rest of the page is left out.
Use this function when the fragment renders the same in both modes, and
fragment_lazy or fragment_envelope when it should render itself
differently depending on the mode.
Example
node.fragment("cart_item_1", [
tag.span([], [node.text("Coffee mug")]),
tag.button([node.attr("hx-post", "/cart/1/remove")], [node.text("Remove")]),
])
pub fn fragment_envelope(
id id: String,
envelope envelope: String,
attrs attrs: List(#(String, AttributeValue)),
children children: List(Node(ctx)),
) -> Node(ctx)
A fragment rendered bare in a full render, and wrapped in an element in a partial render.
This is a shorthand for a common case of fragment_lazy:
in hypermedia frontends such as htmx 4, partials
need to be enveloped in a plain element (e.g. <hx-partial>) telling where
their content should be swapped.
For any other shape, use fragment_lazy.
Example
node.fragment_envelope(
id: "cart_item_1",
envelope: "hx-partial",
attrs: [node.attr("hx-target", "#cart"), node.attr("hx-swap", "beforeend")],
children: [
tag.span([], [node.text("Coffee mug")]),
tag.button([node.attr("hx-post", "/cart/1/remove")], [node.text("Remove")]),
],
)
In a full render the children are rendered as they are; in a partial render they are wrapped:
<hx-partial hx-target="#cart" hx-swap="beforeend">
<span>Coffee mug</span>
<button hx-post="/cart/1/remove">Remove</button>
</hx-partial>
pub fn fragment_lazy(
id id: String,
render render: fn(Bool) -> List(Node(ctx)),
) -> Node(ctx)
A fragment whose children are built at render time, depending on the mode.
The callback runs when the tree is rendered and receives a boolean telling
whether the fragment was requested (meaning its id is in the only list
given to hyperion_html.with_only). This lets the fragment render itself
differently in a full or in a partial page render.
For example, you could wrap it in an envelope element (e.g. htmx 4’s
<hx-partial>) carrying its own placement instructions. When the wrap
is a plain element around unchanged children, prefer fragment_envelope.
Fragments that don’t care about the mode can ignore the parameter,
or simply use fragment.
Example
node.fragment_lazy("cart_item_1", fn(selected) {
let content = [
tag.span([], [node.text("Coffee mug")]),
tag.button([node.attr("hx-post", "/cart/1/remove")], [node.text("Remove")]),
]
case selected {
True -> [
node.element(
"hx-partial",
[node.attr("hx-target", "#cart"), node.attr("hx-swap", "beforeend")],
content,
),
]
False -> content
}
})
In a full render the fragment renders as its content:
<span>Coffee mug</span>
<button hx-post="/cart/1/remove">Remove</button>
In a partial render it wraps itself:
<hx-partial hx-target="#cart" hx-swap="beforeend">
<span>Coffee mug</span>
<button hx-post="/cart/1/remove">Remove</button>
</hx-partial>
pub fn int_attr(
key key: String,
value value: Int,
) -> #(String, AttributeValue)
An attribute with an integer value, rendered as its string form.
pub fn lazy(callback: fn(ctx) -> List(Node(ctx))) -> Node(ctx)
Defer building part of the tree until render time.
The callback receives the context passed to hyperion_html.to_html
and returns the nodes to render. Use it when part of the tree can only
be built during rendering, or to read the context deep in a tree without
having to pass it through every component.
Example
type Ctx { Ctx(username: String) }
node.lazy(fn(ctx: Ctx) { [node.text("Hello " <> ctx.username)] })
pub fn none() -> Node(ctx)
This function renders nothing; it is the same as an empty text node. It’s a convenience to be used in conditional cases.
Example
fn maybe_render_name(name: option.Option(String)) -> node.Node(ctx) {
case name {
option.None -> node.none()
option.Some(name) -> node.text(name)
}
}
pub fn none_attr() -> #(String, AttributeValue)
This function renders nothing; it is the same as a false boolean attribute. It’s a convenience to be used in conditional cases.
Example
fn maybe_add_class(id: option.Option(String)) -> node.Attribute {
case id {
option.None -> node.none_attr()
option.Some(id) -> node.attr("id", id)
}
}
pub fn scope(
update: fn(ctx) -> ctx,
children: List(Node(ctx)),
) -> Node(ctx)
Render children with a modified context. Only the subtree is affected;
nodes outside of it keep rendering with the surrounding context.
Example
type Ctx { Ctx(theme: String, csrf: String) }
node.scope(fn(ctx: Ctx) { Ctx(..ctx, theme: "dark") }, [always_dark_section()])
pub fn self_closing_element(
tag: String,
attrs: List(#(String, AttributeValue)),
) -> Node(ctx)
An element rendered as <tag attrs/>.
The self-closing slash is only meaningful in foreign content such as SVG and MathML, where XML-like syntax applies.
For HTML void elements (br, img, …) use void_element instead.
Example
node.self_closing_element("circle", [node.attr("r", "10")])
The element above would render as
<circle r="10"/>
pub fn text(content: String) -> Node(ctx)
A text node. Special characters are HTML-escaped at render time
(< becomes <, & becomes &, etc.).
pub fn unsafe_raw_text(content: String) -> Node(ctx)
A text node rendered verbatim, without escaping.
Only use it for content you control or that is already escaped, as interpolating user input here would make your app vulnerable to cross-site scripting!
This is also how the raw content of script and style elements is
rendered.
pub fn void_element(
tag: String,
attrs: List(#(String, AttributeValue)),
) -> Node(ctx)
An HTML void element that can’t have children by spec.
Renders as <tag attrs> with no closing tag, which is how void
elements such as br, img, input, link or meta are written.
For the standard ones, prefer the helpers in hyperion_html/tag, which
already route to this function (tag.br, tag.img, tag.input, …).
Example
node.void_element("img", [node.attr("src", "/cat.png")])
The element above would render as
<img src="/cat.png">