Skip to content

Commit aee4aef

Browse files
committed
Add a toggle for opting into/out of the behavior for taking over the App's mount.
1 parent 404fcfc commit aee4aef

File tree

1 file changed

+60
-26
lines changed

1 file changed

+60
-26
lines changed

src/vdom.rs

Lines changed: 60 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ where
142142
{
143143
document: web_sys::Document,
144144
mount_point: web_sys::Element,
145+
takeover_mount: bool,
145146
pub update: UpdateFn<Ms, Mdl, ElC, GMs>,
146147
pub sink: Option<SinkFn<Ms, Mdl, ElC, GMs>>,
147148
view: ViewFn<Mdl, ElC>,
@@ -202,6 +203,7 @@ pub struct AppBuilder<Ms: 'static, Mdl: 'static, ElC: View<Ms>, GMs> {
202203
sink: Option<SinkFn<Ms, Mdl, ElC, GMs>>,
203204
view: ViewFn<Mdl, ElC>,
204205
mount_point: Option<Element>,
206+
takeover_mount: bool,
205207
routes: Option<RoutesFn<Ms>>,
206208
window_events: Option<WindowEvents<Ms, Mdl>>,
207209
}
@@ -235,6 +237,18 @@ impl<Ms, Mdl, ElC: View<Ms> + 'static, GMs: 'static> AppBuilder<Ms, Mdl, ElC, GM
235237
self
236238
}
237239

240+
/// Allows for the [`App`] to takeover all the children of the mount point. The default
241+
/// behavior is that the [`App`] ignores the children and leaves them in place. The new
242+
/// behavior can be useful if SSR is implemented.
243+
///
244+
/// As of right now, nodes found in the root will be destroyed and recreated once. This can
245+
/// cause duplicated scripts, css, and other tags that should not otherwise be duplicated.
246+
/// Unrecognized tags are also converted into spans, so take note.
247+
pub fn takeover_mount(mut self, should_takeover: bool) -> Self {
248+
self.takeover_mount = should_takeover;
249+
self
250+
}
251+
238252
/// Registers a function which maps URLs to messages.
239253
pub fn routes(mut self, routes: RoutesFn<Ms>) -> Self {
240254
self.routes = Some(routes);
@@ -271,6 +285,7 @@ impl<Ms, Mdl, ElC: View<Ms> + 'static, GMs: 'static> AppBuilder<Ms, Mdl, ElC, GM
271285
self.sink,
272286
self.view,
273287
self.mount_point.unwrap(),
288+
self.takeover_mount,
274289
self.routes,
275290
self.window_events,
276291
);
@@ -312,6 +327,7 @@ impl<Ms, Mdl, ElC: View<Ms> + 'static, GMs: 'static> App<Ms, Mdl, ElC, GMs> {
312327
view,
313328
sink: None,
314329
mount_point: None,
330+
takeover_mount: false,
315331
routes: None,
316332
window_events: None,
317333
}
@@ -323,6 +339,7 @@ impl<Ms, Mdl, ElC: View<Ms> + 'static, GMs: 'static> App<Ms, Mdl, ElC, GMs> {
323339
sink: Option<SinkFn<Ms, Mdl, ElC, GMs>>,
324340
view: ViewFn<Mdl, ElC>,
325341
mount_point: Element,
342+
takeover_mount: bool,
326343
routes: Option<RoutesFn<Ms>>,
327344
window_events: Option<WindowEvents<Ms, Mdl>>,
328345
) -> Self {
@@ -333,6 +350,7 @@ impl<Ms, Mdl, ElC: View<Ms> + 'static, GMs: 'static> App<Ms, Mdl, ElC, GMs> {
333350
cfg: Rc::new(AppCfg {
334351
document,
335352
mount_point,
353+
takeover_mount,
336354
update,
337355
sink,
338356
view,
@@ -366,46 +384,60 @@ impl<Ms, Mdl, ElC: View<Ms> + 'static, GMs: 'static> App<Ms, Mdl, ElC, GMs> {
366384
}
367385

368386
/// Bootstrap the dom with the vdom by taking over all children of the mount point and
369-
/// replacing them with the vdom.
387+
/// replacing them with the vdom if requested. Will otherwise ignore the original children of
388+
/// the mount point.
370389
fn bootstrap_vdom(&self) -> El<Ms> {
371-
let mut new = {
372-
// Construct the vdom from the root element. Subsequently strip the workspace so that we
373-
// can recreate it later. There could be missing nodes here.
374-
// TODO: Optimize by utilizing a patching strategy instead of recreating the workspace
390+
let mut new = El::empty(dom_types::Tag::Placeholder);
391+
392+
// Map the DOM's elements onto the virtual DOM if requested to takeover.
393+
if self.cfg.takeover_mount {
394+
// Construct a vdom from the root element. Subsequently strip the workspace so that we
395+
// can recreate it later - this is a kind of simple way to avoid missing nodes (but
396+
// not entirely correct).
397+
// TODO: 1) Optimize by utilizing a patching strategy instead of recreating the workspace
375398
// TODO: nodes.
399+
// TODO: 2) Watch out for elements that should not be recreated, such as script nodes,
400+
// TODO: and other, similar things. For now, leave the warning in the builder's
401+
// TODO: documentation.
376402
let mut dom_nodes = websys_bridge::el_from_ws_element(&self.cfg.mount_point);
377403
dom_nodes.strip_ws_nodes();
378404

379405
// Replace the root dom with a placeholder tag and move the children from the root element
380406
// to the newly created root. Uses `Placeholder` to mimic update logic.
381-
let mut new = El::empty(dom_types::Tag::Placeholder);
382407
new.children = dom_nodes.children;
383-
new
384-
};
408+
}
385409

386410
// Setup listeners
387411
self.setup_window_listeners();
388412
patch::setup_input_listeners(&mut new);
389413
patch::attach_listeners(&mut new, &self.mailbox());
390414

391-
// Recreate the needed nodes.
392-
// TODO: Refer the TODO at the beginning of the function.
393-
let mut new_node = Node::Element(new);
394-
websys_bridge::assign_ws_nodes(&util::document(), &mut new_node);
395-
let mut new = new_node
396-
.el()
397-
.expect("`El` placed into `Node::Element` `new_node` is no longer an `El`.");
398-
399-
// Remove all old elements, and replace them with out newly created elements - we have
400-
// effectively taken over the original DOM and now have free reign over the mount_point.
401-
// Attach all top-level elements to the mount point: This is where our initial render
402-
// occurs.
403-
while let Some(child) = self.cfg.mount_point.first_child() {
404-
self.cfg
405-
.mount_point
406-
.remove_child(&child)
407-
.expect("No problem removing node from parent.");
408-
}
415+
// Recreate the needed nodes. Only do this if requested to takeover the mount point.
416+
let mut new = if self.cfg.takeover_mount {
417+
// TODO: Refer the TODO at the beginning of the function.
418+
let mut new_node = Node::Element(new);
419+
websys_bridge::assign_ws_nodes(&util::document(), &mut new_node);
420+
let new = new_node
421+
.el()
422+
.expect("`El` placed into `Node::Element` `new_node` is no longer an `El`.");
423+
424+
// Remove all old elements. We'll swap them out with the newly created elements later.
425+
// That maneuver will effectively allow us to remove everything in the mount and thus
426+
// takeover the mount point.
427+
while let Some(child) = self.cfg.mount_point.first_child() {
428+
self.cfg
429+
.mount_point
430+
.remove_child(&child)
431+
.expect("No problem removing node from parent.");
432+
}
433+
434+
new
435+
} else {
436+
new
437+
};
438+
439+
// Attach any children that may have been added. This is left outside in case we decide to
440+
// add any default nodes in the future.
409441
for child in &mut new.children {
410442
match child {
411443
Node::Element(child_el) => {
@@ -419,8 +451,10 @@ impl<Ms, Mdl, ElC: View<Ms> + 'static, GMs: 'static> App<Ms, Mdl, ElC, GMs> {
419451
}
420452
}
421453

454+
// Finally return the bootstrapped version of the virtual DOM.
422455
new
423456
}
457+
424458
/// App initialization: Collect its fundamental components, setup, and perform
425459
/// an initial render.
426460
pub fn run(self) -> Self {

0 commit comments

Comments
 (0)