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