yew_stdweb/virtual_dom/
vcomp.rs

1//! This module contains the implementation of a virtual component (`VComp`).
2
3use super::{Key, VDiff, VNode};
4use crate::html::{AnyScope, Component, NodeRef, Scope, Scoped};
5use cfg_if::cfg_if;
6use std::any::TypeId;
7use std::borrow::Borrow;
8use std::fmt;
9use std::ops::Deref;
10cfg_if! {
11    if #[cfg(feature = "std_web")] {
12        use stdweb::web::Element;
13    } else if #[cfg(feature = "web_sys")] {
14        use web_sys::Element;
15    }
16}
17
18/// A virtual component.
19pub struct VComp {
20    type_id: TypeId,
21    scope: Option<Box<dyn Scoped>>,
22    props: Option<Box<dyn Mountable>>,
23    pub(crate) node_ref: NodeRef,
24    pub(crate) key: Option<Key>,
25}
26
27impl Clone for VComp {
28    fn clone(&self) -> Self {
29        if self.scope.is_some() {
30            panic!("Mounted components are not allowed to be cloned!");
31        }
32
33        Self {
34            type_id: self.type_id,
35            scope: None,
36            props: self.props.as_ref().map(|m| m.copy()),
37            node_ref: self.node_ref.clone(),
38            key: self.key.clone(),
39        }
40    }
41}
42
43/// A virtual child component.
44pub struct VChild<COMP: Component> {
45    /// The component properties
46    pub props: COMP::Properties,
47    /// Reference to the mounted node
48    node_ref: NodeRef,
49    key: Option<Key>,
50}
51
52impl<COMP: Component> Clone for VChild<COMP> {
53    fn clone(&self) -> Self {
54        VChild {
55            props: self.props.clone(),
56            node_ref: self.node_ref.clone(),
57            key: self.key.clone(),
58        }
59    }
60}
61
62impl<COMP: Component> PartialEq for VChild<COMP>
63where
64    COMP::Properties: PartialEq,
65{
66    fn eq(&self, other: &VChild<COMP>) -> bool {
67        self.props == other.props
68    }
69}
70
71impl<COMP> VChild<COMP>
72where
73    COMP: Component,
74{
75    /// Creates a child component that can be accessed and modified by its parent.
76    pub fn new(props: COMP::Properties, node_ref: NodeRef, key: Option<Key>) -> Self {
77        Self {
78            props,
79            node_ref,
80            key,
81        }
82    }
83}
84
85impl<COMP> From<VChild<COMP>> for VComp
86where
87    COMP: Component,
88{
89    fn from(vchild: VChild<COMP>) -> Self {
90        VComp::new::<COMP>(vchild.props, vchild.node_ref, vchild.key)
91    }
92}
93
94impl VComp {
95    /// Creates a new `VComp` instance.
96    pub fn new<COMP>(props: COMP::Properties, node_ref: NodeRef, key: Option<Key>) -> Self
97    where
98        COMP: Component,
99    {
100        VComp {
101            type_id: TypeId::of::<COMP>(),
102            node_ref,
103            props: Some(Box::new(PropsWrapper::<COMP>::new(props))),
104            scope: None,
105            key,
106        }
107    }
108
109    #[allow(unused)]
110    pub(crate) fn root_vnode(&self) -> Option<impl Deref<Target = VNode> + '_> {
111        self.scope.as_ref().and_then(|scope| scope.root_vnode())
112    }
113}
114
115trait Mountable {
116    fn copy(&self) -> Box<dyn Mountable>;
117    fn mount(
118        self: Box<Self>,
119        node_ref: NodeRef,
120        parent_scope: &AnyScope,
121        parent: Element,
122        next_sibling: NodeRef,
123    ) -> Box<dyn Scoped>;
124    fn reuse(self: Box<Self>, node_ref: NodeRef, scope: &dyn Scoped, next_sibling: NodeRef);
125}
126
127struct PropsWrapper<COMP: Component> {
128    props: COMP::Properties,
129}
130
131impl<COMP: Component> PropsWrapper<COMP> {
132    pub fn new(props: COMP::Properties) -> Self {
133        Self { props }
134    }
135}
136
137impl<COMP: Component> Mountable for PropsWrapper<COMP> {
138    fn copy(&self) -> Box<dyn Mountable> {
139        let wrapper: PropsWrapper<COMP> = PropsWrapper {
140            props: self.props.clone(),
141        };
142        Box::new(wrapper)
143    }
144
145    fn mount(
146        self: Box<Self>,
147        node_ref: NodeRef,
148        parent_scope: &AnyScope,
149        parent: Element,
150        next_sibling: NodeRef,
151    ) -> Box<dyn Scoped> {
152        let scope: Scope<COMP> = Scope::new(Some(parent_scope.clone()));
153        let scope = scope.mount_in_place(parent, next_sibling, node_ref, self.props);
154
155        Box::new(scope)
156    }
157
158    fn reuse(self: Box<Self>, node_ref: NodeRef, scope: &dyn Scoped, next_sibling: NodeRef) {
159        let scope: Scope<COMP> = scope.to_any().downcast();
160        scope.reuse(self.props, node_ref, next_sibling);
161    }
162}
163
164impl VDiff for VComp {
165    fn detach(&mut self, _parent: &Element) {
166        self.scope.take().expect("VComp is not mounted").destroy();
167    }
168
169    fn apply(
170        &mut self,
171        parent_scope: &AnyScope,
172        parent: &Element,
173        next_sibling: NodeRef,
174        ancestor: Option<VNode>,
175    ) -> NodeRef {
176        let mountable = self.props.take().expect("VComp has already been mounted");
177
178        if let Some(mut ancestor) = ancestor {
179            if let VNode::VComp(ref mut vcomp) = &mut ancestor {
180                // If the ancestor is the same type, reuse it and update its properties
181                if self.type_id == vcomp.type_id && self.key == vcomp.key {
182                    self.node_ref.reuse(vcomp.node_ref.clone());
183                    let scope = vcomp.scope.take().expect("VComp is not mounted");
184                    mountable.reuse(self.node_ref.clone(), scope.borrow(), next_sibling);
185                    self.scope = Some(scope);
186                    return vcomp.node_ref.clone();
187                }
188            }
189
190            ancestor.detach(parent);
191        }
192
193        self.scope = Some(mountable.mount(
194            self.node_ref.clone(),
195            parent_scope,
196            parent.to_owned(),
197            next_sibling,
198        ));
199
200        self.node_ref.clone()
201    }
202}
203
204impl PartialEq for VComp {
205    fn eq(&self, other: &VComp) -> bool {
206        self.type_id == other.type_id
207    }
208}
209
210impl fmt::Debug for VComp {
211    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212        f.write_str("VComp")
213    }
214}
215
216impl<COMP: Component> fmt::Debug for VChild<COMP> {
217    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
218        f.write_str("VChild<_>")
219    }
220}
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225    use crate::{
226        html, utils::document, Children, Component, ComponentLink, Html, NodeRef, Properties,
227        ShouldRender,
228    };
229    use cfg_match::cfg_match;
230
231    cfg_if! {
232        if #[cfg(feature = "std_web")] {
233            use stdweb::web::{INode, Node};
234        } else if #[cfg(feature = "web_sys")] {
235            use web_sys::Node;
236        }
237    }
238
239    #[cfg(feature = "wasm_test")]
240    use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
241
242    #[cfg(feature = "wasm_test")]
243    wasm_bindgen_test_configure!(run_in_browser);
244
245    struct Comp;
246
247    #[derive(Clone, PartialEq, Properties)]
248    struct Props {
249        #[prop_or_default]
250        field_1: u32,
251        #[prop_or_default]
252        field_2: u32,
253    }
254
255    impl Component for Comp {
256        type Message = ();
257        type Properties = Props;
258
259        fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
260            Comp
261        }
262
263        fn update(&mut self, _: Self::Message) -> ShouldRender {
264            unimplemented!();
265        }
266
267        fn change(&mut self, _: Self::Properties) -> ShouldRender {
268            true
269        }
270
271        fn view(&self) -> Html {
272            html! { <div/> }
273        }
274    }
275
276    #[test]
277    fn update_loop() {
278        let document = crate::utils::document();
279        let parent_scope: AnyScope = crate::html::Scope::<Comp>::new(None).into();
280        let parent_element = document.create_element("div").unwrap();
281
282        let mut ancestor = html! { <Comp></Comp> };
283        ancestor.apply(&parent_scope, &parent_element, NodeRef::default(), None);
284
285        for _ in 0..10000 {
286            let mut node = html! { <Comp></Comp> };
287            node.apply(
288                &parent_scope,
289                &parent_element,
290                NodeRef::default(),
291                Some(ancestor),
292            );
293            ancestor = node;
294        }
295    }
296
297    #[test]
298    fn set_properties_to_component() {
299        html! {
300            <Comp />
301        };
302
303        html! {
304            <Comp field_1=1 />
305        };
306
307        html! {
308            <Comp field_2=2 />
309        };
310
311        html! {
312            <Comp field_1=1 field_2=2 />
313        };
314
315        let props = Props {
316            field_1: 1,
317            field_2: 1,
318        };
319
320        html! {
321            <Comp with props />
322        };
323    }
324
325    #[test]
326    fn set_component_key() {
327        let test_key: Key = "test".to_string().into();
328        let check_key = |vnode: VNode| {
329            assert_eq!(vnode.key().as_ref(), Some(&test_key));
330        };
331
332        let props = Props {
333            field_1: 1,
334            field_2: 1,
335        };
336        let props_2 = props.clone();
337
338        check_key(html! { <Comp key=test_key.clone() /> });
339        check_key(html! { <Comp key=test_key.clone() field_1=1 /> });
340        check_key(html! { <Comp field_1=1 key=test_key.clone() /> });
341        check_key(html! { <Comp with props key=test_key.clone() /> });
342        check_key(html! { <Comp key=test_key.clone() with props_2 /> });
343    }
344
345    #[test]
346    fn set_component_node_ref() {
347        let test_node: Node = document().create_text_node("test").into();
348        let test_node_ref = NodeRef::new(test_node);
349        let check_node_ref = |vnode: VNode| {
350            assert_eq!(vnode.first_node(), test_node_ref.get().unwrap());
351        };
352
353        let props = Props {
354            field_1: 1,
355            field_2: 1,
356        };
357        let props_2 = props.clone();
358
359        check_node_ref(html! { <Comp ref=test_node_ref.clone() /> });
360        check_node_ref(html! { <Comp ref=test_node_ref.clone() field_1=1 /> });
361        check_node_ref(html! { <Comp field_1=1 ref=test_node_ref.clone() /> });
362        check_node_ref(html! { <Comp with props ref=test_node_ref.clone() /> });
363        check_node_ref(html! { <Comp ref=test_node_ref.clone() with props_2 /> });
364    }
365
366    #[test]
367    fn vchild_partialeq() {
368        let vchild1: VChild<Comp> = VChild::new(
369            Props {
370                field_1: 1,
371                field_2: 1,
372            },
373            NodeRef::default(),
374            None,
375        );
376
377        let vchild2: VChild<Comp> = VChild::new(
378            Props {
379                field_1: 1,
380                field_2: 1,
381            },
382            NodeRef::default(),
383            None,
384        );
385
386        let vchild3: VChild<Comp> = VChild::new(
387            Props {
388                field_1: 2,
389                field_2: 2,
390            },
391            NodeRef::default(),
392            None,
393        );
394
395        assert_eq!(vchild1, vchild2);
396        assert_ne!(vchild1, vchild3);
397        assert_ne!(vchild2, vchild3);
398    }
399
400    #[derive(Clone, Properties)]
401    pub struct ListProps {
402        pub children: Children,
403    }
404    pub struct List(ListProps);
405    impl Component for List {
406        type Message = ();
407        type Properties = ListProps;
408
409        fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self {
410            Self(props)
411        }
412        fn update(&mut self, _: Self::Message) -> ShouldRender {
413            unimplemented!();
414        }
415        fn change(&mut self, _: Self::Properties) -> ShouldRender {
416            unimplemented!();
417        }
418        fn view(&self) -> Html {
419            let item_iter = self.0.children.iter().map(|item| html! {<li>{ item }</li>});
420            html! {
421                <ul>{ for item_iter }</ul>
422            }
423        }
424    }
425
426    #[cfg(feature = "web_sys")]
427    use super::{AnyScope, Element};
428
429    #[cfg(feature = "web_sys")]
430    fn setup_parent() -> (AnyScope, Element) {
431        let scope = AnyScope {
432            type_id: std::any::TypeId::of::<()>(),
433            parent: None,
434            state: std::rc::Rc::new(()),
435        };
436        let parent = document().create_element("div").unwrap();
437
438        document().body().unwrap().append_child(&parent).unwrap();
439
440        (scope, parent)
441    }
442
443    #[cfg(feature = "web_sys")]
444    fn get_html(mut node: Html, scope: &AnyScope, parent: &Element) -> String {
445        // clear parent
446        parent.set_inner_html("");
447
448        node.apply(&scope, &parent, NodeRef::default(), None);
449        parent.inner_html()
450    }
451
452    #[test]
453    #[cfg(feature = "web_sys")]
454    fn all_ways_of_passing_children_work() {
455        let (scope, parent) = setup_parent();
456
457        let children: Vec<_> = vec!["a", "b", "c"]
458            .drain(..)
459            .map(|text| html! {<span>{ text }</span>})
460            .collect();
461        let children_renderer = Children::new(children.clone());
462        let expected_html = "\
463        <ul>\
464            <li><span>a</span></li>\
465            <li><span>b</span></li>\
466            <li><span>c</span></li>\
467        </ul>";
468
469        let prop_method = html! {
470            <List children=children_renderer.clone()/>
471        };
472        assert_eq!(get_html(prop_method, &scope, &parent), expected_html);
473
474        let children_renderer_method = html! {
475            <List>
476                { children_renderer }
477            </List>
478        };
479        assert_eq!(
480            get_html(children_renderer_method, &scope, &parent),
481            expected_html
482        );
483
484        let direct_method = html! {
485            <List>
486                { children.clone() }
487            </List>
488        };
489        assert_eq!(get_html(direct_method, &scope, &parent), expected_html);
490
491        let for_method = html! {
492            <List>
493                { for children }
494            </List>
495        };
496        assert_eq!(get_html(for_method, &scope, &parent), expected_html);
497    }
498
499    #[test]
500    fn reset_node_ref() {
501        let scope = AnyScope {
502            type_id: std::any::TypeId::of::<()>(),
503            parent: None,
504            state: std::rc::Rc::new(()),
505        };
506        let parent = document().create_element("div").unwrap();
507
508        #[cfg(feature = "std_web")]
509        document().body().unwrap().append_child(&parent);
510        #[cfg(feature = "web_sys")]
511        document().body().unwrap().append_child(&parent).unwrap();
512
513        let node_ref = NodeRef::default();
514        let mut elem: VNode = html! { <Comp ref=node_ref.clone()></Comp> };
515        elem.apply(&scope, &parent, NodeRef::default(), None);
516        let parent_node = cfg_match! {
517            feature = "std_web" => parent.as_node(),
518            feature = "web_sys" => parent.deref(),
519        };
520        assert_eq!(node_ref.get(), parent_node.first_child());
521        elem.detach(&parent);
522        assert!(node_ref.get().is_none());
523    }
524}
525
526#[cfg(all(test, feature = "web_sys"))]
527mod layout_tests {
528    extern crate self as yew;
529
530    use crate::html;
531    use crate::virtual_dom::layout_tests::{diff_layouts, TestLayout};
532    use crate::{Children, Component, ComponentLink, Html, Properties, ShouldRender};
533    use std::marker::PhantomData;
534
535    #[cfg(feature = "wasm_test")]
536    use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
537
538    #[cfg(feature = "wasm_test")]
539    wasm_bindgen_test_configure!(run_in_browser);
540
541    struct Comp<T> {
542        _marker: PhantomData<T>,
543        props: CompProps,
544    }
545
546    #[derive(Properties, Clone)]
547    struct CompProps {
548        #[prop_or_default]
549        children: Children,
550    }
551
552    impl<T: 'static> Component for Comp<T> {
553        type Message = ();
554        type Properties = CompProps;
555
556        fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self {
557            Comp {
558                _marker: PhantomData::default(),
559                props,
560            }
561        }
562
563        fn update(&mut self, _: Self::Message) -> ShouldRender {
564            unimplemented!();
565        }
566
567        fn change(&mut self, props: Self::Properties) -> ShouldRender {
568            self.props = props;
569            true
570        }
571
572        fn view(&self) -> Html {
573            html! {
574                <>{ self.props.children.clone() }</>
575            }
576        }
577    }
578
579    struct A;
580    struct B;
581
582    #[test]
583    fn diff() {
584        let layout1 = TestLayout {
585            name: "1",
586            node: html! {
587                <Comp<A>>
588                    <Comp<B>></Comp<B>>
589                    {"C"}
590                </Comp<A>>
591            },
592            expected: "C",
593        };
594
595        let layout2 = TestLayout {
596            name: "2",
597            node: html! {
598                <Comp<A>>
599                    {"A"}
600                </Comp<A>>
601            },
602            expected: "A",
603        };
604
605        let layout3 = TestLayout {
606            name: "3",
607            node: html! {
608                <Comp<B>>
609                    <Comp<A>></Comp<A>>
610                    {"B"}
611                </Comp<B>>
612            },
613            expected: "B",
614        };
615
616        let layout4 = TestLayout {
617            name: "4",
618            node: html! {
619                <Comp<B>>
620                    <Comp<A>>{"A"}</Comp<A>>
621                    {"B"}
622                </Comp<B>>
623            },
624            expected: "AB",
625        };
626
627        let layout5 = TestLayout {
628            name: "5",
629            node: html! {
630                <Comp<B>>
631                    <>
632                        <Comp<A>>
633                            {"A"}
634                        </Comp<A>>
635                    </>
636                    {"B"}
637                </Comp<B>>
638            },
639            expected: "AB",
640        };
641
642        let layout6 = TestLayout {
643            name: "6",
644            node: html! {
645                <Comp<B>>
646                    <>
647                        <Comp<A>>
648                            {"A"}
649                        </Comp<A>>
650                        {"B"}
651                    </>
652                    {"C"}
653                </Comp<B>>
654            },
655            expected: "ABC",
656        };
657
658        let layout7 = TestLayout {
659            name: "7",
660            node: html! {
661                <Comp<B>>
662                    <>
663                        <Comp<A>>
664                            {"A"}
665                        </Comp<A>>
666                        <Comp<A>>
667                            {"B"}
668                        </Comp<A>>
669                    </>
670                    {"C"}
671                </Comp<B>>
672            },
673            expected: "ABC",
674        };
675
676        let layout8 = TestLayout {
677            name: "8",
678            node: html! {
679                <Comp<B>>
680                    <>
681                        <Comp<A>>
682                            {"A"}
683                        </Comp<A>>
684                        <Comp<A>>
685                            <Comp<A>>
686                                {"B"}
687                            </Comp<A>>
688                        </Comp<A>>
689                    </>
690                    {"C"}
691                </Comp<B>>
692            },
693            expected: "ABC",
694        };
695
696        let layout9 = TestLayout {
697            name: "9",
698            node: html! {
699                <Comp<B>>
700                    <>
701                        <>
702                            {"A"}
703                        </>
704                        <Comp<A>>
705                            <Comp<A>>
706                                {"B"}
707                            </Comp<A>>
708                        </Comp<A>>
709                    </>
710                    {"C"}
711                </Comp<B>>
712            },
713            expected: "ABC",
714        };
715
716        let layout10 = TestLayout {
717            name: "10",
718            node: html! {
719                <Comp<B>>
720                    <>
721                        <Comp<A>>
722                            <Comp<A>>
723                                {"A"}
724                            </Comp<A>>
725                        </Comp<A>>
726                        <>
727                            {"B"}
728                        </>
729                    </>
730                    {"C"}
731                </Comp<B>>
732            },
733            expected: "ABC",
734        };
735
736        let layout11 = TestLayout {
737            name: "11",
738            node: html! {
739                <Comp<B>>
740                    <>
741                        <>
742                            <Comp<A>>
743                                <Comp<A>>
744                                    {"A"}
745                                </Comp<A>>
746                                {"B"}
747                            </Comp<A>>
748                        </>
749                    </>
750                    {"C"}
751                </Comp<B>>
752            },
753            expected: "ABC",
754        };
755
756        let layout12 = TestLayout {
757            name: "12",
758            node: html! {
759                <Comp<B>>
760                    <>
761                        <Comp<A>></Comp<A>>
762                        <>
763                            <Comp<A>>
764                                <>
765                                    <Comp<A>>
766                                        {"A"}
767                                    </Comp<A>>
768                                    <></>
769                                    <Comp<A>>
770                                        <Comp<A>></Comp<A>>
771                                        <></>
772                                        {"B"}
773                                        <></>
774                                        <Comp<A>></Comp<A>>
775                                    </Comp<A>>
776                                </>
777                            </Comp<A>>
778                            <></>
779                        </>
780                        <Comp<A>></Comp<A>>
781                    </>
782                    {"C"}
783                    <Comp<A>></Comp<A>>
784                    <></>
785                </Comp<B>>
786            },
787            expected: "ABC",
788        };
789
790        diff_layouts(vec![
791            layout1, layout2, layout3, layout4, layout5, layout6, layout7, layout8, layout9,
792            layout10, layout11, layout12,
793        ]);
794    }
795}