sauron_core/dom/
dom_node.rs

1use std::{
2    borrow::Cow,
3    cell::{Ref, RefCell},
4    fmt,
5    rc::Rc,
6};
7
8use indexmap::IndexMap;
9use wasm_bindgen::{closure::Closure, JsCast, JsValue};
10use web_sys::{self, Node};
11
12use crate::{
13    dom::{
14        component::StatelessModel, document, dom_patch, events::MountEvent, Application, DomAttr,
15        GroupedDomAttrValues, Program, StatefulComponent, StatefulModel,
16    },
17    html::lookup,
18    vdom::{self, Attribute, Leaf, TreePath},
19};
20
21pub(crate) type EventClosure = Closure<dyn FnMut(web_sys::Event)>;
22pub type NamedEventClosures = IndexMap<&'static str, EventClosure>;
23
24/// A counter part of the vdom Node
25/// This is needed, so that we can
26/// 1. Keep track of event closure and drop them when nodes has been removed
27/// 2. Custom removal of children nodes on a stateful component
28///
29#[derive(Clone, Debug)]
30pub struct DomNode {
31    pub(crate) inner: DomInner,
32}
33
34#[derive(Clone)]
35pub enum DomInner {
36    /// a reference to an element node
37    Element {
38        /// the reference to the actual element
39        element: web_sys::Element,
40        // TODO: put all DomAttr here
41        /// the listeners of this element, which we will drop when this element is removed
42        listeners: Rc<RefCell<Option<NamedEventClosures>>>,
43        /// keeps track of the children nodes
44        /// this needs to be synced with the actual element children
45        children: Rc<RefCell<Vec<DomNode>>>,
46        /// determine if this element needs to dispatch a mount event
47        has_mount_callback: bool,
48    },
49    /// text node
50    Text(web_sys::Text),
51    Symbol(Cow<'static, str>),
52    /// comment node
53    Comment(web_sys::Comment),
54    /// Fragment node
55    Fragment {
56        ///
57        fragment: web_sys::DocumentFragment,
58        ///
59        children: Rc<RefCell<Vec<DomNode>>>,
60    },
61    /// StatefulComponent
62    StatefulComponent {
63        comp: Rc<RefCell<dyn StatefulComponent>>,
64        dom_node: Rc<DomNode>,
65    },
66}
67
68impl fmt::Debug for DomInner {
69    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
70        match self {
71            Self::Element {
72                element, children, ..
73            } => {
74                f.debug_struct("Element")
75                    .field("tag", &element.tag_name().to_lowercase())
76                    .field(
77                        "children",
78                        &children
79                            .borrow()
80                            .iter()
81                            .map(|c| c.tag())
82                            .collect::<Vec<_>>(),
83                    )
84                    .finish()?;
85                Ok(())
86            }
87            Self::Text(text_node) => f
88                .debug_tuple("Text")
89                .field(&text_node.whole_text().expect("whole text"))
90                .finish(),
91            Self::Symbol(symbol) => f.debug_tuple("Symbol").field(&symbol).finish(),
92            Self::Comment(_) => write!(f, "Comment"),
93            Self::Fragment { .. } => write!(f, "Fragment"),
94            Self::StatefulComponent { .. } => write!(f, "StatefulComponent"),
95        }
96    }
97}
98
99impl From<web_sys::Node> for DomNode {
100    fn from(node: web_sys::Node) -> Self {
101        match node.node_type() {
102            Node::ELEMENT_NODE => {
103                let element: web_sys::Element = node.unchecked_into();
104                let child_nodes = element.child_nodes();
105                let children_count = child_nodes.length();
106                let children = (0..children_count)
107                    .map(|i| {
108                        let child = child_nodes.get(i).expect("child");
109                        DomNode::from(child)
110                    })
111                    .collect();
112                DomNode {
113                    inner: DomInner::Element {
114                        element,
115                        listeners: Rc::new(RefCell::new(None)),
116                        children: Rc::new(RefCell::new(children)),
117                        has_mount_callback: false,
118                    },
119                }
120            }
121            Node::TEXT_NODE => {
122                let text_node: web_sys::Text = node.unchecked_into();
123                DomNode {
124                    inner: DomInner::Text(text_node),
125                }
126            }
127            Node::COMMENT_NODE => {
128                let comment: web_sys::Comment = node.unchecked_into();
129                DomNode {
130                    inner: DomInner::Comment(comment),
131                }
132            }
133            Node::DOCUMENT_FRAGMENT_NODE => {
134                let fragment: web_sys::DocumentFragment = node.unchecked_into();
135                DomNode {
136                    inner: DomInner::Fragment {
137                        fragment,
138                        children: Rc::new(RefCell::new(vec![])),
139                    },
140                }
141            }
142            _node_type => todo!("for: {_node_type:?}"),
143        }
144    }
145}
146
147impl PartialEq for DomNode {
148    fn eq(&self, other: &Self) -> bool {
149        match (&self.inner, &other.inner) {
150            (DomInner::Element { element: v, .. }, DomInner::Element { element: o, .. }) => v == o,
151            (DomInner::Fragment { fragment: v, .. }, DomInner::Fragment { fragment: o, .. }) => {
152                v == o
153            }
154            (DomInner::Text(v), DomInner::Text(o)) => v == o,
155            (DomInner::Symbol(v), DomInner::Symbol(o)) => v == o,
156            (DomInner::Comment(v), DomInner::Comment(o)) => v == o,
157            (DomInner::StatefulComponent { .. }, DomInner::StatefulComponent { .. }) => todo!(),
158            _ => false,
159        }
160    }
161}
162
163impl DomNode {
164    pub(crate) fn children(&self) -> Option<Ref<'_, Vec<DomNode>>> {
165        match &self.inner {
166            DomInner::Element { children, .. } => Some(children.borrow()),
167            DomInner::Fragment { children, .. } => Some(children.borrow()),
168            _ => None,
169        }
170    }
171
172    /// returns true if this an element node
173    pub fn is_element(&self) -> bool {
174        matches!(&self.inner, DomInner::Element { .. })
175    }
176
177    /// returns true if this a fragment node
178    pub fn is_fragment(&self) -> bool {
179        matches!(&self.inner, DomInner::Fragment { .. })
180    }
181
182    /// returns true if this a text node
183    pub fn is_text_node(&self) -> bool {
184        matches!(&self.inner, DomInner::Text(_))
185    }
186
187    /// returns true if this Comment node
188    pub fn is_comment(&self) -> bool {
189        matches!(&self.inner, DomInner::Comment(_))
190    }
191
192    /// returns true if this DomNode is a html entity symbol
193    pub fn is_symbol(&self) -> bool {
194        matches!(&self.inner, DomInner::Symbol(_))
195    }
196
197    /// returns true if this is a stateful component
198    pub fn is_stateful_component(&self) -> bool {
199        matches!(&self.inner, DomInner::StatefulComponent { .. })
200    }
201
202    pub(crate) fn tag(&self) -> Option<String> {
203        match &self.inner {
204            DomInner::Element { element, .. } => Some(element.tag_name().to_lowercase()),
205            _ => None,
206        }
207    }
208
209    /// exposed the underlying wrapped node as `web_sys::Node`
210    pub fn as_node(&self) -> web_sys::Node {
211        match &self.inner {
212            DomInner::Element { element, .. } => element.clone().unchecked_into(),
213            DomInner::Fragment { fragment, .. } => fragment.clone().unchecked_into(),
214            DomInner::Text(text_node) => text_node.clone().unchecked_into(),
215            DomInner::Symbol(_) => unreachable!("symbol should be handled separately"),
216            DomInner::Comment(comment_node) => comment_node.clone().unchecked_into(),
217            DomInner::StatefulComponent { dom_node, .. } => dom_node.as_node(),
218        }
219    }
220
221    /// exposed the underlying wrapped node as `web_sys::Element`
222    #[track_caller]
223    pub fn as_element(&self) -> web_sys::Element {
224        match &self.inner {
225            DomInner::Element { element, .. } => element.clone(),
226            DomInner::Fragment { fragment, .. } => {
227                let fragment: web_sys::Element = fragment.clone().unchecked_into();
228                fragment
229            }
230            DomInner::Text(text_node) => text_node.clone().unchecked_into(),
231            DomInner::Symbol(_) => unreachable!("symbol should be handled separately"),
232            DomInner::Comment(comment_node) => comment_node.clone().unchecked_into(),
233            DomInner::StatefulComponent { dom_node, .. } => dom_node.as_element(),
234        }
235    }
236
237    /// return the string content of this symbol
238    pub fn as_symbol(&self) -> Option<&str> {
239        match &self.inner {
240            DomInner::Symbol(symbol) => Some(symbol),
241            _ => None,
242        }
243    }
244
245    /// return the outer html string of an element
246    pub fn outer_html(&self) -> String {
247        let DomInner::Element { element, .. } = &self.inner else {
248            unreachable!("should only be called to element");
249        };
250        element.outer_html()
251    }
252
253    /// append the DomNode `child` into this DomNode `self`
254    pub fn append_children(&self, for_append: Vec<DomNode>) {
255        match &self.inner {
256            DomInner::Element {
257                element, children, ..
258            } => {
259                for child in for_append.into_iter() {
260                    if let Some(symbol) = child.as_symbol() {
261                        element
262                            .insert_adjacent_html(intern("beforeend"), symbol)
263                            .expect("must not error");
264                    } else {
265                        element
266                            .append_child(&child.as_node())
267                            .expect("append child");
268                        child.dispatch_mount_event();
269                    }
270                    children.borrow_mut().push(child);
271                }
272            }
273            DomInner::Fragment {
274                fragment, children, ..
275            } => {
276                for child in for_append.into_iter() {
277                    fragment
278                        .append_child(&child.as_node())
279                        .expect("append child");
280                    child.dispatch_mount_event();
281                    children.borrow_mut().push(child);
282                }
283            }
284            _ => unreachable!(
285                "appending should only be called to Element and Fragment, found: {:#?}",
286                self
287            ),
288        }
289    }
290
291    /// Insert the DomNode `for_insert` before `self` DomNode
292    pub(crate) fn insert_before(&self, target_element: &DomNode, for_insert: Vec<DomNode>) {
293        let DomInner::Element { children, .. } = &self.inner else {
294            unreachable!("parent must be an element");
295        };
296
297        let mut target_index = None;
298        for (i, child) in children.borrow().iter().enumerate() {
299            if target_element == child {
300                target_index = Some(i);
301                break;
302            }
303        }
304        // NOTE: This is not reverse since inserting the last insert_node will always be next
305        // before the target element
306        for insert_node in for_insert.iter() {
307            target_element
308                .as_element()
309                .insert_adjacent_element(intern("beforebegin"), &insert_node.as_element())
310                .expect("must insert before this element");
311            insert_node.dispatch_mount_event();
312        }
313
314        // NOTE: It is important that we reverse the insertion to the wrapper DomNode since it is
315        // just a Vec where inserting from the last will preserve the index to insert into
316        for insert_node in for_insert.into_iter().rev() {
317            if let Some(target_index) = target_index {
318                children.borrow_mut().insert(target_index, insert_node);
319            } else {
320                unreachable!("should have a self index");
321            }
322        }
323    }
324
325    /// Insert the DomNode `for_insert` after `self` DomNode
326    pub(crate) fn insert_after(&self, target_element: &DomNode, for_insert: Vec<DomNode>) {
327        let DomInner::Element { children, .. } = &self.inner else {
328            unreachable!("parent must be an element");
329        };
330        let mut target_index = None;
331        for (i, child) in children.borrow().iter().enumerate() {
332            if target_element == child {
333                target_index = Some(i);
334                break;
335            }
336        }
337        for insert_node in for_insert.into_iter().rev() {
338            target_element
339                .as_element()
340                .insert_adjacent_element(intern("afterend"), &insert_node.as_element())
341                .expect("must insert after this element");
342            insert_node.dispatch_mount_event();
343
344            if let Some(target_index) = target_index {
345                children.borrow_mut().insert(target_index + 1, insert_node);
346            } else {
347                unreachable!("should have a self index");
348            }
349        }
350    }
351
352    /// Replace the child `child` DomNode with a replacement DomNode `replacement`
353    pub(crate) fn replace_child(&self, target_child: &DomNode, replacement: DomNode) {
354        match &self.inner {
355            DomInner::Element { children, .. } => {
356                let mut child_index = None;
357                for (i, ch) in children.borrow().iter().enumerate() {
358                    if ch == target_child {
359                        child_index = Some(i);
360                        break;
361                    }
362                }
363                if let Some(child_index) = child_index {
364                    children.borrow_mut().remove(child_index);
365                    target_child
366                        .as_element()
367                        .replace_with_with_node_1(&replacement.as_node())
368                        .expect("must replace child");
369                    replacement.dispatch_mount_event();
370                    children.borrow_mut().insert(child_index, replacement);
371                } else {
372                    log::warn!(
373                        "unable to find target_child: {target_child:#?} with a replacement: {:?}",
374                        replacement
375                    );
376                    // if can not find the child, then must be the root node
377                    unreachable!("must find the child...");
378                }
379            }
380            _ => todo!(),
381        }
382    }
383
384    /// Remove the DomNode `child` from the children of `self`
385    pub(crate) fn remove_children(&self, for_remove: &[&DomNode]) {
386        match &self.inner {
387            DomInner::Element {
388                element, children, ..
389            } => {
390                let mut child_indexes = vec![];
391                for (i, ch) in children.borrow().iter().enumerate() {
392                    for remove_node in for_remove.iter() {
393                        if ch == *remove_node {
394                            child_indexes.push(i);
395                            break;
396                        }
397                    }
398                }
399                assert_eq!(child_indexes.len(), for_remove.len(), "must find all");
400
401                // NOTE: It is important to remove from the last, since
402                // vec shifts to the left, while removing from the last
403                // with the rev child index, we remove the correct child_index
404                for child_index in child_indexes.into_iter().rev() {
405                    let child = children.borrow_mut().remove(child_index);
406                    element
407                        .remove_child(&child.as_node())
408                        .expect("remove child");
409                }
410            }
411            _ => todo!(),
412        }
413    }
414
415    /// remove all the children of this element
416    pub(crate) fn clear_children(&self) {
417        match &self.inner {
418            DomInner::Element {
419                element, children, ..
420            } => {
421                children.borrow_mut().clear();
422                // NOTE: It is faster to remove from the last
423                // This is removing the children of the actual node
424                // regardless if it is mapped with the DomNode wrapper
425                while let Some(last_child) = element.last_child() {
426                    element
427                        .remove_child(&last_child)
428                        .expect("must remove child");
429                }
430            }
431            _ => todo!(),
432        }
433    }
434
435    pub(crate) fn replace_node(&self, replacement: DomNode) {
436        //NOTE: This must be replacing a mount node
437        self.as_element()
438            .replace_with_with_node_1(&replacement.as_node())
439            .expect("must replace child");
440    }
441
442    /// set the attributes of the dom element
443    pub fn set_dom_attrs(&self, attrs: impl IntoIterator<Item = DomAttr>) -> Result<(), JsValue> {
444        for attr in attrs.into_iter() {
445            self.set_dom_attr(attr)?;
446        }
447        Ok(())
448    }
449
450    /// set the attribute of the dom element
451    pub fn set_dom_attr(&self, attr: DomAttr) -> Result<(), JsValue> {
452        match &self.inner {
453            DomInner::Element {
454                element, listeners, ..
455            } => {
456                let attr_name = intern(attr.name);
457                let attr_namespace = attr.namespace;
458
459                let GroupedDomAttrValues {
460                    listeners: event_callbacks,
461                    plain_values,
462                    styles,
463                } = attr.group_values();
464
465                Self::add_event_dom_listeners(element, attr_name, &event_callbacks)
466                    .expect("event listeners");
467                let is_none = listeners.borrow().is_none();
468                if is_none {
469                    let listener_closures: IndexMap<
470                        &'static str,
471                        Closure<dyn FnMut(web_sys::Event)>,
472                    > = IndexMap::from_iter(event_callbacks.into_iter().map(|c| (attr_name, c)));
473                    *listeners.borrow_mut() = Some(listener_closures);
474                } else if let Some(listeners) = listeners.borrow_mut().as_mut() {
475                    for event_cb in event_callbacks.into_iter() {
476                        listeners.insert(attr_name, event_cb);
477                    }
478                }
479
480                DomAttr::set_element_style(element, attr_name, styles);
481                DomAttr::set_element_simple_values(
482                    element,
483                    attr_name,
484                    attr_namespace,
485                    plain_values,
486                );
487            }
488            DomInner::StatefulComponent { comp, .. } => {
489                log::info!("applying attribute change for stateful component...{attr:?}");
490                comp.borrow_mut().attribute_changed(attr);
491            }
492            _ => {
493                log::info!("set the dom attr for {self:?}, with dom_attr: {attr:?}");
494                unreachable!("should only be called for element");
495            }
496        }
497        Ok(())
498    }
499
500    pub(crate) fn remove_dom_attr(&self, attr: &DomAttr) -> Result<(), JsValue> {
501        let DomInner::Element { element, .. } = &self.inner else {
502            unreachable!("expecting an element");
503        };
504        DomAttr::remove_element_dom_attr(element, attr)
505    }
506
507    /// attach and event listener to an event target
508    pub(crate) fn add_event_dom_listeners(
509        target: &web_sys::EventTarget,
510        attr_name: &'static str,
511        event_listeners: &[EventClosure],
512    ) -> Result<(), JsValue> {
513        for event_cb in event_listeners.iter() {
514            Self::add_event_listener(target, attr_name, event_cb)?;
515        }
516        Ok(())
517    }
518
519    /// add a event listener to a target element
520    pub(crate) fn add_event_listener(
521        event_target: &web_sys::EventTarget,
522        event_name: &str,
523        listener: &EventClosure,
524    ) -> Result<(), JsValue> {
525        event_target.add_event_listener_with_callback(
526            intern(event_name),
527            listener.as_ref().unchecked_ref(),
528        )?;
529        Ok(())
530    }
531
532    /// always dispatch the mount event on stateful component
533    /// dispatch mount event to element that has on_mount callback set.
534    fn should_dispatch_mount_event(&self) -> bool {
535        match self.inner {
536            DomInner::Element {
537                has_mount_callback, ..
538            } => has_mount_callback,
539            DomInner::StatefulComponent { .. } => true,
540            _ => false,
541        }
542    }
543
544    fn dispatch_mount_event(&self) {
545        if self.should_dispatch_mount_event() {
546            let event_target: web_sys::EventTarget = self.as_element().unchecked_into();
547            event_target
548                .dispatch_event(&MountEvent::create_web_event())
549                .expect("must be ok");
550        }
551    }
552
553    #[allow(unused)]
554    pub(crate) fn find_child(&self, target_child: &DomNode, path: TreePath) -> Option<TreePath> {
555        if self == target_child {
556            Some(path)
557        } else {
558            let children = self.children()?;
559            for (i, child) in children.iter().enumerate() {
560                let child_path = path.traverse(i);
561                let found = child.find_child(target_child, child_path);
562                if found.is_some() {
563                    return found;
564                }
565            }
566            None
567        }
568    }
569
570    /// render this DomNode into an html string represenation
571    pub fn render_to_string(&self) -> String {
572        let mut buffer = String::new();
573        self.render(&mut buffer).expect("must render");
574        buffer
575    }
576
577    fn render(&self, buffer: &mut dyn fmt::Write) -> fmt::Result {
578        match &self.inner {
579            DomInner::Text(text_node) => {
580                let text = text_node.whole_text().expect("whole text");
581                write!(buffer, "{text}")?;
582                Ok(())
583            }
584            DomInner::Comment(comment) => {
585                write!(buffer, "<!--{}-->", comment.data())
586            }
587            DomInner::Symbol(symbol) => {
588                write!(buffer, "{symbol}")
589            }
590            DomInner::Element {
591                element, children, ..
592            } => {
593                let tag = element.tag_name().to_lowercase();
594                let is_self_closing = lookup::is_self_closing(&tag);
595
596                write!(buffer, "<{tag}")?;
597                let attrs = element.attributes();
598                let attrs_len = attrs.length();
599                for i in 0..attrs_len {
600                    let attr = attrs.item(i).expect("attr");
601                    write!(buffer, " {}=\"{}\"", attr.local_name(), attr.value())?;
602                }
603                if is_self_closing {
604                    write!(buffer, "/>")?;
605                } else {
606                    write!(buffer, ">")?;
607                }
608
609                for child in children.borrow().iter() {
610                    child.render(buffer)?;
611                }
612                if !is_self_closing {
613                    write!(buffer, "</{tag}>")?;
614                }
615                Ok(())
616            }
617            DomInner::Fragment { children, .. } => {
618                for child in children.borrow().iter() {
619                    child.render(buffer)?;
620                }
621                Ok(())
622            }
623            DomInner::StatefulComponent { comp: _, dom_node } => {
624                dom_node.render(buffer)?;
625                Ok(())
626            }
627        }
628    }
629}
630
631#[cfg(feature = "with-interning")]
632#[inline(always)]
633pub fn intern(s: &str) -> &str {
634    wasm_bindgen::intern(s)
635}
636
637#[cfg(not(feature = "with-interning"))]
638#[inline(always)]
639pub fn intern(s: &str) -> &str {
640    s
641}
642
643impl<APP> Program<APP>
644where
645    APP: Application + 'static,
646{
647    /// Create a dom node
648    pub fn create_dom_node(&self, node: &vdom::Node<APP::MSG>) -> DomNode {
649        let ev_callback = self.create_ev_callback();
650        create_dom_node(node, ev_callback)
651    }
652
653    pub(crate) fn create_ev_callback(&self) -> impl Fn(APP::MSG) + Clone {
654        let program = self.downgrade();
655        let ev_callback = move |msg| {
656            let mut program = program.upgrade().expect("must upgrade");
657            program.dispatch(msg);
658        };
659        ev_callback
660    }
661}
662
663/// Create a dom node
664pub fn create_dom_node<Msg, F>(node: &vdom::Node<Msg>, ev_callback: F) -> DomNode
665where
666    Msg: 'static,
667    F: Fn(Msg) + 'static + Clone,
668{
669    match node {
670        vdom::Node::Element(elm) => create_element_node(elm, ev_callback),
671        vdom::Node::Leaf(leaf) => create_leaf_node(leaf, ev_callback),
672    }
673}
674
675fn create_element_node<Msg, F>(elm: &vdom::Element<Msg>, ev_callback: F) -> DomNode
676where
677    Msg: 'static,
678    F: Fn(Msg) + 'static + Clone,
679{
680    let document = document();
681    let element = if let Some(namespace) = elm.namespace() {
682        document
683            .create_element_ns(Some(intern(namespace)), intern(elm.tag()))
684            .expect("Unable to create element")
685    } else {
686        document
687            .create_element(intern(elm.tag()))
688            .expect("create element")
689    };
690    // TODO: dispatch the mount event recursively after the dom node is mounted into
691    // the root node
692    let attrs = Attribute::merge_attributes_of_same_name(elm.attributes().iter());
693
694    let dom_node = DomNode {
695        inner: DomInner::Element {
696            element,
697            listeners: Rc::new(RefCell::new(None)),
698            children: Rc::new(RefCell::new(vec![])),
699            has_mount_callback: elm.has_mount_callback(),
700        },
701    };
702    let dom_attrs = attrs
703        .iter()
704        .map(|a| dom_patch::convert_attr(a, ev_callback.clone()));
705    dom_node.set_dom_attrs(dom_attrs).expect("set dom attrs");
706    let children: Vec<DomNode> = elm
707        .children()
708        .iter()
709        .map(|child| create_dom_node(child, ev_callback.clone()))
710        .collect();
711    dom_node.append_children(children);
712    dom_node
713}
714
715fn create_leaf_node<Msg, F>(leaf: &vdom::Leaf<Msg>, ev_callback: F) -> DomNode
716where
717    Msg: 'static,
718    F: Fn(Msg) + 'static + Clone,
719{
720    match leaf {
721        Leaf::Text(txt) => DomNode {
722            inner: DomInner::Text(document().create_text_node(txt)),
723        },
724        Leaf::Symbol(symbol) => DomNode {
725            inner: DomInner::Symbol(symbol.clone()),
726        },
727        Leaf::Comment(comment) => DomNode {
728            inner: DomInner::Comment(document().create_comment(comment)),
729        },
730        Leaf::Fragment(nodes) => create_fragment_node(nodes, ev_callback),
731
732        // NodeList that goes here is only possible when it is the root_node,
733        // since node_list as children will be unrolled into as child_elements of the parent
734        // We need to wrap this node_list into doc_fragment since root_node is only 1 element
735        Leaf::NodeList(nodes) => create_fragment_node(nodes, ev_callback),
736
737        Leaf::StatefulComponent(comp) => {
738            //TODO: also put the children and attributes here
739            DomNode {
740                inner: DomInner::StatefulComponent {
741                    comp: Rc::clone(&comp.comp),
742                    dom_node: Rc::new(create_stateful_component(comp, ev_callback)),
743                },
744            }
745        }
746        Leaf::StatelessComponent(comp) => create_stateless_component(comp, ev_callback),
747
748        Leaf::TemplatedView(view) => {
749            unreachable!("template view should not be created: {:#?}", view)
750        }
751        Leaf::DocType(_) => unreachable!("doc type is never converted"),
752    }
753}
754
755fn create_fragment_node<'a, Msg, F>(
756    nodes: impl IntoIterator<Item = &'a vdom::Node<Msg>>,
757    ev_callback: F,
758) -> DomNode
759where
760    Msg: 'static,
761    F: Fn(Msg) + 'static + Clone,
762{
763    let fragment = document().create_document_fragment();
764    let dom_node = DomNode {
765        inner: DomInner::Fragment {
766            fragment,
767            children: Rc::new(RefCell::new(vec![])),
768        },
769    };
770    let children = nodes
771        .into_iter()
772        .map(|node| create_dom_node(node, ev_callback.clone()))
773        .collect();
774    dom_node.append_children(children);
775    dom_node
776}
777
778/// render the underlying real dom node into string
779pub fn render_real_dom_to_string(node: &web_sys::Node) -> String {
780    let mut f = String::new();
781    render_real_dom(node, &mut f).expect("must not error");
782    f
783}
784
785/// render the underlying real dom structure
786pub fn render_real_dom(node: &web_sys::Node, buffer: &mut dyn fmt::Write) -> fmt::Result {
787    match node.node_type() {
788        Node::TEXT_NODE => {
789            let text_node: &web_sys::Text = node.unchecked_ref();
790            let text = text_node.whole_text().expect("whole text");
791            write!(buffer, "{text}")?;
792            Ok(())
793        }
794        Node::COMMENT_NODE => {
795            let comment: &web_sys::Comment = node.unchecked_ref();
796            write!(buffer, "<!--{}-->", comment.data())
797        }
798        Node::ELEMENT_NODE => {
799            let element: &web_sys::Element = node.unchecked_ref();
800            let tag = element.tag_name().to_lowercase();
801            let is_self_closing = lookup::is_self_closing(&tag);
802
803            write!(buffer, "<{tag}")?;
804            let attrs = element.attributes();
805            let attrs_len = attrs.length();
806            for i in 0..attrs_len {
807                let attr = attrs.item(i).expect("attr");
808                write!(buffer, " {}=\"{}\"", attr.local_name(), attr.value())?;
809            }
810            if is_self_closing {
811                write!(buffer, "/>")?;
812            } else {
813                write!(buffer, ">")?;
814            }
815
816            let child_nodes = element.child_nodes();
817            let child_count = child_nodes.length();
818            for i in 0..child_count {
819                let child = child_nodes.get(i).unwrap();
820                render_real_dom(&child, buffer)?;
821            }
822            if !is_self_closing {
823                write!(buffer, "</{tag}>")?;
824            }
825            Ok(())
826        }
827        _ => todo!("for other else"),
828    }
829}
830
831#[allow(unused)]
832pub(crate) fn create_stateless_component<Msg, F>(
833    comp: &StatelessModel<Msg>,
834    ev_callback: F,
835) -> DomNode
836where
837    Msg: 'static,
838    F: Fn(Msg) + 'static + Clone,
839{
840    let comp_view = &comp.view;
841    let real_comp_view = comp_view.unwrap_template_ref();
842    create_dom_node(real_comp_view, ev_callback)
843}
844
845/// TODO: register the template if not yet
846/// pass a program to leaf component and mount itself and its view to the program
847/// There are 2 types of children components of Stateful Component
848/// - Internal children
849/// - External children
850/// Internal children is managed by the Stateful Component
851/// while external children are managed by the top level program.
852/// The external children can be diffed, and send the patches to the StatefulComponent
853///   - The TreePath of the children starts at the external children location
854/// The attributes affects the Stateful component state.
855/// The attributes can be diff and send the patches to the StatefulComponent
856///  - Changes to the attributes will call on attribute_changed of the StatefulComponent
857fn create_stateful_component<Msg, F>(comp: &StatefulModel<Msg>, ev_callback: F) -> DomNode
858where
859    Msg: 'static,
860    F: Fn(Msg) + 'static + Clone,
861{
862    let comp_node = create_dom_node(
863        &crate::html::div(
864            [crate::html::attributes::class("component")]
865                .into_iter()
866                .chain(comp.attrs.clone()),
867            [],
868        ),
869        ev_callback.clone(),
870    );
871
872    let dom_attrs: Vec<DomAttr> = comp
873        .attrs
874        .iter()
875        .map(|a| dom_patch::convert_attr(a, ev_callback.clone()))
876        .collect();
877
878    for dom_attr in dom_attrs.into_iter() {
879        log::info!("calling attribute changed..");
880        comp.comp.borrow_mut().attribute_changed(dom_attr);
881    }
882
883    // the component children is manually appended to the StatefulComponent
884    // here to allow the conversion of dom nodes with its event
885    // listener and removing the generics msg
886    let created_children = comp
887        .children
888        .iter()
889        .map(|child| create_dom_node(child, ev_callback.clone()))
890        .collect();
891    comp.comp.borrow_mut().append_children(created_children);
892    comp_node
893}