Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add parameter for specifying what to do with elements that already exist within the mount. #276

Merged
merged 3 commits into from
Nov 5, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions examples/server_integration/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub use crate::{
fetch::{Method, Request},
routing::{push_route, Url},
util::{body, cookies, document, error, history, html_document, log, update, window},
vdom::{App, AppBuilder},
vdom::{App, AppBuilder, MountType},
AlterionX marked this conversation as resolved.
Show resolved Hide resolved
websys_bridge::{to_html_el, to_input, to_kbevent, to_mouse_event, to_select, to_textarea},
};
use wasm_bindgen::{closure::Closure, JsCast};
Expand Down
64 changes: 49 additions & 15 deletions src/vdom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use next_tick::NextTick;
pub mod alias;
pub use alias::*;
pub mod builder;
pub use builder::{Builder as AppBuilder, Init, UrlHandling};
pub use builder::{Builder as AppBuilder, Init, MountType, UrlHandling};

use crate::{
dom_types::{self, El, MessageMapper, Namespace, Node, View},
Expand Down Expand Up @@ -107,6 +107,7 @@ where
{
document: web_sys::Document,
mount_point: web_sys::Element,
mount_type: RefCell<Option<MountType>>,
pub update: UpdateFn<Ms, Mdl, ElC, GMs>,
pub sink: Option<SinkFn<Ms, Mdl, ElC, GMs>>,
view: ViewFn<Mdl, ElC>,
Expand Down Expand Up @@ -167,6 +168,7 @@ impl<Ms, Mdl, ElC: View<Ms> + 'static, GMs: 'static> App<Ms, Mdl, ElC, GMs> {
view,
window_events,
initial_orders: RefCell::new(None),
mount_type: RefCell::new(None),
}),
data: Rc::new(AppData {
model: RefCell::new(None),
Expand Down Expand Up @@ -195,29 +197,63 @@ impl<Ms, Mdl, ElC: View<Ms> + 'static, GMs: 'static> App<Ms, Mdl, ElC, GMs> {
}
}

/// Bootstrap the dom with the vdom by taking over all children of the mount point and
/// replacing them with the vdom if requested. Will otherwise ignore the original children of
/// the mount point.
fn bootstrap_vdom(&self) -> El<Ms> {
let mount_type = self.cfg.mount_type.borrow().unwrap_or(MountType::Append);
// "new" name is for consistency with `update` function.
// this section parent is a placeholder, so we can iterate over children
// in a way consistent with patching code.
let mut new = El::empty(dom_types::Tag::Placeholder);

// Map the DOM's elements onto the virtual DOM if requested to takeover.
if mount_type == MountType::Takeover {
// Construct a vdom from the root element. Subsequently strip the workspace so that we
// can recreate it later - this is a kind of simple way to avoid missing nodes (but
// not entirely correct).
// TODO: 1) Please refer to [issue #277](https://github.com/David-OConnor/seed/issues/277)
let mut dom_nodes: El<Ms> = (&self.cfg.mount_point).into();
dom_nodes.strip_ws_nodes_from_self_and_children();

// Replace the root dom with a placeholder tag and move the children from the root element
// to the newly created root. Uses `Placeholder` to mimic update logic.
new.children = dom_nodes.children;
}

self.setup_window_listeners();
patch::setup_input_listeners(&mut new);
patch::attach_listeners(&mut new, &self.mailbox());

websys_bridge::assign_ws_nodes_to_el(&util::document(), &mut new);
// Recreate the needed nodes. Only do this if requested to takeover the mount point since
// it should only be needed here.
if mount_type == MountType::Takeover {
// TODO: Please refer to [issue #277](https://github.com/David-OConnor/seed/issues/277)
websys_bridge::assign_ws_nodes_to_el(&util::document(), &mut new);

// Remove all old elements. We'll swap them out with the newly created elements later.
// This maneuver will effectively allow us to remove everything in the mount and thus
// takeover the mount point.
while let Some(child) = self.cfg.mount_point.first_child() {
self.cfg
.mount_point
.remove_child(&child)
.expect("No problem removing node from parent.");
}

// Attach all top-level elements to the mount point.
for child in &mut new.children {
match child {
Node::Element(child_el) => {
websys_bridge::attach_el_and_children(child_el, &self.cfg.mount_point);
patch::attach_listeners(child_el, &self.mailbox());
// Attach all top-level elements to the mount point if present. This means that we have
// effectively taken full control of everything within the mounting element.
for child in &mut new.children {
match child {
Node::Element(child_el) => {
websys_bridge::attach_el_and_children(child_el, &self.cfg.mount_point);
patch::attach_listeners(child_el, &self.mailbox());
}
Node::Text(top_child_text) => {
websys_bridge::attach_text_node(top_child_text, &self.cfg.mount_point);
}
Node::Empty => (),
}
Node::Text(top_child_text) => {
websys_bridge::attach_text_node(top_child_text, &self.cfg.mount_point);
}
Node::Empty => (),
}
}

Expand All @@ -228,9 +264,7 @@ impl<Ms, Mdl, ElC: View<Ms> + 'static, GMs: 'static> App<Ms, Mdl, ElC, GMs> {
/// an initial render.
pub fn run(self) -> Self {
// Bootstrap the virtual DOM.
self.data
.main_el_vdom
.replace(Some(self.bootstrap_vdom()));
self.data.main_el_vdom.replace(Some(self.bootstrap_vdom()));

// Update the state on page load, based
// on the starting URL. Must be set up on the server as well.
Expand Down
38 changes: 36 additions & 2 deletions src/vdom/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,31 +38,64 @@ impl MountPoint for web_sys::HtmlElement {
}

/// Used for handling initial routing.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum UrlHandling {
PassToRoutes,
None,
// todo: Expand later, as-required
}

/// Describes the handling of elements already present in the mount element.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MountType {
AlterionX marked this conversation as resolved.
Show resolved Hide resolved
/// Take control of previously existing elements in the mount. This does not make guarantees of
/// elements added after the [`App`] has been mounted.
///
/// Note that existing elements in the DOM will be recreated. This can be dangerous for script
/// tags and other, similar tags.
Takeover,
/// Leave the previously existing elements in the mount alone. This does not make guarantees of
/// elements added after the [`App`] has been mounted.
Append,
}

/// Used as a flexible wrapper for the init function.
pub struct Init<Mdl> {
// init: InitFn<Ms, Mdl, ElC, GMs>,
model: Mdl,
url_handling: UrlHandling,
/// Initial model to be used when the app begins.
pub model: Mdl,
/// How to handle initial url routing. Defaults to [`UrlHandling::PassToRoutes`] in the
/// constructors.
pub url_handling: UrlHandling,
/// How to handle elements already present in the mount. Defaults to [`MountType::Append`]
/// in the constructors.
pub mount_type: MountType,
}

impl<Mdl> Init<Mdl> {
pub const fn new(model: Mdl) -> Self {
Self {
model,
url_handling: UrlHandling::PassToRoutes,
mount_type: MountType::Append,
}
}

pub const fn new_with_url_handling(model: Mdl, url_handling: UrlHandling) -> Self {
Self {
model,
url_handling,
mount_type: MountType::Append,
}
}
}

impl<Mdl: Default> Default for Init<Mdl> {
fn default() -> Self {
Self {
model: Mdl::default(),
url_handling: UrlHandling::PassToRoutes,
mount_type: MountType::Append,
}
}
}
Expand Down Expand Up @@ -179,6 +212,7 @@ impl<Ms, Mdl, ElC: View<Ms> + 'static, GMs: 'static> Builder<Ms, Mdl, ElC, GMs>
};

app.cfg.initial_orders.replace(Some(initial_orders));
app.cfg.mount_type.replace(Some(init.mount_type));
app.data.model.replace(Some(init.model));

app
Expand Down