vertigo/dom/
dom_element.rs

1use std::rc::Rc;
2
3use crate::{
4    computed::{
5        struct_mut::{VecDequeMut, VecMut},
6        DropResource,
7    },
8    dev::JsJsonListDecoder,
9    driver_module::{api::api_callbacks, get_driver_dom, StaticString},
10    AttrGroupValue, Computed, DomText, DropFileItem, JsJson,
11};
12
13use super::{
14    attr_value::{AttrValue, CssAttrValue},
15    callback::{Callback, Callback1},
16    dom_element_class::DomElementClassMerge,
17    dom_element_ref::DomElementRef,
18    dom_id::DomId,
19    dom_node::DomNode,
20    events::{ClickEvent, DropFileEvent, KeyDownEvent},
21};
22
23/// A Real DOM representative - element kind
24pub struct DomElement {
25    id_dom: DomId,
26    child_node: VecDequeMut<DomNode>,
27    subscriptions: VecMut<DropResource>,
28    class_manager: DomElementClassMerge,
29}
30
31impl DomElement {
32    pub fn new(name: impl Into<StaticString>) -> Self {
33        let name = name.into();
34        let id_dom = DomId::from_name(name.as_str());
35
36        get_driver_dom().create_node(id_dom, name);
37
38        let class_manager = DomElementClassMerge::new(id_dom);
39
40        Self {
41            id_dom,
42            child_node: VecDequeMut::new(),
43            subscriptions: VecMut::new(),
44            class_manager,
45        }
46    }
47
48    pub fn add_attr(&self, name: impl Into<StaticString>, value: impl Into<AttrValue>) {
49        let name = name.into();
50        let value = value.into();
51
52        match value {
53            AttrValue::String(value) => {
54                self.class_manager.set_attr_value(name, Some(value));
55            }
56            AttrValue::Computed(computed) => {
57                let class_manager = self.class_manager.clone();
58
59                self.subscribe(computed, move |value| {
60                    class_manager.set_attr_value(name.clone(), Some(Rc::new(value)));
61                });
62            }
63            AttrValue::ComputedOpt(computed) => {
64                let class_manager = self.class_manager.clone();
65
66                self.subscribe(computed, move |value| {
67                    class_manager.set_attr_value(name.clone(), value.map(Rc::new));
68                });
69            }
70            AttrValue::Value(value) => {
71                let class_manager = self.class_manager.clone();
72
73                self.subscribe(value.to_computed(), move |value| {
74                    class_manager.set_attr_value(name.clone(), Some(Rc::new(value)));
75                });
76            }
77            AttrValue::ValueOpt(value) => {
78                let class_manager = self.class_manager.clone();
79
80                self.subscribe(value.to_computed(), move |value| {
81                    class_manager.set_attr_value(name.clone(), value.map(Rc::new));
82                });
83            }
84        };
85    }
86
87    pub fn add_attr_group(
88        mut self,
89        values: impl IntoIterator<Item = (impl Into<String>, impl Into<AttrGroupValue>)>,
90    ) -> Self {
91        for (key, value) in values.into_iter() {
92            self = self.add_attr_group_item(key.into(), value.into());
93        }
94        self
95    }
96
97    pub fn add_attr_group_item(self, key: String, value: AttrGroupValue) -> Self {
98        match (key.as_str(), value) {
99            ("css", AttrGroupValue::Css { css, class_name }) => {
100                self.css_with_class_name(css, class_name)
101            }
102            ("hook_key_down", AttrGroupValue::HookKeyDown(on_hook_key_down)) => {
103                self.hook_key_down_rc(on_hook_key_down)
104            }
105            ("on_blur", AttrGroupValue::OnBlur(on_blur)) => self.on_blur_rc(on_blur),
106            ("on_change", AttrGroupValue::OnChange(on_change)) => self.on_change_rc(on_change),
107            ("on_click", AttrGroupValue::OnClick(on_click)) => self.on_click_rc(on_click),
108            ("on_dropfile", AttrGroupValue::OnDropfile(on_dropfile)) => {
109                self.on_dropfile_rc(on_dropfile)
110            }
111            ("on_input", AttrGroupValue::OnInput(on_input)) => self.on_input_rc(on_input),
112            ("on_key_down", AttrGroupValue::OnKeyDown(on_key_down)) => {
113                self.on_key_down_rc(on_key_down)
114            }
115            ("on_load", AttrGroupValue::OnLoad(on_load)) => self.on_load_rc(on_load),
116            ("on_mouse_down", AttrGroupValue::OnMouseDown(on_mouse_down)) => {
117                self.on_mouse_down_rc(on_mouse_down)
118            }
119            ("on_mouse_enter", AttrGroupValue::OnMouseEnter(on_mouse_enter)) => {
120                self.on_mouse_enter_rc(on_mouse_enter)
121            }
122            ("on_mouse_leave", AttrGroupValue::OnMouseLeave(on_mouse_leave)) => {
123                self.on_mouse_leave_rc(on_mouse_leave)
124            }
125            ("on_mouse_up", AttrGroupValue::OnMouseUp(on_mouse_up)) => {
126                self.on_mouse_up_rc(on_mouse_up)
127            }
128            ("on_submit", AttrGroupValue::OnSubmit(on_submit))
129            | ("form", AttrGroupValue::OnSubmit(on_submit)) => self.on_submit_rc(on_submit),
130            (_, AttrGroupValue::AttrValue(value)) => self.attr(key, value),
131            (_, _) => {
132                crate::log::error!("Invalid attribute type for key {key}");
133                self
134            }
135        }
136    }
137
138    pub fn add_child(&self, child_node: impl Into<DomNode>) {
139        let child_node = child_node.into();
140
141        let child_id = child_node.id_dom();
142        get_driver_dom().insert_before(self.id_dom, child_id, None);
143
144        self.child_node.push(child_node);
145    }
146
147    pub fn add_child_text(&self, text: impl Into<String>) {
148        let text = text.into();
149        self.add_child(DomNode::Text {
150            node: DomText::new(text),
151        });
152    }
153
154    pub fn attr(self, name: impl Into<StaticString>, value: impl Into<AttrValue>) -> Self {
155        self.add_attr(name, value);
156        self
157    }
158
159    pub fn attrs<T: Into<AttrValue>>(self, attrs: Vec<(impl Into<StaticString>, T)>) -> Self {
160        for (name, value) in attrs.into_iter() {
161            self.add_attr(name, value)
162        }
163        self
164    }
165
166    pub fn child(self, child_node: impl Into<DomNode>) -> Self {
167        self.add_child(child_node);
168        self
169    }
170
171    pub fn child_text(self, text: impl Into<String>) -> Self {
172        self.add_child_text(text);
173        self
174    }
175
176    pub fn children<C: Into<DomNode>>(self, children: Vec<C>) -> Self {
177        for child_node in children.into_iter() {
178            self.add_child(child_node)
179        }
180        self
181    }
182
183    pub fn css(self, css: impl Into<CssAttrValue>) -> Self {
184        self.css_with_class_name(css, None)
185    }
186
187    pub fn css_with_class_name(
188        self,
189        css: impl Into<CssAttrValue>,
190        debug_class_name: Option<String>,
191    ) -> Self {
192        let css = css.into();
193
194        match css {
195            CssAttrValue::Css(css) => {
196                self.class_manager.set_css(css, debug_class_name);
197            }
198            CssAttrValue::Computed(css) => {
199                let class_manager = self.class_manager.clone();
200
201                self.subscribe(css, move |css| {
202                    class_manager.set_css(css, debug_class_name.clone());
203                });
204            }
205        }
206        self
207    }
208
209    pub fn get_ref(&self) -> DomElementRef {
210        DomElementRef::new(self.id_dom)
211    }
212
213    #[cfg(test)]
214    pub fn get_children(&self) -> &VecDequeMut<DomNode> {
215        &self.child_node
216    }
217
218    pub fn hook_key_down(self, on_hook_key_down: impl Into<Callback1<KeyDownEvent, bool>>) -> Self {
219        self.hook_key_down_rc(Rc::new(on_hook_key_down.into()))
220    }
221
222    pub fn hook_key_down_rc(self, on_hook_key_down: Rc<Callback1<KeyDownEvent, bool>>) -> Self {
223        let on_hook_key_down = self.install_callback1(on_hook_key_down);
224
225        self.add_event_listener("hook_keydown", move |data| match get_key_down_event(data) {
226            Ok(event) => {
227                let prevent_default = on_hook_key_down(event);
228
229                match prevent_default {
230                    true => JsJson::True,
231                    false => JsJson::False,
232                }
233            }
234            Err(error) => {
235                log::error!("export_websocket_callback_message -> params decode error -> {error}");
236                JsJson::False
237            }
238        })
239    }
240
241    pub fn id_dom(&self) -> DomId {
242        self.id_dom
243    }
244
245    pub fn on_blur(self, on_blur: impl Into<Callback<()>>) -> Self {
246        self.on_blur_rc(Rc::new(on_blur.into()))
247    }
248
249    pub fn on_blur_rc(self, on_blur: Rc<Callback<()>>) -> Self {
250        let on_blur = self.install_callback(on_blur);
251
252        self.add_event_listener("blur", move |_data| {
253            on_blur();
254            JsJson::Null
255        })
256    }
257
258    pub fn on_change(self, on_change: impl Into<Callback1<String, ()>>) -> Self {
259        self.on_change_rc(Rc::new(on_change.into()))
260    }
261
262    pub fn on_change_rc(self, on_change: Rc<Callback1<String, ()>>) -> Self {
263        let on_change = self.install_callback1(on_change);
264
265        self.add_event_listener("change", move |data| {
266            if let JsJson::String(text) = data {
267                on_change(text);
268            } else {
269                log::error!("Invalid data: on_change: {data:?}");
270            }
271
272            JsJson::Null
273        })
274    }
275
276    pub fn on_click(self, on_click: impl Into<Callback1<ClickEvent, ()>>) -> Self {
277        self.on_click_rc(Rc::new(on_click.into()))
278    }
279
280    pub fn on_click_rc(self, on_click: Rc<Callback1<ClickEvent, ()>>) -> Self {
281        let on_click = self.install_callback1(on_click);
282
283        self.add_event_listener("click", move |_data| {
284            let click_event = ClickEvent::default();
285            on_click(click_event.clone());
286            JsJson::from(click_event)
287        })
288    }
289
290    pub fn on_dropfile(self, on_dropfile: impl Into<Callback1<DropFileEvent, ()>>) -> Self {
291        self.on_dropfile_rc(Rc::new(on_dropfile.into()))
292    }
293
294    pub fn on_dropfile_rc(self, on_dropfile: Rc<Callback1<DropFileEvent, ()>>) -> Self {
295        let on_dropfile = self.install_callback1(on_dropfile);
296
297        self.add_event_listener("drop", move |data| {
298            let params = data.map_list(|mut params: JsJsonListDecoder| {
299                let files = params.get_vec("drop file", |item: JsJson| {
300                    item.map_list(|mut item: JsJsonListDecoder| {
301                        let name = item.get_string("name")?;
302                        let data = item.get_buffer("data")?;
303
304                        Ok(DropFileItem::new(name, data))
305                    })
306                })?;
307
308                Ok(DropFileEvent::new(files))
309            });
310
311            match params {
312                Ok(params) => {
313                    on_dropfile(params);
314                }
315                Err(error) => {
316                    log::error!("on_dropfile -> params decode error -> {error}");
317                }
318            };
319
320            JsJson::Null
321        })
322    }
323
324    pub fn on_input(self, on_input: impl Into<Callback1<String, ()>>) -> Self {
325        self.on_input_rc(Rc::new(on_input.into()))
326    }
327
328    pub fn on_input_rc(self, on_input: Rc<Callback1<String, ()>>) -> Self {
329        let on_input = self.install_callback1(on_input);
330
331        self.add_event_listener("input", move |data| {
332            if let JsJson::String(text) = data {
333                on_input(text);
334            } else {
335                log::error!("Invalid data: on_input: {data:?}");
336            }
337
338            JsJson::Null
339        })
340    }
341
342    pub fn on_key_down(self, on_key_down: impl Into<Callback1<KeyDownEvent, bool>>) -> Self {
343        self.on_key_down_rc(Rc::new(on_key_down.into()))
344    }
345
346    pub fn on_key_down_rc(self, on_key_down: Rc<Callback1<KeyDownEvent, bool>>) -> Self {
347        let on_key_down = self.install_callback1(on_key_down);
348
349        self.add_event_listener("keydown", move |data| match get_key_down_event(data) {
350            Ok(event) => {
351                let prevent_default = on_key_down(event);
352
353                match prevent_default {
354                    true => JsJson::True,
355                    false => JsJson::False,
356                }
357            }
358            Err(error) => {
359                log::error!("export_websocket_callback_message -> params decode error -> {error}");
360                JsJson::False
361            }
362        })
363    }
364
365    pub fn on_load(self, on_load: impl Into<Callback<()>>) -> Self {
366        self.on_load_rc(Rc::new(on_load.into()))
367    }
368
369    pub fn on_load_rc(self, on_load: Rc<Callback<()>>) -> Self {
370        let on_load = self.install_callback(on_load);
371
372        self.add_event_listener("load", move |_data| {
373            on_load();
374            JsJson::Null
375        })
376    }
377
378    pub fn on_mouse_down(self, on_mouse_down: impl Into<Callback<bool>>) -> Self {
379        self.on_mouse_down_rc(Rc::new(on_mouse_down.into()))
380    }
381
382    pub fn on_mouse_down_rc(self, on_mouse_down: Rc<Callback<bool>>) -> Self {
383        let on_mouse_down = self.install_callback(on_mouse_down);
384
385        self.add_event_listener("mousedown", move |_data| {
386            if on_mouse_down() {
387                JsJson::True
388            } else {
389                JsJson::False
390            }
391        })
392    }
393
394    pub fn on_mouse_enter(self, on_mouse_enter: impl Into<Callback<()>>) -> Self {
395        self.on_mouse_enter_rc(Rc::new(on_mouse_enter.into()))
396    }
397
398    pub fn on_mouse_enter_rc(self, on_mouse_enter: Rc<Callback<()>>) -> Self {
399        let on_mouse_enter = self.install_callback(on_mouse_enter);
400
401        self.add_event_listener("mouseenter", move |_data| {
402            on_mouse_enter();
403            JsJson::Null
404        })
405    }
406
407    pub fn on_mouse_leave(self, on_mouse_leave: impl Into<Callback<()>>) -> Self {
408        self.on_mouse_leave_rc(Rc::new(on_mouse_leave.into()))
409    }
410
411    pub fn on_mouse_leave_rc(self, on_mouse_leave: Rc<Callback<()>>) -> Self {
412        let on_mouse_leave = self.install_callback(on_mouse_leave);
413
414        self.add_event_listener("mouseleave", move |_data| {
415            on_mouse_leave();
416            JsJson::Null
417        })
418    }
419
420    pub fn on_mouse_up(self, on_mouse_up: impl Into<Callback<bool>>) -> Self {
421        self.on_mouse_up_rc(Rc::new(on_mouse_up.into()))
422    }
423
424    pub fn on_mouse_up_rc(self, on_mouse_up: Rc<Callback<bool>>) -> Self {
425        let on_mouse_up = self.install_callback(on_mouse_up);
426
427        self.add_event_listener("mouseup", move |_data| {
428            if on_mouse_up() {
429                JsJson::True
430            } else {
431                JsJson::False
432            }
433        })
434    }
435
436    pub fn on_submit(self, on_submit: impl Into<Callback<()>>) -> Self {
437        self.on_submit_rc(Rc::new(on_submit.into()))
438    }
439
440    pub fn on_submit_rc(self, on_submit: Rc<Callback<()>>) -> Self {
441        let on_submit = self.install_callback(on_submit);
442
443        self.add_event_listener("submit", move |_data| {
444            on_submit();
445            JsJson::Null
446        })
447    }
448
449    fn subscribe<T: Clone + PartialEq + 'static>(
450        &self,
451        value: Computed<T>,
452        call: impl Fn(T) + 'static,
453    ) {
454        let client = value.subscribe(call);
455        self.subscriptions.push(client);
456    }
457
458    fn add_event_listener(
459        self,
460        name: &'static str,
461        callback: impl Fn(JsJson) -> JsJson + 'static,
462    ) -> Self {
463        let (callback_id, drop) = api_callbacks().register(callback);
464
465        let drop_event = DropResource::new(move || {
466            get_driver_dom().callback_remove(self.id_dom, name, callback_id);
467            drop.off();
468        });
469
470        get_driver_dom().callback_add(self.id_dom, name, callback_id);
471        self.subscriptions.push(drop_event);
472        self
473    }
474
475    fn install_callback<R: 'static>(
476        &self,
477        callback: Rc<Callback<R>>,
478    ) -> Rc<dyn Fn() -> R + 'static> {
479        let (callback, drop) = callback.subscribe();
480        if let Some(drop) = drop {
481            self.subscriptions.push(drop);
482        }
483        callback
484    }
485
486    fn install_callback1<T: 'static, R: 'static>(
487        &self,
488        callback: Rc<Callback1<T, R>>,
489    ) -> Rc<dyn Fn(T) -> R + 'static> {
490        let (callback, drop) = callback.subscribe();
491        if let Some(drop) = drop {
492            self.subscriptions.push(drop);
493        }
494        callback
495    }
496}
497
498impl Drop for DomElement {
499    fn drop(&mut self) {
500        get_driver_dom().remove_node(self.id_dom);
501    }
502}
503
504fn get_key_down_event(data: JsJson) -> Result<KeyDownEvent, String> {
505    data.map_list(|mut params: JsJsonListDecoder| {
506        let key = params.get_string("key")?;
507        let code = params.get_string("code")?;
508        let alt_key = params.get_bool("altKey")?;
509        let ctrl_key = params.get_bool("ctrlKey")?;
510        let shift_key = params.get_bool("shiftKey")?;
511        let meta_key = params.get_bool("metaKey")?;
512        params.expect_no_more()?;
513
514        Ok(KeyDownEvent {
515            key,
516            code,
517            alt_key,
518            ctrl_key,
519            shift_key,
520            meta_key,
521        })
522    })
523}