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