1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
use std::{
cell::RefCell,
collections::{HashMap, HashSet, VecDeque},
ops::Deref,
rc::Rc,
};
use js_sys::Function;
use wasm_bindgen::{prelude::Closure, JsCast, JsValue};
use web_sys::{console, Document, Element, Event, Node};
use crate::{
component::RenderType,
dom::{
render_position::RenderPosition,
renderable::{DynamicContent, RenderedNode},
EventType,
},
util::HashMapList,
};
use super::{Component, Identifier};
pub struct Controller {
/// The component that this controller is handling.
component: Rc<RefCell<dyn Component>>,
/// A reference to the document object, so that elements can be created.
document: Document,
/// Maps an update type and corresponding identifier for the rendered node (value) to it's
/// dependencies (key).
dependencies: HashMapList<usize, (usize, Identifier)>,
/// Saves a reference to the created elements in a render, so they can be re-used.
rendered_elements: HashMap<RenderType, HashMap<Identifier, RenderedNode>>,
}
impl Controller {
pub fn new<C>(component: C, document: &Document) -> Self
where
C: Component + 'static,
{
Self {
component: Rc::new(RefCell::new(component)) as Rc<RefCell<dyn Component>>,
document: document.clone(),
dependencies: HashMapList::new(),
rendered_elements: HashMap::new(),
}
}
/// Generate a closure that cen be attached to an event listener for a DOM element. The
/// identifier and event types are required, so they can be passed back into the controller in
/// order for it to appropriately handle the update.
///
/// Requires mutable access to the controller, so that it can use it within the callback.
fn get_event_callback_closure(
controller_ref: &ControllerRef,
identifier: Identifier,
event_type: EventType,
) -> Function {
let controller_ref = controller_ref.clone();
Closure::<dyn Fn(Event)>::new(Box::new(move |event: Event| {
// Gain a mutable reference to the controller
let mut controller = controller_ref.0.borrow_mut();
let changed = controller.component.borrow_mut().handle_event(
identifier.clone(),
event_type,
event,
);
// Trigger the update in the component
if let Some(changed) = changed {
controller.handle_update(&controller_ref, &changed).unwrap();
}
}) as Box<dyn Fn(Event)>)
.into_js_value()
.unchecked_into()
}
/// Will update component in the DOM as needed to reflect the changed dependencies.
pub fn handle_update(
&mut self,
controller_ref: &ControllerRef,
changed_dependencies: &[usize],
) -> Result<(), JsValue> {
// Determine what renders need to run
for (render_type, identifier) in changed_dependencies
.iter()
.filter_map(|dependency| self.dependencies.get(dependency))
.flatten()
.cloned()
.collect::<HashSet<_>>()
{
self.perform_render(
controller_ref,
None,
RenderType::Partial(render_type),
&identifier,
)
.unwrap();
}
Ok(())
}
pub fn perform_render(
&mut self,
controller_ref: &ControllerRef,
// Optional parent, as if it is not supplied then it is assumed the node is being renderd
// in place
parent: Option<RenderedNode>,
render_type: RenderType,
root_identifier: &Identifier,
) -> Result<(), JsValue> {
let root_identifier = root_identifier.clone();
let document = self.document.clone();
let component = self.component.borrow();
// Perform the initial render of the component, to retrieve all of the renderables
if let Some(renderables) = component.render(render_type.clone()) {
// Build a queue of renderables to render, it's identifier, and the parent element it
// will be rendered into.
let mut queue = renderables
.into_iter()
.enumerate()
.map(|(i, renderable)| (root_identifier.child(i), renderable, parent.clone()))
.collect::<VecDeque<_>>();
let mut used_element_identifiers = HashSet::new();
let rendered_elements = self.rendered_elements.entry(render_type).or_default();
while let Some((identifier, renderable, parent)) = queue.pop_front() {
// Attempt to find element in the element cache
let element = rendered_elements.get(&identifier).cloned();
// Render each of the child renderables
if let Some(result) = renderable.render(
// Share a reference to the document so that it can create new elements
&document,
// Share a reference to the component so that it can perform partial renders
component.deref(),
element.clone(),
// Share a callback that can be used to create an event closure
&mut |event_type| {
Controller::get_event_callback_closure(
controller_ref,
identifier.clone(),
event_type,
)
},
)? {
if let Some(children) = result.children {
queue.extend(children.into_iter().enumerate().map(|(i, renderable)| {
(
identifier.child(i),
renderable,
if result.in_place {
parent.clone()
} else {
result.element.clone()
},
)
}));
}
// Save the dependencies
for (dependency, update_type) in result.dynamic_content.into_iter().flat_map(
|DynamicContent {
dependencies,
update_type,
}| {
dependencies
.into_iter()
.map(move |dependency| (dependency, update_type))
},
) {
// TODO: Work out way to de-dupe these before insertion
self.dependencies
.insert(dependency, (update_type, identifier.clone()));
}
if let Some(element) = result.element {
// Save the element for future use
rendered_elements.insert(identifier.clone(), element.clone());
used_element_identifiers.insert(identifier);
// Add the element to the parent
if let Some(parent) = parent {
Node::from(&parent).append_child(&(&element).into())?;
}
}
}
}
// Get rid of unused identifiers
// rendered_elements
// .keys()
// .cloned()
// .collect::<HashSet<_>>()
// .difference(&used_element_identifiers)
// .for_each(|key| {
// if let Some(element) = rendered_elements.remove(key) {
// let node = Node::from(&element);
// if let Some(parent) = node.parent_node() {
// parent.remove_child(&node).ok();
// }
// }
// });
} else {
console::log_1(&"nothing to render".into());
}
Ok(())
}
}
#[derive(Clone)]
pub struct ControllerRef(Rc<RefCell<Controller>>);
impl ControllerRef {
pub fn new<C>(component: C, document: &Document) -> Self
where
C: Component + 'static,
{
Self(Rc::new(RefCell::new(Controller::new(component, document))))
}
/// Render the component, returning the created elements.
///
/// Render must (?) be created on the wrapped struct, as it requires passing the [Rc] to the
/// [Controller] to closures, such as for the `get_event_closure` callback. This could be
/// changed if another way of building event closures is used.
pub fn render(&self, parent: &Element) -> Result<Option<Vec<Element>>, JsValue> {
self.0.borrow_mut().perform_render(
self,
Some(RenderedNode::Element(parent.clone())),
RenderType::Root,
&Identifier::new(),
)?;
Ok(None)
}
}