Skip to content

Commit

Permalink
refactor: unify setting of dom_attr into 1 function, allow the statef…
Browse files Browse the repository at this point in the history
…ul component handle its own patching with its attributes by calling the attribute_changed method
  • Loading branch information
ivanceras committed Apr 19, 2024
1 parent 0e3df5d commit 8416eb6
Show file tree
Hide file tree
Showing 6 changed files with 38 additions and 104 deletions.
88 changes: 12 additions & 76 deletions crates/core/src/dom/dom_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use std::cell::RefCell;
use std::fmt;
use std::rc::Rc;
use wasm_bindgen::{closure::Closure, JsCast, JsValue};
use web_sys::{self, Element, Node};
use web_sys::{self, Node};

pub(crate) type EventClosure = Closure<dyn FnMut(web_sys::Event)>;
pub type NamedEventClosures = IndexMap<&'static str, EventClosure>;
Expand Down Expand Up @@ -524,7 +524,14 @@ impl DomNode {
plain_values,
);
}
_ => unreachable!("should only be called for element"),
DomInner::StatefulComponent{comp,.. } => {
log::info!("applying attribute change for stateful component...{attr:?}");
comp.borrow_mut().attribute_changed(attr);
}
_ => {
log::info!("set the dom attr for {self:?}, with dom_attr: {attr:?}");
unreachable!("should only be called for element");
}
}
Ok(())
}
Expand Down Expand Up @@ -707,22 +714,17 @@ where
// the root node
let attrs = Attribute::merge_attributes_of_same_name(elm.attributes().iter());

let listeners = self.set_element_dom_attrs(
&element,
attrs
.iter()
.map(|a| self.convert_attr(a))
.collect::<Vec<_>>(),
);
let dom_node = DomNode {
inner: DomInner::Element {
element,
listeners: Rc::new(RefCell::new(listeners)),
listeners: Rc::new(RefCell::new(None)),
children: Rc::new(RefCell::new(vec![])),
has_mount_callback: elm.has_mount_callback(),
},
parent: parent_node,
};
let dom_attrs = attrs.iter().map(|a|self.convert_attr(a));
dom_node.set_dom_attrs(dom_attrs).expect("set dom attrs");
let dom_node_rc = Rc::new(Some(dom_node.clone()));
let children: Vec<DomNode> = elm
.children()
Expand Down Expand Up @@ -870,72 +872,6 @@ where
self.create_dom_node(parent_node, real_comp_view)
}

/// set element with the dom attrs
pub(crate) fn set_element_dom_attrs(
&self,
element: &Element,
attrs: Vec<DomAttr>,
) -> Option<NamedEventClosures> {
attrs
.into_iter()
.filter_map(|att| self.set_element_dom_attr(element, att))
.reduce(|mut acc, e| {
e.into_iter().for_each(|(k, v)| {
acc.insert(k, v);
});
acc
})
}

fn set_element_dom_attr(&self, element: &Element, attr: DomAttr) -> Option<NamedEventClosures> {
let attr_name = intern(attr.name);
let attr_namespace = attr.namespace;

let GroupedDomAttrValues {
listeners,
plain_values,
styles,
} = attr.group_values();

DomAttr::set_element_style(element, attr_name, styles);
DomAttr::set_element_simple_values(element, attr_name, attr_namespace, plain_values);
self.add_event_listeners(element, attr_name, &listeners)
.unwrap();
if !listeners.is_empty() {
let event_closures =
IndexMap::from_iter(listeners.into_iter().map(|cb| (attr_name, cb)));
Some(event_closures)
} else {
None
}
}

pub(crate) fn add_event_listeners(
&self,
event_target: &web_sys::EventTarget,
event_name: &str,
listeners: &[EventClosure],
) -> Result<(), JsValue> {
for listener in listeners.iter() {
self.add_event_listener(event_target, event_name, listener)
.unwrap();
}
Ok(())
}

/// add a event listener to a target element
pub(crate) fn add_event_listener(
&self,
event_target: &web_sys::EventTarget,
event_name: &str,
listener: &Closure<dyn FnMut(web_sys::Event)>,
) -> Result<(), JsValue> {
event_target.add_event_listener_with_callback(
intern(event_name),
listener.as_ref().unchecked_ref(),
)?;
Ok(())
}
}

/// render the underlying real dom node into string
Expand Down
9 changes: 3 additions & 6 deletions crates/core/src/dom/dom_patch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,17 +80,14 @@ pub enum PatchVariant {
impl DomNode {
pub(crate) fn find_node(&self, path: &mut TreePath) -> Option<DomNode> {
match &self.inner {
DomInner::StatefulComponent { comp, .. } => {
DomInner::StatefulComponent {.. } => {
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)
// just return self and handle its own patches
Some(self.clone())
}
_ => {
if path.is_empty() {
Expand Down
3 changes: 3 additions & 0 deletions crates/core/src/vdom/attribute/attribute_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ impl<MSG> PartialEq for AttributeValue<MSG> {
(AttributeValue::EventListener(this), AttributeValue::EventListener(other)) => {
this == other
}
(AttributeValue::ComponentEventListener(this), AttributeValue::ComponentEventListener(other)) => {
this == other
}
(AttributeValue::Empty, AttributeValue::Empty) => true,
(_, _) => false,
}
Expand Down
13 changes: 2 additions & 11 deletions crates/core/src/vdom/attribute/callback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,19 +113,10 @@ impl<IN, OUT> Clone for Callback<IN, OUT> {
}
}

/// This is the best approximation of comparison whether 2 callbacks are equal.
///
/// There is no 100% guarante that this is true since we can not compare 2 closures event if they
/// have the same logic.
///
/// This is done by comparing the type_id of the input and type_id of the output.
///
/// Compare if the callbacks are equal
/// Note, we are only comparing the type_id of the function, the input and the output
impl<IN, OUT> PartialEq for Callback<IN, OUT> {
fn eq(&self, other: &Self) -> bool {
// NOTE: We are not comparing the func field since it will always
// be false when closures are generated from the view.
// We want the callback comparison to return true as much as possible
// so as to not keep adding the callback to the event listeners
self.event_type_id == other.event_type_id
&& self.msg_type_id == other.msg_type_id
&& self.func_type_id == other.func_type_id
Expand Down
25 changes: 16 additions & 9 deletions crates/core/src/vdom/diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,15 @@ pub fn diff_recursive<'a, MSG>(
patches.extend(patch);
}
(Leaf::StatefulComponent(old_comp), Leaf::StatefulComponent(new_comp)) => {
let attr_patches = create_attribute_patches(&"component", &old_comp.attrs, &new_comp.attrs, path);
if !attr_patches.is_empty(){
log::info!("stateful component attr_patches: {attr_patches:#?}");
}
patches.extend(attr_patches);
let patch = diff_nodes(None, &old_comp.children, &new_comp.children, path);
if !patch.is_empty(){
log::info!("stateful component patch: {patch:#?}");
}
patches.extend(patch);
}
(Leaf::TemplatedView(_old_view), _) => {
Expand All @@ -219,7 +227,7 @@ pub fn diff_recursive<'a, MSG>(
};

if !skip_attributes {
let attr_patches = create_attribute_patches(old_element, new_element, path);
let attr_patches = create_attribute_patches(old_element.tag(), old_element.attributes(), new_element.attributes(), path);
patches.extend(attr_patches);
}

Expand Down Expand Up @@ -326,8 +334,9 @@ fn diff_non_keyed_nodes<'a, MSG>(
/// - merging attributes of the same name
#[allow(clippy::type_complexity)]
fn create_attribute_patches<'a, MSG>(
old_element: &'a Element<MSG>,
new_element: &'a Element<MSG>,
old_tag: &'a Tag,
old_attributes: &'a [Attribute<MSG>],
new_attributes: &'a [Attribute<MSG>],
path: &SkipPath,
) -> Vec<Patch<'a, MSG>> {
let skip_indices = if let Some(skip_diff) = &path.skip_diff {
Expand All @@ -342,8 +351,6 @@ fn create_attribute_patches<'a, MSG>(

let has_skip_indices = !skip_indices.is_empty();

let new_attributes = new_element.attributes();
let old_attributes = old_element.attributes();

let mut patches = vec![];

Expand All @@ -355,8 +362,8 @@ fn create_attribute_patches<'a, MSG>(
let mut add_attributes: Vec<&Attribute<MSG>> = vec![];
let mut remove_attributes: Vec<&Attribute<MSG>> = vec![];

let new_attributes_grouped = new_element.group_indexed_attributes_per_name();
let old_attributes_grouped = old_element.group_indexed_attributes_per_name();
let new_attributes_grouped = Element::group_indexed_attributes_per_name(new_attributes);
let old_attributes_grouped = Element::group_indexed_attributes_per_name(old_attributes);

// for all new elements that doesn't exist in the old elements
// or the values differ
Expand Down Expand Up @@ -412,14 +419,14 @@ fn create_attribute_patches<'a, MSG>(

if !add_attributes.is_empty() {
patches.push(Patch::add_attributes(
&old_element.tag,
old_tag,
path.path.clone(),
add_attributes,
));
}
if !remove_attributes.is_empty() {
patches.push(Patch::remove_attributes(
&old_element.tag,
old_tag,
path.path.clone(),
remove_attributes,
));
Expand Down
4 changes: 2 additions & 2 deletions crates/core/src/vdom/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,11 +191,11 @@ impl<MSG> Element<MSG> {
/// grouped the attributes, but retain the index of the attribute
/// relative to its location in the element
pub fn group_indexed_attributes_per_name<'a>(
&'a self,
attrs: &'a [Attribute<MSG>],
) -> IndexMap<&'a AttributeName, Vec<(usize, &'a Attribute<MSG>)>> {
let mut grouped: IndexMap<&'a AttributeName, Vec<(usize, &'a Attribute<MSG>)>> =
IndexMap::new();
for (i, attr) in self.attributes().iter().enumerate() {
for (i, attr) in attrs.iter().enumerate() {
if let Some(existing) = grouped.get_mut(&attr.name) {
existing.push((i, attr));
} else {
Expand Down

0 comments on commit 8416eb6

Please sign in to comment.