dodrio/
render.rs

1use crate::{Node, RenderContext};
2use std::any::Any;
3use std::rc::Rc;
4use wasm_bindgen::UnwrapThrowExt;
5
6/// A trait for any component that can be rendered to HTML.
7///
8/// Takes a shared reference to `self` and generates the virtual DOM that
9/// represents its rendered HTML.
10///
11/// ## `Bump` Allocation
12///
13/// `Render` implementations can use the `Bump` inside the provided
14/// `RenderContext` for very fast allocation for anything that needs to be
15/// temporarily allocated during rendering.
16///
17/// ## Example
18///
19/// ```no_run
20/// use dodrio::{Node, Render, RenderContext};
21///
22/// pub struct MyComponent;
23///
24/// impl<'a> Render<'a> for MyComponent {
25///     fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
26///         use dodrio::builder::*;
27///
28///         p(&cx)
29///             .children([
30///                 text("This is "),
31///                 strong(&cx).children([text("my component")]).finish(),
32///                 text(" rendered!"),
33///             ])
34///             .finish()
35///     }
36/// }
37/// ```
38pub trait Render<'a> {
39    /// Render `self` as a virtual DOM. Use the given context's `Bump` for
40    /// temporary allocations.
41    fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a>;
42}
43
44impl<'a, 'r, R> Render<'a> for &'r R
45where
46    R: Render<'a>,
47{
48    fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
49        (**self).render(cx)
50    }
51}
52
53impl<'a, R> Render<'a> for Rc<R>
54where
55    R: Render<'a>,
56{
57    fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
58        (**self).render(cx)
59    }
60}
61
62/// A `RootRender` is a render component that can be the root rendering component
63/// mounted to a virtual DOM.
64///
65/// In addition to rendering, it must also be `'static` so that it can be owned
66/// by the virtual DOM and `Any` so that it can be downcast to its concrete type
67/// by event listener callbacks.
68///
69/// You do not need to implement this trait by hand: there is a blanket
70/// implementation for all `Render` types that fulfill the `RootRender`
71/// requirements.
72pub trait RootRender: Any + for<'a> Render<'a> {
73    /// Get this `&RootRender` trait object as an `&Any` trait object reference.
74    fn as_any(&self) -> &dyn Any;
75
76    /// Get this `&mut RootRender` trait object as an `&mut Any` trait object
77    /// reference.
78    fn as_any_mut(&mut self) -> &mut dyn Any;
79}
80
81impl<T> RootRender for T
82where
83    T: Any + for<'a> Render<'a>,
84{
85    fn as_any(&self) -> &dyn Any {
86        self
87    }
88
89    fn as_any_mut(&mut self) -> &mut dyn Any {
90        self
91    }
92}
93
94impl dyn RootRender {
95    /// Downcast this shared `&dyn RootRender` trait object reference to its
96    /// underlying concrete type.
97    ///
98    /// # Panics
99    ///
100    /// Panics if this virtual DOM's root rendering component is not an `R`
101    /// instance.
102    pub fn unwrap_ref<R>(&self) -> &R
103    where
104        R: RootRender,
105    {
106        self.as_any()
107            .downcast_ref::<R>()
108            .expect_throw("bad `RootRender::unwrap_ref` call")
109    }
110
111    /// Downcast this exclusive `&mut dyn RootRender` trait object reference to
112    /// its underlying concrete type.
113    ///
114    /// # Panics
115    ///
116    /// Panics if this virtual DOM's root rendering component is not an `R`
117    /// instance.
118    pub fn unwrap_mut<R>(&mut self) -> &mut R
119    where
120        R: RootRender,
121    {
122        self.as_any_mut()
123            .downcast_mut::<R>()
124            .expect_throw("bad `RootRender::unwrap_ref` call")
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    #[test]
131    fn render_is_object_safe() {
132        #[allow(dead_code)]
133        fn takes_dyn_render(_: &dyn super::Render) {}
134    }
135
136    #[test]
137    fn root_render_is_object_safe() {
138        #[allow(dead_code)]
139        fn takes_dyn_render(_: &dyn super::RootRender) {}
140    }
141
142    #[test]
143    fn render_bump_scoped_child() {
144        use crate::{builder::*, bumpalo::collections::String, Node, Render, RenderContext};
145
146        struct Child<'a> {
147            name: &'a str,
148        }
149
150        impl<'a> Render<'a> for Child<'a> {
151            fn render(&self, _cx: &mut RenderContext<'a>) -> Node<'a> {
152                text(self.name)
153            }
154        }
155
156        struct Parent;
157
158        impl<'a> Render<'a> for Parent {
159            fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
160                let child_name = String::from_str_in("child", cx.bump).into_bump_str();
161
162                div(&cx)
163                    .children([Child { name: child_name }.render(cx)])
164                    .finish()
165            }
166        }
167    }
168}