#closures #node #terrazzo #attributes #html #template #macro

terrazzo-client

A simple macro to clone variables before passing them into a move closure or async block

3 releases

0.1.3 Dec 28, 2024
0.1.2 Dec 28, 2024
0.1.1 Dec 28, 2024

#67 in Template engine

Download history 340/week @ 2024-12-23 10/week @ 2024-12-30

350 downloads per month
Used in 2 crates (via terrazzo)

MIT license

120KB
3K SLoC

Terrazzo client

Template library to generate dynamic HTML documents.

The template library is usually used in tandem with the terrazzo-macro crate.

The #[html] macro

Basic usage

This macro us used to generate dynamic HTML nodes.

# use terrazzo_client::prelude::*;
# use terrazzo_macro::html;
#[html]
fn sample() -> XElement {
    div(
        h1("Section 1"),
        ul(li("Firstly"), li("Secondly")),
        h1("Section 2"),
        ol(li("One"), li("Two"), li("Three")),
    )
}

This function generates:

<div>
    <h1> Section 1 </h1>
    <ul>
        <li> Firstly </li>
        <li> Secondly </li>
    </ul>
    <h1> Section 2 </h1>
    <ol>
        <li> One </li>
        <li> Two </li>
        <li> Three </li>
    </ol>
</div>

List of nodes

List of nodes can be generated from iterators

# use terrazzo_client::prelude::*;
# use terrazzo_macro::html;
#[html]
fn sample() -> XElement {
    let list = [1, 2, 3].map(|i| li("{i}"));
    div(h1("Title"), ul(list..))
}

This function generates:

<div>
    <h1> Title </h1>
    <ul>
        <li> 1 </li>
        <li> 2 </li>
        <li> 3 </li>
    </ul>
</div>

Attributes

# use terrazzo_client::prelude::*;
# use terrazzo_macro::html;
#[html]
fn sample() -> XElement {
    div(
        class = "my-css-class",
        style = format!("width: {}%", 100),
        "Content",
    )
}

Optional attributes

This can be useful when a function generates a node and an attribute may or may not have a value.

# use terrazzo_client::prelude::*;
# use terrazzo_macro::html;
#[html]
fn sample(class: Option<String>) -> XElement {
    div(
        class |= class,
        style = format!("width: {}%", 100),
        "Content",
    )
}

Style properties

Style properties can be set individually.

# use terrazzo_client::prelude::*;
# use terrazzo_macro::html;
#[html]
fn sample(width: i32) -> XElement {
    div(
        style::width = format!("{}%", width),
        "Content",
    )
}

Special attributes

key, before-render and after-render are special attributes.

See XKey and OnRenderCallback for details.

The #[template] macro

This macro can be used to create dynamic nodes.

Simple case.

The tag and key have to be defined at the call site. The node gets updated every time to content signal is updated.

# use terrazzo_client::prelude::*;
# use terrazzo_macro::html;
# use terrazzo_macro::template;
#[template]
#[html]
fn inner(name: String, #[signal] content: String) -> XElement {
    p("id={name}", "Content={content}")
}

#[html]
pub fn outer() -> XElement {
    let name = "Section 1".to_owned();
    let content = XSignal::new("content", "Hello, World!".to_owned());
    div(
        h1("Title"),
        p(key = name.clone(), move |t| {
            inner(t, name.clone(), content.clone())
        }),
    )
}

With tag and key defined in the attribute

When the tag is defined on the attribute itself, the key should be defined on the #[template] attribute at well.

# use terrazzo_client::prelude::*;
# use terrazzo_macro::html;
# use terrazzo_macro::template;
#[template(tag = p, key = name.clone())]
#[html]
fn inner(name: String, #[signal] content: String) -> XElement {
    tag("id={name}", "Content={content}")
}

#[html]
pub fn outer() -> XElement {
    let name = "Section 1".to_owned();
    let content = XSignal::new("content", "Hello, World!".to_owned());
    div(h1("Title"), inner(name, content))
}

Dynamic attributes

Attributes can be generated by signals that depend on dynamic signals.

Updating the signal will recompute and update the attribute or style property.

# use terrazzo_client::prelude::*;
# use terrazzo_macro::html;
# use terrazzo_macro::template;
#[html]
pub fn sample(suffix: XSignal<&'static str>, width: XSignal<i32>) -> XElement {
    div(
        class %= move |t| make_class(t, suffix.clone()),
        style::width %= move |t| make_width(t, width.clone()),
        "Content",
    )
}

#[template]
fn make_class(#[signal] suffix: &'static str) -> XAttributeValue {
    format!("class-{suffix}")
}

#[template]
fn make_width(#[signal] width: i32) -> XAttributeValue {
    format!("{}px", width)
}

Debugging

At runtime

The tracing crate is used to log events and add debugging information.

At compile time

To debug code-generation, add debug = true to the html and template macros, e.g.

  • #[html(debug = true)], and
  • #[template(debug = true)].

Dependencies

~12MB
~218K SLoC