use crate::{
dom::{
created_node,
created_node::{
ActiveClosure,
CreatedNode,
},
},
mt_dom::{
patch::{
AddAttributes,
AppendChildren,
InsertNode,
RemoveAttributes,
RemoveNode,
ReplaceNode,
},
AttValue,
},
Dispatch,
Patch,
};
use js_sys::Function;
use std::collections::{
BTreeMap,
HashMap,
};
use wasm_bindgen::{
JsCast,
JsValue,
};
use web_sys::{
Element,
Node,
Text,
};
pub fn patch<N, DSP, MSG>(
program: Option<&DSP>,
root_node: N,
old_closures: &mut ActiveClosure,
patches: Vec<Patch<MSG>>,
) -> Result<ActiveClosure, JsValue>
where
N: Into<Node>,
MSG: 'static,
DSP: Clone + Dispatch<MSG> + 'static,
{
let root_node: Node = root_node.into();
let mut active_closures = HashMap::new();
let (element_nodes_to_patch, text_nodes_to_patch) =
find_nodes(root_node, &patches);
for patch in patches.iter() {
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(program, &element, old_closures, &patch)?;
active_closures.extend(new_closures);
continue;
}
if let Some(text_node) = text_nodes_to_patch.get(&patch_node_idx) {
apply_text_patch(program, &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<MSG>(
root_node: Node,
patches: &[Patch<MSG>],
) -> (BTreeMap<usize, Element>, BTreeMap<usize, Text>) {
let mut nodes_to_find = BTreeMap::new();
for patch in patches {
nodes_to_find.insert(patch.node_idx(), patch.tag());
}
find_nodes_recursive(root_node, &mut 0, &nodes_to_find)
}
fn find_nodes_recursive(
node: Node,
cur_node_idx: &mut usize,
nodes_to_find: &BTreeMap<usize, Option<&&'static str>>,
) -> (BTreeMap<usize, Element>, BTreeMap<usize, Text>) {
let mut element_nodes_to_patch = BTreeMap::new();
let mut text_nodes_to_patch = BTreeMap::new();
let children = node.child_nodes();
let child_node_count = children.length();
if let Some(_vtag) = nodes_to_find.get(&cur_node_idx) {
match node.node_type() {
Node::ELEMENT_NODE => {
let element: Element = node.unchecked_into();
element_nodes_to_patch.insert(*cur_node_idx, element);
}
Node::TEXT_NODE => {
let text_node: Text = node.unchecked_into();
text_nodes_to_patch.insert(*cur_node_idx, text_node);
}
Node::COMMENT_NODE => {
log::trace!("skipping comment node");
}
other => unimplemented!("Unsupported root node type: {}", other),
}
}
for i in 0..child_node_count {
let child_node = children.item(i).expect("Expecting a child node");
*cur_node_idx += 1;
let (child_element_to_patch, child_text_nodes_to_patch) =
find_nodes_recursive(child_node, cur_node_idx, nodes_to_find);
element_nodes_to_patch.extend(child_element_to_patch);
text_nodes_to_patch.extend(child_text_nodes_to_patch);
}
(element_nodes_to_patch, text_nodes_to_patch)
}
fn get_node_descendant_data_vdom_id(root_element: &Element) -> Vec<u32> {
let mut data_vdom_id = vec![];
if let Some(vdom_id_str) =
root_element.get_attribute(created_node::DATA_SAURON_VDOM_ID)
{
let vdom_id = vdom_id_str
.parse::<u32>()
.expect("unable to parse sauron_vdom-id");
data_vdom_id.push(vdom_id);
}
let children = root_element.child_nodes();
let child_node_count = children.length();
for i in 0..child_node_count {
let child_node = children.item(i).expect("Expecting a child node");
if let Node::ELEMENT_NODE = child_node.node_type() {
let child_element = child_node.unchecked_ref::<Element>();
let child_data_vdom_id =
get_node_descendant_data_vdom_id(child_element);
data_vdom_id.extend(child_data_vdom_id);
}
}
data_vdom_id
}
fn remove_event_listeners(
node: &Element,
old_closures: &mut ActiveClosure,
) -> Result<(), JsValue> {
let all_descendant_vdom_id = get_node_descendant_data_vdom_id(node);
for vdom_id in all_descendant_vdom_id {
if let Some(old_closure) = old_closures.get(&vdom_id) {
for (event, oc) in old_closure.iter() {
let func: &Function = oc.as_ref().unchecked_ref();
node.remove_event_listener_with_callback(event, func)?;
}
old_closures
.remove(&vdom_id)
.expect("Unable to remove old closure");
} else {
log::warn!(
"There is no closure marked with that vdom_id: {}",
vdom_id
);
}
}
Ok(())
}
fn remove_event_listener_with_name(
event_name: &'static str,
node: &Element,
old_closures: &mut ActiveClosure,
) -> Result<(), JsValue> {
let all_descendant_vdom_id = get_node_descendant_data_vdom_id(node);
for vdom_id in all_descendant_vdom_id {
if let Some(old_closure) = old_closures.get_mut(&vdom_id) {
for (event, oc) in old_closure.iter() {
if *event == event_name {
let func: &Function = oc.as_ref().unchecked_ref();
node.remove_event_listener_with_callback(event, func)?;
}
}
old_closure.retain(|(event, _oc)| *event != event_name);
if old_closure.is_empty() {
old_closures
.remove(&vdom_id)
.expect("Unable to remove old closure");
}
} else {
log::warn!(
"There is no closure marked with that vdom_id: {}",
vdom_id
);
}
}
Ok(())
}
fn apply_element_patch<DSP, MSG>(
program: Option<&DSP>,
node: &Element,
old_closures: &mut ActiveClosure,
patch: &Patch<MSG>,
) -> Result<ActiveClosure, JsValue>
where
MSG: 'static,
DSP: Clone + Dispatch<MSG> + 'static,
{
let mut active_closures = ActiveClosure::new();
match patch {
Patch::InsertNode(InsertNode {
tag: _,
node_idx: _,
node: for_insert,
}) => {
let created_node = CreatedNode::<Node>::create_dom_node_opt::<
DSP,
MSG,
>(program, &for_insert, &mut None);
let parent_node =
node.parent_node().expect("must have a parent node");
parent_node
.insert_before(&created_node.node, Some(node))
.expect("must remove target node");
Ok(active_closures)
}
Patch::AddAttributes(AddAttributes {
tag: _,
node_idx: _,
attrs,
}) => {
CreatedNode::<Node>::set_element_attributes(
program,
&mut active_closures,
node,
attrs,
);
Ok(active_closures)
}
Patch::RemoveAttributes(RemoveAttributes {
tag: _,
node_idx: _,
attrs,
}) => {
for attr in attrs.iter() {
for att_value in attr.value() {
match att_value {
AttValue::Plain(_) => {
node.remove_attribute(attr.name())?;
}
AttValue::Callback(_) => {
remove_event_listener_with_name(
attr.name(),
node,
old_closures,
)?;
}
}
}
}
Ok(active_closures)
}
Patch::ReplaceNode(ReplaceNode {
tag: _,
node_idx: _,
replacement,
}) => {
let created_node = CreatedNode::<Node>::create_dom_node_opt::<
DSP,
MSG,
>(program, replacement, &mut None);
remove_event_listeners(&node, old_closures)?;
node.replace_with_with_node_1(&created_node.node)?;
Ok(created_node.closures)
}
Patch::RemoveNode(RemoveNode {
tag: _,
node_idx: _,
}) => {
let parent_node =
node.parent_node().expect("must have a parent node");
if node.node_type() == Node::COMMENT_NODE {
} else {
parent_node
.remove_child(node)
.expect("must remove target node");
if node.node_type() != Node::TEXT_NODE {
let element: &Element = node.unchecked_ref();
remove_event_listeners(&element, old_closures)?;
}
}
Ok(active_closures)
}
Patch::AppendChildren(AppendChildren {
tag: _,
node_idx: _,
children: new_nodes,
}) => {
let parent = &node;
let mut active_closures = HashMap::new();
for new_node in new_nodes {
let created_node =
CreatedNode::<Node>::create_dom_node_opt::<DSP, MSG>(
program, &new_node, &mut None,
);
parent.append_child(&created_node.node)?;
active_closures.extend(created_node.closures);
}
Ok(active_closures)
}
Patch::ChangeText(_ct) => {
unreachable!("Elements should not receive ChangeText patches.")
}
}
}
fn apply_text_patch<DSP, MSG>(
program: Option<&DSP>,
node: &Text,
patch: &Patch<MSG>,
) -> Result<(), JsValue>
where
MSG: 'static,
DSP: Clone + Dispatch<MSG> + 'static,
{
match patch {
Patch::ChangeText(ct) => {
node.set_node_value(Some(ct.get_new()));
}
Patch::ReplaceNode(ReplaceNode {
tag: _,
node_idx: _,
replacement,
}) => {
let created_node = CreatedNode::<Node>::create_dom_node_opt::<
DSP,
MSG,
>(program, replacement, &mut None);
node.replace_with_with_node_1(&created_node.node)?;
}
_other => {
unreachable!(
"Text nodes should only receive ChangeText or Replace patches."
)
}
};
Ok(())
}