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}