1use itertools::{EitherOrBoth, Itertools};
2use std::collections::{HashMap, HashSet};
3use web_sys::{Element, Node};
4
5use crate::{events::EventHandler, virtual_dom::dom};
6
7use super::VNode;
8
9#[derive(Debug)]
11pub struct VElement {
12 pub(crate) tag_name: String,
13 pub(crate) attr: HashMap<String, String>,
14 pub(crate) event_handlers: Vec<EventHandler>,
15 pub(crate) key: Option<String>,
16 pub(crate) children: Vec<VNode>,
17
18 pub(crate) dom: Option<Element>,
19}
20
21impl VElement {
22 pub fn new(
36 tag_name: String,
37 attr: HashMap<String, String>,
38 event_handlers: Vec<EventHandler>,
39 key: Option<String>,
40 children: Vec<VNode>,
41 ) -> VElement {
42 VElement {
43 tag_name,
44 attr,
45 event_handlers,
46 key,
47 children,
48 dom: None,
49 }
50 }
51
52 pub(crate) fn patch(&mut self, last: Option<VNode>, ancestor: &Node) {
53 let mut old_virt: Option<VElement> = None;
54 self.dom = None;
55
56 match last {
57 None => {}
58 Some(VNode::Element(mut velement)) => {
59 if velement
60 .dom
61 .as_ref()
62 .is_some_and(|x| x.parent_node().is_some_and(|y| y.eq(ancestor)))
63 {
64 self.dom = velement.dom.take();
65 old_virt = Some(velement);
66 }
67 }
68 Some(VNode::Text(v)) => {
69 v.erase();
70 }
71 Some(VNode::Component(v)) => {
72 v.erase();
73 }
74 Some(VNode::List(v)) => {
75 v.erase();
76 }
77 }
78
79 self.render(old_virt.as_ref(), ancestor);
80 self.handle_children(old_virt);
81 self.check_if_parents_match(ancestor);
82 }
83
84 pub(crate) fn erase(&self) {
85 if let Some(el) = &self.dom {
86 dom::remove_node(el);
87 }
88 }
89
90 pub(crate) fn set_depth(&mut self, depth: u32) {
91 for child in self.children.iter_mut() {
92 child.set_depth(depth);
93 }
94 }
95}
96
97impl VElement {
98 fn render(&mut self, last: Option<&VElement>, ancestor: &Node) {
99 match last {
100 Some(last) if last.key.is_some() && last.key == self.key => {
102 dom::append_child(ancestor, self.dom.as_ref().unwrap());
103 }
104
105 Some(last) if last.tag_name == self.tag_name => {
106 let target = self
107 .dom
108 .as_mut()
109 .expect("Target dom object not created before rendering element");
110 for (key, val) in self.attr.iter() {
112 dom::set_attribute(target, key, val);
113 }
114 for (key, _val) in last.attr.iter() {
115 if !self.attr.contains_key(key) {
116 dom::remove_attribute(target, key);
117 }
118 }
119
120 for event_handler in &mut self.event_handlers {
121 event_handler.attach(target);
122 }
123 }
124 _ => {
125 let el = dom::create_element(&self.tag_name);
128
129 for (name, value) in self.attr.iter() {
131 dom::set_attribute(&el, name, value);
132 }
133
134 for event_handler in &mut self.event_handlers {
135 event_handler.attach(&el);
136 }
137
138 match &self.dom {
139 Some(old_child) => dom::replace_child(ancestor, old_child, &el),
140 None => dom::append_child(ancestor, &el),
141 };
142 self.dom = Some(el);
143 }
144 }
145 }
146
147 fn handle_children(&mut self, old_element: Option<VElement>) {
148 let target = self.dom.as_mut().unwrap();
149 let old_children = old_element.map_or(Vec::new(), |e| e.children.into_iter().collect());
150
151 for either_child_or_both in self.children.iter_mut().zip_longest(old_children) {
152 match either_child_or_both {
153 EitherOrBoth::Both(child, old_child) => {
154 child.patch(Some(old_child), target);
155 }
156 EitherOrBoth::Left(child) => {
157 child.patch(None, target);
158 }
159 EitherOrBoth::Right(old_child) => {
160 old_child.erase();
162 }
163 }
164 }
165 }
166
167 fn check_if_parents_match(&mut self, ancestor: &Node) {
168 let parent_node = self.dom.as_ref().unwrap().parent_node().unwrap();
170 if !parent_node.eq(ancestor) {
171 let dom_ref = self.dom.as_ref().unwrap();
172 dom::remove_child(&parent_node, dom_ref);
173 dom::append_child(ancestor, dom_ref);
174 }
175 }
176}
177
178impl PartialEq for VElement {
179 fn eq(&self, other: &Self) -> bool {
180 let self_event_handlers: HashSet<_> = self.event_handlers.iter().collect();
181 let other_event_handlers: HashSet<_> = other.event_handlers.iter().collect();
182
183 self.tag_name == other.tag_name
184 && self.attr == other.attr
185 && self.children == other.children
186 && self.dom == other.dom
187 && self_event_handlers == other_event_handlers
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use wasm_bindgen_test::wasm_bindgen_test;
194
195 use crate::{
196 component::{behavior::Behavior, Component},
197 virtual_dom::{dom, VComponent, VList, VNode, VText},
198 };
199
200 use super::VElement;
201 wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
202
203 macro_rules! function_name {
204 () => {{
205 fn f() {}
206 fn type_name_of<T>(_: T) -> &'static str {
207 std::any::type_name::<T>()
208 }
209 let name = type_name_of(f);
210 name.strip_suffix("::f").unwrap()
211 }};
212 }
213
214 const VALID_TEXT: &str = "";
215
216 #[wasm_bindgen_test]
217 fn patch_last_none() {
218 let ancestor = dom::create_element("div");
219 dom::set_attribute(&ancestor, "id", function_name!());
220 dom::append_child(&dom::get_root_element(), &ancestor);
221
222 let mut target = VElement::new(
223 "div".into(),
224 [("id".into(), VALID_TEXT.into())].into(),
225 vec![],
226 None,
227 vec![],
228 );
229 target.patch(None, &ancestor);
230 }
231
232 #[wasm_bindgen_test]
233 fn patch_last_text() {
234 let ancestor = dom::create_element("div");
235 dom::set_attribute(&ancestor, "id", function_name!());
236
237 let current = dom::create_text_node("I dont love Rust");
238 dom::append_child(&ancestor, ¤t);
239
240 dom::append_child(&dom::get_root_element(), &ancestor);
241
242 let text = VNode::Text(VText {
243 text: "I dont love Rust".into(),
244 dom: Some(current),
245 });
246
247 let mut target = VElement::new(
248 "div".into(),
249 [("id".into(), VALID_TEXT.into())].into(),
250 vec![],
251 None,
252 vec![],
253 );
254 target.patch(Some(text), &ancestor);
255 }
256
257 #[wasm_bindgen_test]
258 fn patch_last_elem_different_key() {
259 let ancestor = dom::create_element("div");
260 dom::set_attribute(&ancestor, "id", function_name!());
261
262 let current = dom::create_element("div");
263 dom::set_attribute(¤t, "id", "I dont love Rust");
264 dom::append_child(&ancestor, ¤t);
265
266 dom::append_child(&dom::get_root_element(), &ancestor);
267
268 let elem = VNode::Element(VElement {
269 tag_name: "div".into(),
270 attr: [("id".into(), "I dont love Rust".into())].into(),
271 event_handlers: vec![],
272 key: None,
273 children: vec![],
274 dom: Some(current),
275 });
276
277 let mut target = VElement::new(
278 "div".into(),
279 [("id".into(), VALID_TEXT.into())].into(),
280 vec![],
281 Some("Different_key".to_string()),
282 vec![],
283 );
284 target.patch(Some(elem), &ancestor);
285 }
286
287 #[wasm_bindgen_test]
288 fn patch_last_elem_same_key() {
289 let ancestor = dom::create_element("div");
290 dom::set_attribute(&ancestor, "id", function_name!());
291
292 let current = dom::create_element("div");
293 dom::set_attribute(¤t, "id", "I dont love Rust");
294 dom::append_child(&ancestor, ¤t);
295
296 dom::append_child(&dom::get_root_element(), &ancestor);
297
298 let key = Some("Nice".to_string());
299
300 let elem = VNode::Element(VElement {
301 tag_name: "div".into(),
302 attr: [("id".into(), "I dont love Rust".into())].into(),
303 event_handlers: vec![],
304 key: key.clone(),
305 children: vec![],
306 dom: Some(current),
307 });
308
309 let mut target = VElement::new(
310 "div".into(),
311 [("id".into(), VALID_TEXT.into())].into(),
312 vec![],
313 key,
314 vec![],
315 );
316 target.patch(Some(elem), &ancestor);
317 }
318
319 struct Comp;
320 impl Component for Comp {
321 type Message = ();
322 type Properties = ();
323
324 fn new(_props: Self::Properties) -> Self {
325 Comp
326 }
327 fn view(&self, _behavior: &mut impl Behavior<Self>) -> VNode {
328 VText::new("I dont love Rust").into()
329 }
330 fn update(&mut self, _message: Self::Message) -> bool {
331 false
332 }
333 }
334
335 #[wasm_bindgen_test]
336 fn patch_last_comp() {
337 let ancestor = dom::create_element("div");
338 dom::set_attribute(&ancestor, "id", function_name!());
339 dom::append_child(&dom::get_root_element(), &ancestor);
340
341 let mut comp = VNode::Component(VComponent::new::<Comp>((), None));
342 comp.set_depth(0);
343 comp.patch(None, &ancestor);
344
345 let mut target = VElement::new(
346 "div".into(),
347 [("id".into(), VALID_TEXT.into())].into(),
348 vec![],
349 None,
350 vec![],
351 );
352 target.patch(Some(comp), &ancestor);
353 }
354
355 #[wasm_bindgen_test]
356 fn patch_last_list() {
357 let ancestor = dom::create_element("div");
358 dom::set_attribute(&ancestor, "id", function_name!());
359 dom::append_child(&dom::get_root_element(), &ancestor);
360
361 let mut list = VNode::List(VList::new(
362 vec![VText::new("I dont love Rust").into()],
363 None,
364 ));
365 list.set_depth(0);
366 list.patch(None, &ancestor);
367
368 let mut target = VElement::new(
369 "div".into(),
370 [("id".into(), VALID_TEXT.into())].into(),
371 vec![],
372 None,
373 vec![],
374 );
375 target.patch(Some(list), &ancestor);
376 }
377}