The View Builder API
For those that prefer not to use macros, Sycamore also provides an ergonomic builder API for composing views.
Start by importing all of the builder HTML tags.
use sycamore::web::tags::*;
Elements
Elements can easily be created by calling the corresponding function for the HTML tag.
a()
button()
div()
// etc...
These functions return specific element types such as HtmlButtonElement
. To
convert those into View
s, simply use .into()
. For example, if you are
composing a component, you could write something like:
#[component]
fn Button() -> View {
button().into()
}
Children
You can add children to a element by using the .children(...)
method. Anything
that implements Into<View>
can be appended as a child node. This includes
string literals, numbers, other elements, signals, functions, and views.
div().children(
p().children("Hello World!")
)
Tuples also implement Into<View>
which lets you add multiple children in one
shot.
div().children((
span().children("Hello "),
span().children("World!"),
))
Dynamic views
To add dynamic content, you can pass a function to .children(...)
. This will
automatically make the view dynamic and will trigger a re-render whenever a
dependency changes.
let state = create_signal(0);
p().children((
"value = ",
state,
", doubled = ",
move || state.get() * 2,
))
Attributes
Every attribute can be set on an element via a method of the same name. Custom
attributes (that are not part of the HTML spec or are data-*
or aria-*
attributes) can be set using .attr(...)
.
p().class("my-class").id("my-paragraph").attr("aria-label", "My paragraph")
Setting inner html
The inner HTML can be set using the special .dangerously_set_inner_html(...)
method. This should generally be avoided if possible because it is a possible
security risk. Never pass user input to this attribute as that will create an
XSS (Cross-Site Scripting) vulnerability.
div().dangerously_set_inner_html(inner_html)
Setting node refs
A node ref can be attached using the special r#ref(...)
method.
let node = create_node_ref();
button().r#ref(node)
For more details, see Node Ref
Properties
Properties are set using the .prop(...)
method.
input().r#type("checkbox").prop("indeterminate", true)
There are some properties that do not have a matching attribute, such as
indeterminate
in HTML, and which must be set using the prop:*
directive.
Other properties such as value
have
unintuitive behavior when using
the attribute version.
Events
Events are attached using .on(...)
.
button().on(ev::click, |_| console_log!("clicked!")).children("Click me!")
Optional attributes
Stringy attributes can also be optional. To make an attribute optional, simply
pass in an Option
! For example:
let attr = create_signal(None::<String>);
div().attr("data-attr", attr)
// Will render <div></div> instead of <div data-attr></div> if attr is None.
Components
Unfortunately with the builder API, we have a little more friction when using
components. Since components are just functions, you can call them directly.
However, when passing props, you will usually need to reference the prop type
explicitly and call .build()
at the end to construct the final prop value.
// The component macro automatically generates the `Button_Props` type
// if we are using inline props.
#[component(inline_props)]
fn Button(class: String) -> View { ... }
div()
.children(Button(
Button_Props::builder().class("my-button".to_string()).build()
))