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()
    }
}