Skip to main content

rust_fel/
render.rs

1use crate::element::Element;
2use crate::props::ClosureProp;
3use wasm_bindgen::prelude::*;
4use wasm_bindgen::JsCast;
5use web_sys::{HtmlElement, Node};
6
7/// Recursively builds a DOM tree from a Virtual DOM [rust_fel::Element](../element/struct.Element.html).
8///
9/// # Arguments
10///
11/// * `rust_fel_element` - A [rust_fel::Element](../element/struct.Element.html)
12/// * `container` - A reference to a [web_sys::Node](https://docs.rs/web-sys/0.3.21/web_sys/struct.Node.html)
13/// * `is_update` - A boolean allowing the function to differentiate between first mount of the application and subsequent updates.
14#[doc(hidden)]
15pub fn render(rust_fel_element: Element, container: &Node, is_update: bool) {
16    let window = web_sys::window().expect("no global `window` exists");
17    let document = window.document().expect("should have a document on window");
18
19    if rust_fel_element.html_type == "TEXT_ELEMENT" {
20        match rust_fel_element.props.text {
21            Some(text) => {
22                container
23                    .append_child(&document.create_text_node(&text))
24                    .expect("couldn't append text node");
25            }
26            None => (),
27        };
28    } else {
29        let dom_el = document
30            .create_element(&rust_fel_element.html_type)
31            .unwrap();
32
33        match rust_fel_element.props.text {
34            Some(text) => {
35                dom_el
36                    .append_child(&document.create_text_node(&text))
37                    .expect("couldn't append text node");
38            }
39            None => (),
40        };
41
42        match rust_fel_element.props.class_name {
43            Some(class_name) => {
44                dom_el.set_class_name(&class_name);
45            }
46            None => (),
47        }
48
49        match rust_fel_element.props.href {
50            Some(href) => {
51                dom_el
52                    .set_attribute("href", &href)
53                    .expect("could not set href");
54            }
55            None => (),
56        }
57
58        match rust_fel_element.props.src {
59            Some(src) => {
60                dom_el
61                    .set_attribute("src", &src)
62                    .expect("could not set src");
63            }
64            None => (),
65        }
66
67        match rust_fel_element.props.type_attr {
68            Some(type_attr) => {
69                dom_el
70                    .set_attribute("type", &type_attr)
71                    .expect("could not set type");
72            }
73            None => (),
74        }
75
76        match rust_fel_element.props.data_cy {
77            Some(data_cy) => {
78                dom_el
79                    .set_attribute("data-cy", &data_cy)
80                    .expect("could not set data-cy");
81            }
82            None => (),
83        }
84
85        match rust_fel_element.props.role {
86            Some(role) => {
87                dom_el
88                    .set_attribute("role", &role)
89                    .expect("could not set role");
90            }
91            None => (),
92        }
93
94        match rust_fel_element.props.on_click {
95            Some(mut on_click) => {
96                let closure = Closure::wrap(Box::new(move || on_click()) as ClosureProp);
97                dom_el
98                    .dyn_ref::<HtmlElement>()
99                    .expect("should be an `HtmlElement`")
100                    .set_onclick(Some(closure.as_ref().unchecked_ref()));
101                closure.forget();
102            }
103            None => (),
104        }
105
106        match rust_fel_element.props.mouse {
107            Some(mut mouse) => {
108                let closure = Closure::wrap(Box::new(move || mouse()) as ClosureProp);
109                dom_el
110                    .dyn_ref::<HtmlElement>()
111                    .expect("should be an `HtmlElement`")
112                    .add_event_listener_with_callback("mouseout", closure.as_ref().unchecked_ref())
113                    .expect("could not add event listener");
114                closure.forget();
115            }
116            None => (),
117        }
118
119        let mut id_copy = None;
120        match rust_fel_element.props.id {
121            Some(id) => {
122                dom_el.set_id(&id);
123
124                // Is this really necessary. Kinda ugly
125                id_copy = Some(id);
126            }
127            None => (),
128        }
129
130        // Update or first render?
131        let dom: Node;
132        if is_update {
133            let id = &id_copy.unwrap();
134            let old_child = document
135                .get_element_by_id(&id)
136                .unwrap_or_else(|| panic!("Unable to get element by id {}", id));
137
138            // Here we replace instead of append
139            // We do this because we need to keep an element position in the dom
140            // Possible fastest method? https://stackoverflow.com/a/22966637
141            container
142                .replace_child(&dom_el, &old_child)
143                .expect("Unable to replace child");
144
145            let new_child: Node = Node::from(
146                document
147                    .get_element_by_id(&id)
148                    .unwrap_or_else(|| panic!("Unable to get element by id {}", id)),
149            );
150            dom = new_child;
151        } else {
152            // Here we append_child instead of replace_child
153            // Replace_child only happens to the element starting the update
154
155            dom = container
156                .append_child(&dom_el)
157                .expect("Unable to append child to the container node");
158        };
159
160        match rust_fel_element.props.children {
161            Some(children) => {
162                for child in children {
163                    render(child, &dom, false)
164                }
165            }
166            None => (),
167        }
168    }
169}
170/// Used when a ```rust_fel``` [struct](https://doc.rust-lang.org/std/keyword.struct.html) component updates it's state and wants to propagate the changes
171/// to it's children.    
172/// After first mount this function will update the Virtual [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction) and then the real [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction).  
173/// It works by
174///1. Passing the function a new [rust_fel::Element](../rust_fel/struct.Element.html) who invoked ```re_render``` by updating itself.
175///2. Finding the associated [DOM Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) by ```id```.
176///3. Removing the [DOM Node](https://developer.mozilla.org/en-US/docs/Web/API/Node) and all of it's children.
177///4. Replacing the removed [DOM Node](https://developer.mozilla.org/en-US/docs/Web/API/Node) with the new [rust_fel::Element](../rust_fel/struct.Element.html).
178/// # Arguments
179///
180/// * `rust_fel_element` - A [rust_fel::Element](../element/struct.Element.html)
181/// * `id` - A [String](https://doc.rust-lang.org/std/string/struct.String.html) wrapped in an [Option](https://doc.rust-lang.org/std/option/enum.Option.html)
182///
183/// # Examples
184/// ```ignore
185///    fn reduce_state(&mut self, message: Action) {
186///       match message {
187///             Action::Increment => self.0.borrow_mut().state += 5,
188///             Action::Decrement => self.0.borrow_mut().state -= 5,
189///         }
190///
191///         rust_fel::re_render(self.render(), Some(self.0.borrow().id.clone()));
192///     }
193/// ```
194
195pub fn re_render(rust_fel_element: Element, id: Option<String>) {
196    let window = web_sys::window().expect("no global `window` exists");
197    let document = window.document().expect("should have a document on window");
198    if let Some(i) = id {
199        let child = document
200            .get_element_by_id(&i)
201            .expect("should have a root div");
202
203        let parent = child.parent_node().unwrap();
204
205        render(rust_fel_element, &parent, true);
206    } else {
207        panic!("Components that initalize re-renders must have a Id");
208    }
209}