From 36e8071927475fdc79731458ac7e3f0b1c08fe87 Mon Sep 17 00:00:00 2001 From: DavidOConnor Date: Mon, 27 May 2019 23:32:38 -0400 Subject: [PATCH] Fixed a bug with rendering multiple Els from top level. Fixed typo with viewBox attr --- CHANGELOG.md | 4 ++ Cargo.lock | 16 +++--- Cargo.toml | 2 +- examples/counter/src/lib.rs | 3 +- examples/mathjax/README.md | 3 +- src/dom_types.rs | 16 +++++- src/lib.rs | 2 +- src/vdom.rs | 101 ++++++++++++++++++++---------------- 8 files changed, 90 insertions(+), 57 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ecb9f846..da1f9ec14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v0.3.5 +- Fixed a bug where view functions returning `Vec` weren't rendering properly +- Fixed a type with the `viewBox` attribute + ## v0.3.4 - The `update` fn now accepts a (new) `Orders` struct, and returns nothing. Renders occur implicitly, with the option to skip rendering, update with an additional message, or perform an asynchronous diff --git a/Cargo.lock b/Cargo.lock index f7a91d026..337d5b437 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,7 +100,7 @@ dependencies = [ name = "counter" version = "0.1.0" dependencies = [ - "seed 0.3.4", + "seed 0.3.5", "wasm-bindgen 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", "web-sys 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -346,7 +346,7 @@ version = "0.1.0" dependencies = [ "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "gloo-timers 0.1.0", - "seed 0.3.4", + "seed 0.3.5", "wasm-bindgen 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", "web-sys 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -540,7 +540,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "seed" -version = "0.3.4" +version = "0.3.5" dependencies = [ "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "enclose 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -588,7 +588,7 @@ name = "server-interaction" version = "0.1.0" dependencies = [ "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", - "seed 0.3.4", + "seed 0.3.5", "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", @@ -666,7 +666,7 @@ dependencies = [ name = "todomvc" version = "0.1.0" dependencies = [ - "seed 0.3.4", + "seed 0.3.5", "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", "web-sys 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)", @@ -676,7 +676,7 @@ dependencies = [ name = "trigger_update_from_js" version = "0.1.0" dependencies = [ - "seed 0.3.4", + "seed 0.3.5", "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", @@ -854,7 +854,7 @@ dependencies = [ name = "websocket" version = "0.1.0" dependencies = [ - "seed 0.3.4", + "seed 0.3.5", "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", @@ -921,7 +921,7 @@ dependencies = [ name = "window-events" version = "0.1.0" dependencies = [ - "seed 0.3.4", + "seed 0.3.5", "wasm-bindgen 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", "web-sys 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/Cargo.toml b/Cargo.toml index d17f3cda4..884ce60c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "seed" -version = "0.3.4" +version = "0.3.5" description = "A Rust framework for creating web apps, using WebAssembly" authors = ["DavidOConnor "] license = "MIT" diff --git a/examples/counter/src/lib.rs b/examples/counter/src/lib.rs index 59cfc1c1d..59c1bcbec 100644 --- a/examples/counter/src/lib.rs +++ b/examples/counter/src/lib.rs @@ -52,6 +52,8 @@ fn success_level(clicks: i32) -> El { p![descrip] } +use wasm_bindgen::JsCast; + /// The top-level component we pass to the virtual dom. Must accept the model as its /// only argument, and output has to implement trait ElContainer. fn view(model: &Model) -> El { @@ -84,7 +86,6 @@ fn view(model: &Model) -> El { h2![ style! {"padding" => 50}, "Nice!", - // did_mount(|_| log!("This shows when clicks reach 10")).update2(Msg::ChangeWWC("Did mount".into())), did_mount(|_| log!("This shows when clicks reach 10")), will_unmount(|_| log!("This shows when clicks drop below 10")), ] diff --git a/examples/mathjax/README.md b/examples/mathjax/README.md index ead08d509..d208b9b80 100644 --- a/examples/mathjax/README.md +++ b/examples/mathjax/README.md @@ -1,4 +1,5 @@ An example using Mathjax, for LaTeX-like math notation. Doesn't demonstrate Seed-specific functionality, but provides a syntax example. -# [Linear algebra cheatsheet](https://github.com/David-OConnor/linalg) \ No newline at end of file +# [Linear algebra cheatsheet](https://github.com/David-OConnor/linalg) +# [Hosted](https://david-oconnor.github.io/linalg/) \ No newline at end of file diff --git a/src/dom_types.rs b/src/dom_types.rs index 7374d9eea..90b191cd2 100644 --- a/src/dom_types.rs +++ b/src/dom_types.rs @@ -588,7 +588,7 @@ make_attrs! { CalcMode => "calcMode", CapHeight => "cap-height", Clip => "clip", // todo fill in rest from link above. - Path => "path", D => "d", Xmlns => "xmlns", ViewBox => "ViewBox", Fill => "fill" + Path => "path", D => "d", Xmlns => "xmlns", ViewBox => "viewBox", Fill => "fill" } /// Similar to tag population. @@ -1130,6 +1130,8 @@ pub struct El { // todo temp? // pub key: Option, pub namespace: Option, + // A unique identifier in the vdom. Useful for triggering one-off events. + pub ref_: Option, // control means we keep its text input (value) in sync with the model. // static: bool @@ -1200,6 +1202,8 @@ impl El { hooks: LifecycleHooks::new(), empty: false, optimizations: Vec::new(), + + ref_: None, } } @@ -1236,6 +1240,8 @@ impl El { // hooks: self.hooks, // todo fix empty: self.empty, optimizations: self.optimizations, + + ref_: None, } } @@ -1369,6 +1375,11 @@ impl El { walk_tree_mut_inner(self, &mut f); } + + /// Set the ref + pub fn ref_(&mut self, ref_: S) { + self.ref_ = Some(ref_.to_string()); + } } /// Allow the user to clone their Els. Note that there's no easy way to clone the @@ -1388,6 +1399,8 @@ impl Clone for El { hooks: LifecycleHooks::new(), empty: self.empty, optimizations: self.optimizations.clone(), + + ref_: self.ref_.clone(), } } } @@ -1403,6 +1416,7 @@ impl PartialEq for El { && self.listeners == other.listeners && self.namespace == other.namespace && self.empty == other.empty + && self.ref_ == other.ref_ } } diff --git a/src/lib.rs b/src/lib.rs index 87c95e548..6b4a7fe1f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,7 @@ pub use crate::{ fetch::{spawn_local, Method, Request}, routing::{push_route, Url}, util::{body, document, error, history, log, update, window}, - vdom::App, + vdom::{find_el, App}, websys_bridge::{to_html_el, to_input, to_kbevent, to_mouse_event, to_select, to_textarea}, }; use wasm_bindgen::{closure::Closure, JsCast}; diff --git a/src/vdom.rs b/src/vdom.rs index 683d06946..888ec1755 100644 --- a/src/vdom.rs +++ b/src/vdom.rs @@ -432,7 +432,7 @@ impl + 'static> App { // We'll get a runtime panic if any are left un-removed. detach_listeners(&mut old); - // todo copied from children loop in patch fn (DRY) + // todo much of the code below is copied from the patch fn (DRY) let num_children_in_both = old.children.len().min(new.children.len()); let mut old_children_iter = old.children.into_iter(); let mut new_children_iter = new.children.iter_mut(); @@ -462,6 +462,32 @@ impl + 'static> App { ); } + for child_new in new_children_iter { + // We ran out of old children to patch; create new ones. + setup_websys_el_and_children(&self.cfg.document, child_new); + websys_bridge::attach_el_and_children(child_new, &self.cfg.mount_point, &self.clone()); + attach_listeners(child_new, &self.mailbox()); + } + + // Now purge any existing no-longer-needed children; they're not part of the new vdom. + // while let Some(mut child) = old_children_iter.next() { + for mut child in old_children_iter { + let child_el_ws = child.el_ws.take().expect("Missing child el_ws"); + + // TODO: DRY here between this and earlier in func + if let Some(unmount_actions) = &mut child.hooks.will_unmount { + (unmount_actions.actions)(&child_el_ws); + } + + // todo get to the bottom of this: Ie why we need this code sometimes when using raw html elements. + match self.cfg.mount_point.remove_child(&child_el_ws) { + Ok(_) => {} + Err(_) => { + crate::log("Minor error patching html element. (remove)"); + } + } + } + // Now that we've re-rendered, replace our stored El with the new one; // it will be used as the old El next time. self.data.main_el_vdom.borrow_mut().replace(new); @@ -477,6 +503,20 @@ impl + 'static> App { .push(Box::new(listener)); } + fn find(&self, ref_: &str) -> Option> { + // todo expensive? We're cloning the whole vdom tree. + // todo: Let's iterate through refs instead, once this is working. + + let top_el = &self + .data + .main_el_vdom + .borrow() + .clone() + .expect("Can't find main vdom el in find"); + + find_el(ref_, &top_el) + } + fn mailbox(&self) -> Mailbox { Mailbox::new(enclose!((self => s) move |message| { s.update(message); @@ -776,49 +816,6 @@ pub(crate) fn patch<'a, Ms, Mdl, ElC: ElContainer>( attach_listeners(child_new, &mailbox); } - // // Now pair up children as best we can. - // // If there are the same number of children, assume there's a 1-to-1 mapping, - // // where we will not add or remove any; but patch as needed. - // let avail_old_children = &mut old.children; - // let mut prev_child: Option = None; - // let mut best_match; - //// let mut t; - // for (i_new, child_new) in new.children.iter_mut().enumerate() { - // if avail_old_children.is_empty() { - // // One or more new children has been added, or much content has - // // changed, or we've made a mistake: Attach new children. - // websys_bridge::attach_els(child_new, &old_el_ws); - // let mut child_new = child_new; - // attach_listeners(&mut child_new, &mailbox); - // - // } else { - // // We still have old children to pick a match from. If we pick - // // incorrectly, or there is no "good" match, we'll have some - // // patching and/or attaching (rendering) to do in subsequent recursions. - // let mut scores: Vec<(u32, f32)> = avail_old_children - // .iter() - // .enumerate() - // .map(|(i_old, c_old)| (c_old.id.unwrap(), match_score(c_old, i_old, child_new, i_new))) - // .collect(); - // - // // should put highest score at the end. - // scores.sort_by(|b, a| b.1.partial_cmp(&a.1).unwrap()); - // - // // Sorting children vice picking the best one makes this easier to handle - // // without irking the borrow checker, despite appearing less counter-intuitive, - // // due to the convenient pop method. - // avail_old_children.sort_by(|b, a| { - // scores - // .iter() - // .find(|s| s.0 == b.id.unwrap()) - // .unwrap() - // .1 - // .partial_cmp(&scores.iter().find(|s| s.0 == a.id.unwrap()).unwrap().1) - // .unwrap() - // }); - // - // best_match = avail_old_children.pop().expect("Problem popping"); - // Now purge any existing no-longer-needed children; they're not part of the new vdom. // while let Some(mut child) = old_children_iter.next() { for mut child in old_children_iter { @@ -892,6 +889,22 @@ pub trait DomElLifecycle { fn will_unmount(self) -> Option>; } +/// Find the first element that matches the ref specified. +//pub fn find_el<'a, Msg>(ref_: &str, top_el: &'a El) -> Option<&'a El> { +pub fn find_el(ref_: &str, top_el: &El) -> Option> { + if top_el.ref_ == Some(ref_.to_string()) { + return Some(top_el.clone()); + } + + for child in &top_el.children { + let result = find_el(ref_, child); + if result.is_some() { + return result; + } + } + return None; +} + #[cfg(test)] pub mod tests { use wasm_bindgen_test::*;