123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- use crate::dom_updater::ActiveClosures;
- use crate::patch::Patch;
- use std::cmp::min;
- use std::collections::HashMap;
- use std::collections::HashSet;
- use wasm_bindgen::JsCast;
- use wasm_bindgen::JsValue;
- use web_sys::{Element, Node, Text};
- /// Apply all of the patches to our old root node in order to create the new root node
- /// that we desire.
- /// This is usually used after diffing two virtual nodes.
- pub fn patch<N: Into<Node>>(root_node: N, patches: &Vec<Patch>) -> Result<ActiveClosures, JsValue> {
- let root_node: Node = root_node.into();
- let mut cur_node_idx = 0;
- let mut nodes_to_find = HashSet::new();
- for patch in patches {
- nodes_to_find.insert(patch.node_idx());
- }
- let mut element_nodes_to_patch = HashMap::new();
- let mut text_nodes_to_patch = HashMap::new();
- // Closures that were added to the DOM during this patch operation.
- let mut active_closures = HashMap::new();
- find_nodes(
- root_node,
- &mut cur_node_idx,
- &mut nodes_to_find,
- &mut element_nodes_to_patch,
- &mut text_nodes_to_patch,
- );
- for patch in patches {
- let patch_node_idx = patch.node_idx();
- if let Some(element) = element_nodes_to_patch.get(&patch_node_idx) {
- let new_closures = apply_element_patch(&element, &patch)?;
- active_closures.extend(new_closures);
- continue;
- }
- if let Some(text_node) = text_nodes_to_patch.get(&patch_node_idx) {
- apply_text_patch(&text_node, &patch)?;
- continue;
- }
- unreachable!("Getting here means we didn't find the element or next node that we were supposed to patch.")
- }
- Ok(active_closures)
- }
- fn find_nodes(
- root_node: Node,
- cur_node_idx: &mut usize,
- nodes_to_find: &mut HashSet<usize>,
- element_nodes_to_patch: &mut HashMap<usize, Element>,
- text_nodes_to_patch: &mut HashMap<usize, Text>,
- ) {
- if nodes_to_find.len() == 0 {
- return;
- }
- // We use child_nodes() instead of children() because children() ignores text nodes
- let children = root_node.child_nodes();
- let child_node_count = children.length();
- // If the root node matches, mark it for patching
- if nodes_to_find.get(&cur_node_idx).is_some() {
- match root_node.node_type() {
- Node::ELEMENT_NODE => {
- element_nodes_to_patch.insert(*cur_node_idx, root_node.unchecked_into());
- }
- Node::TEXT_NODE => {
- text_nodes_to_patch.insert(*cur_node_idx, root_node.unchecked_into());
- }
- other => unimplemented!("Unsupported root node type: {}", other),
- }
- nodes_to_find.remove(&cur_node_idx);
- }
- *cur_node_idx += 1;
- for i in 0..child_node_count {
- let node = children.item(i).unwrap();
- match node.node_type() {
- Node::ELEMENT_NODE => {
- find_nodes(
- node,
- cur_node_idx,
- nodes_to_find,
- element_nodes_to_patch,
- text_nodes_to_patch,
- );
- }
- Node::TEXT_NODE => {
- if nodes_to_find.get(&cur_node_idx).is_some() {
- text_nodes_to_patch.insert(*cur_node_idx, node.unchecked_into());
- }
- *cur_node_idx += 1;
- }
- Node::COMMENT_NODE => {
- // At this time we do not support user entered comment nodes, so if we see a comment
- // then it was a delimiter created by virtual-dom-rs in order to ensure that two
- // neighboring text nodes did not get merged into one by the browser. So we skip
- // over this virtual-dom-rs generated comment node.
- }
- _other => {
- // Ignoring unsupported child node type
- // TODO: What do we do with this situation? Log a warning?
- }
- }
- }
- }
- fn apply_element_patch(node: &Element, patch: &Patch) -> Result<ActiveClosures, JsValue> {
- let active_closures = HashMap::new();
- match patch {
- Patch::AddAttributes(_node_idx, attributes) => {
- for (attrib_name, attrib_val) in attributes.iter() {
- node.set_attribute(attrib_name, attrib_val)?;
- }
- Ok(active_closures)
- }
- Patch::RemoveAttributes(_node_idx, attributes) => {
- for attrib_name in attributes.iter() {
- node.remove_attribute(attrib_name)?;
- }
- Ok(active_closures)
- }
- Patch::Replace(_node_idx, new_node) => {
- let created_node = new_node.create_dom_node();
- node.replace_with_with_node_1(&created_node.node)?;
- Ok(created_node.closures)
- }
- Patch::TruncateChildren(_node_idx, num_children_remaining) => {
- let children = node.child_nodes();
- let mut child_count = children.length();
- // We skip over any separators that we placed between two text nodes
- // -> `<!--ptns-->`
- // and trim all children that come after our new desired `num_children_remaining`
- let mut non_separator_children_found = 0;
- for index in 0 as u32..child_count {
- let child = children
- .get(min(index, child_count - 1))
- .expect("Potential child to truncate");
- // If this is a comment node then we know that it is a `<!--ptns-->`
- // text node separator that was created in virtual_node/mod.rs.
- if child.node_type() == Node::COMMENT_NODE {
- continue;
- }
- non_separator_children_found += 1;
- if non_separator_children_found <= *num_children_remaining as u32 {
- continue;
- }
- node.remove_child(&child).expect("Truncated children");
- child_count -= 1;
- }
- Ok(active_closures)
- }
- Patch::AppendChildren(_node_idx, new_nodes) => {
- let parent = &node;
- let mut active_closures = HashMap::new();
- for new_node in new_nodes {
- let created_node = new_node.create_dom_node();
- parent.append_child(&created_node.node)?;
- active_closures.extend(created_node.closures);
- }
- Ok(active_closures)
- }
- Patch::ChangeText(_node_idx, _new_node) => {
- unreachable!("Elements should not receive ChangeText patches.")
- }
- }
- }
- fn apply_text_patch(node: &Text, patch: &Patch) -> Result<(), JsValue> {
- match patch {
- Patch::ChangeText(_node_idx, new_node) => {
- node.set_node_value(Some(&new_node.text));
- }
- Patch::Replace(_node_idx, new_node) => {
- node.replace_with_with_node_1(&new_node.create_dom_node().node)?;
- }
- other => unreachable!(
- "Text nodes should only receive ChangeText or Replace patches, not {:?}.",
- other,
- ),
- };
- Ok(())
- }
|