rocal_core/
traits.rs

1use std::{cell::RefCell, collections::HashMap, rc::Rc};
2use url::Url;
3use wasm_bindgen::{closure::Closure, JsCast};
4use wasm_bindgen_futures::spawn_local;
5use web_sys::{window, Document, Event, FormData, HtmlFormElement};
6
7use crate::{enums::request_method::RequestMethod, router::Router};
8
9pub type SharedRouter = Rc<RefCell<Router>>;
10
11pub trait Controller {
12    type View;
13    fn new(router: SharedRouter, view: Self::View) -> Self;
14}
15
16pub trait View {
17    fn new(router: SharedRouter) -> Self;
18}
19
20pub trait Template {
21    type Data;
22
23    fn new(router: SharedRouter) -> Self;
24    fn router(&self) -> SharedRouter;
25    fn body(&self, data: Self::Data) -> String;
26
27    fn render(&self, data: Self::Data) {
28        self.render_html(&self.body(data));
29        self.register_forms();
30    }
31
32    fn render_html(&self, html: &str) {
33        let doc = match self.get_document() {
34            Some(doc) => doc,
35            None => return,
36        };
37
38        let body = match doc.body() {
39            Some(body) => body,
40            None => return,
41        };
42
43        body.set_inner_html(html);
44    }
45
46    fn register_forms(&self) {
47        let doc = match self.get_document() {
48            Some(doc) => doc,
49            None => return,
50        };
51
52        let forms = match self.get_all_forms(&doc) {
53            Some(forms) => forms,
54            None => return,
55        };
56
57        for i in 0..forms.length() {
58            if let Some(form_node) = forms.get(i) {
59                if let Some(form) = self.reset_form(form_node) {
60                    self.attach_form_listener(&form);
61                }
62            }
63        }
64    }
65
66    fn get_document(&self) -> Option<Document> {
67        window()?.document()
68    }
69
70    fn get_all_forms(&self, doc: &Document) -> Option<web_sys::NodeList> {
71        doc.query_selector_all("form").ok()
72    }
73
74    fn reset_form(&self, form_node: web_sys::Node) -> Option<HtmlFormElement> {
75        let parent = form_node.parent_node()?;
76        let new_node = form_node.clone_node_with_deep(true).ok()?;
77        parent.replace_child(&new_node, &form_node).ok()?;
78        new_node.dyn_into::<HtmlFormElement>().ok()
79    }
80
81    fn attach_form_listener(&self, form: &HtmlFormElement) {
82        let router_for_closure = self.router().clone();
83
84        let closure = Closure::wrap(Box::new(move |e: Event| {
85            e.prevent_default();
86
87            let mut args: HashMap<String, String> = HashMap::new();
88
89            let element: HtmlFormElement = match e
90                .current_target()
91                .and_then(|t| t.dyn_into::<HtmlFormElement>().ok())
92            {
93                Some(el) => el,
94                None => return,
95            };
96
97            let form_data = match FormData::new_with_form(&element) {
98                Ok(data) => data,
99                Err(_) => return,
100            };
101
102            let entries = form_data.entries();
103
104            for entry in entries {
105                if let Ok(entry) = entry {
106                    let entry_array = js_sys::Array::from(&entry);
107                    if entry_array.length() == 2 {
108                        let key = entry_array.get(0).as_string().unwrap_or_default();
109                        let value = entry_array.get(1).as_string().unwrap_or_default();
110                        args.insert(key, value);
111                    }
112                }
113            }
114
115            if let Ok(url) = Url::parse(&element.action()) {
116                let router = router_for_closure.clone();
117                spawn_local(async move {
118                    router
119                        .borrow()
120                        .resolve(RequestMethod::Post, url.path(), Some(args))
121                        .await;
122                });
123            }
124        }) as Box<dyn FnMut(Event)>);
125
126        form.add_event_listener_with_callback("submit", closure.as_ref().unchecked_ref())
127            .expect("Failed to add submit event listeners");
128        closure.forget();
129    }
130}