dioxus/packages/core/src/nodebuilder.rs

678 lines
19 KiB
Rust
Raw Normal View History

2021-02-07 19:14:04 -05:00
//! Helpers for building virtual DOM VNodes.
use std::{any::Any, borrow::BorrowMut, intrinsics::transmute, u128};
2021-02-07 19:14:04 -05:00
use crate::{
context::NodeCtx,
events::VirtualEvent,
2021-03-18 18:54:26 -04:00
innerlude::{DomTree, Properties, VComponent, FC},
2021-02-07 19:14:04 -05:00
nodes::{Attribute, Listener, NodeKey, VNode},
prelude::VElement,
};
2021-02-03 02:26:04 -05:00
/// A virtual DOM element builder.
///
/// Typically constructed with element-specific constructors, eg the `div`
/// function for building `<div>` elements or the `button` function for building
/// `<button>` elements.
#[derive(Debug)]
pub struct ElementBuilder<'a, 'b, Listeners, Attributes, Children>
2021-02-03 02:26:04 -05:00
where
Listeners: 'a + AsRef<[Listener<'a>]>,
Attributes: 'a + AsRef<[Attribute<'a>]>,
2021-02-07 19:14:04 -05:00
Children: 'a + AsRef<[VNode<'a>]>,
2021-02-03 02:26:04 -05:00
{
ctx: &'b NodeCtx<'a>,
2021-02-03 02:26:04 -05:00
key: NodeKey,
tag_name: &'a str,
listeners: Listeners,
attributes: Attributes,
children: Children,
namespace: Option<&'a str>,
}
impl<'a, 'b>
2021-02-03 02:26:04 -05:00
ElementBuilder<
'a,
'b,
2021-02-03 02:26:04 -05:00
bumpalo::collections::Vec<'a, Listener<'a>>,
bumpalo::collections::Vec<'a, Attribute<'a>>,
2021-02-07 19:14:04 -05:00
bumpalo::collections::Vec<'a, VNode<'a>>,
2021-02-03 02:26:04 -05:00
>
{
/// Create a new `ElementBuilder` for an element with the given tag name.
///
/// In general, only use this constructor if the tag is dynamic (i.e. you
/// might build a `<div>` or you might build a `<span>` and you don't know
/// until runtime). Prefer using the tag-specific constructors instead:
/// `div(bump)` or `span(bump)`, etc.
///
/// # Example
///
/// ```
2021-02-10 12:50:34 -05:00
/// use dioxus::{builder::*, bumpalo::Bump};
2021-02-03 02:26:04 -05:00
///
/// let b = Bump::new();
///
/// let tag_name = if flip_coin() {
/// "div"
/// } else {
/// "span"
/// };
///
/// let my_element_builder = ElementBuilder::new(&b, tag_name);
/// # fn flip_coin() -> bool { true }
/// ```
pub fn new(ctx: &'b NodeCtx<'a>, tag_name: &'static str) -> Self {
2021-03-03 23:06:11 -05:00
let bump = ctx.bump;
ElementBuilder {
ctx,
key: NodeKey::NONE,
tag_name,
listeners: bumpalo::collections::Vec::new_in(bump),
attributes: bumpalo::collections::Vec::new_in(bump),
children: bumpalo::collections::Vec::new_in(bump),
namespace: None,
}
2021-02-03 02:26:04 -05:00
}
}
impl<'a, 'b, Listeners, Attributes, Children>
ElementBuilder<'a, 'b, Listeners, Attributes, Children>
2021-02-03 02:26:04 -05:00
where
Listeners: 'a + AsRef<[Listener<'a>]>,
Attributes: 'a + AsRef<[Attribute<'a>]>,
2021-02-07 19:14:04 -05:00
Children: 'a + AsRef<[VNode<'a>]>,
2021-02-03 02:26:04 -05:00
{
/// Set the listeners for this element.
///
/// You can use this method to customize the backing storage for listeners,
/// for example to use a fixed-size array instead of the default
/// dynamically-sized `bumpalo::collections::Vec`.
///
/// Any listeners already added to the builder will be overridden.
///
/// # Example
///
/// ```no_run
2021-02-10 12:50:34 -05:00
/// use dioxus::{builder::*, bumpalo::Bump};
2021-02-03 02:26:04 -05:00
///
/// let b = Bump::new();
///
/// // Create a `<div>` with a fixed-size array of two listeners.
/// let my_div = div(&b)
/// .listeners([
/// on(&b, "click", |root, vdom, event| {
/// // ...
/// }),
/// on(&b, "dblclick", |root, vdom, event| {
/// // ...
/// }),
/// ])
/// .finish();
/// ```
#[inline]
pub fn listeners<L>(self, listeners: L) -> ElementBuilder<'a, 'b, L, Attributes, Children>
2021-02-03 02:26:04 -05:00
where
L: 'a + AsRef<[Listener<'a>]>,
{
ElementBuilder {
ctx: self.ctx,
2021-02-03 02:26:04 -05:00
key: self.key,
tag_name: self.tag_name,
listeners,
attributes: self.attributes,
children: self.children,
namespace: self.namespace,
}
}
/// Set the attributes for this element.
///
/// You can use this method to customize the backing storage for attributes,
/// for example to use a fixed-size array instead of the default
/// dynamically-sized `bumpalo::collections::Vec`.
///
/// Any attributes already added to the builder will be overridden.
///
/// # Example
///
/// ```no_run
2021-02-10 12:50:34 -05:00
/// use dioxus::{builder::*, bumpalo::Bump, Attribute};
2021-02-03 02:26:04 -05:00
///
/// let b = Bump::new();
///
/// // Create a `<div>` with a fixed-size array of two attributes.
/// let my_div = div(&b)
/// .attributes([
/// attr("id", "my-div"),
/// attr("class", "notification"),
/// ])
/// .finish();
/// ```
#[inline]
pub fn attributes<A>(self, attributes: A) -> ElementBuilder<'a, 'b, Listeners, A, Children>
2021-02-03 02:26:04 -05:00
where
A: 'a + AsRef<[Attribute<'a>]>,
{
ElementBuilder {
ctx: self.ctx,
2021-02-03 02:26:04 -05:00
key: self.key,
tag_name: self.tag_name,
listeners: self.listeners,
attributes,
children: self.children,
namespace: self.namespace,
}
}
/// Set the children for this element.
///
/// You can use this method to customize the backing storage for children,
/// for example to use a fixed-size array instead of the default
/// dynamically-sized `bumpalo::collections::Vec`.
///
/// Any children already added to the builder will be overridden.
///
/// # Example
///
/// ```no_run
2021-02-10 12:50:34 -05:00
/// use dioxus::{builder::*, bumpalo::Bump};
2021-02-03 02:26:04 -05:00
///
/// let b = Bump::new();
///
/// // Create a `<div>` with a fixed-size array of two `<span>` children.
/// let my_div = div(&b)
/// .children([
/// span(&b).finish(),
/// span(&b).finish(),
/// ])
/// .finish();
/// ```
#[inline]
pub fn children<C>(self, children: C) -> ElementBuilder<'a, 'b, Listeners, Attributes, C>
2021-02-03 02:26:04 -05:00
where
2021-02-07 19:14:04 -05:00
C: 'a + AsRef<[VNode<'a>]>,
2021-02-03 02:26:04 -05:00
{
ElementBuilder {
ctx: self.ctx,
2021-02-03 02:26:04 -05:00
key: self.key,
tag_name: self.tag_name,
listeners: self.listeners,
attributes: self.attributes,
children,
namespace: self.namespace,
}
}
/// Set the namespace for this element.
///
/// # Example
///
/// ```no_run
2021-02-10 12:50:34 -05:00
/// use dioxus::{builder::*, bumpalo::Bump};
2021-02-03 02:26:04 -05:00
///
/// let b = Bump::new();
///
/// // Create a `<td>` tag with an xhtml namespace
/// let my_td = td(&b)
/// .namespace(Some("http://www.w3.org/1999/xhtml"))
/// .finish();
/// ```
#[inline]
pub fn namespace(self, namespace: Option<&'a str>) -> Self {
ElementBuilder {
ctx: self.ctx,
2021-02-03 02:26:04 -05:00
key: self.key,
tag_name: self.tag_name,
listeners: self.listeners,
attributes: self.attributes,
children: self.children,
namespace,
}
}
/// Set this element's key.
///
/// When diffing sets of siblings, if an old sibling and new sibling share a
2021-02-07 19:14:04 -05:00
/// key, then they will always reuse the same physical DOM VNode. This is
2021-02-03 02:26:04 -05:00
/// important when using CSS animations, web components, third party JS, or
/// anything else that makes the diffing implementation observable.
///
/// Do not use keys if such a scenario does not apply. Keyed diffing is
/// generally more expensive than not, since it is putting greater
/// constraints on the diffing algorithm.
///
/// # Invariants You Must Uphold
///
/// The key may not be `u32::MAX`, which is a reserved key value.
///
/// Keys must be unique among siblings.
///
2021-02-07 19:14:04 -05:00
/// All sibling VNodes must be keyed, or they must all not be keyed. You may
2021-02-03 02:26:04 -05:00
/// not mix keyed and unkeyed siblings.
///
/// # Example
///
/// ```no_run
2021-02-10 12:50:34 -05:00
/// use dioxus::{builder::*, bumpalo::Bump};
2021-02-03 02:26:04 -05:00
///
/// let b = Bump::new();
///
/// let my_li = li(&b)
/// .key(1337)
/// .finish();
/// ```
#[inline]
pub fn key(mut self, key: u32) -> Self {
use std::u32;
debug_assert!(key != u32::MAX);
self.key = NodeKey(key);
self
}
2021-02-07 19:14:04 -05:00
/// Create the virtual DOM VNode described by this builder.
2021-02-03 02:26:04 -05:00
///
/// # Example
///
/// ```no_run
2021-02-10 12:50:34 -05:00
/// use dioxus::{builder::*, bumpalo::Bump, VNode};
2021-02-03 02:26:04 -05:00
///
/// let b = Bump::new();
///
/// // Start with a builder...
/// let builder: ElementBuilder<_, _, _> = div(&b);
///
2021-02-07 19:14:04 -05:00
/// // ...and finish it to create a virtual DOM VNode!
/// let my_div: VNode = builder.finish();
2021-02-03 02:26:04 -05:00
/// ```
#[inline]
2021-02-07 19:14:04 -05:00
pub fn finish(self) -> VNode<'a> {
2021-03-03 23:06:11 -05:00
let children: &'a Children = self.ctx.bump.alloc(self.children);
2021-02-07 19:14:04 -05:00
let children: &'a [VNode<'a>] = children.as_ref();
2021-02-03 02:26:04 -05:00
2021-03-03 23:06:11 -05:00
let listeners: &'a Listeners = self.ctx.bump.alloc(self.listeners);
2021-02-03 02:26:04 -05:00
let listeners: &'a [Listener<'a>] = listeners.as_ref();
2021-03-03 23:06:11 -05:00
let attributes: &'a Attributes = self.ctx.bump.alloc(self.attributes);
2021-02-03 02:26:04 -05:00
let attributes: &'a [Attribute<'a>] = attributes.as_ref();
2021-02-07 19:14:04 -05:00
VNode::element(
2021-03-03 23:06:11 -05:00
self.ctx.bump,
2021-02-07 19:14:04 -05:00
self.key,
self.tag_name,
listeners,
attributes,
children,
self.namespace,
)
2021-02-03 02:26:04 -05:00
}
}
impl<'a, 'b, Attributes, Children>
ElementBuilder<'a, 'b, bumpalo::collections::Vec<'a, Listener<'a>>, Attributes, Children>
2021-02-07 19:14:04 -05:00
where
Attributes: 'a + AsRef<[Attribute<'a>]>,
Children: 'a + AsRef<[VNode<'a>]>,
{
/// Add a new event listener to this element.
///
/// The `event` string specifies which event will be listened for. The
/// `callback` function is the function that will be invoked if the
/// specified event occurs.
///
/// # Example
///
/// ```no_run
2021-02-10 12:50:34 -05:00
/// use dioxus::{builder::*, bumpalo::Bump};
2021-02-07 19:14:04 -05:00
///
/// let b = Bump::new();
///
/// // A button that does something when clicked!
/// let my_button = button(&b)
2021-03-03 02:27:26 -05:00
/// .on("click", |event| {
2021-02-07 19:14:04 -05:00
/// // ...
/// })
/// .finish();
/// ```
2021-03-10 19:42:31 -05:00
pub fn on(self, event: &'static str, callback: impl Fn(VirtualEvent) + 'a) -> Self {
let listener = Listener {
event,
callback: self.ctx.bump.alloc(callback),
id: *self.ctx.idx.borrow(),
scope: self.ctx.scope,
};
self.add_listener(listener)
}
pub fn add_listener(mut self, listener: Listener<'a>) -> Self {
self.listeners.push(listener);
// bump the context id forward
*self.ctx.idx.borrow_mut() += 1;
// Add this listener to the context list
// This casts the listener to a self-referential pointer
// This is okay because the bump arena is stable
self.listeners.last().map(|g| {
let r = unsafe { std::mem::transmute::<&Listener<'a>, &Listener<'static>>(g) };
self.ctx.listeners.borrow_mut().push(r.callback as *const _);
});
2021-02-07 19:14:04 -05:00
self
}
}
2021-02-03 02:26:04 -05:00
impl<'a, 'b, Listeners, Children>
ElementBuilder<'a, 'b, Listeners, bumpalo::collections::Vec<'a, Attribute<'a>>, Children>
2021-02-03 02:26:04 -05:00
where
Listeners: 'a + AsRef<[Listener<'a>]>,
2021-02-07 19:14:04 -05:00
Children: 'a + AsRef<[VNode<'a>]>,
2021-02-03 02:26:04 -05:00
{
/// Add a new attribute to this element.
///
/// # Example
///
/// ```no_run
2021-02-10 12:50:34 -05:00
/// use dioxus::{builder::*, bumpalo::Bump};
2021-02-03 02:26:04 -05:00
///
/// let b = Bump::new();
///
/// // Create the `<div id="my-div"/>` element.
/// let my_div = div(&b).attr("id", "my-div").finish();
/// ```
#[inline]
2021-02-14 23:39:46 -05:00
pub fn attr(mut self, name: &'static str, value: &'a str) -> Self {
2021-02-03 02:26:04 -05:00
self.attributes.push(Attribute { name, value });
self
}
/// Conditionally add a "boolean-style" attribute to this element.
///
/// If the `should_add` parameter is true, then adds an attribute with the
/// given `name` and an empty string value. If the `should_add` parameter is
/// false, then the attribute is not added.
///
/// This method is useful for attributes whose semantics are defined by
/// whether or not the attribute is present or not, and whose value is
/// ignored. Example attributes like this include:
///
/// * `checked`
/// * `hidden`
/// * `selected`
///
/// # Example
///
/// ```no_run
2021-02-10 12:50:34 -05:00
/// use dioxus::{builder::*, bumpalo::Bump};
2021-02-03 02:26:04 -05:00
/// use js_sys::Math;
///
/// let b = Bump::new();
///
/// // Create the `<div>` that is randomly hidden 50% of the time.
/// let my_div = div(&b)
/// .bool_attr("hidden", Math::random() >= 0.5)
/// .finish();
/// ```
2021-02-14 23:39:46 -05:00
pub fn bool_attr(mut self, name: &'static str, should_add: bool) -> Self {
2021-02-03 02:26:04 -05:00
if should_add {
self.attributes.push(Attribute { name, value: "" });
}
self
}
}
impl<'a, 'b, Listeners, Attributes>
ElementBuilder<'a, 'b, Listeners, Attributes, bumpalo::collections::Vec<'a, VNode<'a>>>
2021-02-03 02:26:04 -05:00
where
Listeners: 'a + AsRef<[Listener<'a>]>,
Attributes: 'a + AsRef<[Attribute<'a>]>,
{
/// Add a new child to this element.
///
/// # Example
///
/// ```no_run
2021-02-10 12:50:34 -05:00
/// use dioxus::{builder::*, bumpalo::Bump};
2021-02-03 02:26:04 -05:00
/// use js_sys::Math;
///
/// let b = Bump::new();
///
/// // Create `<p><span></span></p>`.
/// let my_div = p(&b)
/// .child(span(&b).finish())
/// .finish();
/// ```
#[inline]
2021-02-07 19:14:04 -05:00
pub fn child(mut self, child: VNode<'a>) -> Self {
2021-02-03 02:26:04 -05:00
self.children.push(child);
self
}
2021-03-04 12:03:22 -05:00
/// Add multiple children to this element from an iterator.
///
/// # Example
///
/// ```no_run
/// use dioxus::{builder::*, bumpalo::Bump};
///
/// let b = Bump::new();
///
/// let my_div = p(&b)
/// .iter_child((0..10).map(|f| span(&b).finish())
/// .finish();
/// ```
pub fn iter_child(mut self, nodes: impl IntoIterator<Item = impl IntoDomTree<'a>>) -> Self {
for item in nodes {
let child = item.into_vnode(&self.ctx);
self.children.push(child);
2021-03-18 18:54:26 -04:00
}
self
}
}
impl IntoIterator for DomTree {
type Item = DomTree;
type IntoIter = std::iter::Once<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
std::iter::once(self)
}
}
pub trait IntoDomTree<'a> {
fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a>;
2021-03-18 18:54:26 -04:00
}
// Cover the cases where nodes are pre-rendered.
// Likely used by enums.
// ----
// let nodes = ctx.render(rsx!{ ... };
// rsx! { {nodes } }
impl<'a> IntoDomTree<'a> for DomTree {
fn into_vnode(self, _ctx: &NodeCtx<'a>) -> VNode<'a> {
self.root
2021-03-18 18:54:26 -04:00
}
}
// Wrap the the node-builder closure in a concrete type.
// ---
// This is a bit of a hack to implement the IntoDomTree trait for closure types.
pub struct LazyNodes<'a, G>
2021-03-18 18:54:26 -04:00
where
G: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a,
2021-03-18 18:54:26 -04:00
{
inner: G,
_p: std::marker::PhantomData<&'a ()>,
2021-03-18 18:54:26 -04:00
}
impl<'a, G> LazyNodes<'a, G>
2021-03-18 18:54:26 -04:00
where
G: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a,
2021-03-18 18:54:26 -04:00
{
pub fn new(f: G) -> Self {
Self {
inner: f,
_p: std::default::Default::default(),
}
2021-03-18 18:54:26 -04:00
}
}
// Cover the cases where nodes are used by macro.
// Likely used directly.
// ---
// let nodes = rsx!{ ... };
// rsx! { {nodes } }
impl<'a, G> IntoDomTree<'a> for LazyNodes<'a, G>
2021-03-18 18:54:26 -04:00
where
G: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a,
2021-03-18 18:54:26 -04:00
{
fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a> {
(self.inner)(ctx)
}
2021-03-18 18:54:26 -04:00
}
// Required because anything that enters brackets in the rsx! macro needs to implement IntoIterator
impl<'a, G> IntoIterator for LazyNodes<'a, G>
2021-03-18 18:54:26 -04:00
where
G: for<'b, 'c> FnOnce(&'b NodeCtx<'c>) -> VNode<'c> + 'a,
{
type Item = Self;
type IntoIter = std::iter::Once<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
std::iter::once(self)
2021-03-18 18:54:26 -04:00
}
}
#[test]
fn test_iterator_of_nodes<'b>() {
use crate::prelude::*;
2021-03-18 18:54:26 -04:00
static Example: FC<()> = |ctx, props| {
let g: LazyNodes<_> = rsx! {
div {}
};
ctx.render(rsx! {
div {
h1 {}
{}
}
2021-03-18 18:54:26 -04:00
})
};
// let p = (0..10).map(|f| {
2021-03-18 18:54:26 -04:00
// //
// LazyNodes::new(rsx! {
// div {
// "aaa {f}"
// }
// })
// });
// let g = p.into_iter();
// for f in g {}
2021-03-18 18:54:26 -04:00
// static Example: FC<()> = |ctx, props| {
2021-03-18 18:54:26 -04:00
// ctx.render(|c| {
// //
2021-03-18 18:54:26 -04:00
// ElementBuilder::new(c, "div")
// .iter_child({
// // rsx!
// LazyNodes::new(move |n: &NodeCtx| -> VNode {
// //
// ElementBuilder::new(n, "div").finish()
// })
// })
// .iter_child({
// // render to wrapper -> tree
2021-03-18 18:54:26 -04:00
// ctx.render(rsx! {
// div {}
// })
// })
// .iter_child({
// // map rsx!
2021-03-18 18:54:26 -04:00
// (0..10).map(|f| {
// LazyNodes::new(move |n: &NodeCtx| -> VNode {
// //
// ElementBuilder::new(n, "div").finish()
2021-03-18 18:54:26 -04:00
// })
// })
// })
// .finish()
// })
// };
2021-02-03 02:26:04 -05:00
}
2021-02-07 19:14:04 -05:00
/// Construct a text VNode.
2021-02-03 02:26:04 -05:00
///
2021-02-10 12:50:34 -05:00
/// This is `dioxus`'s virtual DOM equivalent of `document.createTextVNode`.
2021-02-03 02:26:04 -05:00
///
/// # Example
///
/// ```no_run
2021-02-10 12:50:34 -05:00
/// use dioxus::builder::*;
2021-02-03 02:26:04 -05:00
///
2021-02-10 12:50:34 -05:00
/// let my_text = text("hello, dioxus!");
2021-02-03 02:26:04 -05:00
/// ```
#[inline]
2021-02-07 19:14:04 -05:00
pub fn text<'a>(contents: &'a str) -> VNode<'a> {
VNode::text(contents)
2021-02-03 02:26:04 -05:00
}
pub fn text2<'a>(contents: bumpalo::collections::String<'a>) -> VNode<'a> {
let f: &'a str = contents.into_bump_str();
VNode::text(f)
}
// pub fn text<'a>(contents: &'a str) -> VNode<'a> {
// VNode::text(contents)
// }
2021-02-03 02:26:04 -05:00
/// Construct an attribute for an element.
///
/// # Example
///
/// This example creates the `id="my-id"` for some element like `<div
/// id="my-id"/>`.
///
/// ```no_run
2021-02-10 12:50:34 -05:00
/// use dioxus::builder::*;
2021-02-03 02:26:04 -05:00
///
/// let my_id_attr = attr("id", "my-id");
/// ```
2021-02-14 23:39:46 -05:00
pub fn attr<'a>(name: &'static str, value: &'a str) -> Attribute<'a> {
2021-02-03 02:26:04 -05:00
Attribute { name, value }
}
// /// Create an event listener.
// ///
// /// `event` is the type of event to listen for, e.g. `"click"`. The `callback`
// /// is the function that will be invoked when the event occurs.
// ///
// /// # Example
// ///
// /// ```no_run
// /// use dioxus::{builder::*, bumpalo::Bump};
// ///
// /// let b = Bump::new();
// ///
// /// let listener = on(&b, "click", |root, vdom, event| {
// /// // do something when a click happens...
// /// });
// /// ```
// pub fn on<'a, 'b>(
2021-03-18 18:54:26 -04:00
// // pub fn on<'a, F: 'static>(
// bump: &'a Bump,
// event: &'static str,
// callback: impl Fn(VirtualEvent) + 'a,
// ) -> Listener<'a> {
// Listener {
// event,
// callback: bump.alloc(callback),
// }
// }
2021-02-20 21:59:16 -05:00
pub fn virtual_child<'a, T: Properties + 'a>(ctx: &NodeCtx<'a>, f: FC<T>, p: T) -> VNode<'a> {
let propsd: &'a mut _ = ctx.bump.alloc(p);
VNode::Component(crate::nodes::VComponent::new(f, propsd))
}