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}