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 std::rc::Rc;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::JsValue;
#[derive(Debug)]
pub struct DomPatch {
pub patch_path: TreePath,
pub target_element: 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 { comp, .. } => {
log::info!(
"This is a stateful component, should return the element
inside relative to the child container at this path: {:?}",
path
);
let child_container = comp
.borrow()
.child_container()
.expect("stateful component should provide the child container");
child_container.find_node(path)
}
_ => {
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> {
let mut nodes_to_patch: IndexMap<TreePath, DomNode> =
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) {
nodes_to_patch.insert((*path).clone(), found);
} 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) = 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, 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>,
target_element: &DomNode,
patch: &Patch<APP::MSG>,
) -> DomPatch {
let target_element = target_element.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(Rc::new(None), for_insert))
.collect();
DomPatch {
patch_path,
target_element,
patch_variant: PatchVariant::InsertBeforeNode { nodes },
}
}
PatchType::InsertAfterNode { nodes } => {
let nodes = nodes
.iter()
.map(|for_insert| self.create_dom_node(Rc::new(None), for_insert))
.collect();
DomPatch {
patch_path,
target_element,
patch_variant: PatchVariant::InsertAfterNode { nodes },
}
}
PatchType::AddAttributes { attrs } => {
let attrs = Attribute::merge_attributes_of_same_name(attrs.iter().copied());
DomPatch {
patch_path,
target_element,
patch_variant: PatchVariant::AddAttributes {
attrs: attrs.iter().map(|a| self.convert_attr(a)).collect(),
},
}
}
PatchType::RemoveAttributes { attrs } => DomPatch {
patch_path,
target_element,
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(Rc::new(None), node))
.collect();
DomPatch {
patch_path,
target_element,
patch_variant: PatchVariant::ReplaceNode { replacement },
}
}
PatchType::RemoveNode => DomPatch {
patch_path,
target_element,
patch_variant: PatchVariant::RemoveNode,
},
PatchType::ClearChildren => DomPatch {
patch_path,
target_element,
patch_variant: PatchVariant::ClearChildren,
},
PatchType::MoveBeforeNode { nodes_path } => {
let for_moving = nodes_path
.iter()
.map(|path| {
nodes_lookup
.get(path)
.expect("must have found the node")
.clone()
})
.collect();
DomPatch {
patch_path,
target_element,
patch_variant: PatchVariant::MoveBeforeNode { for_moving },
}
}
PatchType::MoveAfterNode { nodes_path } => {
let for_moving = nodes_path
.iter()
.map(|path| {
nodes_lookup
.get(path)
.expect("must have found the node")
.clone()
})
.collect();
DomPatch {
patch_path,
target_element,
patch_variant: PatchVariant::MoveAfterNode { for_moving },
}
}
PatchType::AppendChildren { children } => {
let children = children
.iter()
.map(|for_insert| self.create_dom_node(Rc::new(None), for_insert))
.collect();
DomPatch {
patch_path,
target_element,
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,
patch_variant,
} = dom_patch;
match patch_variant {
PatchVariant::InsertBeforeNode { nodes } => {
target_element.insert_before(nodes);
}
PatchVariant::InsertAfterNode { nodes } => {
target_element.insert_after(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 mut first_node = replacement.remove(0);
let parent_node = if patch_path.path.is_empty() {
let mount_node = self.mount_node.borrow();
let mount_node = mount_node.as_ref().expect("must have a mount node");
Rc::new(Some(mount_node.clone()))
} else if let Some(parent_target) = target_element.parent.as_ref() {
Rc::new(Some(parent_target.clone()))
} else {
unreachable!("target element should have a parent");
};
first_node.parent = Rc::clone(&parent_node);
for replace_node in replacement.iter_mut() {
replace_node.parent = Rc::clone(&parent_node);
}
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_element.replace_node(first_node.clone());
}
first_node.insert_after(replacement);
}
if patch_path.path.is_empty() {
*self.root_node.borrow_mut() = Some(first_node);
}
}
PatchVariant::RemoveNode => {
target_element.remove_node();
}
PatchVariant::ClearChildren => {
target_element.clear_children();
}
PatchVariant::MoveBeforeNode { for_moving } => {
if let Some(target_parent) = target_element.parent.as_ref() {
target_parent.remove_children(&for_moving.iter().collect::<Vec<_>>());
target_element.insert_before(for_moving);
} else {
panic!("unable to get the parent node of the target element");
}
}
PatchVariant::MoveAfterNode { for_moving } => {
if let Some(target_parent) = target_element.parent.as_ref() {
target_parent.remove_children(&for_moving.iter().collect::<Vec<_>>());
target_element.insert_after(for_moving);
}
}
}
Ok(())
}
}