use crate::dom;
use crate::dom::dom_node;
use crate::dom::dom_node::DomInner;
use crate::dom::DomAttr;
use crate::dom::DomAttrValue;
use crate::dom::DomNode;
use crate::dom::{Application, Program};
use crate::vdom::ComponentEventCallback;
use crate::vdom::EventCallback;
use crate::vdom::TreePath;
use crate::vdom::{Attribute, AttributeValue, Patch, PatchType};
use indexmap::IndexMap;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::JsValue;
#[derive(Debug)]
pub struct DomPatch {
pub patch_path: TreePath,
pub target_element: DomNode,
pub target_parent: DomNode,
pub patch_variant: PatchVariant,
}
#[derive(Debug)]
pub enum PatchVariant {
InsertBeforeNode {
nodes: Vec<DomNode>,
},
InsertAfterNode {
nodes: Vec<DomNode>,
},
AppendChildren {
children: Vec<DomNode>,
},
AddAttributes {
attrs: Vec<DomAttr>,
},
RemoveAttributes {
attrs: Vec<DomAttr>,
},
ReplaceNode {
replacement: Vec<DomNode>,
},
RemoveNode,
ClearChildren,
MoveBeforeNode {
for_moving: Vec<DomNode>,
},
MoveAfterNode {
for_moving: Vec<DomNode>,
},
}
impl DomNode {
pub(crate) fn find_node(&self, path: &mut TreePath) -> Option<DomNode> {
match &self.inner {
DomInner::StatefulComponent { .. } => {
log::info!(
"This is a stateful component, should return the element
inside relative to the child container at this path: {:?}",
path
);
Some(self.clone())
}
_ => {
if path.is_empty() {
Some(self.clone())
} else {
let idx = path.remove_first();
if let Some(children) = self.children() {
if let Some(child) = children.get(idx) {
child.find_node(path)
} else {
log::warn!("There is no child at index: {idx}");
None
}
} else {
log::warn!("Traversing to a childless node..");
None
}
}
}
}
}
pub(crate) fn find_all_nodes(
&self,
nodes_to_find: &[(&TreePath, Option<&&'static str>)],
) -> IndexMap<TreePath, (DomNode, DomNode)> {
let mut nodes_to_patch = IndexMap::with_capacity(nodes_to_find.len());
for (path, tag) in nodes_to_find {
let mut traverse_path: TreePath = (*path).clone();
if let Some(found) = self.find_node(&mut traverse_path) {
let mut parent_path = path.backtrack();
let target_parent = self
.find_node(&mut parent_path)
.expect("must find the parent");
nodes_to_patch.insert((*path).clone(), (found, target_parent));
} else {
log::warn!(
"can not find: {:?} {:?} target_node: {:?}",
path,
tag,
&self
);
log::info!(
"real entire dom: {:#?}",
dom_node::render_real_dom_to_string(&self.as_node())
);
log::warn!("entire dom: {}", self.render_to_string());
}
}
nodes_to_patch
}
}
impl<APP> Program<APP>
where
APP: Application + 'static,
{
pub(crate) fn convert_attr(&self, attr: &Attribute<APP::MSG>) -> DomAttr {
DomAttr {
namespace: attr.namespace,
name: attr.name,
value: attr
.value
.iter()
.filter_map(|v| self.convert_attr_value(v))
.collect(),
}
}
fn convert_attr_value(&self, attr_value: &AttributeValue<APP::MSG>) -> Option<DomAttrValue> {
match attr_value {
AttributeValue::Simple(v) => Some(DomAttrValue::Simple(v.clone())),
AttributeValue::Style(v) => Some(DomAttrValue::Style(v.clone())),
AttributeValue::EventListener(v) => {
Some(DomAttrValue::EventListener(self.convert_event_listener(v)))
}
AttributeValue::ComponentEventListener(v) => Some(DomAttrValue::EventListener(
self.convert_component_event_listener(v),
)),
AttributeValue::Empty => None,
}
}
fn convert_event_listener(
&self,
event_listener: &EventCallback<APP::MSG>,
) -> Closure<dyn FnMut(web_sys::Event)> {
let program = self.downgrade();
let event_listener = event_listener.clone();
let closure: Closure<dyn FnMut(web_sys::Event)> =
Closure::new(move |event: web_sys::Event| {
let msg = event_listener.emit(dom::Event::from(event));
let mut program = program.upgrade().expect("must upgrade");
program.dispatch(msg);
});
closure
}
fn convert_component_event_listener(
&self,
component_callback: &ComponentEventCallback,
) -> Closure<dyn FnMut(web_sys::Event)> {
let component_callback = component_callback.clone();
let closure: Closure<dyn FnMut(web_sys::Event)> =
Closure::new(move |event: web_sys::Event| {
component_callback.emit(dom::Event::from(event));
});
closure
}
pub(crate) fn convert_patches(
&self,
target_node: &DomNode,
patches: &[Patch<APP::MSG>],
) -> Result<Vec<DomPatch>, JsValue> {
let nodes_to_find: Vec<(&TreePath, Option<&&'static str>)> = patches
.iter()
.map(|patch| (patch.path(), patch.tag()))
.chain(
patches
.iter()
.flat_map(|patch| patch.node_paths())
.map(|path| (path, None)),
)
.collect();
let nodes_lookup = target_node.find_all_nodes(&nodes_to_find);
let dom_patches:Vec<DomPatch> = patches.iter().map(|patch|{
let patch_path = patch.path();
let patch_tag = patch.tag();
if let Some((target_node, target_parent)) = nodes_lookup.get(patch_path) {
let target_tag = target_node.tag();
if let (Some(patch_tag), Some(target_tag)) = (patch_tag, target_tag) {
if **patch_tag != target_tag{
panic!(
"expecting a tag: {patch_tag:?}, but found: {target_tag:?}"
);
}
}
self.convert_patch(&nodes_lookup, target_node, target_parent, patch)
} else {
unreachable!("Getting here means we didn't find the element of next node that we are supposed to patch, patch_path: {:?}, with tag: {:?}", patch_path, patch_tag);
}
}).collect();
Ok(dom_patches)
}
pub fn convert_patch(
&self,
nodes_lookup: &IndexMap<TreePath, (DomNode, DomNode)>,
target_element: &DomNode,
target_parent: &DomNode,
patch: &Patch<APP::MSG>,
) -> DomPatch {
let target_element = target_element.clone();
let target_parent = target_parent.clone();
let Patch {
patch_path,
patch_type,
..
} = patch;
let patch_path = patch_path.clone();
match patch_type {
PatchType::InsertBeforeNode { nodes } => {
let nodes = nodes
.iter()
.map(|for_insert| self.create_dom_node(for_insert))
.collect();
DomPatch {
patch_path,
target_element,
target_parent,
patch_variant: PatchVariant::InsertBeforeNode { nodes },
}
}
PatchType::InsertAfterNode { nodes } => {
let nodes = nodes
.iter()
.map(|for_insert| self.create_dom_node(for_insert))
.collect();
DomPatch {
patch_path,
target_element,
target_parent,
patch_variant: PatchVariant::InsertAfterNode { nodes },
}
}
PatchType::AddAttributes { attrs } => {
let attrs = Attribute::merge_attributes_of_same_name(attrs.iter().copied());
DomPatch {
patch_path,
target_element,
target_parent,
patch_variant: PatchVariant::AddAttributes {
attrs: attrs.iter().map(|a| self.convert_attr(a)).collect(),
},
}
}
PatchType::RemoveAttributes { attrs } => DomPatch {
patch_path,
target_element,
target_parent,
patch_variant: PatchVariant::RemoveAttributes {
attrs: attrs.iter().map(|a| self.convert_attr(a)).collect(),
},
},
PatchType::ReplaceNode { replacement } => {
let replacement = replacement
.iter()
.map(|node| self.create_dom_node(node))
.collect();
DomPatch {
patch_path,
target_element,
target_parent,
patch_variant: PatchVariant::ReplaceNode { replacement },
}
}
PatchType::RemoveNode => DomPatch {
patch_path,
target_element,
target_parent,
patch_variant: PatchVariant::RemoveNode,
},
PatchType::ClearChildren => DomPatch {
patch_path,
target_element,
target_parent,
patch_variant: PatchVariant::ClearChildren,
},
PatchType::MoveBeforeNode { nodes_path } => {
let for_moving = nodes_path
.iter()
.map(|path| {
let (node, _) = nodes_lookup.get(path).expect("must have found the node");
node.clone()
})
.collect();
DomPatch {
patch_path,
target_element,
target_parent,
patch_variant: PatchVariant::MoveBeforeNode { for_moving },
}
}
PatchType::MoveAfterNode { nodes_path } => {
let for_moving = nodes_path
.iter()
.map(|path| {
let (node, _) = nodes_lookup.get(path).expect("must have found the node");
node.clone()
})
.collect();
DomPatch {
patch_path,
target_element,
target_parent,
patch_variant: PatchVariant::MoveAfterNode { for_moving },
}
}
PatchType::AppendChildren { children } => {
let children = children
.iter()
.map(|for_insert| self.create_dom_node(for_insert))
.collect();
DomPatch {
patch_path,
target_element,
target_parent,
patch_variant: PatchVariant::AppendChildren { children },
}
}
}
}
pub(crate) fn apply_dom_patches(
&self,
dom_patches: impl IntoIterator<Item = DomPatch>,
) -> Result<(), JsValue> {
for dom_patch in dom_patches {
self.apply_dom_patch(dom_patch)?;
}
Ok(())
}
pub(crate) fn apply_dom_patch(&self, dom_patch: DomPatch) -> Result<(), JsValue> {
let DomPatch {
patch_path,
target_element,
target_parent,
patch_variant,
} = dom_patch;
match patch_variant {
PatchVariant::InsertBeforeNode { nodes } => {
target_parent.insert_before(&target_element, nodes);
}
PatchVariant::InsertAfterNode { nodes } => {
target_parent.insert_after(&target_element, nodes);
}
PatchVariant::AppendChildren { children } => {
target_element.append_children(children);
}
PatchVariant::AddAttributes { attrs } => {
target_element.set_dom_attrs(attrs).unwrap();
}
PatchVariant::RemoveAttributes { attrs } => {
for attr in attrs.iter() {
for att_value in attr.value.iter() {
match att_value {
DomAttrValue::Simple(_) => {
target_element.remove_dom_attr(attr)?;
}
DomAttrValue::EventListener(_) => {
let DomInner::Element { listeners, .. } = &target_element.inner
else {
unreachable!("must be an element");
};
if let Some(listener) = listeners.borrow_mut().as_mut() {
listener.retain(|event, _| *event != attr.name)
}
}
DomAttrValue::Style(_) => {
target_element.remove_dom_attr(attr)?;
}
DomAttrValue::Empty => (),
}
}
}
}
PatchVariant::ReplaceNode { mut replacement } => {
let first_node = replacement.remove(0);
if target_element.is_fragment() {
assert!(
patch_path.is_empty(),
"this should only happen to root node"
);
let mut mount_node = self.mount_node.borrow_mut();
let mount_node = mount_node.as_mut().expect("must have a mount node");
mount_node.append_children(vec![first_node.clone()]);
mount_node.append_children(replacement);
} else {
if patch_path.path.is_empty() {
let mut mount_node = self.mount_node.borrow_mut();
let mount_node = mount_node.as_mut().expect("must have a mount node");
mount_node.replace_child(&target_element, first_node.clone());
} else {
target_parent.replace_child(&target_element, first_node.clone());
}
target_parent.insert_after(&first_node, replacement);
}
if patch_path.path.is_empty() {
*self.root_node.borrow_mut() = Some(first_node);
}
}
PatchVariant::RemoveNode => {
target_parent.remove_children(&[&target_element]);
}
PatchVariant::ClearChildren => {
target_element.clear_children();
}
PatchVariant::MoveBeforeNode { for_moving } => {
target_parent.remove_children(&for_moving.iter().collect::<Vec<_>>());
target_parent.insert_before(&target_element, for_moving);
}
PatchVariant::MoveAfterNode { for_moving } => {
target_parent.remove_children(&for_moving.iter().collect::<Vec<_>>());
target_parent.insert_after(&target_element, for_moving);
}
}
Ok(())
}
}