sauron_core/dom/component/
stateful_component.rs

1use crate::{
2    dom::{
3        events::on_component_mount, program::MountProcedure, Application, Cmd, Component, DomAttr,
4        DomAttrValue, DomNode, Program,
5    },
6    vdom::{Attribute, AttributeName, Leaf, Node},
7};
8use std::{any::TypeId, cell::RefCell, fmt, rc::Rc};
9
10/// A component that can be used directly in the view without mapping
11pub trait StatefulComponent {
12    /// This will be invoked when a component is used as a custom element
13    /// and the attributes of the custom-element has been modified
14    ///
15    /// if the listed attributes in the observed attributes are modified
16    fn attribute_changed(&mut self, attr: DomAttr);
17    /// remove the attribute with this name
18    fn remove_attribute(&mut self, _attr_name: AttributeName) {}
19
20    /// return the DomNode which contains the children DomNode
21    fn child_container(&self) -> Option<DomNode>;
22
23    /// append a child into this component
24    fn append_children(&mut self, _children: Vec<DomNode>) {}
25
26    /// remove a child in this index
27    fn remove_child(&mut self, _index: usize) {}
28
29    /// the component is attached to the dom
30    fn connected_callback(&mut self) {}
31    /// the component is removed from the DOM
32    fn disconnected_callback(&mut self) {}
33
34    /// the component is moved or attached to the dom
35    fn adopted_callback(&mut self) {}
36}
37
38/// Wrapper for stateful component
39pub struct StatefulModel<MSG> {
40    ///
41    pub comp: Rc<RefCell<dyn StatefulComponent>>,
42    /// component type id
43    pub type_id: TypeId,
44    /// component attributes
45    pub attrs: Vec<Attribute<MSG>>,
46    /// external children component
47    pub children: Vec<Node<MSG>>,
48}
49
50impl<MSG> fmt::Debug for StatefulModel<MSG> {
51    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
52        write!(f, "StatefulModel")
53    }
54}
55
56impl<MSG> StatefulModel<MSG> {
57    /// mape the msg of this Leaf such that `Leaf<MSG>` becomes `Leaf<MSG2>`
58    pub fn map_msg<F, MSG2>(self, cb: F) -> StatefulModel<MSG2>
59    where
60        F: Fn(MSG) -> MSG2 + Clone + 'static,
61        MSG2: 'static,
62        MSG: 'static,
63    {
64        StatefulModel {
65            type_id: self.type_id,
66            comp: self.comp,
67            attrs: self
68                .attrs
69                .into_iter()
70                .map(|a| a.map_msg(cb.clone()))
71                .collect(),
72            children: self
73                .children
74                .into_iter()
75                .map(|c| c.map_msg(cb.clone()))
76                .collect(),
77        }
78    }
79}
80
81impl<MSG> Clone for StatefulModel<MSG> {
82    fn clone(&self) -> Self {
83        Self {
84            comp: Rc::clone(&self.comp),
85            type_id: self.type_id,
86            attrs: self.attrs.clone(),
87            children: self.children.clone(),
88        }
89    }
90}
91
92impl<MSG> PartialEq for StatefulModel<MSG> {
93    fn eq(&self, other: &Self) -> bool {
94        Rc::ptr_eq(&self.comp, &other.comp)
95            && self.type_id == other.type_id
96            && self.attrs == other.attrs
97            && self.children == other.children
98    }
99}
100
101impl<COMP> Application for COMP
102where
103    COMP: Component<XMSG = ()> + StatefulComponent + 'static,
104{
105    type MSG = COMP::MSG;
106
107    fn init(&mut self) -> Cmd<Self::MSG> {
108        Cmd::from(<Self as Component>::init(self))
109    }
110
111    fn update(&mut self, msg: COMP::MSG) -> Cmd<Self::MSG> {
112        let effects = <Self as Component>::update(self, msg);
113        Cmd::from(effects)
114    }
115
116    fn view(&self) -> Node<Self::MSG> {
117        Component::view(self)
118    }
119
120    fn stylesheet() -> Vec<String> {
121        <Self as Component>::stylesheet()
122    }
123
124    fn style(&self) -> Vec<String> {
125        <Self as Component>::style(self)
126    }
127}
128
129/// create a stateful component node
130pub fn stateful_component<COMP, MSG, MSG2>(
131    app: COMP,
132    attrs: impl IntoIterator<Item = Attribute<MSG>>,
133    children: impl IntoIterator<Item = Node<MSG>>,
134) -> Node<MSG>
135where
136    COMP: Component<MSG = MSG2, XMSG = ()> + StatefulComponent + Application<MSG = MSG2> + 'static,
137    MSG: 'static,
138    MSG2: 'static,
139{
140    let type_id = TypeId::of::<COMP>();
141    let attrs = attrs.into_iter().collect::<Vec<_>>();
142
143    let app = Rc::new(RefCell::new(app));
144
145    let mut program = Program::from_rc_app(Rc::clone(&app));
146    let children: Vec<Node<MSG>> = children.into_iter().collect();
147    let mount_event = on_component_mount(move |me| {
148        program.mount(
149            &me.target_node.as_node(),
150            //MountProcedure::append_to_shadow(),
151            MountProcedure::append(),
152        );
153        let stylesheet = <COMP as Component>::stylesheet().join("\n");
154        log::info!("stylesheet: {}", stylesheet);
155        program.inject_style_to_mount(&stylesheet);
156        program.inject_style_to_mount(&program.app_context.dynamic_style());
157        program.update_dom().expect("update dom");
158    });
159    Node::Leaf(Leaf::StatefulComponent(StatefulModel {
160        comp: app,
161        type_id,
162        attrs: attrs.into_iter().chain([mount_event]).collect(),
163        children: children.into_iter().collect(),
164    }))
165}
166
167#[cfg(feature = "with-dom")]
168impl From<wasm_bindgen::JsValue> for DomAttrValue {
169    fn from(val: wasm_bindgen::JsValue) -> Self {
170        if val.is_null() {
171            DomAttrValue::Empty
172        } else if let Some(v) = val.as_bool() {
173            DomAttrValue::Simple(v.into())
174        } else if let Some(v) = val.as_f64() {
175            DomAttrValue::Simple(v.into())
176        } else if let Some(v) = val.as_string() {
177            DomAttrValue::Simple(v.into())
178        } else {
179            todo!("for: {:?}", val)
180        }
181    }
182}