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}