Skip to content

Commit ff4e847

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

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>,
@@ -204,6 +205,7 @@ pub struct AppBuilder<Ms: 'static + Clone, Mdl: 'static, ElC: View<Ms>, GMs> {
204205
sink: Option<SinkFn<Ms, Mdl, ElC, GMs>>,
205206
view: ViewFn<Mdl, ElC>,
206207
mount_point: Option<Element>,
208+
takeover_mount: bool,
207209
routes: Option<RoutesFn<Ms>>,
208210
window_events: Option<WindowEvents<Ms, Mdl>>,
209211
}
@@ -237,6 +239,18 @@ impl<Ms: Clone, Mdl, ElC: View<Ms> + 'static, GMs: 'static> AppBuilder<Ms, Mdl,
237239
self
238240
}
239241

242+
/// Allows for the [`App`] to takeover all the children of the mount point. The default
243+
/// behavior is that the [`App`] ignores the children and leaves them in place. The new
244+
/// behavior can be useful if SSR is implemented.
245+
///
246+
/// As of right now, nodes found in the root will be destroyed and recreated once. This can
247+
/// cause duplicated scripts, css, and other tags that should not otherwise be duplicated.
248+
/// Unrecognized tags are also converted into spans, so take note.
249+
pub fn takeover_mount(mut self, should_takeover: bool) -> Self {
250+
self.takeover_mount = should_takeover;
251+
self
252+
}
253+
240254
/// Registers a function which maps URLs to messages.
241255
pub fn routes(mut self, routes: RoutesFn<Ms>) -> Self {
242256
self.routes = Some(routes);
@@ -273,6 +287,7 @@ impl<Ms: Clone, Mdl, ElC: View<Ms> + 'static, GMs: 'static> AppBuilder<Ms, Mdl,
273287
self.sink,
274288
self.view,
275289
self.mount_point.unwrap(),
290+
self.takeover_mount,
276291
self.routes,
277292
self.window_events,
278293
);
@@ -314,6 +329,7 @@ impl<Ms: Clone, Mdl, ElC: View<Ms> + 'static, GMs: 'static> App<Ms, Mdl, ElC, GM
314329
view,
315330
sink: None,
316331
mount_point: None,
332+
takeover_mount: false,
317333
routes: None,
318334
window_events: None,
319335
}
@@ -325,6 +341,7 @@ impl<Ms: Clone, Mdl, ElC: View<Ms> + 'static, GMs: 'static> App<Ms, Mdl, ElC, GM
325341
sink: Option<SinkFn<Ms, Mdl, ElC, GMs>>,
326342
view: ViewFn<Mdl, ElC>,
327343
mount_point: Element,
344+
takeover_mount: bool,
328345
routes: Option<RoutesFn<Ms>>,
329346
window_events: Option<WindowEvents<Ms, Mdl>>,
330347
) -> Self {
@@ -335,6 +352,7 @@ impl<Ms: Clone, Mdl, ElC: View<Ms> + 'static, GMs: 'static> App<Ms, Mdl, ElC, GM
335352
cfg: Rc::new(AppCfg {
336353
document,
337354
mount_point,
355+
takeover_mount,
338356
update,
339357
sink,
340358
view,
@@ -368,46 +386,60 @@ impl<Ms: Clone, Mdl, ElC: View<Ms> + 'static, GMs: 'static> App<Ms, Mdl, ElC, GM
368386
}
369387

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

381407
// Replace the root dom with a placeholder tag and move the children from the root element
382408
// to the newly created root. Uses `Placeholder` to mimic update logic.
383-
let mut new = El::empty(dom_types::Tag::Placeholder);
384409
new.children = dom_nodes.children;
385-
new
386-
};
410+
}
387411

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

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

456+
// Finally return the bootstrapped version of the virtual DOM.
424457
new
425458
}
459+
426460
/// App initialization: Collect its fundamental components, setup, and perform
427461
/// an initial render.
428462
pub fn run(self) -> Self {

0 commit comments

Comments
 (0)