diff --git a/examples/server_integration/Cargo.lock b/examples/server_integration/Cargo.lock index 5817eafbc..d22de5602 100644 --- a/examples/server_integration/Cargo.lock +++ b/examples/server_integration/Cargo.lock @@ -458,6 +458,15 @@ dependencies = [ "wasm-bindgen 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "cookie" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "copyless" version = "0.1.2" @@ -1307,6 +1316,7 @@ name = "seed" version = "0.4.1" dependencies = [ "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "cookie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", "dbg 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "enclose 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2002,6 +2012,7 @@ dependencies = [ "checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d976903543e0c48546a91908f21588a680a8c8f984df9a5d69feccb2b2a211" +"checksum cookie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "888604f00b3db336d2af898ec3c1d5d0ddf5e6d462220f2ededc33a87ac4bbd5" "checksum copyless 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "59de7722d3b5c7b35dd519d617fe5116c9b879a0f145dc5431d78ab1f61d7c23" "checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" "checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" diff --git a/src/lib.rs b/src/lib.rs index b4ff668b2..959bfe096 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,7 +97,7 @@ pub mod prelude { request_animation_frame, ClosureNew, RequestAnimationFrameHandle, RequestAnimationFrameTime, }, - vdom::{Init, UrlHandling}, + vdom::{Init, UrlHandling, MountType}, }; pub use indexmap::IndexMap; // for attrs and style to work. pub use wasm_bindgen::prelude::*; diff --git a/src/vdom.rs b/src/vdom.rs index 4709c0596..0fb2219ad 100644 --- a/src/vdom.rs +++ b/src/vdom.rs @@ -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}, @@ -107,6 +107,7 @@ where { document: web_sys::Document, mount_point: web_sys::Element, + mount_type: RefCell>, pub update: UpdateFn, pub sink: Option>, view: ViewFn, @@ -167,6 +168,7 @@ impl + 'static, GMs: 'static> App { view, window_events, initial_orders: RefCell::new(None), + mount_type: RefCell::new(None), }), data: Rc::new(AppData { model: RefCell::new(None), @@ -195,29 +197,63 @@ impl + 'static, GMs: 'static> App { } } + /// 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 { + 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 = (&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 => (), } } @@ -228,9 +264,7 @@ impl + 'static, GMs: 'static> App { /// 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. diff --git a/src/vdom/builder.rs b/src/vdom/builder.rs index 1a3f1045e..e349d3f87 100644 --- a/src/vdom/builder.rs +++ b/src/vdom/builder.rs @@ -38,17 +38,38 @@ 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 { + /// 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 { // init: InitFn, - 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 Init { @@ -56,6 +77,7 @@ impl Init { Self { model, url_handling: UrlHandling::PassToRoutes, + mount_type: MountType::Append, } } @@ -63,6 +85,17 @@ impl Init { Self { model, url_handling, + mount_type: MountType::Append, + } + } +} + +impl Default for Init { + fn default() -> Self { + Self { + model: Mdl::default(), + url_handling: UrlHandling::PassToRoutes, + mount_type: MountType::Append, } } } @@ -179,6 +212,7 @@ impl + 'static, GMs: 'static> Builder }; 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