1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
//! Diff virtual-doms and patch the real DOM use crate::diff::diff; use crate::patch::patch; use std::collections::HashMap; use virtual_node::DynClosure; use virtual_node::VirtualNode; use web_sys::{Element, Node}; /// Closures that we are holding on to to make sure that they don't get invalidated after a /// VirtualNode is dropped. /// /// The u32 is a unique identifier that is associated with the DOM element that this closure is /// attached to. /// /// TODO: Periodically check if the DOM element is still there, and if not drop the closure. /// Maybe whenever a DOM node is replaced or truncated we figure out all of it's /// descendants somehow and invalidate those closures..? Need to plan this out.. /// At it stands now this hashmap will grow anytime a new element with closures is /// appended or replaced and we will never free those closures. pub type ActiveClosures = HashMap<u32, Vec<DynClosure>>; /// Used for keeping a real DOM node up to date based on the current VirtualNode /// and a new incoming VirtualNode that represents our latest DOM state. pub struct DomUpdater { current_vdom: VirtualNode, /// The closures that are currently attached to elements in the page. /// /// We keep these around so that they don't get dropped (and thus stop working); /// /// FIXME: Drop them when the element is no longer in the page. Need to figure out /// a good strategy for when to do this. pub active_closures: ActiveClosures, root_node: Node, } impl DomUpdater { /// Create a new `DomUpdater`. /// /// A root `Node` will be created but not added to your DOM. pub fn new(current_vdom: VirtualNode) -> DomUpdater { let created_node = current_vdom.create_dom_node(); DomUpdater { current_vdom, active_closures: created_node.closures, root_node: created_node.node, } } /// Create a new `DomUpdater`. /// /// A root `Node` will be created and appended (as a child) to your passed /// in mount element. pub fn new_append_to_mount(current_vdom: VirtualNode, mount: &Element) -> DomUpdater { let created_node = current_vdom.create_dom_node(); mount .append_child(&created_node.node) .expect("Could not append child to mount"); DomUpdater { current_vdom, active_closures: created_node.closures, root_node: created_node.node, } } /// Create a new `DomUpdater`. /// /// A root `Node` will be created and it will replace your passed in mount /// element. pub fn new_replace_mount(current_vdom: VirtualNode, mount: Element) -> DomUpdater { let created_node = current_vdom.create_dom_node(); mount .replace_with_with_node_1(&created_node.node) .expect("Could not replace mount element"); DomUpdater { current_vdom, active_closures: created_node.closures, root_node: created_node.node, } } /// Diff the current virtual dom with the new virtual dom that is being passed in. /// /// Then use that diff to patch the real DOM in the user's browser so that they are /// seeing the latest state of the application. pub fn update(&mut self, new_vdom: VirtualNode) { let patches = diff(&self.current_vdom, &new_vdom); let active_closures = patch(self.root_node.clone(), &patches).unwrap(); self.active_closures.extend(active_closures); self.current_vdom = new_vdom; } /// Return the root node of your application, the highest ancestor of all other nodes in /// your real DOM tree. pub fn root_node(&self) -> Node { // Note that we're cloning the `web_sys::Node`, not the DOM element. // So we're effectively cloning a pointer here, which is fast. self.root_node.clone() } }