percy_dom/
pdom.rs

1//! Diff virtual-doms and patch the real DOM
2
3use crate::diff::diff;
4use crate::event::VirtualEvents;
5use crate::patch::patch;
6use std::collections::HashMap;
7use virtual_node::VirtualNode;
8use wasm_bindgen::JsValue;
9use web_sys::{Element, Node};
10
11mod events;
12
13/// Used for keeping a real DOM node up to date based on the current VirtualNode
14/// and a new incoming VirtualNode that represents our latest DOM state.
15///
16/// Also powers event delegation.
17pub struct PercyDom {
18    current_vdom: VirtualNode,
19    /// The closures that are currently attached to elements in the page.
20    /// We keep these around so that they don't get dropped,  (and thus stop working).
21    pub events: VirtualEvents,
22    root_node: Node,
23    // We hold onto these since if we drop the listener it can no longer be called.
24    event_delegation_listeners: HashMap<&'static str, Box<dyn AsRef<JsValue>>>,
25}
26
27impl PercyDom {
28    /// Create a new `PercyDom`.
29    ///
30    /// A root `Node` will be created but not added to your DOM.
31    pub fn new(current_vdom: VirtualNode) -> PercyDom {
32        let mut events = VirtualEvents::new();
33        let (created_node, events_node) = current_vdom.create_dom_node(&mut events);
34        events.set_root(events_node);
35
36        let mut pdom = PercyDom {
37            current_vdom,
38            root_node: created_node,
39            events,
40            event_delegation_listeners: HashMap::new(),
41        };
42        pdom.attach_event_listeners();
43
44        pdom
45    }
46
47    /// Create a new `PercyDom`.
48    ///
49    /// A root `Node` will be created and append (as a child) to your passed
50    /// in mount element.
51    pub fn new_append_to_mount(current_vdom: VirtualNode, mount: &Element) -> PercyDom {
52        let pdom = Self::new(current_vdom);
53
54        mount
55            .append_child(&pdom.root_node)
56            .expect("Could not append child to mount");
57
58        pdom
59    }
60
61    /// Create a new `PercyDom`.
62    ///
63    /// A root `Node` will be created and it will replace your passed in mount
64    /// element.
65    pub fn new_replace_mount(current_vdom: VirtualNode, mount: Element) -> PercyDom {
66        let pdom = Self::new(current_vdom);
67
68        mount
69            .replace_with_with_node_1(&pdom.root_node)
70            .expect("Could not replace mount element");
71
72        pdom
73    }
74
75    /// Diff the current virtual dom with the new virtual dom that is being passed in.
76    ///
77    /// Then use that diff to patch the real DOM in the user's browser so that they are
78    /// seeing the latest state of the application.
79    pub fn update(&mut self, new_vdom: VirtualNode) {
80        let patches = diff(&self.current_vdom, &new_vdom);
81
82        patch(
83            self.root_node.clone(),
84            &new_vdom,
85            &mut self.events,
86            &patches,
87        )
88        .unwrap();
89
90        self.current_vdom = new_vdom;
91    }
92
93    /// Return the root node of your application, the highest ancestor of all other nodes in
94    /// your real DOM tree.
95    pub fn root_node(&self) -> Node {
96        // Note that we're cloning the `web_sys::Node`, not the DOM element.
97        // So we're effectively cloning a pointer here, which is fast.
98        self.root_node.clone()
99    }
100}