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
23pub 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}