This is unreleased documentation for Yew Next version.
For up-to-date documentation, see the latest version on docs.rs.

yew/virtual_dom/
vnode.rs

1//! This module contains the implementation of abstract virtual node.
2
3use std::cmp::PartialEq;
4use std::iter::FromIterator;
5use std::rc::Rc;
6use std::{fmt, mem};
7
8use web_sys::Node;
9
10use super::{Key, VChild, VComp, VList, VPortal, VSuspense, VTag, VText};
11use crate::html::{BaseComponent, ImplicitClone};
12use crate::virtual_dom::VRaw;
13use crate::AttrValue;
14
15/// Bind virtual element to a DOM reference.
16#[derive(Clone, PartialEq)]
17#[must_use = "html does not do anything unless returned to Yew for rendering."]
18pub enum VNode {
19    /// A bind between `VTag` and `Element`.
20    VTag(Rc<VTag>),
21    /// A bind between `VText` and `TextNode`.
22    VText(VText),
23    /// A bind between `VComp` and `Element`.
24    VComp(Rc<VComp>),
25    /// A holder for a list of other nodes.
26    VList(Rc<VList>),
27    /// A portal to another part of the document
28    VPortal(Rc<VPortal>),
29    /// A holder for any `Node` (necessary for replacing node).
30    VRef(Node),
31    /// A suspendible document fragment.
32    VSuspense(Rc<VSuspense>),
33    /// A raw HTML string, represented by [`AttrValue`](crate::AttrValue).
34    ///
35    /// Also see: [`VNode::from_html_unchecked`]
36    VRaw(VRaw),
37}
38
39impl ImplicitClone for VNode {}
40
41impl VNode {
42    pub fn key(&self) -> Option<&Key> {
43        match self {
44            VNode::VComp(vcomp) => vcomp.key.as_ref(),
45            VNode::VList(vlist) => vlist.key.as_ref(),
46            VNode::VRef(_) => None,
47            VNode::VTag(vtag) => vtag.key.as_ref(),
48            VNode::VText(_) => None,
49            VNode::VPortal(vportal) => vportal.node.key(),
50            VNode::VSuspense(vsuspense) => vsuspense.key.as_ref(),
51            VNode::VRaw(_) => None,
52        }
53    }
54
55    /// Returns true if the [VNode] has a key.
56    pub fn has_key(&self) -> bool {
57        self.key().is_some()
58    }
59
60    /// Acquires a mutable reference of current VNode as a VList.
61    ///
62    /// Creates a VList with the current node as the first child if current VNode is not a VList.
63    pub fn to_vlist_mut(&mut self) -> &mut VList {
64        loop {
65            match *self {
66                Self::VList(ref mut m) => return Rc::make_mut(m),
67                _ => {
68                    *self =
69                        VNode::VList(Rc::new(VList::with_children(vec![mem::take(self)], None)));
70                }
71            }
72        }
73    }
74
75    /// Create a [`VNode`] from a string of HTML
76    ///
77    /// # Behavior in browser
78    ///
79    /// In the browser, this function creates an element with the same XML namespace as the parent,
80    /// sets the passed HTML to its `innerHTML` and inserts the contents of it into the DOM.
81    ///
82    /// # Behavior on server
83    ///
84    /// When rendering on the server, the contents of HTML are directly injected into the HTML
85    /// stream.
86    ///
87    /// ## Warning
88    ///
89    /// The contents are **not** sanitized or validated. You, as the developer, are responsible to
90    /// ensure the HTML string passed to this method are _valid_ and _not malicious_
91    ///
92    /// # Example
93    ///
94    /// ```rust
95    /// use yew::{html, AttrValue, Html};
96    /// # fn _main() {
97    /// let parsed = Html::from_html_unchecked(AttrValue::from("<div>content</div>"));
98    /// let _: Html = html! {
99    ///     <div>
100    ///         {parsed}
101    ///     </div>
102    /// };
103    /// # }
104    /// ```
105    pub fn from_html_unchecked(html: AttrValue) -> Self {
106        VNode::VRaw(VRaw { html })
107    }
108}
109
110impl Default for VNode {
111    fn default() -> Self {
112        VNode::VList(Rc::new(VList::default()))
113    }
114}
115
116impl From<VText> for VNode {
117    #[inline]
118    fn from(vtext: VText) -> Self {
119        VNode::VText(vtext)
120    }
121}
122
123impl From<VList> for VNode {
124    #[inline]
125    fn from(vlist: VList) -> Self {
126        VNode::VList(Rc::new(vlist))
127    }
128}
129
130impl From<VTag> for VNode {
131    #[inline]
132    fn from(vtag: VTag) -> Self {
133        VNode::VTag(Rc::new(vtag))
134    }
135}
136
137impl From<VComp> for VNode {
138    #[inline]
139    fn from(vcomp: VComp) -> Self {
140        VNode::VComp(Rc::new(vcomp))
141    }
142}
143
144impl From<VSuspense> for VNode {
145    #[inline]
146    fn from(vsuspense: VSuspense) -> Self {
147        VNode::VSuspense(Rc::new(vsuspense))
148    }
149}
150
151impl From<VPortal> for VNode {
152    #[inline]
153    fn from(vportal: VPortal) -> Self {
154        VNode::VPortal(Rc::new(vportal))
155    }
156}
157
158impl<COMP> From<VChild<COMP>> for VNode
159where
160    COMP: BaseComponent,
161{
162    fn from(vchild: VChild<COMP>) -> Self {
163        VNode::VComp(Rc::new(VComp::from(vchild)))
164    }
165}
166
167impl<T: ToString> From<T> for VNode {
168    fn from(value: T) -> Self {
169        VNode::VText(VText::new(value.to_string()))
170    }
171}
172
173impl<A: Into<VNode>> FromIterator<A> for VNode {
174    fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self {
175        VNode::VList(Rc::new(VList::with_children(
176            iter.into_iter().map(|n| n.into()).collect(),
177            None,
178        )))
179    }
180}
181
182impl fmt::Debug for VNode {
183    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184        match *self {
185            VNode::VTag(ref vtag) => vtag.fmt(f),
186            VNode::VText(ref vtext) => vtext.fmt(f),
187            VNode::VComp(ref vcomp) => vcomp.fmt(f),
188            VNode::VList(ref vlist) => vlist.fmt(f),
189            VNode::VRef(ref vref) => write!(f, "VRef ( \"{}\" )", crate::utils::print_node(vref)),
190            VNode::VPortal(ref vportal) => vportal.fmt(f),
191            VNode::VSuspense(ref vsuspense) => vsuspense.fmt(f),
192            VNode::VRaw(ref vraw) => write!(f, "VRaw {{ {} }}", vraw.html),
193        }
194    }
195}
196
197#[cfg(feature = "ssr")]
198mod feat_ssr {
199    use futures::future::{FutureExt, LocalBoxFuture};
200
201    use super::*;
202    use crate::feat_ssr::VTagKind;
203    use crate::html::AnyScope;
204    use crate::platform::fmt::BufWriter;
205
206    impl VNode {
207        pub(crate) fn render_into_stream<'a>(
208            &'a self,
209            w: &'a mut BufWriter,
210            parent_scope: &'a AnyScope,
211            hydratable: bool,
212            parent_vtag_kind: VTagKind,
213        ) -> LocalBoxFuture<'a, ()> {
214            async fn render_into_stream_(
215                this: &VNode,
216                w: &mut BufWriter,
217                parent_scope: &AnyScope,
218                hydratable: bool,
219                parent_vtag_kind: VTagKind,
220            ) {
221                match this {
222                    VNode::VTag(vtag) => vtag.render_into_stream(w, parent_scope, hydratable).await,
223                    VNode::VText(vtext) => {
224                        vtext
225                            .render_into_stream(w, parent_scope, hydratable, parent_vtag_kind)
226                            .await
227                    }
228                    VNode::VComp(vcomp) => {
229                        vcomp
230                            .render_into_stream(w, parent_scope, hydratable, parent_vtag_kind)
231                            .await
232                    }
233                    VNode::VList(vlist) => {
234                        vlist
235                            .render_into_stream(w, parent_scope, hydratable, parent_vtag_kind)
236                            .await
237                    }
238                    // We are pretty safe here as it's not possible to get a web_sys::Node without
239                    // DOM support in the first place.
240                    //
241                    // The only exception would be to use `ServerRenderer` in a browser or wasm32
242                    // environment with jsdom present.
243                    VNode::VRef(_) => {
244                        panic!("VRef is not possible to be rendered in to a string.")
245                    }
246                    // Portals are not rendered.
247                    VNode::VPortal(_) => {}
248                    VNode::VSuspense(vsuspense) => {
249                        vsuspense
250                            .render_into_stream(w, parent_scope, hydratable, parent_vtag_kind)
251                            .await
252                    }
253
254                    VNode::VRaw(vraw) => vraw.render_into_stream(w, parent_scope, hydratable).await,
255                }
256            }
257
258            async move {
259                render_into_stream_(self, w, parent_scope, hydratable, parent_vtag_kind).await
260            }.boxed_local()
261        }
262    }
263}