sycamore_web/
view.rs

1//! This module contains the [`View`] struct which represents a view tree.
2
3use std::any::Any;
4use std::fmt;
5
6use smallvec::{smallvec, SmallVec};
7use sycamore_core::Children;
8
9use crate::*;
10
11/// Represents a view tree.
12///
13/// Internally, this stores a list of nodes. This is the main type that is returned from
14/// components.
15pub struct View<T = HtmlNode> {
16    /// The nodes in the view tree.
17    pub(crate) nodes: SmallVec<[T; 1]>,
18}
19
20impl<T> View<T> {
21    /// Create a new blank view.
22    pub fn new() -> Self {
23        Self {
24            nodes: SmallVec::new(),
25        }
26    }
27
28    /// Create a new view with a single node.
29    pub fn from_node(node: T) -> Self {
30        Self {
31            nodes: smallvec![node],
32        }
33    }
34
35    /// Create a new view with multiple nodes.
36    pub fn from_nodes(nodes: Vec<T>) -> Self {
37        Self {
38            nodes: nodes.into(),
39        }
40    }
41
42    /// Create a new view from a function that returns a view. An alias to
43    /// [`ViewNode::create_dynamic_view`].
44    pub fn from_dynamic<U: Into<Self> + 'static>(f: impl FnMut() -> U + 'static) -> Self
45    where
46        T: ViewNode,
47    {
48        T::create_dynamic_view(f)
49    }
50
51    /// Create a flat list of all the web-sys nodes in the view.
52    pub fn as_web_sys(&self) -> Vec<web_sys::Node>
53    where
54        T: ViewHtmlNode,
55    {
56        self.nodes
57            .iter()
58            .map(|node| node.as_web_sys().clone())
59            .collect()
60    }
61}
62
63impl<T> Default for View<T> {
64    fn default() -> Self {
65        Self::new()
66    }
67}
68
69impl<T> fmt::Debug for View<T> {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        f.debug_struct("View").finish()
72    }
73}
74
75impl<T> From<Children<Self>> for View<T> {
76    fn from(children: Children<Self>) -> Self {
77        children.call()
78    }
79}
80
81impl<T> From<Vec<View<T>>> for View<T> {
82    fn from(nodes: Vec<View<T>>) -> Self {
83        View {
84            nodes: nodes.into_iter().flat_map(|v| v.nodes).collect(),
85        }
86    }
87}
88
89impl<T> From<Option<View<T>>> for View<T> {
90    fn from(node: Option<View<T>>) -> Self {
91        node.unwrap_or_default()
92    }
93}
94
95macro_rules! impl_view_from {
96    ($($ty:ty),*) => {
97        $(
98            impl<T: ViewHtmlNode> From<$ty> for View<T> {
99                fn from(t: $ty) -> Self {
100                    View::from_node(T::create_text_node(t.into()))
101                }
102            }
103        )*
104    }
105}
106
107macro_rules! impl_view_from_to_string {
108    ($($ty:ty),*) => {
109        $(
110            impl<T: ViewHtmlNode> From<$ty> for View<T> {
111                fn from(t: $ty) -> Self {
112                    View::from_node(T::create_text_node(t.to_string().into()))
113                }
114            }
115        )*
116    }
117}
118
119impl_view_from!(&'static str, String, Cow<'static, str>);
120impl_view_from_to_string!(i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64);
121
122impl<T: ViewNode, F: FnMut() -> U + 'static, U: Into<View<T>> + Any + 'static> From<F> for View<T> {
123    fn from(f: F) -> Self {
124        T::create_dynamic_view(f)
125    }
126}
127// Implement `From` for all tuples of types that implement `Into<View<U>>`.
128macro_rules! impl_from_tuple {
129    ($($name:ident),*) => {
130        paste::paste! {
131            impl<U, $($name),*> From<($($name,)*)> for View<U>
132            where
133                $($name: Into<View<U>>),*
134            {
135                fn from(t: ($($name,)*)) -> Self {
136                    let ($([<$name:lower>]),*) = t;
137                    #[allow(unused_mut)]
138                    let mut nodes = SmallVec::new();
139                    $(
140                        nodes.extend([<$name:lower>].into().nodes);
141                    )*
142                    View { nodes }
143                }
144            }
145        }
146    };
147}
148
149impl_from_tuple!();
150impl_from_tuple!(A, B);
151impl_from_tuple!(A, B, C);
152impl_from_tuple!(A, B, C, D);
153impl_from_tuple!(A, B, C, D, E);
154impl_from_tuple!(A, B, C, D, E, F);
155impl_from_tuple!(A, B, C, D, E, F, G);
156impl_from_tuple!(A, B, C, D, E, F, G, H);
157impl_from_tuple!(A, B, C, D, E, F, G, H, I);
158impl_from_tuple!(A, B, C, D, E, F, G, H, I, J);
159
160/// A trait that should be implemented for anything that represents a node in the view tree (UI
161/// tree).
162///
163/// Examples include `DomNode` and `SsrNode` which are used to render views to the browser DOM and
164/// to a string respectively. This trait can be implemented for other types to create custom render
165/// backends.
166pub trait ViewNode: Into<View<Self>> + Sized + 'static {
167    /// Appends a child to the node. Panics if the node is not an element or other node that can
168    /// have children (e.g. text node).
169    fn append_child(&mut self, child: Self);
170
171    /// Append a view to this node. Since a view is just a list of nodes, this essentially appends
172    /// every node in the view to this node.
173    fn append_view(&mut self, view: View<Self>) {
174        for node in view.nodes {
175            self.append_child(node);
176        }
177    }
178
179    /// Create a dynamic view from a function that returns a view.
180    ///
181    /// The returned view will no longer be a function and can be treated as a normal view and,
182    /// e.g., appended as a child to another node.
183    ///
184    /// Some render backends may not support dynamic views (e.g. `SsrNode`). In that case, the
185    /// default behavior is to simply evaluate the function as a static view.
186    fn create_dynamic_view<U: Into<View<Self>> + 'static>(
187        mut f: impl FnMut() -> U + 'static,
188    ) -> View<Self> {
189        f().into()
190    }
191}