From 1163bf662c014f1a03a6bd7b013a26331cd31e17 Mon Sep 17 00:00:00 2001 From: Johann Tuffe Date: Wed, 14 Aug 2019 10:33:13 +0800 Subject: [PATCH 001/193] avoid allocating in diff_classes --- src/virtual_dom/vtag.rs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/virtual_dom/vtag.rs b/src/virtual_dom/vtag.rs index 021d359425e..245cb6b8137 100644 --- a/src/virtual_dom/vtag.rs +++ b/src/virtual_dom/vtag.rs @@ -179,34 +179,33 @@ impl VTag { /// - items that are the same stay the same. /// /// Otherwise just add everything. - fn diff_classes(&mut self, ancestor: &mut Option) -> Vec> { + fn diff_classes<'a>(&'a self, ancestor: &'a Option) -> Vec> { // TODO: Use generator in order to avoid useless alloc - let mut changes = Vec::new(); if let Some(ref ancestor) = ancestor { // Only change what is necessary. + let mut changes = Vec::with_capacity(self.classes.set.len() + ancestor.classes.set.len()); let to_add = self .classes .set .difference(&ancestor.classes.set) - .map(|class| Patch::Add(class.to_owned(), ())); - changes.extend(to_add); + .map(|class| Patch::Add(&**class, ())); let to_remove = ancestor .classes .set .difference(&self.classes.set) - .map(|class| Patch::Remove(class.to_owned())); - changes.extend(to_remove); + .map(|class| Patch::Remove(&**class)); + changes.extend(to_add.chain(to_remove)); + changes } else { // Add everything - let to_add = self + self .classes .set .iter() - .map(|class| Patch::Add(class.to_owned(), ())); - changes.extend(to_add); + .map(|class| Patch::Add(&**class, ())) + .collect() } - changes } /// Similar to diff_classes except for attributes. @@ -297,10 +296,10 @@ impl VTag { let list = element.class_list(); match change { Patch::Add(class, _) | Patch::Replace(class, _) => { - list.add(&class).expect("can't add a class"); + list.add(class).expect("can't add a class"); } Patch::Remove(class) => { - list.remove(&class).expect("can't remove a class"); + list.remove(class).expect("can't remove a class"); } } } From b2dfe0dc3794d7e200ad674a26b69fbbe7f77beb Mon Sep 17 00:00:00 2001 From: Johann Tuffe Date: Wed, 14 Aug 2019 10:40:19 +0800 Subject: [PATCH 002/193] avoid allocating for diff_kind --- src/virtual_dom/vtag.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/virtual_dom/vtag.rs b/src/virtual_dom/vtag.rs index 245cb6b8137..33f870f088b 100644 --- a/src/virtual_dom/vtag.rs +++ b/src/virtual_dom/vtag.rs @@ -252,20 +252,20 @@ impl VTag { } /// Similar to `diff_attributers` except there is only a single `kind`. - fn diff_kind(&mut self, ancestor: &mut Option) -> Option> { + fn diff_kind<'a>(&'a self, ancestor: &'a Option) -> Option> { match ( - &self.kind, - ancestor.as_mut().and_then(|anc| anc.kind.take()), + &self.kind.as_ref(), + ancestor.as_ref().and_then(|anc| anc.kind.as_ref()), ) { (&Some(ref left), Some(ref right)) => { if left != right { - Some(Patch::Replace(left.to_string(), ())) + Some(Patch::Replace(&**left, ())) } else { None } } - (&Some(ref left), None) => Some(Patch::Add(left.to_string(), ())), - (&None, Some(right)) => Some(Patch::Remove(right)), + (&Some(ref left), None) => Some(Patch::Add(&**left, ())), + (&None, Some(right)) => Some(Patch::Remove(&**right)), (&None, None) => None, } } From b3024efd9179eb23c4c258c3082c98bc0c975561 Mon Sep 17 00:00:00 2001 From: Johann Tuffe Date: Wed, 14 Aug 2019 10:42:04 +0800 Subject: [PATCH 003/193] avoid allocating for diff_value --- src/virtual_dom/vtag.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/virtual_dom/vtag.rs b/src/virtual_dom/vtag.rs index 33f870f088b..2c0a42cf2be 100644 --- a/src/virtual_dom/vtag.rs +++ b/src/virtual_dom/vtag.rs @@ -271,20 +271,20 @@ impl VTag { } /// Almost identical in spirit to `diff_kind` - fn diff_value(&mut self, ancestor: &mut Option) -> Option> { + fn diff_value<'a>(&'a self, ancestor: &'a Option) -> Option> { match ( - &self.value, - ancestor.as_mut().and_then(|anc| anc.value.take()), + &self.value.as_ref(), + ancestor.as_ref().and_then(|anc| anc.value.as_ref()), ) { (&Some(ref left), Some(ref right)) => { if left != right { - Some(Patch::Replace(left.to_string(), ())) + Some(Patch::Replace(&**left, ())) } else { None } } - (&Some(ref left), None) => Some(Patch::Add(left.to_string(), ())), - (&None, Some(right)) => Some(Patch::Remove(right)), + (&Some(ref left), None) => Some(Patch::Add(&**left, ())), + (&None, Some(right)) => Some(Patch::Remove(&**right)), (&None, None) => None, } } From 2d466f905222605ee1c4e1cb288d450ea871fa24 Mon Sep 17 00:00:00 2001 From: Johann Tuffe Date: Wed, 14 Aug 2019 11:03:54 +0800 Subject: [PATCH 004/193] simplify diff_attributes and avoid allocations --- src/virtual_dom/vtag.rs | 54 +++++++++++++++-------------------------- 1 file changed, 20 insertions(+), 34 deletions(-) diff --git a/src/virtual_dom/vtag.rs b/src/virtual_dom/vtag.rs index 2c0a42cf2be..9c276fad31a 100644 --- a/src/virtual_dom/vtag.rs +++ b/src/virtual_dom/vtag.rs @@ -5,7 +5,6 @@ use crate::html::{Component, Scope}; use log::warn; use std::borrow::Cow; use std::cmp::PartialEq; -use std::collections::HashSet; use std::fmt; use stdweb::unstable::TryFrom; use stdweb::web::html_element::InputElement; @@ -212,43 +211,30 @@ impl VTag { /// /// This also handles patching of attributes when the keys are equal but /// the values are different. - fn diff_attributes(&mut self, ancestor: &mut Option) -> Vec> { - let mut changes = Vec::new(); - if let Some(ref mut ancestor) = ancestor { + fn diff_attributes<'a>(&'a self, ancestor: &'a Option) -> Vec> { + if let Some(ref ancestor) = ancestor { // Only change what is necessary. - let self_keys = self.attributes.keys().collect::>(); - let ancestor_keys = ancestor.attributes.keys().collect::>(); - let to_add = self_keys.difference(&ancestor_keys).map(|key| { - let value = self.attributes.get(*key).expect("attribute of vtag lost"); - Patch::Add(key.to_string(), value.to_string()) - }); - changes.extend(to_add); - for key in self_keys.intersection(&ancestor_keys) { - let self_value = self - .attributes - .get(*key) - .expect("attribute of self side lost"); - let ancestor_value = ancestor - .attributes - .get(*key) - .expect("attribute of ancestor side lost"); - if self_value != ancestor_value { - let mutator = Patch::Replace(key.to_string(), self_value.to_string()); - changes.push(mutator); - } - } - let to_remove = ancestor_keys - .difference(&self_keys) - .map(|key| Patch::Remove(key.to_string())); - changes.extend(to_remove); + let mut changes = Vec::with_capacity(self.attributes.len() + ancestor.attributes.len()); + let to_add_or_replace = self.attributes + .iter() + .filter_map(|(key, value)| match ancestor.attributes.get(&**key) { + None => Some(Patch::Add(&**key, &**value)), + Some(ancestor_value) if value == ancestor_value => { + Some(Patch::Replace(&**key, &**value)) + } + _ => None, + }); + let to_remove = ancestor + .attributes + .keys() + .filter(|key| !self.attributes.contains_key(&**key)) + .map(|key| Patch::Remove(&**key)); + changes.extend(to_add_or_replace.chain(to_remove)); + changes } else { // Add everything - for (key, value) in &self.attributes { - let mutator = Patch::Add(key.to_string(), value.to_string()); - changes.push(mutator); - } + self.attributes.iter().map(|(key, value)| Patch::Add(&**key, &**value)).collect() } - changes } /// Similar to `diff_attributers` except there is only a single `kind`. From a3bda76f26fc57815015c4395b4d621c0695ab25 Mon Sep 17 00:00:00 2001 From: Johann Tuffe Date: Wed, 14 Aug 2019 11:38:48 +0800 Subject: [PATCH 005/193] return iterator for diff_classes and diff_attributes --- src/virtual_dom/vtag.rs | 74 ++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 46 deletions(-) diff --git a/src/virtual_dom/vtag.rs b/src/virtual_dom/vtag.rs index 9c276fad31a..ae3870efb0f 100644 --- a/src/virtual_dom/vtag.rs +++ b/src/virtual_dom/vtag.rs @@ -178,63 +178,45 @@ impl VTag { /// - items that are the same stay the same. /// /// Otherwise just add everything. - fn diff_classes<'a>(&'a self, ancestor: &'a Option) -> Vec> { - // TODO: Use generator in order to avoid useless alloc - - if let Some(ref ancestor) = ancestor { - // Only change what is necessary. - let mut changes = Vec::with_capacity(self.classes.set.len() + ancestor.classes.set.len()); - let to_add = self - .classes - .set - .difference(&ancestor.classes.set) - .map(|class| Patch::Add(&**class, ())); - let to_remove = ancestor - .classes - .set - .difference(&self.classes.set) - .map(|class| Patch::Remove(&**class)); - changes.extend(to_add.chain(to_remove)); - changes - } else { - // Add everything - self - .classes - .set - .iter() - .map(|class| Patch::Add(&**class, ())) - .collect() - } + fn diff_classes<'a>(&'a self, ancestor: &'a Option) -> impl Iterator> + 'a { + let to_add = self.classes.set.iter() + .filter(move |class| ancestor.as_ref().map_or(true, |ancestor| !ancestor.classes.set.contains(&**class))) + .map(|class| Patch::Add(&**class, ())); + + let to_remove = ancestor + .iter() + .flat_map(move |ancestor| ancestor.classes.set.difference(&self.classes.set)) + .map(|class| Patch::Remove(&**class)); + + to_add.chain(to_remove) } /// Similar to diff_classes except for attributes. /// /// This also handles patching of attributes when the keys are equal but /// the values are different. - fn diff_attributes<'a>(&'a self, ancestor: &'a Option) -> Vec> { - if let Some(ref ancestor) = ancestor { - // Only change what is necessary. - let mut changes = Vec::with_capacity(self.attributes.len() + ancestor.attributes.len()); - let to_add_or_replace = self.attributes - .iter() - .filter_map(|(key, value)| match ancestor.attributes.get(&**key) { + fn diff_attributes<'a>(&'a self, ancestor: &'a Option) + -> impl Iterator> + 'a + { + // Only change what is necessary. + let to_add_or_replace = self.attributes + .iter() + .filter_map(move |(key, value)| { + match ancestor.as_ref().and_then(|ancestor| ancestor.attributes.get(&**key)) { None => Some(Patch::Add(&**key, &**value)), Some(ancestor_value) if value == ancestor_value => { Some(Patch::Replace(&**key, &**value)) } _ => None, - }); - let to_remove = ancestor - .attributes - .keys() - .filter(|key| !self.attributes.contains_key(&**key)) - .map(|key| Patch::Remove(&**key)); - changes.extend(to_add_or_replace.chain(to_remove)); - changes - } else { - // Add everything - self.attributes.iter().map(|(key, value)| Patch::Add(&**key, &**value)).collect() - } + } + }); + let to_remove = ancestor + .iter() + .flat_map(|ancestor| ancestor.attributes.keys()) + .filter(move |key| !self.attributes.contains_key(&**key)) + .map(|key| Patch::Remove(&**key)); + + to_add_or_replace.chain(to_remove) } /// Similar to `diff_attributers` except there is only a single `kind`. From bd4659a399b5030e893b86363af38abb026842e1 Mon Sep 17 00:00:00 2001 From: Johann Tuffe Date: Wed, 14 Aug 2019 11:43:39 +0800 Subject: [PATCH 006/193] rustfmt on vtags --- src/virtual_dom/vtag.rs | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/virtual_dom/vtag.rs b/src/virtual_dom/vtag.rs index ae3870efb0f..bf8a72593ba 100644 --- a/src/virtual_dom/vtag.rs +++ b/src/virtual_dom/vtag.rs @@ -178,9 +178,19 @@ impl VTag { /// - items that are the same stay the same. /// /// Otherwise just add everything. - fn diff_classes<'a>(&'a self, ancestor: &'a Option) -> impl Iterator> + 'a { - let to_add = self.classes.set.iter() - .filter(move |class| ancestor.as_ref().map_or(true, |ancestor| !ancestor.classes.set.contains(&**class))) + fn diff_classes<'a>( + &'a self, + ancestor: &'a Option, + ) -> impl Iterator> + 'a { + let to_add = self + .classes + .set + .iter() + .filter(move |class| { + ancestor + .as_ref() + .map_or(true, |ancestor| !ancestor.classes.set.contains(&**class)) + }) .map(|class| Patch::Add(&**class, ())); let to_remove = ancestor @@ -195,14 +205,17 @@ impl VTag { /// /// This also handles patching of attributes when the keys are equal but /// the values are different. - fn diff_attributes<'a>(&'a self, ancestor: &'a Option) - -> impl Iterator> + 'a - { + fn diff_attributes<'a>( + &'a self, + ancestor: &'a Option, + ) -> impl Iterator> + 'a { // Only change what is necessary. - let to_add_or_replace = self.attributes - .iter() - .filter_map(move |(key, value)| { - match ancestor.as_ref().and_then(|ancestor| ancestor.attributes.get(&**key)) { + let to_add_or_replace = + self.attributes.iter().filter_map(move |(key, value)| { + match ancestor + .as_ref() + .and_then(|ancestor| ancestor.attributes.get(&**key)) + { None => Some(Patch::Add(&**key, &**value)), Some(ancestor_value) if value == ancestor_value => { Some(Patch::Replace(&**key, &**value)) From 02771be405a4ec57d12c1e05303f04a101e87983 Mon Sep 17 00:00:00 2001 From: Johann Tuffe Date: Wed, 14 Aug 2019 15:17:43 +0800 Subject: [PATCH 007/193] clean apply_diff --- src/virtual_dom/vnode.rs | 17 +++--------- src/virtual_dom/vtag.rs | 57 ++++++++++++++++------------------------ 2 files changed, 26 insertions(+), 48 deletions(-) diff --git a/src/virtual_dom/vnode.rs b/src/virtual_dom/vnode.rs index 679df81c3b7..703babcfe34 100644 --- a/src/virtual_dom/vnode.rs +++ b/src/virtual_dom/vnode.rs @@ -121,19 +121,10 @@ impl fmt::Debug for VNode { impl PartialEq for VNode { fn eq(&self, other: &VNode) -> bool { - match *self { - VNode::VTag(ref vtag_a) => match *other { - VNode::VTag(ref vtag_b) => vtag_a == vtag_b, - _ => false, - }, - VNode::VText(ref vtext_a) => match *other { - VNode::VText(ref vtext_b) => vtext_a == vtext_b, - _ => false, - }, - _ => { - // TODO Implement it - false - } + match (self, other) { + (VNode::VTag(vtag_a), VNode::VTag(vtag_b)) => vtag_a == vtag_b, + (VNode::VText(vtext_a), VNode::VText(vtext_b)) => vtext_a == vtext_b, + _ => false, // TODO: Implement other variants } } } diff --git a/src/virtual_dom/vtag.rs b/src/virtual_dom/vtag.rs index bf8a72593ba..0c17b90702e 100644 --- a/src/virtual_dom/vtag.rs +++ b/src/virtual_dom/vtag.rs @@ -270,7 +270,7 @@ impl VTag { } } - fn apply_diffs(&mut self, element: &Element, ancestor: &mut Option) { + fn apply_diffs(&mut self, element: &Element, ancestor: &Option) { // Update parameters let changes = self.diff_classes(ancestor); for change in changes { @@ -303,34 +303,24 @@ impl VTag { // attribute as `checked` parameter, not `defaultChecked` as browsers do if let Ok(input) = InputElement::try_from(element.clone()) { if let Some(change) = self.diff_kind(ancestor) { - match change { - Patch::Add(kind, _) | Patch::Replace(kind, _) => { - //https://github.com/koute/stdweb/commit/3b85c941db00b8e3c942624afd50c5929085fb08 - //input.set_kind(&kind); - let input = &input; - js! { @(no_return) - @{input}.type = @{kind}; - } - } - Patch::Remove(_) => { - //input.set_kind(""); - let input = &input; - js! { @(no_return) - @{input}.type = ""; - } - } + let kind = match change { + Patch::Add(kind, _) | Patch::Replace(kind, _) => kind, + Patch::Remove(_) => "", + }; + //https://github.com/koute/stdweb/commit/3b85c941db00b8e3c942624afd50c5929085fb08 + //input.set_kind(&kind); + let input = &input; + js! { @(no_return) + @{input}.type = @{kind}; } } if let Some(change) = self.diff_value(ancestor) { - match change { - Patch::Add(kind, _) | Patch::Replace(kind, _) => { - input.set_raw_value(&kind); - } - Patch::Remove(_) => { - input.set_raw_value(""); - } - } + let raw_value = match change { + Patch::Add(kind, _) | Patch::Replace(kind, _) => kind, + Patch::Remove(_) => "", + }; + input.set_raw_value(raw_value); } // IMPORTANT! This parameter has to be set every time @@ -338,14 +328,11 @@ impl VTag { set_checked(&input, self.checked); } else if let Ok(tae) = TextAreaElement::try_from(element.clone()) { if let Some(change) = self.diff_value(ancestor) { - match change { - Patch::Add(value, _) | Patch::Replace(value, _) => { - tae.set_value(&value); - } - Patch::Remove(_) => { - tae.set_value(""); - } - } + let value = match change { + Patch::Add(kind, _) | Patch::Replace(kind, _) => kind, + Patch::Remove(_) => "", + }; + tae.set_value(value); } } } @@ -411,7 +398,7 @@ impl VDiff for VTag { Reform::Keep => {} Reform::Before(before) => { let element = if self.tag == "svg" - || parent.namespace_uri() == Some(SVG_NAMESPACE.to_string()) + || parent.namespace_uri().map_or(false, |ns| ns == SVG_NAMESPACE) { document() .create_element_ns(SVG_NAMESPACE, &self.tag) @@ -448,7 +435,7 @@ impl VDiff for VTag { std::mem::swap(&mut ancestor_childs, &mut a.childs); } - self.apply_diffs(&element, &mut ancestor); + self.apply_diffs(&element, &ancestor); // Every render it removes all listeners and attach it back later // TODO Compare references of handler to do listeners update better From 2f1a46be1ce9a5b9e9c313bb17325ecbaa0bdd78 Mon Sep 17 00:00:00 2001 From: Johann Tuffe Date: Wed, 14 Aug 2019 15:51:03 +0800 Subject: [PATCH 008/193] more cleaning --- src/virtual_dom/vtag.rs | 78 ++++++++++------------------------------- 1 file changed, 19 insertions(+), 59 deletions(-) diff --git a/src/virtual_dom/vtag.rs b/src/virtual_dom/vtag.rs index 0c17b90702e..6da401d5292 100644 --- a/src/virtual_dom/vtag.rs +++ b/src/virtual_dom/vtag.rs @@ -398,7 +398,9 @@ impl VDiff for VTag { Reform::Keep => {} Reform::Before(before) => { let element = if self.tag == "svg" - || parent.namespace_uri().map_or(false, |ns| ns == SVG_NAMESPACE) + || parent + .namespace_uri() + .map_or(false, |ns| ns == SVG_NAMESPACE) { document() .create_element_ns(SVG_NAMESPACE, &self.tag) @@ -430,16 +432,11 @@ impl VDiff for VTag { let element = self.reference.clone().expect("element expected"); { - let mut ancestor_childs = Vec::new(); - if let Some(ref mut a) = ancestor { - std::mem::swap(&mut ancestor_childs, &mut a.childs); - } - self.apply_diffs(&element, &ancestor); // Every render it removes all listeners and attach it back later // TODO Compare references of handler to do listeners update better - if let Some(mut ancestor) = ancestor { + if let Some(ref mut ancestor) = ancestor { for handle in ancestor.captured.drain(..) { handle.remove(); } @@ -454,7 +451,7 @@ impl VDiff for VTag { // Start with an empty precursor, because it put childs to itself let mut precursor = None; let mut self_childs = self.childs.iter_mut(); - let mut ancestor_childs = ancestor_childs.drain(..); + let mut ancestor_childs = ancestor.into_iter().flat_map(|a| a.childs); loop { match (self_childs.next(), ancestor_childs.next()) { (Some(left), right) => { @@ -495,56 +492,19 @@ fn set_checked(input: &InputElement, value: bool) { impl PartialEq for VTag { fn eq(&self, other: &VTag) -> bool { - if self.tag != other.tag { - return false; - } - - if self.value != other.value { - return false; - } - - if self.kind != other.kind { - return false; - } - - if self.checked != other.checked { - return false; - } - - if self.listeners.len() != other.listeners.len() { - return false; - } - - for i in 0..self.listeners.len() { - let a = &self.listeners[i]; - let b = &other.listeners[i]; - - if a.kind() != b.kind() { - return false; - } - } - - if self.attributes != other.attributes { - return false; - } - - if self.classes.set.iter().ne(other.classes.set.iter()) { - return false; - } - - if self.childs.len() != other.childs.len() { - return false; - } - - for i in 0..self.childs.len() { - let a = &self.childs[i]; - let b = &other.childs[i]; - - if a != b { - return false; - } - } - - true + self.tag == other.tag + && self.value == other.value + && self.kind == other.kind + && self.checked == other.checked + && self.listeners.len() == other.listeners.len() + && self + .listeners + .iter() + .map(|l| l.kind()) + .eq(other.listeners.iter().map(|l| l.kind())) + && self.attributes == other.attributes + && self.classes.set.len() == other.classes.set.len() + && self.classes.set.iter().eq(other.classes.set.iter()) + && &self.childs == &other.childs } } From 048865f7031cffd3dc5c6913bd9f64138f8a2a0f Mon Sep 17 00:00:00 2001 From: Johann Tuffe Date: Mon, 19 Aug 2019 09:07:03 +0800 Subject: [PATCH 009/193] apply suggestions --- src/virtual_dom/vtag.rs | 53 +++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/src/virtual_dom/vtag.rs b/src/virtual_dom/vtag.rs index 6da401d5292..f0f320d4e60 100644 --- a/src/virtual_dom/vtag.rs +++ b/src/virtual_dom/vtag.rs @@ -182,16 +182,19 @@ impl VTag { &'a self, ancestor: &'a Option, ) -> impl Iterator> + 'a { - let to_add = self - .classes - .set - .iter() - .filter(move |class| { - ancestor - .as_ref() - .map_or(true, |ancestor| !ancestor.classes.set.contains(&**class)) - }) - .map(|class| Patch::Add(&**class, ())); + let to_add = { + let all_or_nothing = not(ancestor) + .iter() + .flat_map(move |_| self.classes.set.iter()) + .map(|class| Patch::Add(&**class, ())); + + let ancestor_difference = ancestor + .iter() + .flat_map(move |ancestor| self.classes.set.difference(&ancestor.classes.set)) + .map(|class| Patch::Add(&**class, ())); + + all_or_nothing.chain(ancestor_difference) + }; let to_remove = ancestor .iter() @@ -235,38 +238,38 @@ impl VTag { /// Similar to `diff_attributers` except there is only a single `kind`. fn diff_kind<'a>(&'a self, ancestor: &'a Option) -> Option> { match ( - &self.kind.as_ref(), + self.kind.as_ref(), ancestor.as_ref().and_then(|anc| anc.kind.as_ref()), ) { - (&Some(ref left), Some(ref right)) => { + (Some(ref left), Some(ref right)) => { if left != right { Some(Patch::Replace(&**left, ())) } else { None } } - (&Some(ref left), None) => Some(Patch::Add(&**left, ())), - (&None, Some(right)) => Some(Patch::Remove(&**right)), - (&None, None) => None, + (Some(ref left), None) => Some(Patch::Add(&**left, ())), + (None, Some(right)) => Some(Patch::Remove(&**right)), + (None, None) => None, } } /// Almost identical in spirit to `diff_kind` fn diff_value<'a>(&'a self, ancestor: &'a Option) -> Option> { match ( - &self.value.as_ref(), + self.value.as_ref(), ancestor.as_ref().and_then(|anc| anc.value.as_ref()), ) { - (&Some(ref left), Some(ref right)) => { + (Some(ref left), Some(ref right)) => { if left != right { Some(Patch::Replace(&**left, ())) } else { None } } - (&Some(ref left), None) => Some(Patch::Add(&**left, ())), - (&None, Some(right)) => Some(Patch::Remove(&**right)), - (&None, None) => None, + (Some(ref left), None) => Some(Patch::Add(&**left, ())), + (None, Some(right)) => Some(Patch::Remove(&**right)), + (None, None) => None, } } @@ -436,7 +439,7 @@ impl VDiff for VTag { // Every render it removes all listeners and attach it back later // TODO Compare references of handler to do listeners update better - if let Some(ref mut ancestor) = ancestor { + if let Some(ancestor) = ancestor.as_mut() { for handle in ancestor.captured.drain(..) { handle.remove(); } @@ -508,3 +511,11 @@ impl PartialEq for VTag { && &self.childs == &other.childs } } + +pub(crate) fn not(option: &Option) -> &Option<()> { + if option.is_some() { + &None + } else { + &Some(()) + } +} From c97d23cb4054cfe7264f7aff9a3a3aac87567381 Mon Sep 17 00:00:00 2001 From: Maximilian Goisser Date: Tue, 20 Aug 2019 23:13:22 +0200 Subject: [PATCH 010/193] Update proc-macro2, syn and quote to 1.0 CLOSES #590 --- crates/macro/Cargo.toml | 6 +++--- crates/macro/src/derive_props/builder.rs | 10 +++------- crates/macro/src/derive_props/field.rs | 9 ++++----- .../macro/src/html_tree/html_tag/tag_attributes.rs | 13 +++++++------ tests/macro/html-node-fail.stderr | 4 ++-- 5 files changed, 19 insertions(+), 23 deletions(-) diff --git a/crates/macro/Cargo.toml b/crates/macro/Cargo.toml index a53d70e3bd6..f6fdfa20652 100644 --- a/crates/macro/Cargo.toml +++ b/crates/macro/Cargo.toml @@ -21,9 +21,9 @@ proc-macro = true boolinator = "2.4.0" lazy_static = "1.3.0" proc-macro-hack = "0.5" -proc-macro2 = "0.4" -quote = "0.6" -syn = { version = "^0.15.34", features = ["full", "extra-traits"] } +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "1.0", features = ["full", "extra-traits"] } [dev-dependencies] yew = { path = "../.." } diff --git a/crates/macro/src/derive_props/builder.rs b/crates/macro/src/derive_props/builder.rs index e374e3a7158..8d1f50800bc 100644 --- a/crates/macro/src/derive_props/builder.rs +++ b/crates/macro/src/derive_props/builder.rs @@ -9,7 +9,6 @@ use super::generics::{to_arguments, with_param_bounds, GenericArguments}; use super::{DerivePropsInput, PropField}; use proc_macro2::{Ident, Span}; use quote::{quote, ToTokens}; -use std::iter; pub struct PropsBuilder<'a> { builder_name: &'a Ident, @@ -36,9 +35,6 @@ impl ToTokens for PropsBuilder<'_> { .. } = props; - let step_trait_repeat = iter::repeat(step_trait); - let vis_repeat = iter::repeat(&vis); - let build_step = self.build_step(); let impl_steps = self.impl_steps(); let set_fields = self.set_fields(); @@ -59,13 +55,13 @@ impl ToTokens for PropsBuilder<'_> { let builder = quote! { #( #[doc(hidden)] - #vis_repeat struct #step_names; + #vis struct #step_names; )* #[doc(hidden)] #vis trait #step_trait {} - #(impl #step_trait_repeat for #step_names {})* + #(impl #step_trait for #step_names {})* #[doc(hidden)] #vis struct #builder_name#step_generics { @@ -73,7 +69,7 @@ impl ToTokens for PropsBuilder<'_> { _marker: ::std::marker::PhantomData<#step_generic_param>, } - #(#impl_steps)* + #impl_steps impl#impl_generics #builder_name<#generic_args> #where_clause { #[doc(hidden)] diff --git a/crates/macro/src/derive_props/field.rs b/crates/macro/src/derive_props/field.rs index b216b66db0e..cfa849448c9 100644 --- a/crates/macro/src/derive_props/field.rs +++ b/crates/macro/src/derive_props/field.rs @@ -4,7 +4,6 @@ use quote::quote; use std::cmp::{Ord, Ordering, PartialEq, PartialOrd}; use std::convert::TryFrom; use syn::parse::Result; -use syn::punctuated; use syn::spanned::Spanned; use syn::{Error, Field, Meta, MetaList, NestedMeta, Type, Visibility}; @@ -121,12 +120,12 @@ impl PropField { return Err(expected_required); }; - let word_ident = match first_nested { - punctuated::Pair::End(NestedMeta::Meta(Meta::Word(ident))) => ident, + let word_path = match first_nested { + NestedMeta::Meta(Meta::Path(path)) => path, _ => return Err(expected_required), }; - if word_ident != "required" { + if !word_path.is_ident("required") { return Err(expected_required); } @@ -149,7 +148,7 @@ impl PropField { _ => None, })?; - if meta_list.ident == "props" { + if meta_list.path.is_ident("props") { Some(meta_list) } else { None diff --git a/crates/macro/src/html_tree/html_tag/tag_attributes.rs b/crates/macro/src/html_tree/html_tag/tag_attributes.rs index efce0733899..879d20e3769 100644 --- a/crates/macro/src/html_tree/html_tag/tag_attributes.rs +++ b/crates/macro/src/html_tree/html_tag/tag_attributes.rs @@ -2,10 +2,10 @@ use crate::html_tree::HtmlProp as TagAttribute; use crate::PeekValue; use lazy_static::lazy_static; use proc_macro2::TokenStream; -use quote::{quote, quote_spanned}; +use quote::{quote, quote_spanned, ToTokens}; use std::collections::HashMap; use syn::parse::{Parse, ParseStream, Result as ParseResult}; -use syn::{Expr, ExprClosure, ExprTuple, Ident}; +use syn::{Expr, ExprClosure, ExprTuple, Ident, Pat}; pub struct TagAttributes { pub attributes: Vec, @@ -144,10 +144,11 @@ impl TagAttributes { )); } - let var = match inputs.first().unwrap().into_value() { - syn::FnArg::Inferred(pat) => pat, - _ => return Err(syn::Error::new_spanned(or_span, "invalid closure argument")), - }; + let var = match inputs.first().unwrap() { + Pat::Ident(pat) => Ok(pat.into_token_stream()), + Pat::Wild(pat) => Ok(pat.into_token_stream()), + _ => Err(syn::Error::new_spanned(or_span, "invalid closure argument")), + }?; let handler = Ident::new(&format!("__yew_{}_handler", name.to_string()), name.span()); let listener = diff --git a/tests/macro/html-node-fail.stderr b/tests/macro/html-node-fail.stderr index 6b23450139a..6cc3a67c90a 100644 --- a/tests/macro/html-node-fail.stderr +++ b/tests/macro/html-node-fail.stderr @@ -22,7 +22,7 @@ error: unsupported type 11 | html! { b"str" }; | ^^^^^^ -error: unsupported type +error: int literal is too large --> $DIR/html-node-fail.rs:12:14 | 12 | html! { 1111111111111111111111111111111111111111111111111111111111111111111111111111 }; @@ -40,7 +40,7 @@ error: unsupported type 14 | html! { { b"str" } }; | ^^^^^^ -error: unsupported type +error: int literal is too large --> $DIR/html-node-fail.rs:15:22 | 15 | html! { { 1111111111111111111111111111111111111111111111111111111111111111111111111111 } }; From 695c91984eb835a11e72ebba3fc01d4af4dcbf44 Mon Sep 17 00:00:00 2001 From: Benjamin Lee Date: Thu, 22 Aug 2019 21:18:27 -0700 Subject: [PATCH 011/193] Fixed typo --- src/app.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index 2ea32d9924b..c11fe7e8497 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,4 +1,4 @@ -//! This module contains `App` struct which used to bootstrap +//! This module contains the `App` struct, which is used to bootstrap //! a component in an isolated scope. use crate::html::{Component, Renderable, Scope}; From f3179f2e9c16aa05635cf0f0580883df4efb5a6c Mon Sep 17 00:00:00 2001 From: Wodann Date: Fri, 23 Aug 2019 20:45:59 +0200 Subject: [PATCH 012/193] Add support for optional callbacks to component properties --- src/virtual_dom/vcomp.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/virtual_dom/vcomp.rs b/src/virtual_dom/vcomp.rs index d3aa1e60f73..09b4f2bc475 100644 --- a/src/virtual_dom/vcomp.rs +++ b/src/virtual_dom/vcomp.rs @@ -158,6 +158,24 @@ where } } +impl<'a, COMP, F, IN> Transformer>> for VComp +where + COMP: Component + Renderable, + F: Fn(IN) -> COMP::Message + 'static, +{ + fn transform(scope: ScopeHolder, from: F) -> Option> { + let callback = move |arg| { + let msg = from(arg); + if let Some(ref mut sender) = *scope.borrow_mut() { + sender.send_message(msg); + } else { + panic!("unactivated callback, parent component have to activate it"); + } + }; + Some(callback.into()) + } +} + impl Unmounted { /// mount a virtual component with a generator. fn mount( From bc447ad22d578b41b43a86ed5ec12f6876c4baf7 Mon Sep 17 00:00:00 2001 From: Wodann Date: Fri, 23 Aug 2019 21:46:47 +0200 Subject: [PATCH 013/193] Add tests for a component with optional callback --- tests/macro/html-component-pass.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/macro/html-component-pass.rs b/tests/macro/html-component-pass.rs index 9977f2ff5a3..c70e9e505cb 100644 --- a/tests/macro/html-component-pass.rs +++ b/tests/macro/html-component-pass.rs @@ -9,6 +9,7 @@ pub struct ChildProperties { #[props(required)] pub int: i32, pub vec: Vec, + pub optional_callback: Option>, } pub struct ChildComponent; @@ -80,6 +81,13 @@ pass_helper! { html! { }; + + html! { + <> + + + + }; } fn main() {} From d6917443fb3daf73447a4e2ccfd2f434c593eef1 Mon Sep 17 00:00:00 2001 From: king6cong Date: Fri, 23 Aug 2019 15:02:33 +0800 Subject: [PATCH 014/193] Fix typo --- src/virtual_dom/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/virtual_dom/mod.rs b/src/virtual_dom/mod.rs index ce461f536fe..9ba19f2a03c 100644 --- a/src/virtual_dom/mod.rs +++ b/src/virtual_dom/mod.rs @@ -24,7 +24,7 @@ pub trait Listener { /// Returns standard name of DOM's event. fn kind(&self) -> &'static str; /// Attaches listener to the element and uses scope instance to send - /// prepaired event back to the yew main loop. + /// prepared event back to the yew main loop. fn attach(&mut self, element: &Element, scope: Scope) -> EventListenerHandle; } From a7e251cdb05bfe389e28d2867e48eee91d8b9377 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Tue, 13 Aug 2019 09:22:38 -0400 Subject: [PATCH 015/193] First pass at component children --- Cargo.toml | 1 + crates/macro/src/derive_props/field.rs | 20 +- crates/macro/src/html_tree/html_component.rs | 222 +++++++++++++++---- examples/nested_components/Cargo.toml | 8 + examples/nested_components/src/child.rs | 48 ++++ examples/nested_components/src/lib.rs | 37 ++++ examples/nested_components/src/main.rs | 3 + examples/nested_components/src/parent.rs | 51 +++++ src/html/mod.rs | 9 + tests/macro/html-component-fail.rs | 3 + 10 files changed, 360 insertions(+), 42 deletions(-) create mode 100644 examples/nested_components/Cargo.toml create mode 100644 examples/nested_components/src/child.rs create mode 100644 examples/nested_components/src/lib.rs create mode 100644 examples/nested_components/src/main.rs create mode 100644 examples/nested_components/src/parent.rs diff --git a/Cargo.toml b/Cargo.toml index 98c4418619f..14c907298cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,7 @@ members = [ "examples/minimal", "examples/mount_point", "examples/multi_thread", + "examples/nested_components", "examples/npm_and_rest", "examples/routing", "examples/server", diff --git a/crates/macro/src/derive_props/field.rs b/crates/macro/src/derive_props/field.rs index cfa849448c9..ef3e470d8b9 100644 --- a/crates/macro/src/derive_props/field.rs +++ b/crates/macro/src/derive_props/field.rs @@ -170,13 +170,29 @@ impl TryFrom for PropField { impl PartialOrd for PropField { fn partial_cmp(&self, other: &PropField) -> Option { - self.name.partial_cmp(&other.name) + if self.name == other.name { + Some(Ordering::Equal) + } else if self.name == "children" { + Some(Ordering::Greater) + } else if other.name == "children" { + Some(Ordering::Less) + } else { + self.name.partial_cmp(&other.name) + } } } impl Ord for PropField { fn cmp(&self, other: &PropField) -> Ordering { - self.name.cmp(&other.name) + if self.name == other.name { + Ordering::Equal + } else if self.name == "children" { + Ordering::Greater + } else if other.name == "children" { + Ordering::Less + } else { + self.name.cmp(&other.name) + } } } diff --git a/crates/macro/src/html_tree/html_component.rs b/crates/macro/src/html_tree/html_component.rs index 725b939502d..8535a2770ca 100644 --- a/crates/macro/src/html_tree/html_component.rs +++ b/crates/macro/src/html_tree/html_component.rs @@ -1,5 +1,6 @@ use super::HtmlProp; use super::HtmlPropSuffix; +use super::HtmlTree; use crate::PeekValue; use boolinator::Boolinator; use proc_macro2::Span; @@ -7,47 +8,82 @@ use quote::{quote, quote_spanned, ToTokens}; use syn::buffer::Cursor; use syn::parse; use syn::parse::{Parse, ParseStream, Result as ParseResult}; +use syn::punctuated::Punctuated; use syn::spanned::Spanned; -use syn::{Ident, Token, Type}; +use syn::{Ident, Path, PathArguments, PathSegment, Token, Type, TypePath}; -pub struct HtmlComponent(HtmlComponentInner); +pub struct HtmlComponent { + ty: Type, + props: Option, + children: Vec, +} -impl PeekValue<()> for HtmlComponent { - fn peek(cursor: Cursor) -> Option<()> { +impl PeekValue for HtmlComponent { + fn peek(cursor: Cursor) -> Option { let (punct, cursor) = cursor.punct()?; (punct.as_char() == '<').as_option()?; - HtmlComponent::peek_type(cursor) + let (ty, _) = HtmlComponent::peek_type(cursor)?; + Some(ty) } } impl Parse for HtmlComponent { fn parse(input: ParseStream) -> ParseResult { - let lt = input.parse::()?; - let HtmlPropSuffix { stream, div, gt } = input.parse()?; - if div.is_none() { - return Err(syn::Error::new_spanned( - HtmlComponentTag { lt, gt }, - "expected component tag be of form `< .. />`", - )); + if HtmlComponentClose::peek(input.cursor()).is_some() { + return match input.parse::() { + Ok(close) => Err(syn::Error::new_spanned( + close, + "this close tag has no corresponding open tag", + )), + Err(err) => Err(err), + }; + } + + let open = input.parse::()?; + // Return early if it's a self-closing tag + if open.div.is_some() { + return Ok(HtmlComponent { + ty: open.ty, + props: open.props, + children: Vec::new(), + }); } - match parse(stream) { - Ok(comp) => Ok(HtmlComponent(comp)), - Err(err) => { - if err.to_string().starts_with("unexpected end of input") { - Err(syn::Error::new_spanned(div, err.to_string())) - } else { - Err(err) + let mut children: Vec = vec![]; + loop { + if input.is_empty() { + return Err(syn::Error::new_spanned( + open, + "this open tag has no corresponding close tag", + )); + } + if let Some(next_ty) = HtmlComponentClose::peek(input.cursor()) { + if open.ty == next_ty { + break; } } + + children.push(input.parse()?); } + + input.parse::()?; + + Ok(HtmlComponent { + ty: open.ty, + props: open.props, + children, + }) } } impl ToTokens for HtmlComponent { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let HtmlComponentInner { ty, props } = &self.0; + let Self { + ty, + props, + children, + } = self; let vcomp_scope = Ident::new("__yew_vcomp_scope", Span::call_site()); let validate_props = if let Some(Props::List(ListProps(vec_props))) = props { @@ -56,6 +92,12 @@ impl ToTokens for HtmlComponent { quote! { #prop_ref.#label; } }); + let check_children = if !children.is_empty() { + quote! { #prop_ref.children; } + } else { + quote! {} + }; + // This is a hack to avoid allocating memory but still have a reference to a props // struct so that attributes can be checked against it @@ -71,12 +113,29 @@ impl ToTokens for HtmlComponent { quote! { #unallocated_prop_ref + #check_children #(#check_props)* } } else { quote! {} }; + let set_children = if !children.is_empty() { + quote! { + .children(::std::boxed::Box::new(move || { + || -> ::yew::html::Html<#ty> { + ::yew::virtual_dom::VNode::VList( + ::yew::virtual_dom::vlist::VList { + childs: vec![#(#children,)*], + } + ) + } + }())) + } + } else { + quote! {} + }; + let init_props = if let Some(props) = props { match props { Props::List(ListProps(vec_props)) => { @@ -89,6 +148,7 @@ impl ToTokens for HtmlComponent { quote! { <<#ty as ::yew::html::Component>::Properties as ::yew::html::Properties>::builder() #(#set_props)* + #set_children .build() } } @@ -96,7 +156,9 @@ impl ToTokens for HtmlComponent { } } else { quote! { - <<#ty as ::yew::html::Component>::Properties as ::yew::html::Properties>::builder().build() + <<#ty as ::yew::html::Component>::Properties as ::yew::html::Properties>::builder() + #set_children + .build() } }; @@ -135,13 +197,18 @@ impl HtmlComponent { Some(cursor) } - fn peek_type(mut cursor: Cursor) -> Option<()> { + fn peek_type(mut cursor: Cursor) -> Option<(Type, Cursor)> { let mut colons_optional = true; let mut last_ident = None; + let mut leading_colon = None; + let mut segments = Punctuated::new(); loop { let mut post_colons_cursor = cursor; if let Some(c) = Self::double_colon(post_colons_cursor) { + if colons_optional { + leading_colon = Some(Token![::](Span::call_site())); + } post_colons_cursor = c; } else if !colons_optional { break; @@ -149,7 +216,11 @@ impl HtmlComponent { if let Some((ident, c)) = post_colons_cursor.ident() { cursor = c; - last_ident = Some(ident); + last_ident = Some(ident.clone()); + segments.push(PathSegment { + ident, + arguments: PathArguments::None, + }); } else { break; } @@ -160,43 +231,103 @@ impl HtmlComponent { let type_str = last_ident?.to_string(); type_str.is_ascii().as_option()?; - type_str.bytes().next()?.is_ascii_uppercase().as_option() + type_str.bytes().next()?.is_ascii_uppercase().as_option()?; + + Some(( + Type::Path(TypePath { + qself: None, + path: Path { + leading_colon, + segments, + }, + }), + cursor, + )) } } -pub struct HtmlComponentInner { +struct HtmlComponentOpen { + lt: Token![<], ty: Type, props: Option, + div: Option, + gt: Token![>], } -impl Parse for HtmlComponentInner { +impl PeekValue for HtmlComponentOpen { + fn peek(cursor: Cursor) -> Option { + let (punct, cursor) = cursor.punct()?; + (punct.as_char() == '<').as_option()?; + + let (ty, _) = HtmlComponent::peek_type(cursor)?; + Some(ty) + } +} + +impl Parse for HtmlComponentOpen { fn parse(input: ParseStream) -> ParseResult { + let lt = input.parse::()?; let ty = input.parse()?; // backwards compat let _ = input.parse::(); + let HtmlPropSuffix { stream, div, gt } = input.parse()?; + let props: Option = parse(stream).ok(); + + Ok(HtmlComponentOpen { + lt, + ty, + props, + div, + gt, + }) + } +} - let props = if let Some(prop_type) = Props::peek(input.cursor()) { - match prop_type { - PropType::List => input.parse().map(Props::List).map(Some)?, - PropType::With => input.parse().map(Props::With).map(Some)?, - } - } else { - None - }; - - Ok(HtmlComponentInner { ty, props }) +impl ToTokens for HtmlComponentOpen { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let HtmlComponentOpen { lt, gt, .. } = self; + tokens.extend(quote! {#lt#gt}); } } -struct HtmlComponentTag { +struct HtmlComponentClose { lt: Token![<], + div: Token![/], + ty: Type, gt: Token![>], } -impl ToTokens for HtmlComponentTag { +impl PeekValue for HtmlComponentClose { + fn peek(cursor: Cursor) -> Option { + let (punct, cursor) = cursor.punct()?; + (punct.as_char() == '<').as_option()?; + + let (punct, cursor) = cursor.punct()?; + (punct.as_char() == '/').as_option()?; + + let (ty, cursor) = HtmlComponent::peek_type(cursor)?; + + let (punct, _) = cursor.punct()?; + (punct.as_char() == '>').as_option()?; + + Some(ty) + } +} +impl Parse for HtmlComponentClose { + fn parse(input: ParseStream) -> ParseResult { + Ok(HtmlComponentClose { + lt: input.parse()?, + div: input.parse()?, + ty: input.parse()?, + gt: input.parse()?, + }) + } +} + +impl ToTokens for HtmlComponentClose { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let HtmlComponentTag { lt, gt } = self; - tokens.extend(quote! {#lt#gt}); + let HtmlComponentClose { lt, div, ty, gt } = self; + tokens.extend(quote! {#lt#div#ty#gt}); } } @@ -223,6 +354,17 @@ impl PeekValue for Props { } } +impl Parse for Props { + fn parse(input: ParseStream) -> ParseResult { + let prop_type = Props::peek(input.cursor()) + .ok_or_else(|| syn::Error::new(Span::call_site(), "ignore - no props found"))?; + match prop_type { + PropType::List => input.parse().map(Props::List), + PropType::With => input.parse().map(Props::With), + } + } +} + struct ListProps(Vec); impl Parse for ListProps { fn parse(input: ParseStream) -> ParseResult { diff --git a/examples/nested_components/Cargo.toml b/examples/nested_components/Cargo.toml new file mode 100644 index 00000000000..11c6b56398f --- /dev/null +++ b/examples/nested_components/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "nested_components" +version = "0.1.0" +authors = ["Justin Starry "] +edition = "2018" + +[dependencies] +yew = { path = "../.." } diff --git a/examples/nested_components/src/child.rs b/examples/nested_components/src/child.rs new file mode 100644 index 00000000000..0e246055519 --- /dev/null +++ b/examples/nested_components/src/child.rs @@ -0,0 +1,48 @@ +use yew::prelude::*; + +pub struct Child { + props: Props, +} + +#[derive(Properties)] +pub struct Props { + #[props(required)] + pub on_click: Callback<()>, + #[props(required)] + pub name: String, +} + +pub enum Msg { + Click, +} + +impl Component for Child { + type Message = Msg; + type Properties = Props; + + fn create(props: Self::Properties, _: ComponentLink) -> Self { + Child { props } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + Msg::Click => { + self.props.on_click.emit(()); + } + } + false + } +} + +impl Renderable for Child { + fn view(&self) -> Html { + html! { +
+ { format!("My name is {}", self.props.name) } + +
+ } + } +} diff --git a/examples/nested_components/src/lib.rs b/examples/nested_components/src/lib.rs new file mode 100644 index 00000000000..e4ceb68ef44 --- /dev/null +++ b/examples/nested_components/src/lib.rs @@ -0,0 +1,37 @@ +use yew::prelude::*; + +mod child; +mod parent; + +use child::Child; +use parent::{Msg as ParentMsg, Parent}; + +pub struct Model { + child_name: String, +} + +impl Component for Model { + type Message = (); + type Properties = (); + + fn create(_: Self::Properties, _: ComponentLink) -> Self { + Model { + child_name: "Bobby".to_owned(), + } + } + + fn update(&mut self, _: Self::Message) -> ShouldRender { + true + } +} + +impl Renderable for Model { + fn view(&self) -> Html { + let child_name = self.child_name.clone(); + html! { + + + + } + } +} diff --git a/examples/nested_components/src/main.rs b/examples/nested_components/src/main.rs new file mode 100644 index 00000000000..cf2d79e71f4 --- /dev/null +++ b/examples/nested_components/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + yew::start_app::(); +} diff --git a/examples/nested_components/src/parent.rs b/examples/nested_components/src/parent.rs new file mode 100644 index 00000000000..58cc987d11e --- /dev/null +++ b/examples/nested_components/src/parent.rs @@ -0,0 +1,51 @@ +use yew::prelude::*; + +type Children = Box>; + +pub enum Msg { + Click, + ChildClick, +} + +#[derive(Properties)] +pub struct Props { + #[props(required)] + pub children: Children, +} + +pub struct Parent { + props: Props, + clicker: String, +} + +impl Component for Parent { + type Message = Msg; + type Properties = Props; + + fn create(props: Self::Properties, _: ComponentLink) -> Self { + Parent { + clicker: "none".to_owned(), + props, + } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + Msg::Click => self.clicker = "self".to_string(), + Msg::ChildClick => self.clicker = "child".to_string(), + } + true + } +} + +impl Renderable for Parent { + fn view(&self) -> Html { + html! { +
+ { format!("Last clicked by {}", self.clicker) } + + { self.props.children.view() } +
+ } + } +} diff --git a/src/html/mod.rs b/src/html/mod.rs index 16f841161ab..7dd9a7f82c4 100644 --- a/src/html/mod.rs +++ b/src/html/mod.rs @@ -54,6 +54,15 @@ pub trait Renderable { fn view(&self) -> Html; } +impl Renderable for F +where + F: Fn() -> Html, +{ + fn view(&self) -> Html { + self() + } +} + /// Trait for building properties for a component pub trait Properties { /// Builder that will be used to construct properties diff --git a/tests/macro/html-component-fail.rs b/tests/macro/html-component-fail.rs index 85c83aed675..fd442dc36e1 100644 --- a/tests/macro/html-component-fail.rs +++ b/tests/macro/html-component-fail.rs @@ -29,6 +29,8 @@ impl Renderable for ChildComponent { } } +// TODO add test for nested component with children prop set +// TODO add test for nested component `with props` fn compile_fail() { html! { }; html! { }; @@ -46,6 +48,7 @@ fn compile_fail() { html! { }; html! { }; html! { }; + html! { }; } fn main() {} From bddb2f0b8e4218b865ebc2be5fe5203eb4d9c612 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Sat, 17 Aug 2019 22:13:52 -0400 Subject: [PATCH 016/193] Props --- crates/macro/src/html_tree/html_component.rs | 19 ++++----- crates/macro/src/html_tree/html_iterable.rs | 2 +- crates/macro/src/html_tree/html_list.rs | 8 ++-- crates/macro/src/html_tree/html_node.rs | 8 +--- crates/macro/src/html_tree/html_tag/mod.rs | 2 +- crates/macro/src/html_tree/mod.rs | 43 ++++++++++++++------ examples/nested_components/src/child.rs | 6 +++ examples/nested_components/src/lib.rs | 16 ++++---- examples/nested_components/src/parent.rs | 10 +++-- src/html/mod.rs | 9 ---- src/virtual_dom/mod.rs | 2 +- src/virtual_dom/vcomp.rs | 29 +++++++++++++ src/virtual_dom/vnode.rs | 12 +++++- tests/macro/html-component-fail.rs | 3 ++ 14 files changed, 109 insertions(+), 60 deletions(-) diff --git a/crates/macro/src/html_tree/html_component.rs b/crates/macro/src/html_tree/html_component.rs index 8535a2770ca..07a7452f562 100644 --- a/crates/macro/src/html_tree/html_component.rs +++ b/crates/macro/src/html_tree/html_component.rs @@ -1,6 +1,6 @@ use super::HtmlProp; use super::HtmlPropSuffix; -use super::HtmlTree; +use super::HtmlTreeNested; use crate::PeekValue; use boolinator::Boolinator; use proc_macro2::Span; @@ -15,7 +15,7 @@ use syn::{Ident, Path, PathArguments, PathSegment, Token, Type, TypePath}; pub struct HtmlComponent { ty: Type, props: Option, - children: Vec, + children: Vec, } impl PeekValue for HtmlComponent { @@ -50,7 +50,7 @@ impl Parse for HtmlComponent { }); } - let mut children: Vec = vec![]; + let mut children: Vec = vec![]; loop { if input.is_empty() { return Err(syn::Error::new_spanned( @@ -123,12 +123,9 @@ impl ToTokens for HtmlComponent { let set_children = if !children.is_empty() { quote! { .children(::std::boxed::Box::new(move || { - || -> ::yew::html::Html<#ty> { - ::yew::virtual_dom::VNode::VList( - ::yew::virtual_dom::vlist::VList { - childs: vec![#(#children,)*], - } - ) + #[allow(unused_must_use)] + || -> ::std::vec::Vec<_> { + vec![#(#children.into(),)*] } }())) } @@ -179,9 +176,7 @@ impl ToTokens for HtmlComponent { } let #vcomp_scope: ::yew::virtual_dom::vcomp::ScopeHolder<_> = ::std::default::Default::default(); - ::yew::virtual_dom::VNode::VComp( - ::yew::virtual_dom::VComp::new::<#ty>(#init_props, #vcomp_scope) - ) + ::yew::virtual_dom::VChild::<#ty, _>::new(#init_props, #vcomp_scope) }}); } } diff --git a/crates/macro/src/html_tree/html_iterable.rs b/crates/macro/src/html_tree/html_iterable.rs index 47fac43a8ad..b82c83f4921 100644 --- a/crates/macro/src/html_tree/html_iterable.rs +++ b/crates/macro/src/html_tree/html_iterable.rs @@ -45,7 +45,7 @@ impl ToTokens for HtmlIterable { for __yew_node in __yew_nodes.into_iter() { __yew_vlist.add_child(__yew_node.into()); } - ::yew::virtual_dom::VNode::from(__yew_vlist) + __yew_vlist }}; tokens.extend(new_tokens); diff --git a/crates/macro/src/html_tree/html_list.rs b/crates/macro/src/html_tree/html_list.rs index 5da411c0333..3b6fbb970d5 100644 --- a/crates/macro/src/html_tree/html_list.rs +++ b/crates/macro/src/html_tree/html_list.rs @@ -51,11 +51,9 @@ impl ToTokens for HtmlList { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let html_trees = &self.0; tokens.extend(quote! { - ::yew::virtual_dom::VNode::VList( - ::yew::virtual_dom::vlist::VList { - childs: vec![#(#html_trees,)*], - } - ) + ::yew::virtual_dom::vlist::VList { + childs: vec![#(#html_trees,)*], + } }); } } diff --git a/crates/macro/src/html_tree/html_node.rs b/crates/macro/src/html_tree/html_node.rs index 41fce73ea89..8aabb80cb14 100644 --- a/crates/macro/src/html_tree/html_node.rs +++ b/crates/macro/src/html_tree/html_node.rs @@ -46,12 +46,8 @@ impl ToTokens for HtmlNode { impl ToTokens for Node { fn to_tokens(&self, tokens: &mut TokenStream) { let node_token = match &self { - Node::Literal(lit) => quote! { - ::yew::virtual_dom::VNode::from(#lit) - }, - Node::Raw(stream) => quote_spanned! {stream.span()=> - ::yew::virtual_dom::VNode::from({#stream}) - }, + Node::Literal(lit) => quote! { #lit }, + Node::Raw(stream) => quote_spanned! { stream.span()=> {#stream} }, }; tokens.extend(node_token); diff --git a/crates/macro/src/html_tree/html_tag/mod.rs b/crates/macro/src/html_tree/html_tag/mod.rs index f1e020813a3..dcd69f16594 100644 --- a/crates/macro/src/html_tree/html_tag/mod.rs +++ b/crates/macro/src/html_tree/html_tag/mod.rs @@ -153,7 +153,7 @@ impl ToTokens for HtmlTag { #vtag.add_attributes(vec![#((#attr_labels.to_owned(), (#attr_values).to_string())),*]); #vtag.add_listeners(vec![#(::std::boxed::Box::new(#listeners)),*]); #vtag.add_children(vec![#(#children),*]); - ::yew::virtual_dom::VNode::VTag(#vtag) + #vtag }}); } } diff --git a/crates/macro/src/html_tree/mod.rs b/crates/macro/src/html_tree/mod.rs index ebf5e0ed2f4..403fbd67612 100644 --- a/crates/macro/src/html_tree/mod.rs +++ b/crates/macro/src/html_tree/mod.rs @@ -18,7 +18,7 @@ use html_prop::HtmlProp; use html_prop::HtmlPropSuffix; use html_tag::HtmlTag; use proc_macro2::TokenStream; -use quote::ToTokens; +use quote::{quote, ToTokens}; use syn::buffer::Cursor; use syn::parse::{Parse, ParseStream, Result}; @@ -105,17 +105,36 @@ impl PeekValue for HtmlTree { impl ToTokens for HtmlTree { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let empty_html_el = HtmlList(Vec::new()); - let html_tree_el: &dyn ToTokens = match self { - HtmlTree::Empty => &empty_html_el, - HtmlTree::Component(comp) => comp, - HtmlTree::Tag(tag) => tag, - HtmlTree::List(list) => list, - HtmlTree::Node(node) => node, - HtmlTree::Iterable(iterable) => iterable, - HtmlTree::Block(block) => block, - }; + let node = self.token_stream(); + tokens.extend(quote! { + ::yew::virtual_dom::VNode::from(#node) + }); + } +} + +impl HtmlTree { + fn token_stream(&self) -> proc_macro2::TokenStream { + match self { + HtmlTree::Empty => HtmlList(Vec::new()).into_token_stream(), + HtmlTree::Component(comp) => comp.into_token_stream(), + HtmlTree::Tag(tag) => tag.into_token_stream(), + HtmlTree::List(list) => list.into_token_stream(), + HtmlTree::Node(node) => node.into_token_stream(), + HtmlTree::Iterable(iterable) => iterable.into_token_stream(), + HtmlTree::Block(block) => block.into_token_stream(), + } + } +} + +pub struct HtmlTreeNested(HtmlTree); +impl Parse for HtmlTreeNested { + fn parse(input: ParseStream) -> Result { + Ok(HtmlTreeNested(HtmlTree::parse(input)?)) + } +} - html_tree_el.to_tokens(tokens); +impl ToTokens for HtmlTreeNested { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + tokens.extend(self.0.token_stream()); } } diff --git a/examples/nested_components/src/child.rs b/examples/nested_components/src/child.rs index 0e246055519..0f212cda4f6 100644 --- a/examples/nested_components/src/child.rs +++ b/examples/nested_components/src/child.rs @@ -4,12 +4,17 @@ pub struct Child { props: Props, } +type Children = Box Vec>>; + #[derive(Properties)] pub struct Props { + pub hide: bool, #[props(required)] pub on_click: Callback<()>, #[props(required)] pub name: String, + #[props(required)] + pub children: Children, } pub enum Msg { @@ -39,6 +44,7 @@ impl Renderable for Child { html! {
{ format!("My name is {}", self.props.name) } + { for (self.props.children)().into_iter() } diff --git a/examples/nested_components/src/lib.rs b/examples/nested_components/src/lib.rs index e4ceb68ef44..70d74be52eb 100644 --- a/examples/nested_components/src/lib.rs +++ b/examples/nested_components/src/lib.rs @@ -6,18 +6,14 @@ mod parent; use child::Child; use parent::{Msg as ParentMsg, Parent}; -pub struct Model { - child_name: String, -} +pub struct Model; impl Component for Model { type Message = (); type Properties = (); fn create(_: Self::Properties, _: ComponentLink) -> Self { - Model { - child_name: "Bobby".to_owned(), - } + Model } fn update(&mut self, _: Self::Message) -> ShouldRender { @@ -27,10 +23,14 @@ impl Component for Model { impl Renderable for Model { fn view(&self) -> Html { - let child_name = self.child_name.clone(); html! { - + +

{"Rusty says hi"}

+
+ +

{"Rustifer says hello"}

+
} } diff --git a/examples/nested_components/src/parent.rs b/examples/nested_components/src/parent.rs index 58cc987d11e..2cf2339c3b9 100644 --- a/examples/nested_components/src/parent.rs +++ b/examples/nested_components/src/parent.rs @@ -1,16 +1,18 @@ +use crate::child::Child; use yew::prelude::*; - -type Children = Box>; +use yew::virtual_dom::VChild; pub enum Msg { Click, ChildClick, } +type Children = Box Vec>>; + #[derive(Properties)] pub struct Props { #[props(required)] - pub children: Children, + pub children: Children, } pub struct Parent { @@ -44,7 +46,7 @@ impl Renderable for Parent {
{ format!("Last clicked by {}", self.clicker) } - { self.props.children.view() } + { for (self.props.children)().into_iter().filter(|c| !c.props.hide) }
} } diff --git a/src/html/mod.rs b/src/html/mod.rs index 7dd9a7f82c4..16f841161ab 100644 --- a/src/html/mod.rs +++ b/src/html/mod.rs @@ -54,15 +54,6 @@ pub trait Renderable { fn view(&self) -> Html; } -impl Renderable for F -where - F: Fn() -> Html, -{ - fn view(&self) -> Html { - self() - } -} - /// Trait for building properties for a component pub trait Properties { /// Builder that will be used to construct properties diff --git a/src/virtual_dom/mod.rs b/src/virtual_dom/mod.rs index ce461f536fe..600d7b5b680 100644 --- a/src/virtual_dom/mod.rs +++ b/src/virtual_dom/mod.rs @@ -11,7 +11,7 @@ use std::collections::HashMap; use std::fmt; use stdweb::web::{Element, EventListenerHandle, Node}; -pub use self::vcomp::VComp; +pub use self::vcomp::{VChild, VComp}; pub use self::vlist::VList; pub use self::vnode::VNode; pub use self::vtag::VTag; diff --git a/src/virtual_dom/vcomp.rs b/src/virtual_dom/vcomp.rs index 09b4f2bc475..0ac4543fa18 100644 --- a/src/virtual_dom/vcomp.rs +++ b/src/virtual_dom/vcomp.rs @@ -31,6 +31,35 @@ pub struct VComp { state: Rc>>, } +/// A virtual child component. +pub struct VChild { + /// The component properties + pub props: SELF::Properties, + /// The parent component scope + scope: ScopeHolder, +} + +impl VChild +where + SELF: Component, + PARENT: Component, +{ + /// Creates a child component that can be accessed and modified by its parent. + pub fn new(props: SELF::Properties, scope: ScopeHolder) -> Self { + Self { props, scope } + } +} + +impl From> for VComp +where + COMP: Component, + CHILD: Component + Renderable, +{ + fn from(vchild: VChild) -> Self { + VComp::new::(vchild.props, vchild.scope) + } +} + enum MountState { Unmounted(Unmounted), Mounted(Mounted), diff --git a/src/virtual_dom/vnode.rs b/src/virtual_dom/vnode.rs index 703babcfe34..2839fefcb04 100644 --- a/src/virtual_dom/vnode.rs +++ b/src/virtual_dom/vnode.rs @@ -1,6 +1,6 @@ //! This module contains the implementation of abstract virtual node. -use super::{VComp, VDiff, VList, VTag, VText}; +use super::{VChild, VComp, VDiff, VList, VTag, VText}; use crate::html::{Component, Renderable, Scope}; use std::cmp::PartialEq; use std::fmt; @@ -95,6 +95,16 @@ impl From> for VNode { } } +impl From> for VNode +where + COMP: Component, + CHILD: Component + Renderable, +{ + fn from(vchild: VChild) -> Self { + VNode::VComp(VComp::from(vchild)) + } +} + impl From for VNode { fn from(value: T) -> Self { VNode::VText(VText::new(value.to_string())) diff --git a/tests/macro/html-component-fail.rs b/tests/macro/html-component-fail.rs index fd442dc36e1..5668faf987d 100644 --- a/tests/macro/html-component-fail.rs +++ b/tests/macro/html-component-fail.rs @@ -31,6 +31,9 @@ impl Renderable for ChildComponent { // TODO add test for nested component with children prop set // TODO add test for nested component `with props` +// TODO add test for nested component with no children +// TODO add test for nested component with block rendered components + fn compile_fail() { html! { }; html! { }; From 8e4a7eda3687e800e3e97fb67f42b65b686517f9 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Tue, 20 Aug 2019 20:56:39 -0400 Subject: [PATCH 017/193] Add default implementation for children prop --- crates/macro/src/html_tree/html_component.rs | 14 ++++++----- examples/nested_components/src/child.rs | 6 ++--- examples/nested_components/src/lib.rs | 2 ++ examples/nested_components/src/parent.rs | 8 +++--- src/html/mod.rs | 26 +++++++++++++++++++- 5 files changed, 40 insertions(+), 16 deletions(-) diff --git a/crates/macro/src/html_tree/html_component.rs b/crates/macro/src/html_tree/html_component.rs index 07a7452f562..7dd5714ed86 100644 --- a/crates/macro/src/html_tree/html_component.rs +++ b/crates/macro/src/html_tree/html_component.rs @@ -122,12 +122,14 @@ impl ToTokens for HtmlComponent { let set_children = if !children.is_empty() { quote! { - .children(::std::boxed::Box::new(move || { - #[allow(unused_must_use)] - || -> ::std::vec::Vec<_> { - vec![#(#children.into(),)*] - } - }())) + .children(::yew::html::ChildrenRenderer( + ::std::boxed::Box::new(move || { + #[allow(unused_must_use)] + || -> ::std::vec::Vec<_> { + vec![#(#children.into(),)*] + } + }()) + )) } } else { quote! {} diff --git a/examples/nested_components/src/child.rs b/examples/nested_components/src/child.rs index 0f212cda4f6..3fdf8a3a2a6 100644 --- a/examples/nested_components/src/child.rs +++ b/examples/nested_components/src/child.rs @@ -1,11 +1,10 @@ +use yew::html::Children; use yew::prelude::*; pub struct Child { props: Props, } -type Children = Box Vec>>; - #[derive(Properties)] pub struct Props { pub hide: bool, @@ -13,7 +12,6 @@ pub struct Props { pub on_click: Callback<()>, #[props(required)] pub name: String, - #[props(required)] pub children: Children, } @@ -44,7 +42,7 @@ impl Renderable for Child { html! {
{ format!("My name is {}", self.props.name) } - { for (self.props.children)().into_iter() } + { for (*self.props.children)().into_iter() } diff --git a/examples/nested_components/src/lib.rs b/examples/nested_components/src/lib.rs index 70d74be52eb..0016eedd16a 100644 --- a/examples/nested_components/src/lib.rs +++ b/examples/nested_components/src/lib.rs @@ -1,3 +1,4 @@ +#![recursion_limit = "128"] use yew::prelude::*; mod child; @@ -31,6 +32,7 @@ impl Renderable for Model {

{"Rustifer says hello"}

+ } } diff --git a/examples/nested_components/src/parent.rs b/examples/nested_components/src/parent.rs index 2cf2339c3b9..c8576aa095c 100644 --- a/examples/nested_components/src/parent.rs +++ b/examples/nested_components/src/parent.rs @@ -1,18 +1,16 @@ use crate::child::Child; +use yew::html::ChildrenWithProps; use yew::prelude::*; -use yew::virtual_dom::VChild; pub enum Msg { Click, ChildClick, } -type Children = Box Vec>>; - #[derive(Properties)] pub struct Props { #[props(required)] - pub children: Children, + pub children: ChildrenWithProps, } pub struct Parent { @@ -46,7 +44,7 @@ impl Renderable for Parent {
{ format!("Last clicked by {}", self.clicker) } - { for (self.props.children)().into_iter().filter(|c| !c.props.hide) } + { for (*self.props.children)().into_iter().filter(|c| !c.props.hide) }
} } diff --git a/src/html/mod.rs b/src/html/mod.rs index 16f841161ab..c12961fc458 100644 --- a/src/html/mod.rs +++ b/src/html/mod.rs @@ -9,9 +9,10 @@ mod scope; pub use listener::*; pub(crate) use scope::ComponentUpdate; pub use scope::{NodeCell, Scope}; +use std::ops::Deref; use crate::callback::Callback; -use crate::virtual_dom::VNode; +use crate::virtual_dom::{VChild, VNode}; /// This type indicates that component should be rendered again. pub type ShouldRender = bool; @@ -45,6 +46,29 @@ pub trait Component: Sized + 'static { fn destroy(&mut self) {} // TODO Replace with `Drop` } +/// A type used for rendering children html. +pub struct ChildrenRenderer(pub Box Vec>); + +impl Deref for ChildrenRenderer { + type Target = Box Vec>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Default for ChildrenRenderer { + fn default() -> Self { + Self(Box::new(|| Vec::new())) + } +} + +/// A type used for accepting children elements in Component::Properties. +pub type Children = ChildrenRenderer>; + +/// A type used for accepting children elements in Component::Properties and accessing their props. +pub type ChildrenWithProps = ChildrenRenderer>; + /// A type which expected as a result of `view` function implementation. pub type Html = VNode; From bd2fc4254f316624a0e93699442ff23314184bf8 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Sun, 18 Aug 2019 00:07:07 -0400 Subject: [PATCH 018/193] Use holder to avoid box paren wrapping --- crates/macro/src/html_tree/html_component.rs | 2 +- examples/nested_components/src/child.rs | 2 +- examples/nested_components/src/parent.rs | 5 ++- src/html/mod.rs | 44 +++++++++++++------- src/lib.rs | 2 +- 5 files changed, 35 insertions(+), 20 deletions(-) diff --git a/crates/macro/src/html_tree/html_component.rs b/crates/macro/src/html_tree/html_component.rs index 7dd5714ed86..3cce118f033 100644 --- a/crates/macro/src/html_tree/html_component.rs +++ b/crates/macro/src/html_tree/html_component.rs @@ -122,7 +122,7 @@ impl ToTokens for HtmlComponent { let set_children = if !children.is_empty() { quote! { - .children(::yew::html::ChildrenRenderer( + .children(::yew::html::ChildrenRenderer::new( ::std::boxed::Box::new(move || { #[allow(unused_must_use)] || -> ::std::vec::Vec<_> { diff --git a/examples/nested_components/src/child.rs b/examples/nested_components/src/child.rs index 3fdf8a3a2a6..5eafb1fa4f0 100644 --- a/examples/nested_components/src/child.rs +++ b/examples/nested_components/src/child.rs @@ -42,7 +42,7 @@ impl Renderable for Child { html! {
{ format!("My name is {}", self.props.name) } - { for (*self.props.children)().into_iter() } + { for self.props.children.iter() } diff --git a/examples/nested_components/src/parent.rs b/examples/nested_components/src/parent.rs index c8576aa095c..bf3ee20f337 100644 --- a/examples/nested_components/src/parent.rs +++ b/examples/nested_components/src/parent.rs @@ -44,7 +44,10 @@ impl Renderable for Parent {
{ format!("Last clicked by {}", self.clicker) } - { for (*self.props.children)().into_iter().filter(|c| !c.props.hide) } + { for self.props.children.iter().filter(|c| !c.props.hide).map(|mut c| { + c.props.name = format!("{} Imposter", c.props.name); + c + }) }
} } diff --git a/src/html/mod.rs b/src/html/mod.rs index c12961fc458..0f18839a871 100644 --- a/src/html/mod.rs +++ b/src/html/mod.rs @@ -9,7 +9,6 @@ mod scope; pub use listener::*; pub(crate) use scope::ComponentUpdate; pub use scope::{NodeCell, Scope}; -use std::ops::Deref; use crate::callback::Callback; use crate::virtual_dom::{VChild, VNode}; @@ -46,32 +45,45 @@ pub trait Component: Sized + 'static { fn destroy(&mut self) {} // TODO Replace with `Drop` } +/// A type which expected as a result of `view` function implementation. +pub type Html = VNode; + +/// A type used for accepting children elements in Component::Properties. +pub type Children = ChildrenRenderer>; + +/// A type used for accepting children elements in Component::Properties and accessing their props. +pub type ChildrenWithProps = ChildrenRenderer>; + /// A type used for rendering children html. -pub struct ChildrenRenderer(pub Box Vec>); +pub struct ChildrenRenderer { + boxed_render: Box Vec>, +} -impl Deref for ChildrenRenderer { - type Target = Box Vec>; +impl ChildrenRenderer { + /// Create children + pub fn new(boxed_render: Box Vec>) -> Self { + Self { boxed_render } + } - fn deref(&self) -> &Self::Target { - &self.0 + /// Build children components and return `Vec` + pub fn to_vec(&self) -> Vec { + (&self.boxed_render)() + } + + /// Render children components and return `Iterator` + pub fn iter(&self) -> impl Iterator { + (&self.boxed_render)().into_iter() } } impl Default for ChildrenRenderer { fn default() -> Self { - Self(Box::new(|| Vec::new())) + Self { + boxed_render: Box::new(|| Vec::new()), + } } } -/// A type used for accepting children elements in Component::Properties. -pub type Children = ChildrenRenderer>; - -/// A type used for accepting children elements in Component::Properties and accessing their props. -pub type ChildrenWithProps = ChildrenRenderer>; - -/// A type which expected as a result of `view` function implementation. -pub type Html = VNode; - /// Should be rendered relative to context and component environment. pub trait Renderable { /// Called by rendering loop. diff --git a/src/lib.rs b/src/lib.rs index 153250cdc48..0e5f5caf990 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -149,7 +149,7 @@ pub mod prelude { pub use crate::callback::Callback; pub use crate::events::*; pub use crate::html::{ - Component, ComponentLink, Href, Html, Properties, Renderable, ShouldRender, + Children, Component, ComponentLink, Href, Html, Properties, Renderable, ShouldRender, }; pub use crate::macros::*; From 599e4d0764af28cec34868ba35a757270d2537d5 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Tue, 20 Aug 2019 23:14:57 -0400 Subject: [PATCH 019/193] Support generic components --- crates/macro/src/html_tree/html_component.rs | 44 +++++++------------- 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/crates/macro/src/html_tree/html_component.rs b/crates/macro/src/html_tree/html_component.rs index 3cce118f033..d0f37ceeacd 100644 --- a/crates/macro/src/html_tree/html_component.rs +++ b/crates/macro/src/html_tree/html_component.rs @@ -18,13 +18,13 @@ pub struct HtmlComponent { children: Vec, } -impl PeekValue for HtmlComponent { - fn peek(cursor: Cursor) -> Option { +impl PeekValue<()> for HtmlComponent { + fn peek(cursor: Cursor) -> Option<()> { let (punct, cursor) = cursor.punct()?; (punct.as_char() == '<').as_option()?; - let (ty, _) = HtmlComponent::peek_type(cursor)?; - Some(ty) + HtmlComponent::peek_type(cursor)?; + Some(()) } } @@ -58,10 +58,8 @@ impl Parse for HtmlComponent { "this open tag has no corresponding close tag", )); } - if let Some(next_ty) = HtmlComponentClose::peek(input.cursor()) { - if open.ty == next_ty { - break; - } + if HtmlComponentClose::peek(input.cursor()).is_some() { + break; } children.push(input.parse()?); @@ -194,7 +192,7 @@ impl HtmlComponent { Some(cursor) } - fn peek_type(mut cursor: Cursor) -> Option<(Type, Cursor)> { + fn peek_type(mut cursor: Cursor) -> Option { let mut colons_optional = true; let mut last_ident = None; let mut leading_colon = None; @@ -230,16 +228,13 @@ impl HtmlComponent { type_str.is_ascii().as_option()?; type_str.bytes().next()?.is_ascii_uppercase().as_option()?; - Some(( - Type::Path(TypePath { - qself: None, - path: Path { - leading_colon, - segments, - }, - }), - cursor, - )) + Some(Type::Path(TypePath { + qself: None, + path: Path { + leading_colon, + segments, + }, + })) } } @@ -255,9 +250,7 @@ impl PeekValue for HtmlComponentOpen { fn peek(cursor: Cursor) -> Option { let (punct, cursor) = cursor.punct()?; (punct.as_char() == '<').as_option()?; - - let (ty, _) = HtmlComponent::peek_type(cursor)?; - Some(ty) + HtmlComponent::peek_type(cursor) } } @@ -302,12 +295,7 @@ impl PeekValue for HtmlComponentClose { let (punct, cursor) = cursor.punct()?; (punct.as_char() == '/').as_option()?; - let (ty, cursor) = HtmlComponent::peek_type(cursor)?; - - let (punct, _) = cursor.punct()?; - (punct.as_char() == '>').as_option()?; - - Some(ty) + HtmlComponent::peek_type(cursor) } } impl Parse for HtmlComponentClose { From 0e78f7aafbe543f6bc6723d13c9492034d5f89b4 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Sun, 25 Aug 2019 16:53:08 -0400 Subject: [PATCH 020/193] Update example --- Cargo.toml | 2 +- examples/nested_components/src/child.rs | 52 ------ examples/nested_components/src/lib.rs | 39 ---- examples/nested_components/src/main.rs | 3 - examples/nested_components/src/parent.rs | 54 ------ .../Cargo.toml | 2 +- examples/nested_list/src/header.rs | 46 +++++ examples/nested_list/src/item.rs | 68 +++++++ examples/nested_list/src/lib.rs | 43 +++++ examples/nested_list/src/list.rs | 170 ++++++++++++++++++ examples/nested_list/src/main.rs | 3 + examples/nested_list/static/index.html | 11 ++ examples/nested_list/static/styles.css | 77 ++++++++ src/virtual_dom/vcomp.rs | 2 +- 14 files changed, 421 insertions(+), 151 deletions(-) delete mode 100644 examples/nested_components/src/child.rs delete mode 100644 examples/nested_components/src/lib.rs delete mode 100644 examples/nested_components/src/main.rs delete mode 100644 examples/nested_components/src/parent.rs rename examples/{nested_components => nested_list}/Cargo.toml (83%) create mode 100644 examples/nested_list/src/header.rs create mode 100644 examples/nested_list/src/item.rs create mode 100644 examples/nested_list/src/lib.rs create mode 100644 examples/nested_list/src/list.rs create mode 100644 examples/nested_list/src/main.rs create mode 100644 examples/nested_list/static/index.html create mode 100644 examples/nested_list/static/styles.css diff --git a/Cargo.toml b/Cargo.toml index 14c907298cb..8bfa012bb67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,7 +72,7 @@ members = [ "examples/minimal", "examples/mount_point", "examples/multi_thread", - "examples/nested_components", + "examples/nested_list", "examples/npm_and_rest", "examples/routing", "examples/server", diff --git a/examples/nested_components/src/child.rs b/examples/nested_components/src/child.rs deleted file mode 100644 index 5eafb1fa4f0..00000000000 --- a/examples/nested_components/src/child.rs +++ /dev/null @@ -1,52 +0,0 @@ -use yew::html::Children; -use yew::prelude::*; - -pub struct Child { - props: Props, -} - -#[derive(Properties)] -pub struct Props { - pub hide: bool, - #[props(required)] - pub on_click: Callback<()>, - #[props(required)] - pub name: String, - pub children: Children, -} - -pub enum Msg { - Click, -} - -impl Component for Child { - type Message = Msg; - type Properties = Props; - - fn create(props: Self::Properties, _: ComponentLink) -> Self { - Child { props } - } - - fn update(&mut self, msg: Self::Message) -> ShouldRender { - match msg { - Msg::Click => { - self.props.on_click.emit(()); - } - } - false - } -} - -impl Renderable for Child { - fn view(&self) -> Html { - html! { -
- { format!("My name is {}", self.props.name) } - { for self.props.children.iter() } - -
- } - } -} diff --git a/examples/nested_components/src/lib.rs b/examples/nested_components/src/lib.rs deleted file mode 100644 index 0016eedd16a..00000000000 --- a/examples/nested_components/src/lib.rs +++ /dev/null @@ -1,39 +0,0 @@ -#![recursion_limit = "128"] -use yew::prelude::*; - -mod child; -mod parent; - -use child::Child; -use parent::{Msg as ParentMsg, Parent}; - -pub struct Model; - -impl Component for Model { - type Message = (); - type Properties = (); - - fn create(_: Self::Properties, _: ComponentLink) -> Self { - Model - } - - fn update(&mut self, _: Self::Message) -> ShouldRender { - true - } -} - -impl Renderable for Model { - fn view(&self) -> Html { - html! { - - -

{"Rusty says hi"}

-
- -

{"Rustifer says hello"}

-
- -
- } - } -} diff --git a/examples/nested_components/src/main.rs b/examples/nested_components/src/main.rs deleted file mode 100644 index cf2d79e71f4..00000000000 --- a/examples/nested_components/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - yew::start_app::(); -} diff --git a/examples/nested_components/src/parent.rs b/examples/nested_components/src/parent.rs deleted file mode 100644 index bf3ee20f337..00000000000 --- a/examples/nested_components/src/parent.rs +++ /dev/null @@ -1,54 +0,0 @@ -use crate::child::Child; -use yew::html::ChildrenWithProps; -use yew::prelude::*; - -pub enum Msg { - Click, - ChildClick, -} - -#[derive(Properties)] -pub struct Props { - #[props(required)] - pub children: ChildrenWithProps, -} - -pub struct Parent { - props: Props, - clicker: String, -} - -impl Component for Parent { - type Message = Msg; - type Properties = Props; - - fn create(props: Self::Properties, _: ComponentLink) -> Self { - Parent { - clicker: "none".to_owned(), - props, - } - } - - fn update(&mut self, msg: Self::Message) -> ShouldRender { - match msg { - Msg::Click => self.clicker = "self".to_string(), - Msg::ChildClick => self.clicker = "child".to_string(), - } - true - } -} - -impl Renderable for Parent { - fn view(&self) -> Html { - html! { -
- { format!("Last clicked by {}", self.clicker) } - - { for self.props.children.iter().filter(|c| !c.props.hide).map(|mut c| { - c.props.name = format!("{} Imposter", c.props.name); - c - }) } -
- } - } -} diff --git a/examples/nested_components/Cargo.toml b/examples/nested_list/Cargo.toml similarity index 83% rename from examples/nested_components/Cargo.toml rename to examples/nested_list/Cargo.toml index 11c6b56398f..aefc4ba6ae8 100644 --- a/examples/nested_components/Cargo.toml +++ b/examples/nested_list/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "nested_components" +name = "nested_list" version = "0.1.0" authors = ["Justin Starry "] edition = "2018" diff --git a/examples/nested_list/src/header.rs b/examples/nested_list/src/header.rs new file mode 100644 index 00000000000..89fcf90f90b --- /dev/null +++ b/examples/nested_list/src/header.rs @@ -0,0 +1,46 @@ +use crate::list::Hovered; +use yew::prelude::*; + +pub struct ListHeader { + props: Props, +} + +#[derive(Properties)] +pub struct Props { + #[props(required)] + pub on_hover: Callback, + #[props(required)] + pub text: String, +} + +pub enum Msg { + Hover, +} + +impl Component for ListHeader { + type Message = Msg; + type Properties = Props; + + fn create(props: Self::Properties, _: ComponentLink) -> Self { + ListHeader { props } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + Msg::Hover => { + self.props.on_hover.emit(Hovered::Header); + } + } + false + } +} + +impl Renderable for ListHeader { + fn view(&self) -> Html { + html! { +
+ { &self.props.text } +
+ } + } +} diff --git a/examples/nested_list/src/item.rs b/examples/nested_list/src/item.rs new file mode 100644 index 00000000000..de828c4c1de --- /dev/null +++ b/examples/nested_list/src/item.rs @@ -0,0 +1,68 @@ +use crate::list::Hovered; +use yew::html::Children; +use yew::prelude::*; + +pub struct ListItem { + props: Props, +} + +#[derive(Properties)] +// #[props(ListItem)] +pub struct Props { + pub hide: bool, + #[props(required)] + pub on_hover: Callback, + #[props(required)] + pub name: String, + pub children: Children, +} + +pub enum Msg { + Hover, +} + +impl Component for ListItem { + type Message = Msg; + type Properties = Props; + + fn create(props: Self::Properties, _: ComponentLink) -> Self { + ListItem { props } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + Msg::Hover => { + self.props + .on_hover + .emit(Hovered::Item(self.props.name.clone())); + } + } + false + } +} + +impl Renderable for ListItem { + fn view(&self) -> Html { + html! { +
+ { &self.props.name } + { self.view_details() } +
+ } + } +} + +impl ListItem { + fn view_details(&self) -> Html { + let children = self.props.children.to_vec(); + if children.is_empty() { + return html! {}; + } + + html! { +
+ { for children.into_iter() } +
+ } + } +} diff --git a/examples/nested_list/src/lib.rs b/examples/nested_list/src/lib.rs new file mode 100644 index 00000000000..52d73b7ddd4 --- /dev/null +++ b/examples/nested_list/src/lib.rs @@ -0,0 +1,43 @@ +#![recursion_limit = "128"] +use yew::prelude::*; + +mod header; +mod item; +mod list; + +use header::ListHeader; +use item::ListItem; +use list::{List, Msg as ListMsg}; + +pub struct Model; + +impl Component for Model { + type Message = (); + type Properties = (); + + fn create(_: Self::Properties, _: ComponentLink) -> Self { + Model + } + + fn update(&mut self, _: Self::Message) -> ShouldRender { + true + } +} + +impl Renderable for Model { + fn view(&self) -> Html { + html! { +
+

{ "Nested List Demo" }

+ + + + + + {"Hello!"} + + +
+ } + } +} diff --git a/examples/nested_list/src/list.rs b/examples/nested_list/src/list.rs new file mode 100644 index 00000000000..c5a8fc1d5a9 --- /dev/null +++ b/examples/nested_list/src/list.rs @@ -0,0 +1,170 @@ +use crate::{header::Props as HeaderProps, ListHeader}; +use crate::{item::Props as ItemProps, ListItem}; +use std::fmt; +use yew::html::ChildrenRenderer; +use yew::prelude::*; +use yew::virtual_dom::vcomp::ScopeHolder; +use yew::virtual_dom::{VChild, VComp, VNode}; + +#[derive(Debug)] +pub enum Hovered { + Header, + Item(String), + List, + None, +} + +impl fmt::Display for Hovered { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + match self { + Hovered::Header => "Header", + Hovered::Item(name) => name, + Hovered::List => "List container", + Hovered::None => "Nothing", + } + ) + } +} + +pub enum Msg { + Hover(Hovered), +} + +pub enum Variants { + Item(::Properties), + Header(::Properties), +} + +impl From for Variants { + fn from(props: ItemProps) -> Self { + Variants::Item(props) + } +} + +impl From for Variants { + fn from(props: HeaderProps) -> Self { + Variants::Header(props) + } +} + +pub struct ListVariant { + props: Variants, + scope: ScopeHolder, +} + +#[derive(Properties)] +pub struct Props { + #[props(required)] + pub children: ChildrenRenderer, +} + +impl From> for ListVariant +where + CHILD: Component + Renderable, + CHILD::Properties: Into, +{ + fn from(vchild: VChild) -> Self { + ListVariant { + props: vchild.props.into(), + scope: vchild.scope, + } + } +} + +impl Into> for ListVariant { + fn into(self) -> VNode { + match self.props { + Variants::Header(props) => VComp::new::(props, self.scope).into(), + Variants::Item(props) => VComp::new::(props, self.scope).into(), + } + } +} + +pub struct List { + props: Props, + hovered: Hovered, +} + +impl Component for List { + type Message = Msg; + type Properties = Props; + + fn create(props: Self::Properties, _: ComponentLink) -> Self { + List { + props, + hovered: Hovered::None, + } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + Msg::Hover(hovered) => self.hovered = hovered, + } + true + } +} + +impl Renderable for List { + fn view(&self) -> Html { + html! { +
+
+ {self.view_header()} +
+ {self.view_items()} +
+
+ {self.view_last_hovered()} +
+ } + } +} + +impl List { + fn view_last_hovered(&self) -> Html { + html! { +
+ { "Last hovered:"} + + { &self.hovered } + +
+ } + } +} + +impl List { + fn view_header(&self) -> Html { + html! { + { + for self.props.children.iter().filter(|c| match c.props { + Variants::Header(_) => true, + _ => false + }) + } + } + } + + fn view_items(&self) -> Html { + html! { + { + for self.props.children.iter().filter(|c| match &c.props { + Variants::Item(props) => !props.hide, + _ => false, + }).enumerate().map(|(i, mut c)| { + if let Variants::Item(ref mut props) = c.props { + props.name = format!("#{} - {}", i + 1, props.name); + } + c + }) + } + } + } +} diff --git a/examples/nested_list/src/main.rs b/examples/nested_list/src/main.rs new file mode 100644 index 00000000000..9dab1b82774 --- /dev/null +++ b/examples/nested_list/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + yew::start_app::(); +} diff --git a/examples/nested_list/static/index.html b/examples/nested_list/static/index.html new file mode 100644 index 00000000000..0d34f497c81 --- /dev/null +++ b/examples/nested_list/static/index.html @@ -0,0 +1,11 @@ + + + + + Yew • Nested List + + + + + + diff --git a/examples/nested_list/static/styles.css b/examples/nested_list/static/styles.css new file mode 100644 index 00000000000..51b72cc14b5 --- /dev/null +++ b/examples/nested_list/static/styles.css @@ -0,0 +1,77 @@ +html, body { + width: 100%; + background: #FAFAFA; + font-family: monospace; +} + +.main { + display: flex; + flex-direction: column; + margin-top: 40px; + width: 100%; + align-items: center; +} + +.list-container { + margin-top: 20px; + display: flex; + flex-direction: column; + align-items: center; + padding: 30px; + border-radius: 4px; + background: #EEE; +} + +.list-container:hover { + background: #EAEAEA; +} + +.list { + display: flex; + flex-direction: column; + overflow: hidden; + border-radius: 3px; + border: 1px solid #666; + min-width: 30vw; +} + +.list-header { + background: #FEECAA; + border-bottom: 1px solid #666; + padding: 10px; +} + +.list-header:hover { + background: #FEE3A0; +} + +.list-item { + background: white; + border-bottom: 1px solid #666; + padding: 10px; +} + +.list-item:hover { + background: #FAFAFA; +} + +.list-item:last-child { + border-bottom: 0px; +} + +.list-item-details { + background: #EEE; + border: 1px solid #666; + border-radius: 3px; + margin-top: 10px; + padding: 10px; +} + +.last-hovered { + margin-top: 20px; +} + +.last-hovered-text { + color: #666; + margin-left: 5px; +} diff --git a/src/virtual_dom/vcomp.rs b/src/virtual_dom/vcomp.rs index 0ac4543fa18..47bdd634574 100644 --- a/src/virtual_dom/vcomp.rs +++ b/src/virtual_dom/vcomp.rs @@ -36,7 +36,7 @@ pub struct VChild { /// The component properties pub props: SELF::Properties, /// The parent component scope - scope: ScopeHolder, + pub scope: ScopeHolder, } impl VChild From 7ae088647bf30d2c7c8c03de72898ea0a2d22571 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Sun, 25 Aug 2019 18:12:14 -0400 Subject: [PATCH 021/193] Add tests --- crates/macro/src/html_tree/html_component.rs | 17 ++- crates/macro/src/html_tree/html_iterable.rs | 2 +- crates/macro/src/html_tree/html_list.rs | 8 +- crates/macro/src/html_tree/html_node.rs | 8 +- crates/macro/src/html_tree/html_tag/mod.rs | 2 +- tests/macro/helpers.rs | 1 - tests/macro/html-block-fail.rs | 6 +- tests/macro/html-block-fail.stderr | 30 ++--- tests/macro/html-component-fail.rs | 7 +- tests/macro/html-component-fail.stderr | 114 +++++++++++++------ tests/macro/html-component-pass.rs | 60 +++++++++- 11 files changed, 188 insertions(+), 67 deletions(-) diff --git a/crates/macro/src/html_tree/html_component.rs b/crates/macro/src/html_tree/html_component.rs index d0f37ceeacd..27bc58d4d9d 100644 --- a/crates/macro/src/html_tree/html_component.rs +++ b/crates/macro/src/html_tree/html_component.rs @@ -5,6 +5,7 @@ use crate::PeekValue; use boolinator::Boolinator; use proc_macro2::Span; use quote::{quote, quote_spanned, ToTokens}; +use std::cmp::Ordering; use syn::buffer::Cursor; use syn::parse; use syn::parse::{Parse, ParseStream, Result as ParseResult}; @@ -369,10 +370,18 @@ impl Parse for ListProps { // alphabetize props.sort_by(|a, b| { - a.label - .to_string() - .partial_cmp(&b.label.to_string()) - .unwrap() + if a.label == b.label { + Ordering::Equal + } else if a.label.to_string() == "children" { + Ordering::Greater + } else if b.label.to_string() == "children" { + Ordering::Less + } else { + a.label + .to_string() + .partial_cmp(&b.label.to_string()) + .unwrap() + } }); Ok(ListProps(props)) diff --git a/crates/macro/src/html_tree/html_iterable.rs b/crates/macro/src/html_tree/html_iterable.rs index b82c83f4921..47fac43a8ad 100644 --- a/crates/macro/src/html_tree/html_iterable.rs +++ b/crates/macro/src/html_tree/html_iterable.rs @@ -45,7 +45,7 @@ impl ToTokens for HtmlIterable { for __yew_node in __yew_nodes.into_iter() { __yew_vlist.add_child(__yew_node.into()); } - __yew_vlist + ::yew::virtual_dom::VNode::from(__yew_vlist) }}; tokens.extend(new_tokens); diff --git a/crates/macro/src/html_tree/html_list.rs b/crates/macro/src/html_tree/html_list.rs index 3b6fbb970d5..5da411c0333 100644 --- a/crates/macro/src/html_tree/html_list.rs +++ b/crates/macro/src/html_tree/html_list.rs @@ -51,9 +51,11 @@ impl ToTokens for HtmlList { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let html_trees = &self.0; tokens.extend(quote! { - ::yew::virtual_dom::vlist::VList { - childs: vec![#(#html_trees,)*], - } + ::yew::virtual_dom::VNode::VList( + ::yew::virtual_dom::vlist::VList { + childs: vec![#(#html_trees,)*], + } + ) }); } } diff --git a/crates/macro/src/html_tree/html_node.rs b/crates/macro/src/html_tree/html_node.rs index 8aabb80cb14..41fce73ea89 100644 --- a/crates/macro/src/html_tree/html_node.rs +++ b/crates/macro/src/html_tree/html_node.rs @@ -46,8 +46,12 @@ impl ToTokens for HtmlNode { impl ToTokens for Node { fn to_tokens(&self, tokens: &mut TokenStream) { let node_token = match &self { - Node::Literal(lit) => quote! { #lit }, - Node::Raw(stream) => quote_spanned! { stream.span()=> {#stream} }, + Node::Literal(lit) => quote! { + ::yew::virtual_dom::VNode::from(#lit) + }, + Node::Raw(stream) => quote_spanned! {stream.span()=> + ::yew::virtual_dom::VNode::from({#stream}) + }, }; tokens.extend(node_token); diff --git a/crates/macro/src/html_tree/html_tag/mod.rs b/crates/macro/src/html_tree/html_tag/mod.rs index dcd69f16594..f1e020813a3 100644 --- a/crates/macro/src/html_tree/html_tag/mod.rs +++ b/crates/macro/src/html_tree/html_tag/mod.rs @@ -153,7 +153,7 @@ impl ToTokens for HtmlTag { #vtag.add_attributes(vec![#((#attr_labels.to_owned(), (#attr_values).to_string())),*]); #vtag.add_listeners(vec![#(::std::boxed::Box::new(#listeners)),*]); #vtag.add_children(vec![#(#children),*]); - #vtag + ::yew::virtual_dom::VNode::VTag(#vtag) }}); } } diff --git a/tests/macro/helpers.rs b/tests/macro/helpers.rs index e6e00ac9641..2ef58680f8b 100644 --- a/tests/macro/helpers.rs +++ b/tests/macro/helpers.rs @@ -12,7 +12,6 @@ macro_rules! pass_helper { ( $($content:tt)* ) => { mod test_component; use test_component::TestComponent; - // #[allow(unused_imports)] use yew::prelude::*; impl Renderable for TestComponent { fn view(&self) -> Html { diff --git a/tests/macro/html-block-fail.rs b/tests/macro/html-block-fail.rs index a36527b015a..006fb018690 100644 --- a/tests/macro/html-block-fail.rs +++ b/tests/macro/html-block-fail.rs @@ -1,7 +1,11 @@ use yew::prelude::*; fn compile_fail() { - html! { () }; + html! { + <> + { () } + + }; let not_tree = || (); html! { diff --git a/tests/macro/html-block-fail.stderr b/tests/macro/html-block-fail.stderr index abd7e1d6c99..f13d27d9b55 100644 --- a/tests/macro/html-block-fail.stderr +++ b/tests/macro/html-block-fail.stderr @@ -1,8 +1,8 @@ error[E0277]: `()` doesn't implement `std::fmt::Display` - --> $DIR/html-block-fail.rs:4:13 + --> $DIR/html-block-fail.rs:6:15 | -4 | html! { () }; - | ^^ `()` cannot be formatted with the default formatter +6 | { () } + | ^^ `()` cannot be formatted with the default formatter | = help: the trait `std::fmt::Display` is not implemented for `()` = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead @@ -11,21 +11,21 @@ error[E0277]: `()` doesn't implement `std::fmt::Display` = note: required by `std::convert::From::from` error[E0277]: `()` doesn't implement `std::fmt::Display` - --> $DIR/html-block-fail.rs:8:16 - | -8 |
{ not_tree() }
- | ^^^^^^^^ `()` cannot be formatted with the default formatter - | - = help: the trait `std::fmt::Display` is not implemented for `()` - = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead - = note: required because of the requirements on the impl of `std::string::ToString` for `()` - = note: required because of the requirements on the impl of `std::convert::From<()>` for `yew::virtual_dom::vnode::VNode<_>` - = note: required by `std::convert::From::from` + --> $DIR/html-block-fail.rs:12:16 + | +12 |
{ not_tree() }
+ | ^^^^^^^^ `()` cannot be formatted with the default formatter + | + = help: the trait `std::fmt::Display` is not implemented for `()` + = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead + = note: required because of the requirements on the impl of `std::string::ToString` for `()` + = note: required because of the requirements on the impl of `std::convert::From<()>` for `yew::virtual_dom::vnode::VNode<_>` + = note: required by `std::convert::From::from` error[E0277]: `()` doesn't implement `std::fmt::Display` - --> $DIR/html-block-fail.rs:11:17 + --> $DIR/html-block-fail.rs:15:17 | -11 | <>{ for (0..3).map(|_| not_tree()) } +15 | <>{ for (0..3).map(|_| not_tree()) } | ^^^^^^ `()` cannot be formatted with the default formatter | = help: the trait `std::fmt::Display` is not implemented for `()` diff --git a/tests/macro/html-component-fail.rs b/tests/macro/html-component-fail.rs index 5668faf987d..2adbb3c87c5 100644 --- a/tests/macro/html-component-fail.rs +++ b/tests/macro/html-component-fail.rs @@ -29,11 +29,6 @@ impl Renderable for ChildComponent { } } -// TODO add test for nested component with children prop set -// TODO add test for nested component `with props` -// TODO add test for nested component with no children -// TODO add test for nested component with block rendered components - fn compile_fail() { html! { }; html! { }; @@ -52,6 +47,8 @@ fn compile_fail() { html! { }; html! { }; html! { }; + html! { }; + html! { }; } fn main() {} diff --git a/tests/macro/html-component-fail.stderr b/tests/macro/html-component-fail.stderr index 0462d2280d1..252b2772ccc 100644 --- a/tests/macro/html-component-fail.stderr +++ b/tests/macro/html-component-fail.stderr @@ -1,56 +1,38 @@ -error: expected component tag be of form `< .. />` +error: this open tag has no corresponding close tag --> $DIR/html-component-fail.rs:33:13 | 33 | html! { }; | ^^^^^^^^^^^^^^^^ -error: unexpected end of input, expected identifier +error: expected identifier --> $DIR/html-component-fail.rs:34:31 | 34 | html! { }; | ^ -error: unexpected end of input, expected identifier - --> $DIR/html-component-fail.rs:35:34 - | -35 | html! { }; - | ^ - -error: unexpected token - --> $DIR/html-component-fail.rs:36:29 - | -36 | html! { }; - | ^^^^^ - -error: expected component tag be of form `< .. />` +error: this open tag has no corresponding close tag --> $DIR/html-component-fail.rs:37:13 | 37 | html! { }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: unexpected token - --> $DIR/html-component-fail.rs:39:40 +error: expected type, found `/` + --> $DIR/html-component-fail.rs:49:14 | -39 | html! { }; - | ^^ - -error: expected identifier - --> $DIR/html-component-fail.rs:40:29 - | -40 | html! { }; - | ^^^^ +49 | html! { }; + | ^ -error: expected identifier - --> $DIR/html-component-fail.rs:41:29 +error: this open tag has no corresponding close tag + --> $DIR/html-component-fail.rs:50:13 | -41 | html! { }; - | ^^^^^^^^^^^^^^^^^ +50 | html! { }; + | ^^^^^^^^^^^^^^^^ -error: unexpected end of input, expected expression - --> $DIR/html-component-fail.rs:43:37 +error: only one root html element allowed + --> $DIR/html-component-fail.rs:51:46 | -43 | html! { }; - | ^ +51 | html! { }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0425]: cannot find value `blah` in this scope --> $DIR/html-component-fail.rs:38:34 @@ -58,6 +40,61 @@ error[E0425]: cannot find value `blah` in this scope 38 | html! { }; | ^^^^ not found in this scope +error[E0599]: no method named `build` found for type `ChildPropertiesBuilder` in the current scope + --> $DIR/html-component-fail.rs:35:5 + | +5 | #[derive(Properties, PartialEq)] + | - method `build` not found for this +... +35 | html! { }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +error[E0599]: no method named `build` found for type `ChildPropertiesBuilder` in the current scope + --> $DIR/html-component-fail.rs:36:5 + | +5 | #[derive(Properties, PartialEq)] + | - method `build` not found for this +... +36 | html! { }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +error[E0599]: no method named `build` found for type `ChildPropertiesBuilder` in the current scope + --> $DIR/html-component-fail.rs:39:5 + | +5 | #[derive(Properties, PartialEq)] + | - method `build` not found for this +... +39 | html! { }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +error[E0599]: no method named `build` found for type `ChildPropertiesBuilder` in the current scope + --> $DIR/html-component-fail.rs:40:5 + | +5 | #[derive(Properties, PartialEq)] + | - method `build` not found for this +... +40 | html! { }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +error[E0599]: no method named `build` found for type `ChildPropertiesBuilder` in the current scope + --> $DIR/html-component-fail.rs:41:5 + | +5 | #[derive(Properties, PartialEq)] + | - method `build` not found for this +... +41 | html! { }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + error[E0609]: no field `unknown` on type `ChildProperties` --> $DIR/html-component-fail.rs:42:29 | @@ -75,6 +112,17 @@ error[E0599]: no method named `unknown` found for type `ChildPropertiesBuilder }; | ^^^^^^^ +error[E0599]: no method named `build` found for type `ChildPropertiesBuilder` in the current scope + --> $DIR/html-component-fail.rs:43:5 + | +5 | #[derive(Properties, PartialEq)] + | - method `build` not found for this +... +43 | html! { }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + error[E0308]: mismatched types --> $DIR/html-component-fail.rs:44:42 | diff --git a/tests/macro/html-component-pass.rs b/tests/macro/html-component-pass.rs index c70e9e505cb..7e5557b5ea8 100644 --- a/tests/macro/html-component-pass.rs +++ b/tests/macro/html-component-pass.rs @@ -1,8 +1,10 @@ -#![recursion_limit = "128"] +#![recursion_limit = "256"] #[macro_use] mod helpers; +use yew::html::ChildrenRenderer; + #[derive(Properties, Default, PartialEq)] pub struct ChildProperties { pub string: String, @@ -32,8 +34,36 @@ impl Renderable for ChildComponent { } } +#[derive(Properties, Default)] +pub struct ParentProperties { + #[props(required)] + pub int: i32, + pub children: Children, +} + +pub struct ParentComponent; +impl Component for ParentComponent { + type Message = (); + type Properties = ParentProperties; + + fn create(_: Self::Properties, _: ComponentLink) -> Self { + ParentComponent + } + + fn update(&mut self, _: Self::Message) -> ShouldRender { + unimplemented!() + } +} + +impl Renderable for ParentComponent { + fn view(&self) -> Html { + unimplemented!() + } +} + mod scoped { pub use super::ChildComponent; + pub use super::ParentComponent; } pass_helper! { @@ -88,6 +118,34 @@ pass_helper! { }; + + let props = ::Properties::default(); + html! { + <> + + + + + <> + + + + + + + + + + + ::std::vec::Vec<_> { + vec![html!{ "String" }] + } + }()) + ) /> + + }; } fn main() {} From 23c23d74ec9042698f84869273da0576e7b40867 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Sun, 25 Aug 2019 18:28:51 -0400 Subject: [PATCH 022/193] Cleanup --- README.md | 3 + examples/nested_list/src/item.rs | 1 - examples/nested_list/src/lib.rs | 2 +- examples/nested_list/src/list.rs | 110 ++++++++++++------------- src/lib.rs | 3 +- tests/macro/html-component-fail.rs | 1 + tests/macro/html-component-fail.stderr | 11 +++ 7 files changed, 70 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index e009a0f69fd..3779695f62e 100644 --- a/README.md +++ b/README.md @@ -218,6 +218,9 @@ html! { } ``` diff --git a/examples/nested_list/src/item.rs b/examples/nested_list/src/item.rs index de828c4c1de..397f641b1ba 100644 --- a/examples/nested_list/src/item.rs +++ b/examples/nested_list/src/item.rs @@ -7,7 +7,6 @@ pub struct ListItem { } #[derive(Properties)] -// #[props(ListItem)] pub struct Props { pub hide: bool, #[props(required)] diff --git a/examples/nested_list/src/lib.rs b/examples/nested_list/src/lib.rs index 52d73b7ddd4..fd12e0ce1c8 100644 --- a/examples/nested_list/src/lib.rs +++ b/examples/nested_list/src/lib.rs @@ -1,5 +1,4 @@ #![recursion_limit = "128"] -use yew::prelude::*; mod header; mod item; @@ -8,6 +7,7 @@ mod list; use header::ListHeader; use item::ListItem; use list::{List, Msg as ListMsg}; +use yew::prelude::*; pub struct Model; diff --git a/examples/nested_list/src/list.rs b/examples/nested_list/src/list.rs index c5a8fc1d5a9..7b50ccb1aef 100644 --- a/examples/nested_list/src/list.rs +++ b/examples/nested_list/src/list.rs @@ -14,21 +14,6 @@ pub enum Hovered { None, } -impl fmt::Display for Hovered { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}", - match self { - Hovered::Header => "Header", - Hovered::Item(name) => name, - Hovered::List => "List container", - Hovered::None => "Nothing", - } - ) - } -} - pub enum Msg { Hover(Hovered), } @@ -61,28 +46,6 @@ pub struct Props { pub children: ChildrenRenderer, } -impl From> for ListVariant -where - CHILD: Component + Renderable, - CHILD::Properties: Into, -{ - fn from(vchild: VChild) -> Self { - ListVariant { - props: vchild.props.into(), - scope: vchild.scope, - } - } -} - -impl Into> for ListVariant { - fn into(self) -> VNode { - match self.props { - Variants::Header(props) => VComp::new::(props, self.scope).into(), - Variants::Item(props) => VComp::new::(props, self.scope).into(), - } - } -} - pub struct List { props: Props, hovered: Hovered, @@ -128,6 +91,29 @@ impl Renderable for List { } impl List { + fn view_header(&self) -> Html { + html! {{ + for self.props.children.iter().filter(|c| match c.props { + Variants::Header(_) => true, + _ => false + }) + }} + } + + fn view_items(&self) -> Html { + html! {{ + for self.props.children.iter().filter(|c| match &c.props { + Variants::Item(props) => !props.hide, + _ => false, + }).enumerate().map(|(i, mut c)| { + if let Variants::Item(ref mut props) = c.props { + props.name = format!("#{} - {}", i + 1, props.name); + } + c + }) + }} + } + fn view_last_hovered(&self) -> Html { html! {
@@ -140,31 +126,39 @@ impl List { } } -impl List { - fn view_header(&self) -> Html { - html! { - { - for self.props.children.iter().filter(|c| match c.props { - Variants::Header(_) => true, - _ => false - }) +impl fmt::Display for Hovered { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + match self { + Hovered::Header => "Header", + Hovered::Item(name) => name, + Hovered::List => "List container", + Hovered::None => "Nothing", } + ) + } +} + +impl From> for ListVariant +where + CHILD: Component + Renderable, + CHILD::Properties: Into, +{ + fn from(vchild: VChild) -> Self { + ListVariant { + props: vchild.props.into(), + scope: vchild.scope, } } +} - fn view_items(&self) -> Html { - html! { - { - for self.props.children.iter().filter(|c| match &c.props { - Variants::Item(props) => !props.hide, - _ => false, - }).enumerate().map(|(i, mut c)| { - if let Variants::Item(ref mut props) = c.props { - props.name = format!("#{} - {}", i + 1, props.name); - } - c - }) - } +impl Into> for ListVariant { + fn into(self) -> VNode { + match self.props { + Variants::Header(props) => VComp::new::(props, self.scope).into(), + Variants::Item(props) => VComp::new::(props, self.scope).into(), } } } diff --git a/src/lib.rs b/src/lib.rs index 0e5f5caf990..fd81dd76699 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -149,7 +149,8 @@ pub mod prelude { pub use crate::callback::Callback; pub use crate::events::*; pub use crate::html::{ - Children, Component, ComponentLink, Href, Html, Properties, Renderable, ShouldRender, + Children, ChildrenWithProps, Component, ComponentLink, Href, Html, Properties, Renderable, + ShouldRender, }; pub use crate::macros::*; diff --git a/tests/macro/html-component-fail.rs b/tests/macro/html-component-fail.rs index 2adbb3c87c5..988f048e13f 100644 --- a/tests/macro/html-component-fail.rs +++ b/tests/macro/html-component-fail.rs @@ -49,6 +49,7 @@ fn compile_fail() { html! { }; html! { }; html! { }; + html! { { "Not allowed" } }; } fn main() {} diff --git a/tests/macro/html-component-fail.stderr b/tests/macro/html-component-fail.stderr index 252b2772ccc..74ffbd2da03 100644 --- a/tests/macro/html-component-fail.stderr +++ b/tests/macro/html-component-fail.stderr @@ -171,5 +171,16 @@ error[E0599]: no method named `string` found for type `ChildPropertiesBuilder }; | ^^^^^^ +error[E0599]: no method named `children` found for type `ChildPropertiesBuilder` in the current scope + --> $DIR/html-component-fail.rs:52:5 + | +5 | #[derive(Properties, PartialEq)] + | - method `children` not found for this +... +52 | html! { { "Not allowed" } }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + Some errors occurred: E0308, E0425, E0599, E0609. For more information about an error, try `rustc --explain E0308`. From 5c367d2c6a67ac2d57f8bddec668b4322a84def5 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Sun, 25 Aug 2019 21:54:58 -0400 Subject: [PATCH 023/193] Update tests --- tests/macro/html-component-fail.rs | 78 +++++++---- tests/macro/html-component-fail.stderr | 185 +++++++++++++++---------- tests/macro/html-component-pass.rs | 124 +++++++++++------ 3 files changed, 249 insertions(+), 138 deletions(-) diff --git a/tests/macro/html-component-fail.rs b/tests/macro/html-component-fail.rs index 988f048e13f..90876cfcd4e 100644 --- a/tests/macro/html-component-fail.rs +++ b/tests/macro/html-component-fail.rs @@ -9,13 +9,13 @@ pub struct ChildProperties { pub int: i32, } -pub struct ChildComponent; -impl Component for ChildComponent { +pub struct Child; +impl Component for Child { type Message = (); type Properties = ChildProperties; fn create(props: Self::Properties, _: ComponentLink) -> Self { - ChildComponent + Child } fn update(&mut self, _: Self::Message) -> ShouldRender { @@ -23,33 +23,63 @@ impl Component for ChildComponent { } } -impl Renderable for ChildComponent { +impl Renderable for Child { + fn view(&self) -> Html { + unimplemented!() + } +} + +#[derive(Properties)] +pub struct ChildContainerProperties { + pub children: ChildrenWithProps, +} + +pub struct ChildContainer; +impl Component for ChildContainer { + type Message = (); + type Properties = ChildContainerProperties; + + fn create(props: Self::Properties, _: ComponentLink) -> Self { + ChildContainer + } + + fn update(&mut self, _: Self::Message) -> ShouldRender { + unimplemented!() + } +} + +impl Renderable for ChildContainer { fn view(&self) -> Html { unimplemented!() } } fn compile_fail() { - html! { }; - html! { }; - html! { }; - html! { }; - html! { }; - html! { }; - html! { }; - html! { }; - html! { }; - html! { }; - html! { }; - html! { }; - html! { }; - html! { }; - html! { }; - html! { }; - html! { }; - html! { }; - html! { }; - html! { { "Not allowed" } }; + html! { }; + html! { }; + html! { }; + html! { }; + html! { }; + html! { }; + html! { }; + html! { }; + html! { }; + html! { }; + html! { }; + html! { }; + html! { }; + html! { }; + html! { }; + html! { }; + html! { }; + html! { }; + html! { }; + html! { { "Not allowed" } }; + html! { { "Not allowed" } }; + html! { <> }; + html! { }; + html! { }; + html! { }; } fn main() {} diff --git a/tests/macro/html-component-fail.stderr b/tests/macro/html-component-fail.stderr index 74ffbd2da03..ce939ad182a 100644 --- a/tests/macro/html-component-fail.stderr +++ b/tests/macro/html-component-fail.stderr @@ -1,186 +1,231 @@ error: this open tag has no corresponding close tag - --> $DIR/html-component-fail.rs:33:13 + --> $DIR/html-component-fail.rs:58:13 | -33 | html! { }; - | ^^^^^^^^^^^^^^^^ +58 | html! { }; + | ^^^^^^^ error: expected identifier - --> $DIR/html-component-fail.rs:34:31 + --> $DIR/html-component-fail.rs:59:22 | -34 | html! { }; - | ^ +59 | html! { }; + | ^ error: this open tag has no corresponding close tag - --> $DIR/html-component-fail.rs:37:13 + --> $DIR/html-component-fail.rs:62:13 | -37 | html! { }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +62 | html! { }; + | ^^^^^^^^^^^^^^^^^^^ error: expected type, found `/` - --> $DIR/html-component-fail.rs:49:14 + --> $DIR/html-component-fail.rs:74:14 | -49 | html! { }; +74 | html! { }; | ^ error: this open tag has no corresponding close tag - --> $DIR/html-component-fail.rs:50:13 + --> $DIR/html-component-fail.rs:75:13 | -50 | html! { }; - | ^^^^^^^^^^^^^^^^ +75 | html! { }; + | ^^^^^^^ error: only one root html element allowed - --> $DIR/html-component-fail.rs:51:46 + --> $DIR/html-component-fail.rs:76:28 | -51 | html! { }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +76 | html! { }; + | ^^^^^^^^^^^^^^^ error[E0425]: cannot find value `blah` in this scope - --> $DIR/html-component-fail.rs:38:34 + --> $DIR/html-component-fail.rs:63:25 | -38 | html! { }; - | ^^^^ not found in this scope +63 | html! { }; + | ^^^^ not found in this scope error[E0599]: no method named `build` found for type `ChildPropertiesBuilder` in the current scope - --> $DIR/html-component-fail.rs:35:5 + --> $DIR/html-component-fail.rs:60:5 | 5 | #[derive(Properties, PartialEq)] | - method `build` not found for this ... -35 | html! { }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +60 | html! { }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) error[E0599]: no method named `build` found for type `ChildPropertiesBuilder` in the current scope - --> $DIR/html-component-fail.rs:36:5 + --> $DIR/html-component-fail.rs:61:5 | 5 | #[derive(Properties, PartialEq)] | - method `build` not found for this ... -36 | html! { }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +61 | html! { }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) error[E0599]: no method named `build` found for type `ChildPropertiesBuilder` in the current scope - --> $DIR/html-component-fail.rs:39:5 + --> $DIR/html-component-fail.rs:64:5 | 5 | #[derive(Properties, PartialEq)] | - method `build` not found for this ... -39 | html! { }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +64 | html! { }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) error[E0599]: no method named `build` found for type `ChildPropertiesBuilder` in the current scope - --> $DIR/html-component-fail.rs:40:5 + --> $DIR/html-component-fail.rs:65:5 | 5 | #[derive(Properties, PartialEq)] | - method `build` not found for this ... -40 | html! { }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +65 | html! { }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) error[E0599]: no method named `build` found for type `ChildPropertiesBuilder` in the current scope - --> $DIR/html-component-fail.rs:41:5 + --> $DIR/html-component-fail.rs:66:5 | 5 | #[derive(Properties, PartialEq)] | - method `build` not found for this ... -41 | html! { }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +66 | html! { }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) error[E0609]: no field `unknown` on type `ChildProperties` - --> $DIR/html-component-fail.rs:42:29 + --> $DIR/html-component-fail.rs:67:20 | -42 | html! { }; - | ^^^^^^^ unknown field +67 | html! { }; + | ^^^^^^^ unknown field | = note: available fields are: `string`, `int` error[E0599]: no method named `unknown` found for type `ChildPropertiesBuilder` in the current scope - --> $DIR/html-component-fail.rs:42:29 + --> $DIR/html-component-fail.rs:67:20 | 5 | #[derive(Properties, PartialEq)] | - method `unknown` not found for this ... -42 | html! { }; - | ^^^^^^^ +67 | html! { }; + | ^^^^^^^ error[E0599]: no method named `build` found for type `ChildPropertiesBuilder` in the current scope - --> $DIR/html-component-fail.rs:43:5 + --> $DIR/html-component-fail.rs:68:5 | 5 | #[derive(Properties, PartialEq)] | - method `build` not found for this ... -43 | html! { }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +68 | html! { }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) error[E0308]: mismatched types - --> $DIR/html-component-fail.rs:44:42 + --> $DIR/html-component-fail.rs:69:33 | -44 | html! { }; - | ^^ expected struct `std::string::String`, found () +69 | html! { }; + | ^^ expected struct `std::string::String`, found () | = note: expected type `std::string::String` found type `()` error[E0308]: mismatched types - --> $DIR/html-component-fail.rs:45:42 + --> $DIR/html-component-fail.rs:70:33 | -45 | html! { }; - | ^ - | | - | expected struct `std::string::String`, found integer - | help: try using a conversion method: `3.to_string()` +70 | html! { }; + | ^ + | | + | expected struct `std::string::String`, found integer + | help: try using a conversion method: `3.to_string()` | = note: expected type `std::string::String` found type `{integer}` error[E0308]: mismatched types - --> $DIR/html-component-fail.rs:46:42 + --> $DIR/html-component-fail.rs:71:33 | -46 | html! { }; - | ^^^ - | | - | expected struct `std::string::String`, found integer - | help: try using a conversion method: `{3}.to_string()` +71 | html! { }; + | ^^^ + | | + | expected struct `std::string::String`, found integer + | help: try using a conversion method: `{3}.to_string()` | = note: expected type `std::string::String` found type `{integer}` error[E0308]: mismatched types - --> $DIR/html-component-fail.rs:47:33 + --> $DIR/html-component-fail.rs:72:24 | -47 | html! { }; - | ^^^^ expected i32, found u32 +72 | html! { }; + | ^^^^ expected i32, found u32 error[E0599]: no method named `string` found for type `ChildPropertiesBuilder` in the current scope - --> $DIR/html-component-fail.rs:48:29 + --> $DIR/html-component-fail.rs:73:20 | 5 | #[derive(Properties, PartialEq)] | - method `string` not found for this ... -48 | html! { }; - | ^^^^^^ +73 | html! { }; + | ^^^^^^ error[E0599]: no method named `children` found for type `ChildPropertiesBuilder` in the current scope - --> $DIR/html-component-fail.rs:52:5 + --> $DIR/html-component-fail.rs:77:5 | 5 | #[derive(Properties, PartialEq)] | - method `children` not found for this ... -52 | html! { { "Not allowed" } }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +77 | html! { { "Not allowed" } }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +error[E0277]: the trait bound `yew::virtual_dom::vcomp::VChild: std::convert::From>` is not satisfied + --> $DIR/html-component-fail.rs:78:5 + | +78 | html! { { "Not allowed" } }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From>` is not implemented for `yew::virtual_dom::vcomp::VChild` + | + = note: required because of the requirements on the impl of `std::convert::Into>` for `yew::virtual_dom::vnode::VNode<_>` + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +error[E0277]: the trait bound `yew::virtual_dom::vcomp::VChild: std::convert::From>` is not satisfied + --> $DIR/html-component-fail.rs:79:5 + | +79 | html! { <> }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From>` is not implemented for `yew::virtual_dom::vcomp::VChild` + | + = note: required because of the requirements on the impl of `std::convert::Into>` for `yew::virtual_dom::vnode::VNode<_>` + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +error[E0277]: the trait bound `yew::virtual_dom::vcomp::VChild: std::convert::From>` is not satisfied + --> $DIR/html-component-fail.rs:80:5 + | +80 | html! { }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From>` is not implemented for `yew::virtual_dom::vcomp::VChild` + | + = note: required because of the requirements on the impl of `std::convert::Into>` for `yew::virtual_dom::vcomp::VChild` + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +error[E0277]: the trait bound `yew::virtual_dom::vcomp::VChild: std::convert::From>` is not satisfied + --> $DIR/html-component-fail.rs:81:5 + | +81 | html! { }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From>` is not implemented for `yew::virtual_dom::vcomp::VChild` + | + = note: required because of the requirements on the impl of `std::convert::Into>` for `yew::virtual_dom::vcomp::VChild` + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +error[E0277]: the trait bound `yew::virtual_dom::vcomp::VChild: std::convert::From>` is not satisfied + --> $DIR/html-component-fail.rs:82:5 + | +82 | html! { }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From>` is not implemented for `yew::virtual_dom::vcomp::VChild` | + = note: required because of the requirements on the impl of `std::convert::Into>` for `yew::virtual_dom::vnode::VNode<_>` = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) -Some errors occurred: E0308, E0425, E0599, E0609. -For more information about an error, try `rustc --explain E0308`. +Some errors occurred: E0277, E0308, E0425, E0599, E0609. +For more information about an error, try `rustc --explain E0277`. diff --git a/tests/macro/html-component-pass.rs b/tests/macro/html-component-pass.rs index 7e5557b5ea8..50e5f386877 100644 --- a/tests/macro/html-component-pass.rs +++ b/tests/macro/html-component-pass.rs @@ -14,13 +14,13 @@ pub struct ChildProperties { pub optional_callback: Option>, } -pub struct ChildComponent; -impl Component for ChildComponent { +pub struct Child; +impl Component for Child { type Message = (); type Properties = ChildProperties; fn create(_: Self::Properties, _: ComponentLink) -> Self { - ChildComponent + Child } fn update(&mut self, _: Self::Message) -> ShouldRender { @@ -28,26 +28,26 @@ impl Component for ChildComponent { } } -impl Renderable for ChildComponent { +impl Renderable for Child { fn view(&self) -> Html { unimplemented!() } } #[derive(Properties, Default)] -pub struct ParentProperties { +pub struct ContainerProperties { #[props(required)] pub int: i32, - pub children: Children, + pub children: Children, } -pub struct ParentComponent; -impl Component for ParentComponent { +pub struct Container; +impl Component for Container { type Message = (); - type Properties = ParentProperties; + type Properties = ContainerProperties; fn create(_: Self::Properties, _: ComponentLink) -> Self { - ParentComponent + Container } fn update(&mut self, _: Self::Message) -> ShouldRender { @@ -55,89 +55,116 @@ impl Component for ParentComponent { } } -impl Renderable for ParentComponent { +impl Renderable for Container { + fn view(&self) -> Html { + unimplemented!() + } +} + +#[derive(Properties, Default)] +pub struct ChildContainerProperties { + #[props(required)] + pub int: i32, + pub children: ChildrenWithProps, +} + +pub struct ChildContainer; +impl Component for ChildContainer { + type Message = (); + type Properties = ChildContainerProperties; + + fn create(_: Self::Properties, _: ComponentLink) -> Self { + ChildContainer + } + + fn update(&mut self, _: Self::Message) -> ShouldRender { + unimplemented!() + } +} + +impl Renderable for ChildContainer { fn view(&self) -> Html { unimplemented!() } } mod scoped { - pub use super::ChildComponent; - pub use super::ParentComponent; + pub use super::Child; + pub use super::Container; } pass_helper! { - html! { }; + html! { }; // backwards compat - html! { }; + html! { }; html! { <> - - + + // backwards compat - - + + }; - let props = ::Properties::default(); - let props2 = ::Properties::default(); + let props = ::Properties::default(); + let props2 = ::Properties::default(); html! { <> - + // backwards compat - + }; html! { <> - - - - - + + + + + // backwards compat - + }; let name_expr = "child"; html! { - + }; html! { <> - - + + }; - let props = ::Properties::default(); + let props = ::Properties::default(); html! { <> - - + + - + <> - + - - - + + + - - - + + + - ::std::vec::Vec<_> { vec![html!{ "String" }] @@ -146,6 +173,15 @@ pass_helper! { ) /> }; + + html! { + <> + + + + + + }; } fn main() {} From 14954571f7b9da683577fada1ce81ddda7d28bb4 Mon Sep 17 00:00:00 2001 From: Kelly Thomas Kline Date: Sun, 25 Aug 2019 19:28:12 -0700 Subject: [PATCH 024/193] Add `Classes` to prelude --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 153250cdc48..977fc43601a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -152,6 +152,7 @@ pub mod prelude { Component, ComponentLink, Href, Html, Properties, Renderable, ShouldRender, }; pub use crate::macros::*; + pub use crate::virtual_dom::Classes; /// Prelude module for creating worker. pub mod worker { From d574a2ca7ba7bd81ea0d2f36dd8999a2ac37c56b Mon Sep 17 00:00:00 2001 From: Cameron Taggart Date: Thu, 29 Aug 2019 16:47:22 +0200 Subject: [PATCH 025/193] in yewstack org --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e009a0f69fd..63719be074d 100644 --- a/README.md +++ b/README.md @@ -443,7 +443,7 @@ your project's `Cargo.toml`: ```toml [dependencies] -yew = { git = "https://github.com/DenisKolodin/yew", features = ["toml", "yaml", "msgpack", "cbor"] } +yew = { git = "https://github.com/yewstack/yew", features = ["toml", "yaml", "msgpack", "cbor"] } ``` ## Development setup From 107fcaf506bced776f3b70774a5f519913e887ac Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Thu, 29 Aug 2019 19:18:58 -0400 Subject: [PATCH 026/193] Initial implementation using an iterator adaptor to provide a coherent struct to implement Into (via From<>) for --- src/virtual_dom/vnode.rs | 50 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/virtual_dom/vnode.rs b/src/virtual_dom/vnode.rs index 703babcfe34..f601b692d94 100644 --- a/src/virtual_dom/vnode.rs +++ b/src/virtual_dom/vnode.rs @@ -107,6 +107,56 @@ impl<'a, COMP: Component> From<&'a dyn Renderable> for VNode { } } +/// Iterator adaptor for displaying html. +pub struct HtmlAdaptor { + it: I +} +/// Trait to convert to html adaptor. +pub trait ToHtmlAdaptor { + /// Converts existing iterator to an html adaptor + fn html(self) -> HtmlAdaptor; +} + +impl ToHtmlAdaptor for II + where + II: IntoIterator, + I: Iterator + { + fn html(self) -> HtmlAdaptor { + HtmlAdaptor { + it: self.into_iter() + } + } +} + +impl<'a, I, T: 'a> Iterator for HtmlAdaptor +where I: Iterator, +{ + type Item = T; + + fn next(&mut self) -> Option { + self.it.next() + } +} + + +impl From> for VNode +where + COMP: Component, + I: Iterator, + T: Into> +{ + fn from(i: HtmlAdaptor) -> Self { + let vlist = i + .into_iter() + .fold(VList::new(), |mut acc, x| { + acc.add_child(x.into()); + acc + }); + VNode::VList(vlist) + } +} + impl fmt::Debug for VNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { From b6b55ab5f56eabd566de8c7a843f62e6607a4432 Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Thu, 29 Aug 2019 19:25:01 -0400 Subject: [PATCH 027/193] update large table example to demonstrate new .html() method instead of 'for' --- examples/large_table/src/lib.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/large_table/src/lib.rs b/examples/large_table/src/lib.rs index 7c4766413a5..6f45360ce20 100644 --- a/examples/large_table/src/lib.rs +++ b/examples/large_table/src/lib.rs @@ -57,11 +57,10 @@ fn view_row(selected: Option<(u32, u32)>, row: u32) -> Html { impl Renderable for Model { fn view(&self) -> Html { + use yew::virtual_dom::vnode::ToHtmlAdaptor; html! { - {for (0..99).map(|row| { - view_row(self.selected, row) - })} + { (0..99).map(|row| view_row(self.selected, row)).html() }
} } From 4a9a4af6ab0c63a1e75421268174142f4bf7f974 Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Thu, 29 Aug 2019 20:15:41 -0400 Subject: [PATCH 028/193] ran cargo fmt --- src/virtual_dom/vnode.rs | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/virtual_dom/vnode.rs b/src/virtual_dom/vnode.rs index f601b692d94..834264b57df 100644 --- a/src/virtual_dom/vnode.rs +++ b/src/virtual_dom/vnode.rs @@ -109,7 +109,7 @@ impl<'a, COMP: Component> From<&'a dyn Renderable> for VNode { /// Iterator adaptor for displaying html. pub struct HtmlAdaptor { - it: I + it: I, } /// Trait to convert to html adaptor. pub trait ToHtmlAdaptor { @@ -117,20 +117,21 @@ pub trait ToHtmlAdaptor { fn html(self) -> HtmlAdaptor; } -impl ToHtmlAdaptor for II - where - II: IntoIterator, - I: Iterator - { +impl ToHtmlAdaptor for II +where + II: IntoIterator, + I: Iterator, +{ fn html(self) -> HtmlAdaptor { HtmlAdaptor { - it: self.into_iter() + it: self.into_iter(), } } } impl<'a, I, T: 'a> Iterator for HtmlAdaptor -where I: Iterator, +where + I: Iterator, { type Item = T; @@ -139,20 +140,18 @@ where I: Iterator, } } - impl From> for VNode where COMP: Component, - I: Iterator, - T: Into> + I: Iterator, + T: Into>, { fn from(i: HtmlAdaptor) -> Self { - let vlist = i - .into_iter() - .fold(VList::new(), |mut acc, x| { - acc.add_child(x.into()); - acc - }); + let vlist = i.into_iter().fold(VList::new(), |mut acc, x| { + // TODO, adding a VList::with_size(usize) might improve performance via eliminating reallocation as the vec grows. + acc.add_child(x.into()); + acc + }); VNode::VList(vlist) } } From c976e1afb3e66e62ac431b2b20d1e7da35c6c942 Mon Sep 17 00:00:00 2001 From: Kelly Thomas Kline Date: Sat, 31 Aug 2019 01:07:42 -0700 Subject: [PATCH 029/193] Add a section for project templates to the README --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index e009a0f69fd..8dc609a5778 100644 --- a/README.md +++ b/README.md @@ -509,3 +509,9 @@ if you tell the `cargo-web` to build for them using the `--target` parameter. [todomvc]: examples/todomvc [two_apps]: examples/two_apps [cargo-web]: https://github.com/koute/cargo-web + + +## Project templates + +* [`yew-wasm-pack-template`](https://github.com/yewstack/yew-wasm-pack-template) +* [`yew-wasm-pack-minimal`](https://github.com/yewstack/yew-wasm-pack-minimal) From c4fa17004b21db5d97955b2bde3debb464a0f1bd Mon Sep 17 00:00:00 2001 From: Denis Kolodin Date: Sun, 1 Sep 2019 00:02:24 +0900 Subject: [PATCH 030/193] Change org to YewStack --- Cargo.toml | 6 +++--- crates/macro/Cargo.toml | 6 +++--- crates/macro/src/lib.rs | 2 +- examples/game_of_life/src/lib.rs | 2 +- examples/js_callback/README.md | 2 +- src/virtual_dom/vlist.rs | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 98c4418619f..510c4809786 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,8 +6,8 @@ authors = [ "Denis Kolodin ", "Justin Starry ", ] -repository = "https://github.com/DenisKolodin/yew" -homepage = "https://github.com/DenisKolodin/yew" +repository = "https://github.com/yewstack/yew" +homepage = "https://github.com/yewstack/yew" documentation = "https://docs.rs/yew/" license = "MIT/Apache-2.0" readme = "README.md" @@ -16,7 +16,7 @@ categories = ["gui", "web-programming"] description = "A framework for making client-side single-page apps" [badges] -travis-ci = { repository = "DenisKolodin/yew" } +travis-ci = { repository = "yewstack/yew" } [dependencies] anymap = "0.12" diff --git a/crates/macro/Cargo.toml b/crates/macro/Cargo.toml index f6fdfa20652..d2dd610aa1e 100644 --- a/crates/macro/Cargo.toml +++ b/crates/macro/Cargo.toml @@ -3,8 +3,8 @@ name = "yew-macro" version = "0.9.0" edition = "2018" authors = ["Justin Starry "] -repository = "https://github.com/DenisKolodin/yew" -homepage = "https://github.com/DenisKolodin/yew" +repository = "https://github.com/yewstack/yew" +homepage = "https://github.com/yewstack/yew" documentation = "https://docs.rs/yew-macro/" license = "MIT/Apache-2.0" keywords = ["web", "wasm", "frontend", "webasm", "webassembly"] @@ -12,7 +12,7 @@ categories = ["gui", "web-programming", "wasm"] description = "A framework for making client-side single-page apps" [badges] -travis-ci = { repository = "DenisKolodin/yew" } +travis-ci = { repository = "yewstack/yew" } [lib] proc-macro = true diff --git a/crates/macro/src/lib.rs b/crates/macro/src/lib.rs index 012f53c5763..66a28296a50 100644 --- a/crates/macro/src/lib.rs +++ b/crates/macro/src/lib.rs @@ -51,7 +51,7 @@ //! # fn main() {} //! ``` //! -//! Please refer to [https://github.com/DenisKolodin/yew](https://github.com/DenisKolodin/yew) for how to set this up. +//! Please refer to [https://github.com/yewstack/yew](https://github.com/yewstack/yew) for how to set this up. #![recursion_limit = "128"] extern crate proc_macro; diff --git a/examples/game_of_life/src/lib.rs b/examples/game_of_life/src/lib.rs index 085962706d4..fd4736ccb0f 100644 --- a/examples/game_of_life/src/lib.rs +++ b/examples/game_of_life/src/lib.rs @@ -233,7 +233,7 @@ impl Renderable for Model { { "Game of Life - a yew experiment " } - { "source" } + { "source" }
} diff --git a/examples/js_callback/README.md b/examples/js_callback/README.md index e722ab5a4de..027f020adec 100644 --- a/examples/js_callback/README.md +++ b/examples/js_callback/README.md @@ -4,5 +4,5 @@ The purpose of this example is to demonstrate a simple case of asynchronously sending a message back into the component update loop. -See https://github.com/DenisKolodin/yew/issues/316 for discussion on what +See https://github.com/yewstack/yew/issues/316 for discussion on what motivated this example. diff --git a/src/virtual_dom/vlist.rs b/src/virtual_dom/vlist.rs index e1a4a65725a..e3d9d88db02 100644 --- a/src/virtual_dom/vlist.rs +++ b/src/virtual_dom/vlist.rs @@ -64,7 +64,7 @@ impl VDiff for VList { }; if self.childs.is_empty() { - // Fixes: https://github.com/DenisKolodin/yew/issues/294 + // Fixes: https://github.com/yewstack/yew/issues/294 // Without a placeholder the next element becomes first // and corrupts the order of rendering // We use empty text element to stake out a place From c1628e70d2b7a54dcb6b1c2cad6c4c742548af93 Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Sat, 31 Aug 2019 17:17:15 -0400 Subject: [PATCH 031/193] Implement FromIterator instead of wrapping iterator --- examples/large_table/src/lib.rs | 3 +- src/virtual_dom/vnode.rs | 95 ++++++++++++++++++--------------- 2 files changed, 54 insertions(+), 44 deletions(-) diff --git a/examples/large_table/src/lib.rs b/examples/large_table/src/lib.rs index 6f45360ce20..69b5240af89 100644 --- a/examples/large_table/src/lib.rs +++ b/examples/large_table/src/lib.rs @@ -57,10 +57,9 @@ fn view_row(selected: Option<(u32, u32)>, row: u32) -> Html { impl Renderable for Model { fn view(&self) -> Html { - use yew::virtual_dom::vnode::ToHtmlAdaptor; html! { - { (0..99).map(|row| view_row(self.selected, row)).html() } + { (0..99).map(|row| view_row(self.selected, row)).collect::>() }
} } diff --git a/src/virtual_dom/vnode.rs b/src/virtual_dom/vnode.rs index 834264b57df..3b11379bbac 100644 --- a/src/virtual_dom/vnode.rs +++ b/src/virtual_dom/vnode.rs @@ -5,6 +5,7 @@ use crate::html::{Component, Renderable, Scope}; use std::cmp::PartialEq; use std::fmt; use stdweb::web::{Element, INode, Node}; +use std::iter::FromIterator; /// Bind virtual element to a DOM reference. pub enum VNode { @@ -107,54 +108,64 @@ impl<'a, COMP: Component> From<&'a dyn Renderable> for VNode { } } -/// Iterator adaptor for displaying html. -pub struct HtmlAdaptor { - it: I, -} -/// Trait to convert to html adaptor. -pub trait ToHtmlAdaptor { - /// Converts existing iterator to an html adaptor - fn html(self) -> HtmlAdaptor; -} - -impl ToHtmlAdaptor for II -where - II: IntoIterator, - I: Iterator, -{ - fn html(self) -> HtmlAdaptor { - HtmlAdaptor { - it: self.into_iter(), - } - } -} - -impl<'a, I, T: 'a> Iterator for HtmlAdaptor -where - I: Iterator, -{ - type Item = T; - - fn next(&mut self) -> Option { - self.it.next() - } -} - -impl From> for VNode -where - COMP: Component, - I: Iterator, - T: Into>, -{ - fn from(i: HtmlAdaptor) -> Self { - let vlist = i.into_iter().fold(VList::new(), |mut acc, x| { - // TODO, adding a VList::with_size(usize) might improve performance via eliminating reallocation as the vec grows. +impl >> FromIterator for VNode { + fn from_iter>(iter: T) -> Self { + let vlist = iter.into_iter().fold(VList::new(), |mut acc, x| { acc.add_child(x.into()); acc }); VNode::VList(vlist) } } +// +///// Iterator adaptor for displaying html. +//pub struct HtmlAdaptor { +// it: I, +//} +///// Trait to convert to html adaptor. +//pub trait ToHtmlAdaptor { +// /// Converts existing iterator to an html adaptor +// fn html(self) -> HtmlAdaptor; +//} +// +//impl ToHtmlAdaptor for II +//where +// II: IntoIterator, +// I: Iterator, +//{ +// fn html(self) -> HtmlAdaptor { +// HtmlAdaptor { +// it: self.into_iter(), +// } +// } +//} +// +//impl<'a, I, T: 'a> Iterator for HtmlAdaptor +//where +// I: Iterator, +//{ +// type Item = T; +// +// fn next(&mut self) -> Option { +// self.it.next() +// } +//} +// +//impl From> for VNode +//where +// COMP: Component, +// I: Iterator, +// T: Into>, +//{ +// fn from(i: HtmlAdaptor) -> Self { +// let vlist = i.into_iter().fold(VList::new(), |mut acc, x| { +// // TODO, adding a VList::with_size(usize) might improve performance via eliminating reallocation as the vec grows. +// acc.add_child(x.into()); +// acc +// }); +// VNode::VList(vlist) +// } +//} impl fmt::Debug for VNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { From b1849d00e73e5d6f87f8cd756170ad5ad692f16f Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Sat, 31 Aug 2019 17:17:36 -0400 Subject: [PATCH 032/193] remove dead code --- src/virtual_dom/vnode.rs | 49 ---------------------------------------- 1 file changed, 49 deletions(-) diff --git a/src/virtual_dom/vnode.rs b/src/virtual_dom/vnode.rs index 3b11379bbac..b3892c7ae57 100644 --- a/src/virtual_dom/vnode.rs +++ b/src/virtual_dom/vnode.rs @@ -117,55 +117,6 @@ impl >> FromIterator for VNode { VNode::VList(vlist) } } -// -///// Iterator adaptor for displaying html. -//pub struct HtmlAdaptor { -// it: I, -//} -///// Trait to convert to html adaptor. -//pub trait ToHtmlAdaptor { -// /// Converts existing iterator to an html adaptor -// fn html(self) -> HtmlAdaptor; -//} -// -//impl ToHtmlAdaptor for II -//where -// II: IntoIterator, -// I: Iterator, -//{ -// fn html(self) -> HtmlAdaptor { -// HtmlAdaptor { -// it: self.into_iter(), -// } -// } -//} -// -//impl<'a, I, T: 'a> Iterator for HtmlAdaptor -//where -// I: Iterator, -//{ -// type Item = T; -// -// fn next(&mut self) -> Option { -// self.it.next() -// } -//} -// -//impl From> for VNode -//where -// COMP: Component, -// I: Iterator, -// T: Into>, -//{ -// fn from(i: HtmlAdaptor) -> Self { -// let vlist = i.into_iter().fold(VList::new(), |mut acc, x| { -// // TODO, adding a VList::with_size(usize) might improve performance via eliminating reallocation as the vec grows. -// acc.add_child(x.into()); -// acc -// }); -// VNode::VList(vlist) -// } -//} impl fmt::Debug for VNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { From 87f1b82d2edaaa20a60521a141b03337ea834802 Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Sat, 31 Aug 2019 19:23:22 -0400 Subject: [PATCH 033/193] ran fmt --- src/virtual_dom/vnode.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/virtual_dom/vnode.rs b/src/virtual_dom/vnode.rs index b3892c7ae57..2ad03054498 100644 --- a/src/virtual_dom/vnode.rs +++ b/src/virtual_dom/vnode.rs @@ -4,8 +4,8 @@ use super::{VComp, VDiff, VList, VTag, VText}; use crate::html::{Component, Renderable, Scope}; use std::cmp::PartialEq; use std::fmt; -use stdweb::web::{Element, INode, Node}; use std::iter::FromIterator; +use stdweb::web::{Element, INode, Node}; /// Bind virtual element to a DOM reference. pub enum VNode { @@ -108,8 +108,8 @@ impl<'a, COMP: Component> From<&'a dyn Renderable> for VNode { } } -impl >> FromIterator for VNode { - fn from_iter>(iter: T) -> Self { +impl>> FromIterator for VNode { + fn from_iter>(iter: T) -> Self { let vlist = iter.into_iter().fold(VList::new(), |mut acc, x| { acc.add_child(x.into()); acc From db52aa40bf57b0cee71a511ed8222ea87cc391b1 Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Sun, 1 Sep 2019 10:22:21 -0400 Subject: [PATCH 034/193] Add extend method to Classes --- src/virtual_dom/mod.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/virtual_dom/mod.rs b/src/virtual_dom/mod.rs index 9ba19f2a03c..57ab86d9e50 100644 --- a/src/virtual_dom/mod.rs +++ b/src/virtual_dom/mod.rs @@ -17,6 +17,7 @@ pub use self::vnode::VNode; pub use self::vtag::VTag; pub use self::vtext::VText; use crate::html::{Component, Scope}; +use std::ops::RangeFull; /// `Listener` trait is an universal implementation of an event listener /// which helps to bind Rust-listener to JS-listener (DOM). @@ -41,7 +42,7 @@ type Listeners = Vec>>; type Attributes = HashMap; /// A set of classes. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Classes { set: IndexSet, } @@ -63,6 +64,13 @@ impl Classes { pub fn contains(&self, class: &str) -> bool { self.set.contains(class) } + + /// Adds other classes to this class; returning itself. + pub fn extend>(mut self, other: T) -> Self { + let mut other: Classes = other.into(); + self.set.extend(other.set.drain(RangeFull)); + self + } } impl ToString for Classes { From 77958489ee513b3c9cb757dd855f1287ecf1de81 Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Sun, 1 Sep 2019 11:13:39 -0400 Subject: [PATCH 035/193] change to union --- src/virtual_dom/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/virtual_dom/mod.rs b/src/virtual_dom/mod.rs index 57ab86d9e50..35022559fee 100644 --- a/src/virtual_dom/mod.rs +++ b/src/virtual_dom/mod.rs @@ -66,7 +66,7 @@ impl Classes { } /// Adds other classes to this class; returning itself. - pub fn extend>(mut self, other: T) -> Self { + pub fn union>(mut self, other: T) -> Self { let mut other: Classes = other.into(); self.set.extend(other.set.drain(RangeFull)); self From 7965123db85caba84e1c802cda435c4f02aa4752 Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Sun, 1 Sep 2019 12:35:30 -0400 Subject: [PATCH 036/193] renamed union back to extend --- src/virtual_dom/mod.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/virtual_dom/mod.rs b/src/virtual_dom/mod.rs index 35022559fee..93a1dbc5add 100644 --- a/src/virtual_dom/mod.rs +++ b/src/virtual_dom/mod.rs @@ -56,6 +56,8 @@ impl Classes { } /// Adds a class to a set. + /// + /// Prevents duplication of class names. pub fn push(&mut self, class: &str) { self.set.insert(class.into()); } @@ -65,10 +67,11 @@ impl Classes { self.set.contains(class) } - /// Adds other classes to this class; returning itself. - pub fn union>(mut self, other: T) -> Self { - let mut other: Classes = other.into(); - self.set.extend(other.set.drain(RangeFull)); + /// Adds other classes to this set of classes; returning itself. + /// + /// Takes the logical union of both `Classes`. + pub fn extend>(mut self, other: T) -> Self { + self.set.extend(other.into().set.into_iter()); self } } From 527cb9865e1632c077730e8f180083ccc380ad8e Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Wed, 4 Sep 2019 20:16:37 -0400 Subject: [PATCH 037/193] Add is_empty and len methods to ChildrenRenderer --- crates/macro/src/html_tree/html_component.rs | 4 +++- examples/nested_list/src/item.rs | 5 ++--- src/html/mod.rs | 16 ++++++++++++++-- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/crates/macro/src/html_tree/html_component.rs b/crates/macro/src/html_tree/html_component.rs index 27bc58d4d9d..35b00654516 100644 --- a/crates/macro/src/html_tree/html_component.rs +++ b/crates/macro/src/html_tree/html_component.rs @@ -120,14 +120,16 @@ impl ToTokens for HtmlComponent { }; let set_children = if !children.is_empty() { + let children_len = children.len(); quote! { .children(::yew::html::ChildrenRenderer::new( + #children_len, ::std::boxed::Box::new(move || { #[allow(unused_must_use)] || -> ::std::vec::Vec<_> { vec![#(#children.into(),)*] } - }()) + }()), )) } } else { diff --git a/examples/nested_list/src/item.rs b/examples/nested_list/src/item.rs index 397f641b1ba..bb4c30d2ba2 100644 --- a/examples/nested_list/src/item.rs +++ b/examples/nested_list/src/item.rs @@ -53,14 +53,13 @@ impl Renderable for ListItem { impl ListItem { fn view_details(&self) -> Html { - let children = self.props.children.to_vec(); - if children.is_empty() { + if self.props.children.is_empty() { return html! {}; } html! {
- { for children.into_iter() } + { for self.props.children.iter() }
} } diff --git a/src/html/mod.rs b/src/html/mod.rs index 0f18839a871..60edf698a1f 100644 --- a/src/html/mod.rs +++ b/src/html/mod.rs @@ -56,13 +56,24 @@ pub type ChildrenWithProps = ChildrenRenderer>; /// A type used for rendering children html. pub struct ChildrenRenderer { + len: usize, boxed_render: Box Vec>, } impl ChildrenRenderer { /// Create children - pub fn new(boxed_render: Box Vec>) -> Self { - Self { boxed_render } + pub fn new(len: usize, boxed_render: Box Vec>) -> Self { + Self { len, boxed_render } + } + + /// Children list is empty + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + /// Number of children elements + pub fn len(&self) -> usize { + self.len } /// Build children components and return `Vec` @@ -79,6 +90,7 @@ impl ChildrenRenderer { impl Default for ChildrenRenderer { fn default() -> Self { Self { + len: 0, boxed_render: Box::new(|| Vec::new()), } } From 563807a861a2ddf49e0f7c4f80019129d3546a3c Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Wed, 4 Sep 2019 21:26:36 -0400 Subject: [PATCH 038/193] Implement Renderable trait and add docs --- examples/nested_list/src/item.rs | 2 +- src/html/mod.rs | 72 +++++++++++++++++++++++++++++- tests/macro/html-component-pass.rs | 3 +- 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/examples/nested_list/src/item.rs b/examples/nested_list/src/item.rs index bb4c30d2ba2..6fffcc2af7f 100644 --- a/examples/nested_list/src/item.rs +++ b/examples/nested_list/src/item.rs @@ -59,7 +59,7 @@ impl ListItem { html! {
- { for self.props.children.iter() } + { self.props.children.view() }
} } diff --git a/src/html/mod.rs b/src/html/mod.rs index 60edf698a1f..51a98deeaea 100644 --- a/src/html/mod.rs +++ b/src/html/mod.rs @@ -11,7 +11,7 @@ pub(crate) use scope::ComponentUpdate; pub use scope::{NodeCell, Scope}; use crate::callback::Callback; -use crate::virtual_dom::{VChild, VNode}; +use crate::virtual_dom::{VChild, VNode, VList}; /// This type indicates that component should be rendered again. pub type ShouldRender = bool; @@ -49,9 +49,71 @@ pub trait Component: Sized + 'static { pub type Html = VNode; /// A type used for accepting children elements in Component::Properties. +/// +/// # Example +/// **`model.rs`** +/// +/// In this example, the Wrapper component is used to wrap other elements. +/// ``` +/// html!{ +/// +///

{"Hi"}

+///
{"Hello"}
+///
+/// } +/// ``` +/// +/// **`wrapper.rs`** +/// +/// The Wrapper component must define a `children` property in order to wrap other elements. The +/// children property can be used to render the wrapped elements. +/// ``` +/// #[derive(Properties)] +/// struct WrapperProps { +/// children: Children, +/// } +/// +/// html!{ +///
+/// { self.props.children.view() } +///
+/// } +/// ``` pub type Children = ChildrenRenderer>; /// A type used for accepting children elements in Component::Properties and accessing their props. +/// +/// # Example +/// **`model.rs`** +/// +/// In this example, the `List` component can wrap `ListItem` components. +/// ``` +/// html!{ +/// +/// +/// +/// +/// +/// } +/// ``` +/// +/// **`list.rs`** +/// +/// The `List` component must define a `children` property in order to wrap the list items. The +/// `children` property can be used to filter, mutate, and render the items. +/// ``` +/// #[derive(Properties)] +/// struct ListProps { +/// children: ChildrenWithProps, +/// } +/// +/// html!{{ +/// for self.props.children.iter().map(|mut item| { +/// item.props.value = format!("item-{}", item.props.value); +/// item +/// }) +/// }} +/// ``` pub type ChildrenWithProps = ChildrenRenderer>; /// A type used for rendering children html. @@ -96,6 +158,14 @@ impl Default for ChildrenRenderer { } } +impl Renderable for ChildrenRenderer where T: Into>{ + fn view(&self) -> Html { + VList { + childs: self.iter().map(|c| c.into()).collect() + }.into() + } +} + /// Should be rendered relative to context and component environment. pub trait Renderable { /// Called by rendering loop. diff --git a/tests/macro/html-component-pass.rs b/tests/macro/html-component-pass.rs index 50e5f386877..c6a3d318778 100644 --- a/tests/macro/html-component-pass.rs +++ b/tests/macro/html-component-pass.rs @@ -165,11 +165,12 @@ pass_helper! { ::std::vec::Vec<_> { vec![html!{ "String" }] } - }()) + }()), ) /> }; From 429ab739c1dfc338ee82d69207c0b7a895d9bbdc Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Wed, 4 Sep 2019 22:29:42 -0400 Subject: [PATCH 039/193] cargo fmt --- src/html/mod.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/html/mod.rs b/src/html/mod.rs index 51a98deeaea..9f6f3bf9298 100644 --- a/src/html/mod.rs +++ b/src/html/mod.rs @@ -11,7 +11,7 @@ pub(crate) use scope::ComponentUpdate; pub use scope::{NodeCell, Scope}; use crate::callback::Callback; -use crate::virtual_dom::{VChild, VNode, VList}; +use crate::virtual_dom::{VChild, VList, VNode}; /// This type indicates that component should be rendered again. pub type ShouldRender = bool; @@ -158,11 +158,15 @@ impl Default for ChildrenRenderer { } } -impl Renderable for ChildrenRenderer where T: Into>{ +impl Renderable for ChildrenRenderer +where + T: Into>, +{ fn view(&self) -> Html { VList { - childs: self.iter().map(|c| c.into()).collect() - }.into() + childs: self.iter().map(|c| c.into()).collect(), + } + .into() } } From 7ae0c0efa5f1a8fda7435b8f07a8c924006b31ed Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Wed, 4 Sep 2019 23:37:21 -0400 Subject: [PATCH 040/193] removed unused import of RangeFull --- src/virtual_dom/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/virtual_dom/mod.rs b/src/virtual_dom/mod.rs index 93a1dbc5add..4859f1d1c0e 100644 --- a/src/virtual_dom/mod.rs +++ b/src/virtual_dom/mod.rs @@ -17,7 +17,6 @@ pub use self::vnode::VNode; pub use self::vtag::VTag; pub use self::vtext::VText; use crate::html::{Component, Scope}; -use std::ops::RangeFull; /// `Listener` trait is an universal implementation of an event listener /// which helps to bind Rust-listener to JS-listener (DOM). From 16c7864d631085bbc28fdd689b5da019a2d473eb Mon Sep 17 00:00:00 2001 From: Andrew Straw Date: Sun, 15 Sep 2019 11:35:51 +0200 Subject: [PATCH 041/193] fix #638 --- crates/macro/src/derive_props/builder.rs | 4 +++- crates/macro/src/derive_props/wrapper.rs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/macro/src/derive_props/builder.rs b/crates/macro/src/derive_props/builder.rs index 8d1f50800bc..198ef9b243d 100644 --- a/crates/macro/src/derive_props/builder.rs +++ b/crates/macro/src/derive_props/builder.rs @@ -64,7 +64,9 @@ impl ToTokens for PropsBuilder<'_> { #(impl #step_trait for #step_names {})* #[doc(hidden)] - #vis struct #builder_name#step_generics { + #vis struct #builder_name#step_generics + #where_clause + { wrapped: ::std::boxed::Box<#wrapper_name#ty_generics>, _marker: ::std::marker::PhantomData<#step_generic_param>, } diff --git a/crates/macro/src/derive_props/wrapper.rs b/crates/macro/src/derive_props/wrapper.rs index 82cb3aab903..d9b85cd3b12 100644 --- a/crates/macro/src/derive_props/wrapper.rs +++ b/crates/macro/src/derive_props/wrapper.rs @@ -24,7 +24,9 @@ impl ToTokens for PropsWrapper<'_> { let wrapper_default_setters = self.default_setters(); let wrapper = quote! { - struct #wrapper_name#generics { + struct #wrapper_name#generics + #where_clause + { #(#wrapper_field_defs)* } From 2256c042889dda9463f3d4413ea398d7f699d545 Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Fri, 20 Sep 2019 23:35:05 -0400 Subject: [PATCH 042/193] Fix VNode orphaning inside of VTags --- src/virtual_dom/vtag.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/virtual_dom/vtag.rs b/src/virtual_dom/vtag.rs index f0f320d4e60..06502d6a415 100644 --- a/src/virtual_dom/vtag.rs +++ b/src/virtual_dom/vtag.rs @@ -350,6 +350,12 @@ impl VDiff for VTag { .reference .take() .expect("tried to remove not rendered VTag from DOM"); + + // recursively remove its children + self.childs.drain(..).for_each(|mut v|{ + v.detach(&node); + }); + let sibling = node.next_sibling(); if parent.remove_child(&node).is_err() { warn!("Node not found to remove VTag"); From 5dc34e6e270cd6c5846f70b4d70e24557617b44c Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Sat, 21 Sep 2019 08:09:46 -0400 Subject: [PATCH 043/193] cargo fmt --- src/virtual_dom/vtag.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/virtual_dom/vtag.rs b/src/virtual_dom/vtag.rs index 06502d6a415..394553679bd 100644 --- a/src/virtual_dom/vtag.rs +++ b/src/virtual_dom/vtag.rs @@ -352,7 +352,7 @@ impl VDiff for VTag { .expect("tried to remove not rendered VTag from DOM"); // recursively remove its children - self.childs.drain(..).for_each(|mut v|{ + self.childs.drain(..).for_each(|mut v| { v.detach(&node); }); From 109504d517661d83386ade4db81513a134a4750c Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Sat, 21 Sep 2019 08:18:20 -0400 Subject: [PATCH 044/193] improve naming --- src/virtual_dom/vtag.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/virtual_dom/vtag.rs b/src/virtual_dom/vtag.rs index 394553679bd..263f3b15ff0 100644 --- a/src/virtual_dom/vtag.rs +++ b/src/virtual_dom/vtag.rs @@ -352,8 +352,8 @@ impl VDiff for VTag { .expect("tried to remove not rendered VTag from DOM"); // recursively remove its children - self.childs.drain(..).for_each(|mut v| { - v.detach(&node); + self.childs.drain(..).for_each(|mut child| { + child.detach(&node); }); let sibling = node.next_sibling(); From f8d07237cc5e885a7bf8072d19108b594908a3fe Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Sun, 22 Sep 2019 09:15:41 -1000 Subject: [PATCH 045/193] Add test --- crates/macro/src/derive_props/builder.rs | 2 +- tests/derive_props/pass.rs | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/crates/macro/src/derive_props/builder.rs b/crates/macro/src/derive_props/builder.rs index 198ef9b243d..d17ae1ca343 100644 --- a/crates/macro/src/derive_props/builder.rs +++ b/crates/macro/src/derive_props/builder.rs @@ -65,7 +65,7 @@ impl ToTokens for PropsBuilder<'_> { #[doc(hidden)] #vis struct #builder_name#step_generics - #where_clause + #where_clause { wrapped: ::std::boxed::Box<#wrapper_name#ty_generics>, _marker: ::std::marker::PhantomData<#step_generic_param>, diff --git a/tests/derive_props/pass.rs b/tests/derive_props/pass.rs index dc661769d37..57165bb19a7 100644 --- a/tests/derive_props/pass.rs +++ b/tests/derive_props/pass.rs @@ -83,4 +83,22 @@ mod t5 { } } +mod t6 { + use super::*; + use std::str::FromStr; + + #[derive(Properties, Clone)] + pub struct Props + where + ::Err: Clone, + { + #[props(required)] + value: Result::Err>, + } + + fn required_prop_generics_with_where_clause_should_work() { + Props::::builder().value(Ok(String::from(""))).build(); + } +} + fn main() {} From 30e67d06d4e61147dc51f61eab7cbe744ed7dd9a Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Sun, 22 Sep 2019 15:28:22 -0400 Subject: [PATCH 046/193] derive Debug for Classes (#650) --- src/virtual_dom/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/virtual_dom/mod.rs b/src/virtual_dom/mod.rs index 7b190b9534d..0f2ed28f936 100644 --- a/src/virtual_dom/mod.rs +++ b/src/virtual_dom/mod.rs @@ -41,7 +41,7 @@ type Listeners = Vec>>; type Attributes = HashMap; /// A set of classes. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct Classes { set: IndexSet, } From 258ed838ac4a0599f4a24809b696a18c30e986fa Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Sun, 22 Sep 2019 15:29:32 -0400 Subject: [PATCH 047/193] Implement Debug for ComponentLink and AgentLink (#652) --- src/agent.rs | 7 +++++++ src/html/mod.rs | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/agent.rs b/src/agent.rs index 409efb99033..583d0ee55a9 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -8,6 +8,7 @@ use log::warn; use serde::{Deserialize, Serialize}; use slab::Slab; use std::cell::RefCell; +use std::fmt; use std::marker::PhantomData; use std::rc::Rc; use stdweb::Value; @@ -653,6 +654,12 @@ impl AgentLink { } } +impl fmt::Debug for AgentLink { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("AgentLink<_>") + } +} + struct AgentRunnable { agent: Option, // TODO Use agent field to control create message this flag diff --git a/src/html/mod.rs b/src/html/mod.rs index 9f6f3bf9298..d28f0a1b575 100644 --- a/src/html/mod.rs +++ b/src/html/mod.rs @@ -12,6 +12,7 @@ pub use scope::{NodeCell, Scope}; use crate::callback::Callback; use crate::virtual_dom::{VChild, VList, VNode}; +use std::fmt; /// This type indicates that component should be rendered again. pub type ShouldRender = bool; @@ -236,6 +237,12 @@ where } } +impl fmt::Debug for ComponentLink { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("ComponentLink<_>") + } +} + /// A bridging type for checking `href` attribute value. #[derive(Debug)] pub struct Href { From 07a30d713f2d0c9e10ebdfd43a8629692c74a01f Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Sun, 22 Sep 2019 18:57:26 -0400 Subject: [PATCH 048/193] update readme (#646) --- README.md | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 3a0c088e258..13a0ca313d9 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ This framework is designed to be compiled into modern browsers' runtimes: wasm, To prepare the development environment use the installation instruction here: [wasm-and-rust](https://github.com/raphamorim/wasm-and-rust). -### Clean MVC approach inspired by Elm and Redux +### Clean Flux architecture inspired by Elm and Redux Yew implements strict application state management based on message passing and updates: @@ -123,12 +123,11 @@ html! { } ``` -### Agents - actors model inspired by Erlang and Actix +### Agents - actor model inspired by Erlang and Actix Every `Component` can spawn an agent and attach to it. -Agents are separate tasks that work concurrently. - -Create your worker/agent (in `context.rs` for example): +Agents can coordinate global state, spawn long-running tasks, and offload tasks to a web worker. +They run independently of components, but hook nicely into their update mechanism. ```rust use yew::worker::*; @@ -149,10 +148,11 @@ pub enum Response { impl Agent for Worker { // Available: - // - `Job` (one per bridge) - // - `Context` (shared in the same thread) - // - `Public` (separate thread). - type Reach = Context; // Spawn only one instance per thread (all components could reach this) + // - `Job` (one per bridge on the main thread) + // - `Context` (shared in the main thread) + // - `Private` (one per bridge in a separate thread) + // - `Public` (shared in a separate thread) + type Reach = Context; // Spawn only one instance on the main thread (all components can share this agent) type Message = Msg; type Input = Request; type Output = Response; @@ -205,7 +205,7 @@ You can use as many agents as you want. For example you could separate all inter with a server to a separate thread (a real OS thread because Web Workers map to the native threads). > **REMEMBER!** Not every API is available for every environment. For example you can't use -`StorageService` from a separate thread. It won't work with `Public` agents, +`StorageService` from a separate thread. It won't work with `Public` or `Private` agents, only with `Job` and `Context` ones. ### Components @@ -259,7 +259,7 @@ html! { ### Fragments -Yew supports fragments: elements without a parent which could be attached somewhere later. +Yew supports fragments: elements without a parent which can be attached to one somewhere else. ```rust html! { @@ -294,9 +294,8 @@ fn update(&mut self, msg: Self::Message) -> ShouldRender { } ``` -Using `ShouldRender` is more effective than comparing the model after every update because not every model -change leads to a view update. It allows the framework to skip the model comparison checks entirely. -This also allows you to control updates as precisely as possible. +Using `ShouldRender` is more effective than comparing the model after every update because not every change to the model +causes an update to the view. It allows the framework to only compare parts of the model essential to rendering the view. ### Rust/JS/C-style comments in templates @@ -342,9 +341,11 @@ It's a handy alternative to subscriptions. Implemented: * `IntervalService` * `RenderService` +* `ResizeService` * `TimeoutService` * `StorageService` * `DialogService` +* `ConsoleService` * `FetchService` * `WebSocketService` @@ -373,7 +374,7 @@ impl Component for Model { ``` Can't find an essential service? Want to use a library from `npm`? -You can reuse `JavaScript` libraries with `stdweb` capabilities and create +You can wrap `JavaScript` libraries using `stdweb` and create your own service implementation. Here's an example below of how to wrap the [ccxt](https://www.npmjs.com/package/ccxt) library: From 567a945ba17e0958fa9a779e80858dc93d458ddb Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Tue, 24 Sep 2019 22:21:10 -0400 Subject: [PATCH 049/193] Fix touch events (#656) --- crates/macro/src/html_tree/html_tag/tag_attributes.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/macro/src/html_tree/html_tag/tag_attributes.rs b/crates/macro/src/html_tree/html_tag/tag_attributes.rs index 879d20e3769..af2621b1067 100644 --- a/crates/macro/src/html_tree/html_tag/tag_attributes.rs +++ b/crates/macro/src/html_tree/html_tag/tag_attributes.rs @@ -46,11 +46,11 @@ lazy_static! { m.insert("onmousewheel", "MouseWheelEvent"); m.insert("onmouseover", "MouseOverEvent"); m.insert("onmouseup", "MouseUpEvent"); - m.insert("touchcancel", "TouchCancel"); - m.insert("touchend", "TouchEnd"); - m.insert("touchenter", "TouchEnter"); - m.insert("touchmove", "TouchMove"); - m.insert("touchstart", "TouchStart"); + m.insert("ontouchcancel", "TouchCancel"); + m.insert("ontouchend", "TouchEnd"); + m.insert("ontouchenter", "TouchEnter"); + m.insert("ontouchmove", "TouchMove"); + m.insert("ontouchstart", "TouchStart"); m.insert("ongotpointercapture", "GotPointerCaptureEvent"); m.insert("onlostpointercapture", "LostPointerCaptureEvent"); m.insert("onpointercancel", "PointerCancelEvent"); From 66b036b372b17de73d3b38441605cde807513a95 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Tue, 24 Sep 2019 22:52:35 -0400 Subject: [PATCH 050/193] Update changelog for v0.9 release (#657) --- CHANGELOG.md | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 988d7181e82..2a78e0e1e7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,85 @@ # Changelog -## ✨ **0.9** *(TBD)* +## ✨ **0.10** *(TBD)* - #### ⚡️ Features +- #### 🛠 Fixes + +- #### 🚨 Breaking changes + +## ✨ **0.9** *(2019-09-24)* + +- #### ⚡️ Features + + - Components can now accept children in the `html!` macro. [[@jstarry], [#589](https://github.com/yewstack/yew/pull/589)] + + ```rust + // app.rs + + html! { + + + + } + ``` + + ```rust + // my_list.rs + + use yew::prelude::*; + + pub struct MyList(Props); + + #[derive(Properties)] + pub struct Props { + #[props(required)] + pub name: String, + pub children: Children, + } + + impl Renderable for MyList { + fn view(&self) -> Html { + html! {{ + self.props.children.iter().collect::>() + }} + } + } + ``` + + - Iterators can now be rendered in the `html!` macro without using the `for` keyword. [[@hgzimmerman], [#622](https://github.com/yewstack/yew/pull/622)] + + Before: + ```rust + html! {{ + for self.props.items.iter().map(renderItem) + }} + ``` + + After: + ```rust + html! {{ + self.props.items.iter().map(renderItem).collect::>() + }} + ``` + + - Closures are now able to be transformed into optional `Callback` properties. [[@Wodann], [#612](https://github.com/yewstack/yew/pull/612)] + - Improved CSS class ergonomics with new `Classes` type. [[@DenisKolodin], [#585](https://github.com/yewstack/yew/pull/585)], [[@hgzimmerman], [#626](https://github.com/yewstack/yew/pull/626)] + - Touch events are now supported `
` [[@boydjohnson], [#584](https://github.com/yewstack/yew/pull/584)], [[@jstarry], [#656](https://github.com/yewstack/yew/pull/656)] + - The `Component` trait now has an `mounted` method which can be implemented to react to when your components have been mounted to the DOM. [[@hgzimmerman], [#583](https://github.com/yewstack/yew/pull/583)] + - Additional Fetch options `mode`, `cache`, and `redirect` are now supported [[@davidkna], [#579](https://github.com/yewstack/yew/pull/579)] - The derive props macro now supports Properties with lifetimes [[@jstarry], [#580](https://github.com/yewstack/yew/pull/580)] - New `ResizeService` for registering for `window` size updates [[@hgzimmerman], [#577](https://github.com/yewstack/yew/pull/577)] - #### 🛠 Fixes + - Fixed `VNode` orphaning bug when destroying `VTag` elements. This caused some `Component`s to not be properly destroyed when they should have been. [[@hgzimmerman], [#651](https://github.com/yewstack/yew/pull/651)] + - Fix mishandling of Properties `where` clause in derive_props macro [[@astraw], [#640](https://github.com/yewstack/yew/pull/640)] + - #### 🚨 Breaking changes + None + ## ✨ **0.8** *(2019-08-10)* ***Props! Props! Props!*** @@ -148,7 +217,10 @@ This release introduces the concept of an `Agent`. Agents are separate activitie ## ✨ **0.1** *(2017-12-31)* [Web Workers API]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API +[@astraw]: https://github.com/astraw +[@boydjohnson]: https://github.com/boydjohnson [@charvp]: https://github.com/charvp +[@davidkna]: https://github.com/davidkna [@DenisKolodin]: https://github.com/DenisKolodin [@dermetfan]: https://github.com/dermetfan [@hgzimmerman]: https://github.com/hgzimmerman @@ -156,3 +228,4 @@ This release introduces the concept of an `Agent`. Agents are separate activitie [@kellytk]: https://github.com/kellytk [@tiziano88]: https://github.com/tiziano88 [@totorigolo]: https://github.com/totorigolo +[@Wodann]: https://github.com/Wodann From 0d96d5b9dde971769ba8e680c2b52f26885fc07c Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Tue, 24 Sep 2019 22:53:13 -0400 Subject: [PATCH 051/193] Implement Debug for ChildRenderer (#655) * Implement Debug for ChildRenderer * fix formatter type lifetime * remove fmt * cargo fmt --- src/html/mod.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/html/mod.rs b/src/html/mod.rs index d28f0a1b575..95e10ebe984 100644 --- a/src/html/mod.rs +++ b/src/html/mod.rs @@ -159,6 +159,12 @@ impl Default for ChildrenRenderer { } } +impl fmt::Debug for ChildrenRenderer { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("ChildrenRenderer<_>") + } +} + impl Renderable for ChildrenRenderer where T: Into>, From dd59cf039283e4320d0a3eb788b478570c72549f Mon Sep 17 00:00:00 2001 From: Terry Raimondo Date: Wed, 25 Sep 2019 04:53:31 +0200 Subject: [PATCH 052/193] Emit initial route to router subscribers (#634) --- examples/routing/src/router.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/routing/src/router.rs b/examples/routing/src/router.rs index 8df891c372e..bbb3bb5e730 100644 --- a/examples/routing/src/router.rs +++ b/examples/routing/src/router.rs @@ -173,8 +173,11 @@ where } fn connected(&mut self, id: HandlerId) { + self.link + .response(id, Route::current_route(&self.route_service)); self.subscribers.insert(id); } + fn disconnected(&mut self, id: HandlerId) { self.subscribers.remove(&id); } From f794d555e3b70935963d4bbd4aa46c06c1089820 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Tue, 24 Sep 2019 23:01:45 -0400 Subject: [PATCH 053/193] Fix typo in RenderService (#658) --- CHANGELOG.md | 1 + src/services/render.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a78e0e1e7a..0b02fbc2dff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,7 @@ - #### 🛠 Fixes + - Fixed JS typo in RenderService. This was causing animation frames to not be dropped correctly. [[@jstarry], [#658](https://github.com/yewstack/yew/pull/658)] - Fixed `VNode` orphaning bug when destroying `VTag` elements. This caused some `Component`s to not be properly destroyed when they should have been. [[@hgzimmerman], [#651](https://github.com/yewstack/yew/pull/651)] - Fix mishandling of Properties `where` clause in derive_props macro [[@astraw], [#640](https://github.com/yewstack/yew/pull/640)] diff --git a/src/services/render.rs b/src/services/render.rs index 036914a3e4d..f213bff1693 100644 --- a/src/services/render.rs +++ b/src/services/render.rs @@ -54,7 +54,7 @@ impl Task for RenderTask { let handle = self.0.take().expect("tried to cancel render twice"); js! { @(no_return) var handle = @{handle}; - cancelAnimationFrame(handle.timeout_id); + cancelAnimationFrame(handle.render_id); handle.callback.drop(); } } From ff41a69c70b1b0f954132c37701a5740bb45c4d3 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Tue, 24 Sep 2019 23:31:04 -0400 Subject: [PATCH 054/193] Add From<&String> for Classes implementation --- src/virtual_dom/mod.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/virtual_dom/mod.rs b/src/virtual_dom/mod.rs index 0f2ed28f936..1febe455830 100644 --- a/src/virtual_dom/mod.rs +++ b/src/virtual_dom/mod.rs @@ -101,6 +101,13 @@ impl From for Classes { } } +impl From<&String> for Classes { + fn from(t: &String) -> Self { + let set = t.split_whitespace().map(String::from).collect(); + Self { set } + } +} + impl> From> for Classes { fn from(t: Vec) -> Self { let set = t.iter().map(|x| x.as_ref().to_string()).collect(); From 97f5aa3812f5c6c908872da6afc0b2458e6fc454 Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Wed, 25 Sep 2019 21:42:36 -0400 Subject: [PATCH 055/193] Add Dispatchers (#639) * Add Dispatchers, which act like bridges, but don't require a callback to create; updated router example * cargo fmt * improve comment * Another approach * add newtype around dispatcher bridges * added debug impl, run cargo fmt * fix example * make button on routing example start on loading variant * revert singleton_id changes * actually revert singleton_id changes * slabs own option * cargo fmt * remove dead lines * address bad doc comment * fix router example * fix handler id initialization in local agent * add appropriate error message when id is not associated with callback * remove misleading comment * use a type alias to the shared output slab --- examples/routing/src/b_component.rs | 2 +- examples/routing/src/lib.rs | 49 +++---- examples/routing/src/router_button.rs | 81 ++++++++++++ src/agent.rs | 184 ++++++++++++++++++-------- 4 files changed, 225 insertions(+), 91 deletions(-) create mode 100644 examples/routing/src/router_button.rs diff --git a/examples/routing/src/b_component.rs b/examples/routing/src/b_component.rs index 7a429f2f1fb..a2d3eee6334 100644 --- a/examples/routing/src/b_component.rs +++ b/examples/routing/src/b_component.rs @@ -6,7 +6,7 @@ use yew::{html, Bridge, Component, ComponentLink, Html, Renderable, ShouldRender pub struct BModel { number: Option, sub_path: Option, - router: Box>>, + router: Box>>, } pub enum Msg { diff --git a/examples/routing/src/lib.rs b/examples/routing/src/lib.rs index 67a13e38d35..e1b75199e25 100644 --- a/examples/routing/src/lib.rs +++ b/examples/routing/src/lib.rs @@ -2,9 +2,11 @@ mod b_component; mod router; +mod router_button; mod routing; use b_component::BModel; +use crate::router_button::RouterButton; use log::info; use router::Route; use yew::agent::Bridged; @@ -14,15 +16,15 @@ pub enum Child { A, B, PathNotFound(String), + Loading, } pub struct Model { child: Child, - router: Box>>, + router: Box>>, } pub enum Msg { - NavigateTo(Child), HandleRoute(Route<()>), } @@ -32,40 +34,20 @@ impl Component for Model { fn create(_: Self::Properties, mut link: ComponentLink) -> Self { let callback = link.send_back(|route: Route<()>| Msg::HandleRoute(route)); - let mut router = router::Router::bridge(callback); - - // TODO Not sure if this is technically correct. This should be sent _after_ the component has been created. - // I think the `Component` trait should have a hook called `on_mount()` - // that is called after the component has been attached to the vdom. - // It seems like this only works because the JS engine decides to activate the - // router worker logic after the mounting has finished. - router.send(router::Request::GetCurrentRoute); - + let router = router::Router::bridge(callback); Model { - child: Child::A, // This should be quickly overwritten by the actual route. + child: Child::Loading, // This should be quickly overwritten by the actual route. router, } } + fn mounted(&mut self) -> ShouldRender { + self.router.send(router::Request::GetCurrentRoute); + false + } + fn update(&mut self, msg: Self::Message) -> ShouldRender { match msg { - Msg::NavigateTo(child) => { - let path_segments = match child { - Child::A => vec!["a".into()], - Child::B => vec!["b".into()], - Child::PathNotFound(_) => vec!["path_not_found".into()], - }; - - let route = router::Route { - path_segments, - query: None, - fragment: None, - state: (), - }; - - self.router.send(router::Request::ChangeRoute(route)); - false - } Msg::HandleRoute(route) => { info!("Routing: {}", route.to_route_string()); // Instead of each component selecting which parts of the path are important to it, @@ -92,8 +74,8 @@ impl Renderable for Model { html! {
{self.child.view()} @@ -105,7 +87,7 @@ impl Renderable for Model { impl Renderable for Child { fn view(&self) -> Html { - match *self { + match self { Child::A => html! { <> {"This corresponds to route 'a'"} @@ -122,6 +104,9 @@ impl Renderable for Child { {format!("Invalid path: '{}'", path)} }, + Child::Loading => html! { + {"Loading"} + }, } } } diff --git a/examples/routing/src/router_button.rs b/examples/routing/src/router_button.rs new file mode 100644 index 00000000000..efb42807c4f --- /dev/null +++ b/examples/routing/src/router_button.rs @@ -0,0 +1,81 @@ +//! A component wrapping a + } + } +} diff --git a/src/agent.rs b/src/agent.rs index 583d0ee55a9..487448072ed 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -10,6 +10,7 @@ use slab::Slab; use std::cell::RefCell; use std::fmt; use std::marker::PhantomData; +use std::ops::{Deref, DerefMut}; use std::rc::Rc; use stdweb::Value; #[allow(unused_imports)] @@ -56,28 +57,75 @@ impl Packed for T { } } +/// Type alias to a sharable Slab that owns optional callbacks that emit messages of the type of the specified Agent. +type SharedOutputSlab = Shared::Output>>>>; + /// Id of responses handler. #[derive(Serialize, Deserialize, Eq, PartialEq, Hash, Clone, Copy)] -pub struct HandlerId(usize); - -impl From for HandlerId { - fn from(id: usize) -> Self { - HandlerId(id) - } -} +pub struct HandlerId(usize, bool); impl HandlerId { + fn new(id: usize, respondable: bool) -> Self { + HandlerId(id, respondable) + } fn raw_id(self) -> usize { self.0 } + /// Indicates if a handler id corresponds to callback in the Agent runtime. + pub fn is_respondable(&self) -> bool { + self.1 + } } -/// This traits allow to get addres or register worker. +/// This trait allows registering or getting the address of a worker. pub trait Bridged: Agent + Sized + 'static { /// Creates a messaging bridge between a worker and the component. fn bridge(callback: Callback) -> Box>; } +/// This trait allows the creation of a dispatcher to an existing agent that will not send replies when messages are sent. +pub trait Dispatched: Agent + Sized + 'static { + /// Creates a dispatcher to the agent that will not send messages back. + /// + /// # Note + /// Dispatchers don't have `HandlerId`s and therefore `Agent::handle` will be supplied `None` + /// for the `id` parameter, and `connected` and `disconnected` will not be called. + /// + /// # Important + /// Because the Agents using Context or Public reaches use the number of existing bridges to + /// keep track of if the agent itself should exist, creating dispatchers will not guarantee that + /// an Agent will exist to service requests sent from Dispatchers. You **must** keep at least one + /// bridge around if you wish to use a dispatcher. If you are using agents in a write-only manner, + /// then it is suggested that you create a bridge that handles no-op responses as high up in the + /// component hierarchy as possible - oftentimes the root component for simplicity's sake. + fn dispatcher() -> Dispatcher; +} + +/// A newtype around a bridge to indicate that it is distinct from a normal bridge +pub struct Dispatcher(Box>); + +impl fmt::Debug for Dispatcher { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Dispatcher<_>") + } +} + +impl Deref for Dispatcher { + type Target = dyn Bridge; + + fn deref(&self) -> &Self::Target { + self.0.deref() + } +} +impl DerefMut for Dispatcher { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0.deref_mut() + } +} + +/// Marker trait to indicate which Discoverers are able to be used with dispatchers. +pub trait Dispatchable: Discoverer {} + /// Implements rules to register a worker in a separate thread. pub trait Threaded { /// Executes an agent in the current environment. @@ -137,7 +185,17 @@ where T: Agent, { fn bridge(callback: Callback) -> Box> { - Self::Reach::spawn_or_join(callback) + Self::Reach::spawn_or_join(Some(callback)) + } +} + +impl Dispatched for T +where + T: Agent, + ::Reach: Dispatchable, +{ + fn dispatcher() -> Dispatcher { + Dispatcher(Self::Reach::spawn_or_join::(None)) } } @@ -145,7 +203,7 @@ where #[doc(hidden)] pub trait Discoverer { /// Spawns an agent and returns `Bridge` implementation. - fn spawn_or_join(_callback: Callback) -> Box> { + fn spawn_or_join(_callback: Option>) -> Box> { unimplemented!(); } } @@ -160,7 +218,7 @@ pub trait Bridge { struct LocalAgent { scope: AgentScope, - slab: Shared>>, + slab: SharedOutputSlab, } type Last = bool; @@ -174,15 +232,17 @@ impl LocalAgent { } } - fn slab(&self) -> Shared>> { + fn slab(&self) -> SharedOutputSlab { self.slab.clone() } - fn create_bridge(&mut self, callback: Callback) -> ContextBridge { - let id = self.slab.borrow_mut().insert(callback); + fn create_bridge(&mut self, callback: Option>) -> ContextBridge { + let respondable = callback.is_some(); + let id: usize = self.slab.borrow_mut().insert(callback); + let id = HandlerId::new(id, respondable); ContextBridge { scope: self.scope.clone(), - id: id.into(), + id, } } @@ -201,7 +261,7 @@ thread_local! { pub struct Context; impl Discoverer for Context { - fn spawn_or_join(callback: Callback) -> Box> { + fn spawn_or_join(callback: Option>) -> Box> { let mut scope_to_init = None; let bridge = LOCAL_AGENTS_POOL.with(|pool| { match pool.borrow_mut().entry::>() { @@ -231,18 +291,29 @@ impl Discoverer for Context { } } +impl Dispatchable for Context {} + struct SlabResponder { - slab: Shared>>, + slab: Shared>>>, } impl Responder for SlabResponder { fn response(&self, id: HandlerId, output: AGN::Output) { - let callback = self.slab.borrow().get(id.raw_id()).cloned(); - if let Some(callback) = callback { - callback.emit(output); - } else { - warn!("Id of handler not exists : {}", id.raw_id()); - } + locate_callback_and_respond::(&self.slab, id, output); + } +} + +/// The slab contains the callback, the id is used to look up the callback, +/// and the output is the message that will be sent via the callback. +fn locate_callback_and_respond( + slab: &SharedOutputSlab, + id: HandlerId, + output: AGN::Output, +) { + match slab.borrow().get(id.raw_id()).cloned() { + Some(Some(callback)) => callback.emit(output), + Some(None) => warn!("The Id of the handler: {}, while present in the slab, is not associated with a callback.", id.raw_id()), + None => warn!("Id of handler does not exist in the slab: {}.", id.raw_id()), } } @@ -268,8 +339,10 @@ impl Drop for ContextBridge { false } }; + let upd = AgentUpdate::Disconnected(self.id); self.scope.send(upd); + if terminate_worker { let upd = AgentUpdate::Destroy; self.scope.send(upd); @@ -283,7 +356,8 @@ impl Drop for ContextBridge { pub struct Job; impl Discoverer for Job { - fn spawn_or_join(callback: Callback) -> Box> { + fn spawn_or_join(callback: Option>) -> Box> { + let callback = callback.expect("Callback required for Job"); let scope = AgentScope::::new(); let responder = CallbackResponder { callback }; let agent_link = AgentLink::connect(&scope, responder); @@ -296,7 +370,7 @@ impl Discoverer for Job { } } -const SINGLETON_ID: HandlerId = HandlerId(0); +const SINGLETON_ID: HandlerId = HandlerId(0, true); struct CallbackResponder { callback: Callback, @@ -335,7 +409,8 @@ impl Drop for JobBridge { pub struct Private; impl Discoverer for Private { - fn spawn_or_join(callback: Callback) -> Box> { + fn spawn_or_join(callback: Option>) -> Box> { + let callback = callback.expect("Callback required for Private agents"); let handler = move |data: Vec| { let msg = FromWorker::::unpack(&data); match msg { @@ -395,22 +470,24 @@ impl Drop for PrivateBridge { struct RemoteAgent { worker: Value, - slab: Shared>>, + slab: SharedOutputSlab, } impl RemoteAgent { - pub fn new(worker: &Value, slab: Shared>>) -> Self { + pub fn new(worker: &Value, slab: SharedOutputSlab) -> Self { RemoteAgent { worker: worker.clone(), slab, } } - fn create_bridge(&mut self, callback: Callback) -> PublicBridge { - let id = self.slab.borrow_mut().insert(callback); + fn create_bridge(&mut self, callback: Option>) -> PublicBridge { + let respondable = callback.is_some(); + let id: usize = self.slab.borrow_mut().insert(callback); + let id = HandlerId::new(id, respondable); PublicBridge { worker: self.worker.clone(), - id: id.into(), + id, _agent: PhantomData, } } @@ -430,7 +507,7 @@ thread_local! { pub struct Public; impl Discoverer for Public { - fn spawn_or_join(callback: Callback) -> Box> { + fn spawn_or_join(callback: Option>) -> Box> { let bridge = REMOTE_AGENTS_POOL.with(|pool| { match pool.borrow_mut().entry::>() { Entry::Occupied(mut entry) => { @@ -438,7 +515,7 @@ impl Discoverer for Public { entry.get_mut().create_bridge(callback) } Entry::Vacant(entry) => { - let slab_base: Shared>> = + let slab_base: Shared>>> = Rc::new(RefCell::new(Slab::new())); let slab = slab_base.clone(); let handler = move |data: Vec| { @@ -449,15 +526,7 @@ impl Discoverer for Public { // TODO Send `Connected` message } FromWorker::ProcessOutput(id, output) => { - let callback = slab.borrow().get(id.raw_id()).cloned(); - if let Some(callback) = callback { - callback.emit(output); - } else { - warn!( - "Id of handler for remote worker not exists : {}", - id.raw_id() - ); - } + locate_callback_and_respond::(&slab, id, output); } } }; @@ -479,6 +548,8 @@ impl Discoverer for Public { } } +impl Dispatchable for Public {} + /// A connection manager for components interaction with workers. pub struct PublicBridge { worker: Value, @@ -486,25 +557,22 @@ pub struct PublicBridge { _agent: PhantomData, } -impl PublicBridge { - fn send_to_remote(&self, msg: ToWorker) { - // TODO Important! Implement. - // Use a queue to collect a messages if an instance is not ready - // and send them to an agent when it will reported readiness. - let msg = msg.pack(); - let worker = &self.worker; - js! { - var worker = @{worker}; - var bytes = @{msg}; - worker.postMessage(bytes); - }; - } +fn send_to_remote(worker: &Value, msg: ToWorker) { + // TODO Important! Implement. + // Use a queue to collect a messages if an instance is not ready + // and send them to an agent when it will reported readiness. + let msg = msg.pack(); + js! { + var worker = @{worker}; + var bytes = @{msg}; + worker.postMessage(bytes); + }; } impl Bridge for PublicBridge { fn send(&mut self, msg: AGN::Input) { let msg = ToWorker::ProcessInput(self.id, msg); - self.send_to_remote(msg); + send_to_remote::(&self.worker, msg); } } @@ -519,10 +587,10 @@ impl Drop for PublicBridge { } }; let upd = ToWorker::Disconnected(self.id); - self.send_to_remote(upd); + send_to_remote::(&self.worker, upd); if terminate_worker { let upd = ToWorker::Destroy; - self.send_to_remote(upd); + send_to_remote::(&self.worker, upd); pool.borrow_mut().remove::>(); } }); From 9757adf42b650de9f99dbe9084dca9477ad2c65f Mon Sep 17 00:00:00 2001 From: Thomas Gotwig <30773779+TGotwig@users.noreply.github.com> Date: Thu, 26 Sep 2019 04:07:12 +0200 Subject: [PATCH 056/193] Fix comment-typo in agent (#644) Closes #628 --- src/agent.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agent.rs b/src/agent.rs index 487448072ed..6abac09ef22 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -628,7 +628,7 @@ pub trait Agent: Sized + 'static { /// This method called on when a new bridge destroyed. fn disconnected(&mut self, _id: HandlerId) {} - /// Creates an instance of an agent. + /// This method called when the agent is destroyed. fn destroy(&mut self) {} /// Represents the name of loading resorce for remote workers which From 58495a1e5bd8ced2614d9dc5fb88c313fb3264a7 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Wed, 25 Sep 2019 22:17:38 -0400 Subject: [PATCH 057/193] Add a few more items to changelog for 0.9 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b02fbc2dff..8f9df093f89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ - #### ⚡️ Features + - Messages to Public Agents will now be queued if the Agent hasn't finished setting up yet. [[@serzhiio], [#596](https://github.com/yewstack/yew/pull/596)] + - Agents can now be connected to without a callback. Instead of creating a bridge to the agent, create a dispatcher like so: `MyAgent::dispatcher()`. [[@hgzimmerman], [#639](https://github.com/yewstack/yew/pull/639)] - Components can now accept children in the `html!` macro. [[@jstarry], [#589](https://github.com/yewstack/yew/pull/589)] ```rust @@ -227,6 +229,7 @@ This release introduces the concept of an `Agent`. Agents are separate activitie [@hgzimmerman]: https://github.com/hgzimmerman [@jstarry]: https://github.com/jstarry [@kellytk]: https://github.com/kellytk +[@serzhiio]: https://github.com/serzhiio [@tiziano88]: https://github.com/tiziano88 [@totorigolo]: https://github.com/totorigolo [@Wodann]: https://github.com/Wodann From dfc4968203560f247b9716da2a0df38f7fcfd91d Mon Sep 17 00:00:00 2001 From: serzhiio Date: Thu, 26 Sep 2019 18:28:09 +0300 Subject: [PATCH 058/193] Early msgs queue for Public worker (#596) * Early msgs queue for Public worker * Early msgs queue for Public worker * update (#1) * Fix typo * Require fmt in travis script * Apply cargo format to all modules * ?? * Merge (#2) * Fix typo * Require fmt in travis script * Apply cargo format to all modules * avoid allocating in diff_classes * avoid allocating for diff_kind * avoid allocating for diff_value * simplify diff_attributes and avoid allocations * return iterator for diff_classes and diff_attributes * rustfmt on vtags * clean apply_diff * more cleaning * apply suggestions * Update proc-macro2, syn and quote to 1.0 CLOSES #590 * Fixed typo * Add support for optional callbacks to component properties * Add tests for a component with optional callback * Fix typo * Add `Classes` to prelude * binary ser/de issue fix * merge (#3) * Fix typo * Require fmt in travis script * Apply cargo format to all modules * avoid allocating in diff_classes * avoid allocating for diff_kind * avoid allocating for diff_value * simplify diff_attributes and avoid allocations * return iterator for diff_classes and diff_attributes * rustfmt on vtags * clean apply_diff * more cleaning * apply suggestions * Update proc-macro2, syn and quote to 1.0 CLOSES #590 * Fixed typo * Add support for optional callbacks to component properties * Add tests for a component with optional callback * Fix typo * Add `Classes` to prelude * added bincode type for data ser de * fixed Into func * Update (#5) * in yewstack org * Initial implementation using an iterator adaptor to provide a coherent struct to implement Into (via From<>) for * update large table example to demonstrate new .html() method instead of 'for' * ran cargo fmt * Add a section for project templates to the README * Change org to YewStack * Implement FromIterator instead of wrapping iterator * remove dead code * ran fmt * Add extend method to Classes * change to union * renamed union back to extend * removed unused import of RangeFull * update * Fix touch events (#656) * Update changelog for v0.9 release (#657) * Implement Debug for ChildRenderer (#655) * Implement Debug for ChildRenderer * fix formatter type lifetime * remove fmt * cargo fmt * Emit initial route to router subscribers (#634) * Fix typo in RenderService (#658) * Add From<&String> for Classes implementation * @jstarry feedback - cargo fmt - rename DEDICATED_WORKERS_* to REMOTE_AGENTS_* - remove unrelated changes * TypeId ask key instead &str * Remove .gitignore changes * Update agent.rs * Update agent.rs * Fix merge conflict --- src/agent.rs | 97 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 73 insertions(+), 24 deletions(-) diff --git a/src/agent.rs b/src/agent.rs index 6abac09ef22..67184f820ba 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -2,12 +2,14 @@ use crate::callback::Callback; use crate::scheduler::{scheduler, Runnable, Shared}; -use anymap::{AnyMap, Entry}; +use anymap::{self, AnyMap}; use bincode; use log::warn; use serde::{Deserialize, Serialize}; use slab::Slab; +use std::any::TypeId; use std::cell::RefCell; +use std::collections::{hash_map, HashMap, HashSet}; use std::fmt; use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; @@ -265,11 +267,11 @@ impl Discoverer for Context { let mut scope_to_init = None; let bridge = LOCAL_AGENTS_POOL.with(|pool| { match pool.borrow_mut().entry::>() { - Entry::Occupied(mut entry) => { + anymap::Entry::Occupied(mut entry) => { // TODO Insert callback! entry.get_mut().create_bridge(callback) } - Entry::Vacant(entry) => { + anymap::Entry::Vacant(entry) => { let scope = AgentScope::::new(); let launched = LocalAgent::new(&scope); let responder = SlabResponder { @@ -474,11 +476,8 @@ struct RemoteAgent { } impl RemoteAgent { - pub fn new(worker: &Value, slab: SharedOutputSlab) -> Self { - RemoteAgent { - worker: worker.clone(), - slab, - } + pub fn new(worker: Value, slab: SharedOutputSlab) -> Self { + RemoteAgent { worker, slab } } fn create_bridge(&mut self, callback: Option>) -> PublicBridge { @@ -501,6 +500,8 @@ impl RemoteAgent { thread_local! { static REMOTE_AGENTS_POOL: RefCell = RefCell::new(AnyMap::new()); + static REMOTE_AGENTS_LOADED: RefCell> = RefCell::new(HashSet::new()); + static REMOTE_AGENTS_EARLY_MSGS_QUEUE: RefCell>>> = RefCell::new(HashMap::new()); } /// Create a single instance in a tab. @@ -510,23 +511,37 @@ impl Discoverer for Public { fn spawn_or_join(callback: Option>) -> Box> { let bridge = REMOTE_AGENTS_POOL.with(|pool| { match pool.borrow_mut().entry::>() { - Entry::Occupied(mut entry) => { + anymap::Entry::Occupied(mut entry) => { // TODO Insert callback! entry.get_mut().create_bridge(callback) } - Entry::Vacant(entry) => { - let slab_base: Shared>>> = + anymap::Entry::Vacant(entry) => { + let slab: Shared>>> = Rc::new(RefCell::new(Slab::new())); - let slab = slab_base.clone(); - let handler = move |data: Vec| { - let msg = FromWorker::::unpack(&data); - match msg { - FromWorker::WorkerLoaded => { - // TODO Use `AtomicBool` lock to check its loaded - // TODO Send `Connected` message - } - FromWorker::ProcessOutput(id, output) => { - locate_callback_and_respond::(&slab, id, output); + let handler = { + let slab = slab.clone(); + move |data: Vec, worker: Value| { + let msg = FromWorker::::unpack(&data); + match msg { + FromWorker::WorkerLoaded => { + // TODO Send `Connected` message + let _ = REMOTE_AGENTS_LOADED.with(|local| { + local.borrow_mut().insert(TypeId::of::()) + }); + REMOTE_AGENTS_EARLY_MSGS_QUEUE.with(|local| { + if let Some(msgs) = + local.borrow_mut().get_mut(&TypeId::of::()) + { + for msg in msgs.drain(..) { + let worker = &worker; + js! {@{worker}.postMessage(@{msg});}; + } + } + }); + } + FromWorker::ProcessOutput(id, output) => { + locate_callback_and_respond::(&slab, id, output); + } } } }; @@ -535,11 +550,11 @@ impl Discoverer for Public { var worker = new Worker(@{name_of_resource}); var handler = @{handler}; worker.onmessage = function(event) { - handler(event.data); + handler(event.data, worker); }; return worker; }; - let launched = RemoteAgent::new(&worker, slab_base); + let launched = RemoteAgent::new(worker, slab); entry.insert(launched).create_bridge(callback) } } @@ -557,6 +572,29 @@ pub struct PublicBridge { _agent: PhantomData, } +impl PublicBridge { + fn worker_is_loaded(&self) -> bool { + REMOTE_AGENTS_LOADED.with(|local| local.borrow().contains(&TypeId::of::())) + } + + fn msg_to_queue(&self, msg: Vec) { + REMOTE_AGENTS_EARLY_MSGS_QUEUE.with(|local| { + match local.borrow_mut().entry(TypeId::of::()) { + hash_map::Entry::Vacant(record) => { + record.insert({ + let mut v = Vec::new(); + v.push(msg); + v + }); + } + hash_map::Entry::Occupied(ref mut record) => { + record.get_mut().push(msg); + } + } + }); + } +} + fn send_to_remote(worker: &Value, msg: ToWorker) { // TODO Important! Implement. // Use a queue to collect a messages if an instance is not ready @@ -572,7 +610,12 @@ fn send_to_remote(worker: &Value, msg: ToWorker) { impl Bridge for PublicBridge { fn send(&mut self, msg: AGN::Input) { let msg = ToWorker::ProcessInput(self.id, msg); - send_to_remote::(&self.worker, msg); + if self.worker_is_loaded() { + send_to_remote::(&self.worker, msg); + } else { + let msg = msg.pack(); + self.msg_to_queue(msg); + } } } @@ -592,6 +635,12 @@ impl Drop for PublicBridge { let upd = ToWorker::Destroy; send_to_remote::(&self.worker, upd); pool.borrow_mut().remove::>(); + REMOTE_AGENTS_LOADED.with(|pool| { + pool.borrow_mut().remove(&TypeId::of::()); + }); + REMOTE_AGENTS_EARLY_MSGS_QUEUE.with(|pool| { + pool.borrow_mut().remove(&TypeId::of::()); + }); } }); } From 9faefde9d16354aaf50130775edc5ebf0da856f0 Mon Sep 17 00:00:00 2001 From: Kevin Tan Date: Fri, 27 Sep 2019 10:04:53 +0800 Subject: [PATCH 059/193] Add a method to emit multiple messages in one callback (#660) * feat: add create_effect method to help create effect callback * style: fmt code with rustfmt * feat: support bunch update in one render loop * typo: change bunch to batch * style: fmt with cargo --- src/html/mod.rs | 13 +++++++++++++ src/html/scope.rs | 12 +++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/html/mod.rs b/src/html/mod.rs index 95e10ebe984..597dc57eb40 100644 --- a/src/html/mod.rs +++ b/src/html/mod.rs @@ -224,6 +224,19 @@ where } } + /// This method sends batch of messages back to the component's loop + pub fn send_back_batch(&mut self, function: F) -> Callback + where + F: Fn(IN) -> Vec + 'static, + { + let scope = self.scope.clone(); + let closure = move |input| { + let messages = function(input); + scope.clone().send_message_batch(messages); + }; + closure.into() + } + /// This method sends messages back to the component's loop. pub fn send_back(&mut self, function: F) -> Callback where diff --git a/src/html/scope.rs b/src/html/scope.rs index 280a5dd05c5..cf94a0f9530 100644 --- a/src/html/scope.rs +++ b/src/html/scope.rs @@ -13,6 +13,8 @@ pub type NodeCell = Rc>>; pub(crate) enum ComponentUpdate { /// Wraps messages for a component. Message(COMP::Message), + /// Wraps batch of messages for a component. + MessageBatch(Vec), /// Wraps properties for a component. Properties(COMP::Properties), } @@ -59,6 +61,11 @@ where pub fn send_message(&mut self, msg: COMP::Message) { self.update(ComponentUpdate::Message(msg)); } + + /// send batch of messages to the component + pub fn send_message_batch(&mut self, messages: Vec) { + self.update(ComponentUpdate::MessageBatch(messages)); + } } enum ComponentState { @@ -249,7 +256,10 @@ where self.shared_state.replace(match current_state { ComponentState::Created(mut this) => { let should_update = match self.update { - ComponentUpdate::Message(msg) => this.component.update(msg), + ComponentUpdate::Message(message) => this.component.update(message), + ComponentUpdate::MessageBatch(messages) => messages + .into_iter() + .fold(false, |acc, msg| this.component.update(msg) || acc), ComponentUpdate::Properties(props) => this.component.change(props), }; let next_state = if should_update { this.update() } else { this }; From 0ae9cd52fd3e7667b263731cb1e2bab7a5c434d6 Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Fri, 27 Sep 2019 10:46:19 -0400 Subject: [PATCH 060/193] Add keyboard service (#647) * Add keyboard service * fix not working implementation; run cargo fmt * make callback to keyboard event use KeyPressEvent instead of String * add key_down and key_up events as well * link to documentation and provide compatibility notice --- README.md | 1 + src/services/keyboard.rs | 88 ++++++++++++++++++++++++++++++++++++++++ src/services/mod.rs | 1 + 3 files changed, 90 insertions(+) create mode 100644 src/services/keyboard.rs diff --git a/README.md b/README.md index 13a0ca313d9..80209a92aef 100644 --- a/README.md +++ b/README.md @@ -348,6 +348,7 @@ Implemented: * `ConsoleService` * `FetchService` * `WebSocketService` +* `KeyboardService` ```rust use yew::services::{ConsoleService, TimeoutService}; diff --git a/src/services/keyboard.rs b/src/services/keyboard.rs new file mode 100644 index 00000000000..fb58bd77feb --- /dev/null +++ b/src/services/keyboard.rs @@ -0,0 +1,88 @@ +//! Service to register key press event listeners on elements. +use crate::callback::Callback; +use stdweb::web::event::{KeyDownEvent, KeyPressEvent, KeyUpEvent}; +use stdweb::web::{EventListenerHandle, IEventTarget}; + +/// Service for registering callbacks on elements to get keystrokes from the user. +/// +/// # Note +/// Elements that natively support keyboard input (input or textarea) can set an +/// `onkeypress` or `oninput` attribute within the html macro. You **should** use those events instead of +/// locating the element and registering it with this service. +/// +/// This service is for adding key event listeners to elements that don't support these attributes, +/// like the `document` or `` elements for example. +pub struct KeyboardService {} + +/// Handle to the key event listener. +/// +/// When it goes out of scope, the listener will be removed from the element. +pub struct KeyListenerHandle(Option); + +impl KeyboardService { + /// Registers a callback that listens to KeyPressEvents on a provided element. + /// + /// # Documentation + /// [keypress event](https://developer.mozilla.org/en-US/docs/Web/API/Document/keypress_event) + /// + /// # Warning + /// This API has been deprecated in the HTML standard and it is not recommended for use in new projects. + /// Consult with the browser compatibility chart in the linked MDN documentation. + pub fn register_key_press( + element: &T, + callback: Callback, + ) -> KeyListenerHandle { + let handle = element.add_event_listener(move |event: KeyPressEvent| { + callback.emit(event); + }); + KeyListenerHandle(Some(handle)) + } + + /// Registers a callback that listens to KeyDownEvents on a provided element. + /// + /// # Documentation + /// [keydown event](https://developer.mozilla.org/en-US/docs/Web/API/Document/keydown_event) + /// + /// # Note + /// This browser feature is relatively new and is set to replace keypress events. + /// Not all browsers may support it completely. + /// Consult with the browser compatibility chart in the linked MDN documentation. + pub fn register_key_down( + element: &T, + callback: Callback, + ) -> KeyListenerHandle { + let handle = element.add_event_listener(move |event: KeyDownEvent| { + callback.emit(event); + }); + KeyListenerHandle(Some(handle)) + } + + /// Registers a callback that listens to KeyUpEvents on a provided element. + /// + /// # Documentation + /// [keyup event](https://developer.mozilla.org/en-US/docs/Web/API/Document/keyup_event) + /// + /// # Note + /// This browser feature is relatively new and is set to replace keypress events. + /// Not all browsers may support it completely. + /// Consult with the browser compatibility chart in the linked MDN documentation. + pub fn register_key_up( + element: &T, + callback: Callback, + ) -> KeyListenerHandle { + let handle = element.add_event_listener(move |event: KeyUpEvent| { + callback.emit(event); + }); + KeyListenerHandle(Some(handle)) + } +} + +impl Drop for KeyListenerHandle { + fn drop(&mut self) { + if let Some(handle) = self.0.take() { + handle.remove() + } else { + panic!("Tried to drop KeyListenerHandle twice") + } + } +} diff --git a/src/services/mod.rs b/src/services/mod.rs index ce89ef9abf1..350349e2566 100644 --- a/src/services/mod.rs +++ b/src/services/mod.rs @@ -7,6 +7,7 @@ pub mod console; pub mod dialog; pub mod fetch; pub mod interval; +pub mod keyboard; pub mod reader; pub mod render; pub mod resize; From 5454948d11367a78c061333d777fbcb27bd4abe9 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Fri, 27 Sep 2019 10:56:28 -0400 Subject: [PATCH 061/193] Update CHANGELOG.md --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f9df093f89..e52be34f695 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,12 @@ - #### 🚨 Breaking changes -## ✨ **0.9** *(2019-09-24)* +## ✨ **0.9** *(2019-09-27)* - #### ⚡️ Features + - KeyboardService has been added which allows setting key listeners for browsers which support the feature. [[@hgzimmerman], [#647](https://github.com/yewstack/yew/pull/647)] + - ComponentLink can now create a Callback with more than one Message. The Messages will be batched together so that the Component will not be re-rendered more than necessary. [[@stkevintan], [#660](https://github.com/yewstack/yew/pull/660)] - Messages to Public Agents will now be queued if the Agent hasn't finished setting up yet. [[@serzhiio], [#596](https://github.com/yewstack/yew/pull/596)] - Agents can now be connected to without a callback. Instead of creating a bridge to the agent, create a dispatcher like so: `MyAgent::dispatcher()`. [[@hgzimmerman], [#639](https://github.com/yewstack/yew/pull/639)] - Components can now accept children in the `html!` macro. [[@jstarry], [#589](https://github.com/yewstack/yew/pull/589)] @@ -230,6 +232,7 @@ This release introduces the concept of an `Agent`. Agents are separate activitie [@jstarry]: https://github.com/jstarry [@kellytk]: https://github.com/kellytk [@serzhiio]: https://github.com/serzhiio +[@stkevintan]: https://github.com/stkevintan [@tiziano88]: https://github.com/tiziano88 [@totorigolo]: https://github.com/totorigolo [@Wodann]: https://github.com/Wodann From 2104ad406281f5a41ff3796f4f67bcbadccfc18d Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Fri, 27 Sep 2019 10:59:47 -0400 Subject: [PATCH 062/193] Update CHANGELOG.md --- CHANGELOG.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e52be34f695..cdf4c31d299 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,11 +12,11 @@ - #### ⚡️ Features - - KeyboardService has been added which allows setting key listeners for browsers which support the feature. [[@hgzimmerman], [#647](https://github.com/yewstack/yew/pull/647)] - - ComponentLink can now create a Callback with more than one Message. The Messages will be batched together so that the Component will not be re-rendered more than necessary. [[@stkevintan], [#660](https://github.com/yewstack/yew/pull/660)] - - Messages to Public Agents will now be queued if the Agent hasn't finished setting up yet. [[@serzhiio], [#596](https://github.com/yewstack/yew/pull/596)] - - Agents can now be connected to without a callback. Instead of creating a bridge to the agent, create a dispatcher like so: `MyAgent::dispatcher()`. [[@hgzimmerman], [#639](https://github.com/yewstack/yew/pull/639)] - - Components can now accept children in the `html!` macro. [[@jstarry], [#589](https://github.com/yewstack/yew/pull/589)] + - New `KeyboardService` for setting up key listeners on browsers which support the feature. [[@hgzimmerman], [#647](https://github.com/yewstack/yew/pull/647)] + - `ComponentLink` can now create a `Callback` with more than one `Message`. The `Message`'s will be batched together so that the `Component` will not be re-rendered more than necessary. [[@stkevintan], [#660](https://github.com/yewstack/yew/pull/660)] + - `Message`'s to `Public` `Agent`'s will now be queued if the `Agent` hasn't finished setting up yet. [[@serzhiio], [#596](https://github.com/yewstack/yew/pull/596)] + - `Agent`'s can now be connected to without a `Callback`. Instead of creating a bridge to the agent, create a dispatcher like so: `MyAgent::dispatcher()`. [[@hgzimmerman], [#639](https://github.com/yewstack/yew/pull/639)] + - `Component`'s can now accept children in the `html!` macro. [[@jstarry], [#589](https://github.com/yewstack/yew/pull/589)] ```rust // app.rs @@ -51,7 +51,7 @@ } ``` - - Iterators can now be rendered in the `html!` macro without using the `for` keyword. [[@hgzimmerman], [#622](https://github.com/yewstack/yew/pull/622)] + - `Iterator`s can now be rendered in the `html!` macro without using the `for` keyword. [[@hgzimmerman], [#622](https://github.com/yewstack/yew/pull/622)] Before: ```rust From afb832d4b8190d981b402239dfa1ca9520dadcfe Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Fri, 27 Sep 2019 11:15:40 -0400 Subject: [PATCH 063/193] Bump version to v0.10.0 (#662) --- Cargo.toml | 4 ++-- crates/macro/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9eccb5b18b5..49ba6c8bcfa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yew" -version = "0.9.0" +version = "0.10.0" edition = "2018" authors = [ "Denis Kolodin ", @@ -35,7 +35,7 @@ serde_yaml = { version = "0.8.3", optional = true } slab = "0.4" stdweb = "^0.4.16" toml = { version = "0.4", optional = true } -yew-macro = { version = "0.9.0", path = "crates/macro" } +yew-macro = { version = "0.10.0", path = "crates/macro" } [target.'cfg(all(target_arch = "wasm32", not(cargo_web)))'.dependencies] wasm-bindgen = "=0.2.42" diff --git a/crates/macro/Cargo.toml b/crates/macro/Cargo.toml index d2dd610aa1e..60338c3a070 100644 --- a/crates/macro/Cargo.toml +++ b/crates/macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yew-macro" -version = "0.9.0" +version = "0.10.0" edition = "2018" authors = ["Justin Starry "] repository = "https://github.com/yewstack/yew" From d04ffd980ab1b476942147e37d9c21482c104008 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Sat, 28 Sep 2019 23:13:32 -0400 Subject: [PATCH 064/193] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 80209a92aef..5439eaeb3b8 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ This framework is designed to be compiled into modern browsers' runtimes: wasm, To prepare the development environment use the installation instruction here: [wasm-and-rust](https://github.com/raphamorim/wasm-and-rust). -### Clean Flux architecture inspired by Elm and Redux +### Architecture inspired by Elm and Redux Yew implements strict application state management based on message passing and updates: From 9c26fab0458ddb364c9c933382929cdc5b05fc3e Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Sun, 29 Sep 2019 21:44:46 -0400 Subject: [PATCH 065/193] Fix spelling, grammar, and incorrect copied comments (#671) --- src/agent.rs | 6 +++--- src/virtual_dom/mod.rs | 6 +++--- src/virtual_dom/vlist.rs | 4 ++-- src/virtual_dom/vtext.rs | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/agent.rs b/src/agent.rs index 67184f820ba..5d66f67879f 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -653,9 +653,9 @@ impl Discoverer for Global {} /// Declares the behavior of the agent. pub trait Agent: Sized + 'static { - /// Reach capaility of the agent. + /// Reach capability of the agent. type Reach: Discoverer; - /// Type of an input messagae. + /// Type of an input message. type Message; /// Incoming message type. type Input: Transferable; @@ -687,7 +687,7 @@ pub trait Agent: Sized + 'static { } } -/// This sctruct holds a reference to a component and to a global scheduler. +/// This struct holds a reference to a component and to a global scheduler. pub struct AgentScope { shared_agent: Shared>, } diff --git a/src/virtual_dom/mod.rs b/src/virtual_dom/mod.rs index 1febe455830..902acf5b3b0 100644 --- a/src/virtual_dom/mod.rs +++ b/src/virtual_dom/mod.rs @@ -135,7 +135,7 @@ enum Reform { /// new node in the correct slot of the parent. /// /// If it does not exist, a `precursor` must be - /// speccified (see `VDiff::apply()`). + /// specified (see `VDiff::apply()`). Before(Option), } @@ -143,7 +143,7 @@ enum Reform { // In makes possible to include ANY element into the tree. // `Ace` editor embedding for example? -/// This trait provides features to update a tree by other tree comparsion. +/// This trait provides features to update a tree by calculating a difference against another tree. pub trait VDiff { /// The component which this instance put into. type Component: Component; @@ -154,7 +154,7 @@ pub trait VDiff { /// Scoped diff apply to other tree. /// /// Virtual rendering for the node. It uses parent node and existing children (virtual and DOM) - /// to check the difference and apply patches to the actual DOM represenatation. + /// to check the difference and apply patches to the actual DOM representation. /// /// Parameters: /// - `parent`: the parent node in the DOM. diff --git a/src/virtual_dom/vlist.rs b/src/virtual_dom/vlist.rs index e3d9d88db02..020ca21a7b0 100644 --- a/src/virtual_dom/vlist.rs +++ b/src/virtual_dom/vlist.rs @@ -5,7 +5,7 @@ use stdweb::web::{Element, Node}; /// This struct represents a fragment of the Virtual DOM tree. pub struct VList { - /// The list of children nodes. Which also could have own children. + /// The list of children nodes. Which also could have their own children. pub childs: Vec>, } @@ -16,7 +16,7 @@ impl Default for VList { } impl VList { - /// Creates a new `VTag` instance with `tag` name (cannot be changed later in DOM). + /// Creates a new empty `VList` instance. pub fn new() -> Self { VList { childs: Vec::new() } } diff --git a/src/virtual_dom/vtext.rs b/src/virtual_dom/vtext.rs index 9e3adfd679a..2b88b485fe4 100644 --- a/src/virtual_dom/vtext.rs +++ b/src/virtual_dom/vtext.rs @@ -10,7 +10,7 @@ use stdweb::web::{document, Element, INode, Node, TextNode}; /// A type for a virtual /// [`TextNode`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createTextNode) -/// represenation. +/// representation. pub struct VText { /// Contains a text of the node. pub text: String, @@ -33,7 +33,7 @@ impl VText { impl VDiff for VText { type Component = COMP; - /// Remove VTag from parent. + /// Remove VText from parent. fn detach(&mut self, parent: &Element) -> Option { let node = self .reference @@ -48,7 +48,7 @@ impl VDiff for VText { /// Renders virtual node over existent `TextNode`, but /// only if value of text had changed. - /// Parameter `precursor` is necesssary for `VTag` and `VList` which + /// Parameter `precursor` is necessary for `VTag` and `VList` which /// has children and renders them. fn apply( &mut self, From 74583ed64e7e3b66e509decfbdba0d1356d9dec2 Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Sun, 29 Sep 2019 23:26:29 -0400 Subject: [PATCH 066/193] Remove Transferable trait (#319) * remove transferable trait * remove transferable from router example, run cargo fmt * cargo fmt * Remove Transferable from multi_thread example --- examples/multi_thread/src/context.rs | 4 ---- examples/multi_thread/src/job.rs | 4 ---- examples/multi_thread/src/native_worker.rs | 4 ---- examples/routing/src/router.rs | 4 ---- src/agent.rs | 21 +++++---------------- src/lib.rs | 1 - src/prelude.rs | 0 7 files changed, 5 insertions(+), 33 deletions(-) create mode 100644 src/prelude.rs diff --git a/examples/multi_thread/src/context.rs b/examples/multi_thread/src/context.rs index 532333f6ad9..144c62b002f 100644 --- a/examples/multi_thread/src/context.rs +++ b/examples/multi_thread/src/context.rs @@ -12,15 +12,11 @@ pub enum Request { GetDataFromServer, } -impl Transferable for Request {} - #[derive(Serialize, Deserialize, Debug)] pub enum Response { DataFetched, } -impl Transferable for Response {} - pub enum Msg { Updating, } diff --git a/examples/multi_thread/src/job.rs b/examples/multi_thread/src/job.rs index 97abefba7e8..8000726785f 100644 --- a/examples/multi_thread/src/job.rs +++ b/examples/multi_thread/src/job.rs @@ -12,15 +12,11 @@ pub enum Request { GetDataFromServer, } -impl Transferable for Request {} - #[derive(Serialize, Deserialize, Debug)] pub enum Response { DataFetched, } -impl Transferable for Response {} - pub enum Msg { Updating, } diff --git a/examples/multi_thread/src/native_worker.rs b/examples/multi_thread/src/native_worker.rs index c9e2f1b7661..5980ab061d0 100644 --- a/examples/multi_thread/src/native_worker.rs +++ b/examples/multi_thread/src/native_worker.rs @@ -12,15 +12,11 @@ pub enum Request { GetDataFromServer, } -impl Transferable for Request {} - #[derive(Serialize, Deserialize, Debug)] pub enum Response { DataFetched, } -impl Transferable for Response {} - pub enum Msg { Updating, } diff --git a/examples/routing/src/router.rs b/examples/routing/src/router.rs index bbb3bb5e730..3c7302501cd 100644 --- a/examples/routing/src/router.rs +++ b/examples/routing/src/router.rs @@ -71,8 +71,6 @@ where BrowserNavigationRouteChanged((String, T)), } -impl Transferable for Route where for<'de> T: Serialize + Deserialize<'de> {} - #[derive(Serialize, Deserialize, Debug)] pub enum Request { /// Changes the route using a RouteInfo struct and alerts connected components to the route change. @@ -82,8 +80,6 @@ pub enum Request { GetCurrentRoute, } -impl Transferable for Request where for<'de> T: Serialize + Deserialize<'de> {} - /// The Router worker holds on to the RouteService singleton and mediates access to it. pub struct Router where diff --git a/src/agent.rs b/src/agent.rs index 5d66f67879f..b5f595e782a 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -26,8 +26,6 @@ enum ToWorker { Destroy, } -impl Transferable for ToWorker where T: Serialize + for<'de> Deserialize<'de> {} - #[derive(Serialize, Deserialize)] enum FromWorker { /// Worker sends this message when `wasm` bundle has loaded. @@ -35,27 +33,18 @@ enum FromWorker { ProcessOutput(HandlerId, T), } -impl Transferable for FromWorker where T: Serialize + for<'de> Deserialize<'de> {} - -/// Represents a message which you could send to an agent. -pub trait Transferable -where - Self: Serialize + for<'de> Deserialize<'de>, -{ -} - trait Packed { fn pack(&self) -> Vec; fn unpack(data: &[u8]) -> Self; } -impl Packed for T { +impl Deserialize<'de>> Packed for T { fn pack(&self) -> Vec { - bincode::serialize(&self).expect("can't serialize a transferable object") + bincode::serialize(&self).expect("can't serialize an agent message") } fn unpack(data: &[u8]) -> Self { - bincode::deserialize(&data).expect("can't deserialize a transferable object") + bincode::deserialize(&data).expect("can't deserialize an agent message") } } @@ -658,9 +647,9 @@ pub trait Agent: Sized + 'static { /// Type of an input message. type Message; /// Incoming message type. - type Input: Transferable; + type Input: Serialize + for<'de> Deserialize<'de>; /// Outgoing message type. - type Output: Transferable; + type Output: Serialize + for<'de> Deserialize<'de>; /// Creates an instance of an agent. fn create(link: AgentLink) -> Self; diff --git a/src/lib.rs b/src/lib.rs index c5de77580d0..71452f0e6e2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -159,7 +159,6 @@ pub mod prelude { pub mod worker { pub use crate::agent::{ Agent, AgentLink, Bridge, Bridged, Context, Global, HandlerId, Job, Private, Public, - Transferable, }; } } diff --git a/src/prelude.rs b/src/prelude.rs new file mode 100644 index 00000000000..e69de29bb2d From 933f6665030a30676c8f59b6537da28b27b19eac Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Mon, 30 Sep 2019 11:21:34 -0400 Subject: [PATCH 067/193] Implement Default for VNode (#672) * implement Default for VNode * cargo fmt --- src/virtual_dom/vnode.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/virtual_dom/vnode.rs b/src/virtual_dom/vnode.rs index 8ac6d720574..c24a852365a 100644 --- a/src/virtual_dom/vnode.rs +++ b/src/virtual_dom/vnode.rs @@ -72,6 +72,12 @@ impl VDiff for VNode { } } +impl Default for VNode { + fn default() -> Self { + VNode::VList(VList::new()) + } +} + impl From> for VNode { fn from(vtext: VText) -> Self { VNode::VText(vtext) From 70a95f87bf85d100fb8a332563c9b39dfff9e6fc Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Mon, 30 Sep 2019 19:06:04 -0400 Subject: [PATCH 068/193] Fix Doc Tests (#674) * fix doc tests * fix tests for non-default format types * make fetch example representative of using JSON * add doc tests to ci * clean up fetch docs * update blurb above a fetch example * minor grammar fix --- ci/run_tests.sh | 3 + src/components/select.rs | 20 +++++- src/format/cbor.rs | 6 +- src/format/json.rs | 4 +- src/format/msgpack.rs | 7 +- src/format/toml.rs | 6 +- src/format/yaml.rs | 7 +- src/html/mod.rs | 102 ++++++++++++++++++++++++--- src/lib.rs | 5 +- src/services/fetch.rs | 148 ++++++++++++++++++++++++++++----------- 10 files changed, 249 insertions(+), 59 deletions(-) diff --git a/ci/run_tests.sh b/ci/run_tests.sh index 6bba2d2f655..df517a4c428 100755 --- a/ci/run_tests.sh +++ b/ci/run_tests.sh @@ -32,6 +32,9 @@ cargo test --test macro_test echo "Testing derive props macro..." cargo test --test derive_props_test +echo "Testing docs" +cargo test --doc + echo "Testing macro docs..." (cd crates/macro && cargo test) diff --git a/src/components/select.rs b/src/components/select.rs index 3b1dc6b768d..052db641f7f 100644 --- a/src/components/select.rs +++ b/src/components/select.rs @@ -3,17 +3,35 @@ //! helps you to track selected value in an original type. Example: //! //! ``` +//!# use yew::{Html, Component, components::Select, ComponentLink, Renderable, html}; +//!# struct Model; +//!# impl Component for Model { +//!# type Message = ();type Properties = (); +//!# fn create(props: Self::Properties,link: ComponentLink) -> Self {unimplemented!()} +//!# fn update(&mut self,msg: Self::Message) -> bool {unimplemented!()} +//!# } +//!# impl Renderable for Model {fn view(&self) -> Html {unimplemented!()}} +//! #[derive(PartialEq, Clone)] //! enum Scene { //! First, //! Second, //! } +//! impl ToString for Scene { +//! fn to_string(&self) -> String { +//! match self { +//! Scene::First => "First".to_string(), +//! Scene::Second => "Second".to_string() +//! } +//! } +//! } //! //! fn view() -> Html { //! let scenes = vec![Scene::First, Scene::Second]; //! html! { -//! options=scenes /> +//! options=scenes onchange=|_| () /> //! } //! } +//! ``` use crate::callback::Callback; use crate::html::{ChangeData, Component, ComponentLink, Html, Renderable, ShouldRender}; diff --git a/src/format/cbor.rs b/src/format/cbor.rs index 8fcbfb3299b..7705f74257a 100644 --- a/src/format/cbor.rs +++ b/src/format/cbor.rs @@ -5,12 +5,16 @@ use serde_cbor; /// A representation of a CBOR data. Use it as wrapper to /// set a format you want to use for conversion: /// -/// ```rust +/// ``` /// // Converts (lazy) data to a Cbor +///# use yew::format::Cbor; +///# fn dont_execute() { +///# let data: String = unimplemented!(); /// let dump = Cbor(&data); /// /// // Converts CBOR string to a data (lazy). /// let Cbor(data) = dump; +///# } /// ``` pub struct Cbor(pub T); diff --git a/src/format/json.rs b/src/format/json.rs index f034afb9a2f..9bed4ad38d6 100644 --- a/src/format/json.rs +++ b/src/format/json.rs @@ -3,8 +3,10 @@ /// A representation of a JSON data. Use it as wrapper to /// set a format you want to use for conversion: /// -/// ```rust +/// ``` /// // Converts (lazy) data to a Json +/// use yew::format::Json; +/// let data: String = r#"{lorem: "ipsum"}"#.to_string(); /// let dump = Json(&data); /// /// // Converts JSON string to a data (lazy). diff --git a/src/format/msgpack.rs b/src/format/msgpack.rs index ecfa071bf67..355d47c23c6 100644 --- a/src/format/msgpack.rs +++ b/src/format/msgpack.rs @@ -5,12 +5,17 @@ use rmp_serde; /// A representation of a MessagePack data. Use it as wrapper to /// set a format you want to use for conversion: /// -/// ```rust +/// ``` /// // Converts (lazy) data to a MsgPack +/// +///# use yew::format::MsgPack; +///# fn dont_execute() { +///# let data: String = unimplemented!(); /// let dump = MsgPack(&data); /// /// // Converts MessagePack string to a data (lazy). /// let MsgPack(data) = dump; +///# } /// ``` pub struct MsgPack(pub T); diff --git a/src/format/toml.rs b/src/format/toml.rs index 55af48accd4..807de8f3f57 100644 --- a/src/format/toml.rs +++ b/src/format/toml.rs @@ -5,12 +5,16 @@ use toml; /// A representation of a TOML data. Use it as wrapper to /// set a format you want to use for conversion: /// -/// ```rust +/// ``` /// // Converts (lazy) data to a Toml +///# use yew::format::Toml; +///# fn dont_execute() { +///# let data: String = unimplemented!(); /// let dump = Toml(&data); /// /// // Converts TOML string to a data (lazy). /// let Toml(data) = dump; +/// } /// ``` pub struct Toml(pub T); diff --git a/src/format/yaml.rs b/src/format/yaml.rs index 9aa8f189e21..72fd5c5a313 100644 --- a/src/format/yaml.rs +++ b/src/format/yaml.rs @@ -5,12 +5,17 @@ use serde_yaml; /// A representation of a YAML data. Use it as wrapper to /// set a format you want to use for conversion: /// -/// ```rust +/// ``` /// // Converts (lazy) data to a Yaml +///# use yew::format::Yaml; +/// +///# fn dont_execute() { +///# let data: String = unimplemented!(); /// let dump = Yaml(&data); /// /// // Converts YAML string to a data (lazy). /// let Yaml(data) = dump; +///# } /// ``` pub struct Yaml(pub T); diff --git a/src/html/mod.rs b/src/html/mod.rs index 597dc57eb40..478c8bf54fa 100644 --- a/src/html/mod.rs +++ b/src/html/mod.rs @@ -56,12 +56,28 @@ pub type Html = VNode; /// /// In this example, the Wrapper component is used to wrap other elements. /// ``` +/// +///# use yew::{Children, Html, Renderable, Properties, Component, ComponentLink, html}; +/// #[derive(Properties)] +///# struct WrapperProps { +///# children: Children, +///# } +///# struct Wrapper; +///# impl Component for Wrapper{ +///# type Message = ();type Properties = WrapperProps; +///# fn create(props: Self::Properties,link: ComponentLink) -> Self {unimplemented!()} +///# fn update(&mut self,msg: Self::Message) -> bool {unimplemented!()} +///# } +///# impl Renderable for Wrapper { +///# fn view(&self) -> Html { // This is a recursively defined render impl - which would never work. This is done for space convenience. /// html!{ /// ///

{"Hi"}

///
{"Hello"}
///
/// } +///# } +///# } /// ``` /// /// **`wrapper.rs`** @@ -69,15 +85,25 @@ pub type Html = VNode; /// The Wrapper component must define a `children` property in order to wrap other elements. The /// children property can be used to render the wrapped elements. /// ``` +///# use yew::{Children, Html, Renderable, Properties, Component, ComponentLink, html}; +///# struct Wrapper {props: WrapperProps}; +///# impl Component for Wrapper{ +///# type Message = ();type Properties = WrapperProps; +///# fn create(props: Self::Properties,link: ComponentLink) -> Self {unimplemented!()} +///# fn update(&mut self,msg: Self::Message) -> bool {unimplemented!()} +///# } /// #[derive(Properties)] /// struct WrapperProps { /// children: Children, /// } -/// -/// html!{ -///
-/// { self.props.children.view() } -///
+/// impl Renderable for Wrapper { +/// fn view(&self) -> Html { +/// html!{ +///
+/// { self.props.children.view() } +///
+/// } +/// } /// } /// ``` pub type Children = ChildrenRenderer>; @@ -89,6 +115,34 @@ pub type Children = ChildrenRenderer>; /// /// In this example, the `List` component can wrap `ListItem` components. /// ``` +/// use yew::{html, Component, Renderable, Html, ComponentLink, ChildrenWithProps, Properties}; +/// +///# #[derive(Properties)] +///# struct ListProps { +///# children: ChildrenWithProps, +///# } +/// +///# struct List; +///# impl Component for List { +///# type Message = ();type Properties = ListProps; +///# fn create(props: Self::Properties,link: ComponentLink) -> Self {unimplemented!()} +///# fn update(&mut self,msg: Self::Message) -> bool {unimplemented!()} +///# } +///# impl Renderable for List {fn view(&self) -> Html {unimplemented!()}} +/// +/// +///# #[derive(Properties)] +///# struct ListItemProps { +///# value: String +///# } +///# struct ListItem; +///# impl Component for ListItem { +///# type Message = ();type Properties = ListItemProps; +///# fn create(props: Self::Properties,link: ComponentLink) -> Self {unimplemented!()} +///# fn update(&mut self,msg: Self::Message) -> bool {unimplemented!()} +///# } +///# impl Renderable for ListItem {fn view(&self) -> Html {unimplemented!()}} +///# fn view() -> Html { /// html!{ /// /// @@ -96,6 +150,7 @@ pub type Children = ChildrenRenderer>; /// /// /// } +///# } /// ``` /// /// **`list.rs`** @@ -103,17 +158,42 @@ pub type Children = ChildrenRenderer>; /// The `List` component must define a `children` property in order to wrap the list items. The /// `children` property can be used to filter, mutate, and render the items. /// ``` +/// use yew::ChildrenWithProps; +///# use yew::{html, Component, Renderable, Html, ComponentLink, Properties}; +///# struct List {props: ListProps}; +///# impl Component for List { +///# type Message = ();type Properties = ListProps; +///# fn create(props: Self::Properties,link: ComponentLink) -> Self {unimplemented!()} +///# fn update(&mut self,msg: Self::Message) -> bool {unimplemented!()} +///# } +/// +///# #[derive(Properties)] +///# struct ListItemProps { +///# value: String +///# } +///# struct ListItem; +///# impl Component for ListItem { +///# type Message = ();type Properties = ListItemProps; +///# fn create(props: Self::Properties,link: ComponentLink) -> Self {unimplemented!()} +///# fn update(&mut self,msg: Self::Message) -> bool {unimplemented!()} +///# } +///# impl Renderable for ListItem {fn view(&self) -> Html {unimplemented!()}} +/// /// #[derive(Properties)] /// struct ListProps { /// children: ChildrenWithProps, /// } /// -/// html!{{ -/// for self.props.children.iter().map(|mut item| { -/// item.props.value = format!("item-{}", item.props.value); -/// item -/// }) -/// }} +/// impl Renderable for List { +/// fn view(&self) -> Html { +/// html!{{ +/// for self.props.children.iter().map(|mut item| { +/// item.props.value = format!("item-{}", item.props.value); +/// item +/// }) +/// }} +/// } +/// } /// ``` pub type ChildrenWithProps = ChildrenRenderer>; diff --git a/src/lib.rs b/src/lib.rs index 71452f0e6e2..88b95c8359f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,8 +8,6 @@ //! Minimal example: //! //! ```rust -//! #[macro_use] -//! extern crate yew; //! use yew::prelude::*; //! //! struct Model { @@ -47,12 +45,13 @@ //! } //! } //! } -//! +//!# fn dont_execute() { //! fn main() { //! yew::initialize(); //! App::::new().mount_to_body(); //! yew::run_loop(); //! } +//!# } //! ``` //! diff --git a/src/services/fetch.rs b/src/services/fetch.rs index b1a57a450be..9c36d9904dd 100644 --- a/src/services/fetch.rs +++ b/src/services/fetch.rs @@ -115,48 +115,99 @@ impl FetchService { /// You may use a Request builder to build your request declaratively as on the /// following examples: /// - /// ```rust - /// let post_request = Request::post("https://my.api/v1/resource") - /// .header("Content-Type", "application/json") - /// .body(Json(&json!({"foo": "bar"}))) - /// .expect("Failed to build request."); + /// ``` + ///# use yew::format::{Nothing, Json}; + ///# use yew::services::fetch::Request; + ///# use serde_json::json; + /// let post_request = Request::post("https://my.api/v1/resource") + /// .header("Content-Type", "application/json") + /// .body(Json(&json!({"foo": "bar"}))) + /// .expect("Failed to build request."); /// - /// let get_request = Request::get("https://my.api/v1/resource") - /// .body(Nothing) - /// .expect("Failed to build request."); + /// let get_request = Request::get("https://my.api/v1/resource") + /// .body(Nothing) + /// .expect("Failed to build request."); /// ``` /// /// The callback function can build a loop message by passing or analizing the /// response body and metadata. /// - /// ```rust - /// context.web.fetch( - /// post_request, - /// |response| { - /// if response.status().is_success() { - /// Msg::Noop - /// } else { - /// Msg::Error - /// } + /// ``` + ///# use yew::{Component, ComponentLink, Html, Renderable}; + ///# use yew::services::FetchService; + ///# use yew::services::fetch::{Response, Request}; + ///# struct Comp; + ///# impl Component for Comp { + ///# type Message = Msg;type Properties = (); + ///# fn create(props: Self::Properties,link: ComponentLink) -> Self {unimplemented!()} + ///# fn update(&mut self,msg: Self::Message) -> bool {unimplemented!()} + ///# } + ///# impl Renderable for Comp {fn view(&self) -> Html {unimplemented!()}} + ///# enum Msg { + ///# Noop, + ///# Error + ///# } + ///# fn dont_execute() { + ///# let mut link: ComponentLink = unimplemented!(); + ///# let mut fetch_service: FetchService = FetchService::new(); + ///# let post_request: Request> = unimplemented!(); + /// let task = fetch_service.fetch( + /// post_request, + /// link.send_back(|response: Response>| { + /// if response.status().is_success() { + /// Msg::Noop + /// } else { + /// Msg::Error /// } - /// ) + /// }) + /// ); + ///# } /// ``` /// - /// One can also simply consume and pass the response or body object into - /// the message. + /// For a full example, you can specify that the response must be in the JSON format, + /// and be a specific serialized data type. If the mesage isn't Json, or isn't the specified + /// data type, then you will get a message indicating failure. /// - /// ```rust - /// context.web.fetch( - /// get_request, - /// |response| { - /// let (meta, Json(body)) = response.into_parts(); - /// if meta.status.is_success() { - /// Msg::FetchResourceComplete(body) - /// } else { - /// Msg::FetchResourceFailed - /// } + /// ``` + ///# use yew::format::{Json, Nothing, Format}; + ///# use yew::services::FetchService; + ///# use http::Request; + ///# use yew::services::fetch::Response; + ///# use yew::{Component, ComponentLink, Renderable, Html}; + ///# use serde_derive::Deserialize; + ///# struct Comp; + ///# impl Component for Comp { + ///# type Message = Msg;type Properties = (); + ///# fn create(props: Self::Properties,link: ComponentLink) -> Self {unimplemented!()} + ///# fn update(&mut self,msg: Self::Message) -> bool {unimplemented!()} + ///# } + ///# impl Renderable for Comp {fn view(&self) -> Html {unimplemented!()}} + ///# enum Msg { + ///# FetchResourceComplete(Data), + ///# FetchResourceFailed + ///# } + /// #[derive(Deserialize)] + /// struct Data { + /// value: String + /// } + ///# fn dont_execute() { + ///# let mut link: ComponentLink = unimplemented!(); + /// let get_request = Request::get("/thing").body(Nothing).unwrap(); + /// let callback = link.send_back(|response: Response>>| { + /// if let (meta, Json(Ok(body))) = response.into_parts() { + /// if meta.status.is_success() { + /// return Msg::FetchResourceComplete(body); + /// } /// } - /// ) + /// Msg::FetchResourceFailed + /// } + /// ); + /// + /// let task = FetchService::new().fetch( + /// get_request, + /// callback + /// ); + ///# } /// ``` /// pub fn fetch( @@ -173,14 +224,33 @@ impl FetchService { /// `fetch` with provided `FetchOptions` object. /// Use it if you need to send cookies with a request: - /// ```rust - /// let request = fetch::Request::get("/path/") - /// .body(Nothing).unwrap(); - /// let options = FetchOptions { - /// credentials: Some(Credentials::SameOrigin), - /// ..FetchOptions::default() - /// }; - /// let task = fetch_service.fetch_with_options(request, options, callback); + /// ``` + ///# use yew::format::Nothing; + ///# use yew::services::fetch::{self, FetchOptions, Credentials}; + ///# use yew::{Renderable, Html, Component, ComponentLink}; + ///# use yew::services::FetchService; + ///# use http::Response; + ///# struct Comp; + ///# impl Component for Comp { + ///# type Message = Msg;type Properties = (); + ///# fn create(props: Self::Properties,link: ComponentLink) -> Self {unimplemented!()} + ///# fn update(&mut self,msg: Self::Message) -> bool {unimplemented!()} + ///# } + ///# impl Renderable for Comp {fn view(&self) -> Html {unimplemented!()}} + ///# pub enum Msg {} + ///# fn dont_execute() { + ///# let mut link: ComponentLink = unimplemented!(); + ///# let callback = link.send_back(|response: Response>| unimplemented!()); + /// let request = fetch::Request::get("/path/") + /// .body(Nothing) + /// .unwrap(); + /// let options = FetchOptions { + /// credentials: Some(Credentials::SameOrigin), + /// ..FetchOptions::default() + /// }; + ///# let mut fetch_service = FetchService::new(); + /// let task = fetch_service.fetch_with_options(request, options, callback); + ///# } /// ``` pub fn fetch_with_options( &mut self, From 43e93472691733b2fdacd707841bb9380e0bbc06 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Mon, 30 Sep 2019 22:23:25 -0400 Subject: [PATCH 069/193] Add render method to Component and auto implement Renderable (#563) * Add render method to Component and auto implement Renderable * More cleanup * Rename Renderable method from view to render * Doc fixes * fix * Update CHANGELOG.md --- README.md | 6 +- crates/macro/src/lib.rs | 2 - examples/counter/src/lib.rs | 4 +- examples/crm/src/lib.rs | 6 +- examples/custom_components/src/barrier.rs | 2 - examples/custom_components/src/button.rs | 2 - examples/custom_components/src/counter.rs | 2 - examples/custom_components/src/lib.rs | 2 - examples/dashboard/src/lib.rs | 4 +- examples/file_upload/src/lib.rs | 4 +- examples/fragments/src/lib.rs | 4 +- examples/game_of_life/src/lib.rs | 4 +- examples/inner_html/src/lib.rs | 20 ++- examples/js_callback/src/lib.rs | 2 - examples/large_table/src/lib.rs | 20 ++- examples/minimal/src/lib.rs | 4 +- examples/mount_point/src/lib.rs | 4 +- examples/multi_thread/src/lib.rs | 4 +- examples/nested_list/src/header.rs | 2 - examples/nested_list/src/item.rs | 4 +- examples/nested_list/src/lib.rs | 2 - examples/nested_list/src/list.rs | 4 +- examples/npm_and_rest/src/lib.rs | 4 +- examples/routing/src/b_component.rs | 5 +- examples/routing/src/lib.rs | 6 +- examples/routing/src/router_button.rs | 3 +- examples/showcase/src/main.rs | 4 +- examples/textarea/src/lib.rs | 4 +- examples/timer/src/lib.rs | 4 +- examples/todomvc/src/lib.rs | 4 +- examples/two_apps/src/lib.rs | 4 +- src/app.rs | 8 +- src/components/select.rs | 24 ++-- src/html/listener.rs | 2 +- src/html/mod.rs | 160 ++++++++++++---------- src/html/scope.rs | 16 +-- src/lib.rs | 6 +- src/services/fetch.rs | 41 +++--- src/virtual_dom/vcomp.rs | 10 +- src/virtual_dom/vnode.rs | 4 +- tests/macro/helpers.rs | 7 +- tests/macro/html-component-fail.rs | 4 - tests/macro/html-component-fail.stderr | 104 +++++++------- tests/macro/html-component-pass.rs | 6 - tests/macro/html-iterable-pass.rs | 8 +- tests/macro/test_component.rs | 8 ++ tests/vcomp_test.rs | 4 +- tests/vlist_test.rs | 4 +- tests/vtag_test.rs | 8 +- tests/vtext_test.rs | 4 +- 50 files changed, 249 insertions(+), 325 deletions(-) diff --git a/README.md b/README.md index 5439eaeb3b8..e0702532ee5 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ Yew implements strict application state management based on message passing and `src/main.rs` ```rust -use yew::{html, Component, ComponentLink, Html, Renderable, ShouldRender}; +use yew::{html, Component, ComponentLink, Html, ShouldRender}; struct Model { } @@ -82,9 +82,7 @@ impl Component for Model { } } } -} -impl Renderable for Model { fn view(&self) -> Html { html! { // Render your model here @@ -322,7 +320,7 @@ extern crate chrono; use chrono::prelude::*; impl Renderable for Model { - fn view(&self) -> Html { + fn render(&self) -> Html { html! {

{ Local::now() }

} diff --git a/crates/macro/src/lib.rs b/crates/macro/src/lib.rs index 66a28296a50..ea3b8687e7f 100644 --- a/crates/macro/src/lib.rs +++ b/crates/macro/src/lib.rs @@ -28,9 +28,7 @@ //! # fn update(&mut self, msg: Self::Message) -> ShouldRender { //! # unimplemented!() //! # } -//! # } //! # -//! # impl Renderable for Component { //! # fn view(&self) -> Html { //! # //! // ... diff --git a/examples/counter/src/lib.rs b/examples/counter/src/lib.rs index 4710546cdaf..02a5f574ce5 100644 --- a/examples/counter/src/lib.rs +++ b/examples/counter/src/lib.rs @@ -2,7 +2,7 @@ use stdweb::web::Date; use yew::services::ConsoleService; -use yew::{html, Component, ComponentLink, Html, Renderable, ShouldRender}; +use yew::{html, Component, ComponentLink, Html, ShouldRender}; pub struct Model { console: ConsoleService, @@ -45,9 +45,7 @@ impl Component for Model { } true } -} -impl Renderable for Model { fn view(&self) -> Html { html! {
diff --git a/examples/crm/src/lib.rs b/examples/crm/src/lib.rs index 4010fd66f70..fcd7446413f 100644 --- a/examples/crm/src/lib.rs +++ b/examples/crm/src/lib.rs @@ -143,15 +143,13 @@ impl Component for Model { } true } -} -impl Renderable for Model { fn view(&self) -> Html { match self.scene { Scene::ClientsList => html! {
- { for self.database.clients.iter().map(Renderable::view) } + { for self.database.clients.iter().map(Renderable::render) }
@@ -180,7 +178,7 @@ impl Renderable for Model { } impl Renderable for Client { - fn view(&self) -> Html { + fn render(&self) -> Html { html! {

{ format!("First Name: {}", self.first_name) }

diff --git a/examples/custom_components/src/barrier.rs b/examples/custom_components/src/barrier.rs index dcc606427ad..7fa3b762bd1 100644 --- a/examples/custom_components/src/barrier.rs +++ b/examples/custom_components/src/barrier.rs @@ -48,9 +48,7 @@ impl Component for Barrier { self.onsignal = props.onsignal; true } -} -impl Renderable for Barrier { fn view(&self) -> Html { html! {
diff --git a/examples/custom_components/src/button.rs b/examples/custom_components/src/button.rs index 4d5ab7de4db..fe190c68f55 100644 --- a/examples/custom_components/src/button.rs +++ b/examples/custom_components/src/button.rs @@ -41,9 +41,7 @@ impl Component for Button { self.onsignal = props.onsignal; true } -} -impl Renderable diff --git a/examples/custom_components/src/counter.rs b/examples/custom_components/src/counter.rs index ae621edc19e..97d1e937c29 100644 --- a/examples/custom_components/src/counter.rs +++ b/examples/custom_components/src/counter.rs @@ -58,9 +58,7 @@ impl Component for Counter { self.onclick = props.onclick; true } -} -impl Renderable for Counter { fn view(&self) -> Html { let colorize = { match self.color { diff --git a/examples/custom_components/src/lib.rs b/examples/custom_components/src/lib.rs index 7557d23a466..05a6cf3d33f 100644 --- a/examples/custom_components/src/lib.rs +++ b/examples/custom_components/src/lib.rs @@ -43,9 +43,7 @@ impl Component for Model { Msg::ChildClicked(_value) => false, } } -} -impl Renderable for Model { fn view(&self) -> Html { let counter = |x| { html! { diff --git a/examples/dashboard/src/lib.rs b/examples/dashboard/src/lib.rs index 1b2ca53059f..cf655813eb5 100644 --- a/examples/dashboard/src/lib.rs +++ b/examples/dashboard/src/lib.rs @@ -6,7 +6,7 @@ use yew::format::{Json, Nothing, Toml}; use yew::services::fetch::{FetchService, FetchTask, Request, Response}; use yew::services::websocket::{WebSocketService, WebSocketStatus, WebSocketTask}; use yew::services::Task; -use yew::{html, Component, ComponentLink, Html, Renderable, ShouldRender}; +use yew::{html, Component, ComponentLink, Html, ShouldRender}; type AsBinary = bool; @@ -167,9 +167,7 @@ impl Component for Model { } true } -} -impl Renderable for Model { fn view(&self) -> Html { html! {
diff --git a/examples/file_upload/src/lib.rs b/examples/file_upload/src/lib.rs index 4d4ea002e55..8b236ae125e 100644 --- a/examples/file_upload/src/lib.rs +++ b/examples/file_upload/src/lib.rs @@ -1,7 +1,7 @@ #![recursion_limit = "256"] use yew::services::reader::{File, FileChunk, FileData, ReaderService, ReaderTask}; -use yew::{html, ChangeData, Component, ComponentLink, Html, Renderable, ShouldRender}; +use yew::{html, ChangeData, Component, ComponentLink, Html, ShouldRender}; pub struct Model { link: ComponentLink, @@ -64,9 +64,7 @@ impl Component for Model { } true } -} -impl Renderable for Model { fn view(&self) -> Html { let flag = self.by_chunks; html! { diff --git a/examples/fragments/src/lib.rs b/examples/fragments/src/lib.rs index c22f59d541b..ae27e82f55f 100644 --- a/examples/fragments/src/lib.rs +++ b/examples/fragments/src/lib.rs @@ -1,6 +1,6 @@ #![recursion_limit = "128"] -use yew::{html, Component, ComponentLink, Html, Renderable, ShouldRender}; +use yew::{html, Component, ComponentLink, Html, ShouldRender}; pub struct Model { counter: usize, @@ -32,9 +32,7 @@ impl Component for Model { } true } -} -impl Renderable for Model { fn view(&self) -> Html { html! { <> diff --git a/examples/game_of_life/src/lib.rs b/examples/game_of_life/src/lib.rs index fd4736ccb0f..0a07293e85b 100644 --- a/examples/game_of_life/src/lib.rs +++ b/examples/game_of_life/src/lib.rs @@ -4,7 +4,7 @@ use log::info; use rand::Rng; use std::time::Duration; use yew::services::{IntervalService, Task}; -use yew::{html, Component, ComponentLink, Html, Renderable, ShouldRender}; +use yew::{html, Component, ComponentLink, Html, ShouldRender}; #[derive(Clone, Copy, PartialEq)] enum LifeState { @@ -205,9 +205,7 @@ impl Component for Model { } true } -} -impl Renderable for Model { fn view(&self) -> Html { html! {
diff --git a/examples/inner_html/src/lib.rs b/examples/inner_html/src/lib.rs index 78a2f1d7048..4db3b96e84c 100644 --- a/examples/inner_html/src/lib.rs +++ b/examples/inner_html/src/lib.rs @@ -5,7 +5,15 @@ extern crate stdweb; use stdweb::unstable::TryFrom; use stdweb::web::Node; use yew::virtual_dom::VNode; -use yew::{Component, ComponentLink, Html, Renderable, ShouldRender}; +use yew::{Component, ComponentLink, Html, ShouldRender}; + +const SVG: &str = r#" +

Inline SVG or any HTML:

+ + + Sorry, your browser does not support inline SVG. + +"#; pub struct Model { pub value: i64, @@ -24,17 +32,7 @@ impl Component for Model { fn update(&mut self, _: Self::Message) -> ShouldRender { true } -} - -const SVG: &str = r#" -

Inline SVG or any HTML:

- - - Sorry, your browser does not support inline SVG. - -"#; -impl Renderable for Model { fn view(&self) -> Html { let js_svg = js! { var div = document.createElement("div"); diff --git a/examples/js_callback/src/lib.rs b/examples/js_callback/src/lib.rs index 8950f80d554..b12bfb9a641 100644 --- a/examples/js_callback/src/lib.rs +++ b/examples/js_callback/src/lib.rs @@ -52,9 +52,7 @@ impl Component for Model { fn change(&mut self, _: Self::Properties) -> ShouldRender { false } -} -impl Renderable for Model { fn view(&self) -> Html { html! {
diff --git a/examples/large_table/src/lib.rs b/examples/large_table/src/lib.rs index 69b5240af89..a7edf982bfe 100644 --- a/examples/large_table/src/lib.rs +++ b/examples/large_table/src/lib.rs @@ -1,7 +1,7 @@ //! This demo originally created by https://github.com/qthree //! Source: https://github.com/qthree/yew_table100x100_test -use yew::{html, Component, ComponentLink, Html, Renderable, ShouldRender}; +use yew::{html, Component, ComponentLink, Html, ShouldRender}; pub struct Model { selected: Option<(u32, u32)>, @@ -28,6 +28,14 @@ impl Component for Model { } true } + + fn view(&self) -> Html { + html! { + + { (0..99).map(|row| view_row(self.selected, row)).collect::>() } +
+ } + } } fn square_class(this: (u32, u32), selected: Option<(u32, u32)>) -> &'static str { @@ -54,13 +62,3 @@ fn view_row(selected: Option<(u32, u32)>, row: u32) -> Html { } } - -impl Renderable for Model { - fn view(&self) -> Html { - html! { - - { (0..99).map(|row| view_row(self.selected, row)).collect::>() } -
- } - } -} diff --git a/examples/minimal/src/lib.rs b/examples/minimal/src/lib.rs index 00420682847..d9e1f41afc9 100644 --- a/examples/minimal/src/lib.rs +++ b/examples/minimal/src/lib.rs @@ -1,4 +1,4 @@ -use yew::{html, Component, ComponentLink, Html, Renderable, ShouldRender}; +use yew::{html, Component, ComponentLink, Html, ShouldRender}; pub struct Model {} @@ -20,9 +20,7 @@ impl Component for Model { } true } -} -impl Renderable for Model { fn view(&self) -> Html { html! {
diff --git a/examples/mount_point/src/lib.rs b/examples/mount_point/src/lib.rs index c8a6d6e6a4f..7d2b98dc155 100644 --- a/examples/mount_point/src/lib.rs +++ b/examples/mount_point/src/lib.rs @@ -1,4 +1,4 @@ -use yew::{html, Component, ComponentLink, Html, Renderable, ShouldRender}; +use yew::{html, Component, ComponentLink, Html, ShouldRender}; pub struct Model { name: String, @@ -26,9 +26,7 @@ impl Component for Model { } true } -} -impl Renderable for Model { fn view(&self) -> Html { html! {
diff --git a/examples/multi_thread/src/lib.rs b/examples/multi_thread/src/lib.rs index 1652d9c9d8e..bb09a315225 100644 --- a/examples/multi_thread/src/lib.rs +++ b/examples/multi_thread/src/lib.rs @@ -6,7 +6,7 @@ pub mod native_worker; use log::info; use yew::worker::*; -use yew::{html, Component, ComponentLink, Html, Renderable, ShouldRender}; +use yew::{html, Component, ComponentLink, Html, ShouldRender}; pub struct Model { worker: Box>, @@ -65,9 +65,7 @@ impl Component for Model { } true } -} -impl Renderable for Model { fn view(&self) -> Html { html! {
diff --git a/examples/nested_list/src/header.rs b/examples/nested_list/src/header.rs index 89fcf90f90b..32fbf2b0a11 100644 --- a/examples/nested_list/src/header.rs +++ b/examples/nested_list/src/header.rs @@ -33,9 +33,7 @@ impl Component for ListHeader { } false } -} -impl Renderable for ListHeader { fn view(&self) -> Html { html! {
diff --git a/examples/nested_list/src/item.rs b/examples/nested_list/src/item.rs index 6fffcc2af7f..46326a8ac91 100644 --- a/examples/nested_list/src/item.rs +++ b/examples/nested_list/src/item.rs @@ -38,9 +38,7 @@ impl Component for ListItem { } false } -} -impl Renderable for ListItem { fn view(&self) -> Html { html! {
@@ -59,7 +57,7 @@ impl ListItem { html! {
- { self.props.children.view() } + { self.props.children.render() }
} } diff --git a/examples/nested_list/src/lib.rs b/examples/nested_list/src/lib.rs index fd12e0ce1c8..4ea0811ed10 100644 --- a/examples/nested_list/src/lib.rs +++ b/examples/nested_list/src/lib.rs @@ -22,9 +22,7 @@ impl Component for Model { fn update(&mut self, _: Self::Message) -> ShouldRender { true } -} -impl Renderable for Model { fn view(&self) -> Html { html! {
diff --git a/examples/nested_list/src/list.rs b/examples/nested_list/src/list.rs index 7b50ccb1aef..b38ddd5d613 100644 --- a/examples/nested_list/src/list.rs +++ b/examples/nested_list/src/list.rs @@ -68,9 +68,7 @@ impl Component for List { } true } -} -impl Renderable for List { fn view(&self) -> Html { html! {
From> for ListVariant where - CHILD: Component + Renderable, + CHILD: Component, CHILD::Properties: Into, { fn from(vchild: VChild) -> Self { diff --git a/examples/npm_and_rest/src/lib.rs b/examples/npm_and_rest/src/lib.rs index 5ff89a8a370..a2916d75064 100644 --- a/examples/npm_and_rest/src/lib.rs +++ b/examples/npm_and_rest/src/lib.rs @@ -9,7 +9,7 @@ pub mod gravatar; use failure::Error; use yew::services::fetch::FetchTask; -use yew::{html, Callback, Component, ComponentLink, Html, Renderable, ShouldRender}; +use yew::{html, Callback, Component, ComponentLink, Html, ShouldRender}; use ccxt::CcxtService; use gravatar::{GravatarService, Profile}; @@ -64,9 +64,7 @@ impl Component for Model { } true } -} -impl Renderable for Model { fn view(&self) -> Html { let view_exchange = |exchange| { html! { diff --git a/examples/routing/src/b_component.rs b/examples/routing/src/b_component.rs index a2d3eee6334..90f04dec374 100644 --- a/examples/routing/src/b_component.rs +++ b/examples/routing/src/b_component.rs @@ -1,7 +1,7 @@ use crate::router::{Request, Route, Router}; use log::info; use yew::agent::Bridged; -use yew::{html, Bridge, Component, ComponentLink, Html, Renderable, ShouldRender}; +use yew::{html, Bridge, Component, ComponentLink, Html, ShouldRender}; pub struct BModel { number: Option, @@ -105,8 +105,7 @@ impl Component for BModel { // Apparently change MUST be implemented in this case, even though no props were changed true } -} -impl Renderable for BModel { + fn view(&self) -> Html { html! {
diff --git a/examples/routing/src/lib.rs b/examples/routing/src/lib.rs index e1b75199e25..f5fad039fe2 100644 --- a/examples/routing/src/lib.rs +++ b/examples/routing/src/lib.rs @@ -67,9 +67,7 @@ impl Component for Model { } } } -} -impl Renderable for Model { fn view(&self) -> Html { html! {
@@ -78,7 +76,7 @@ impl Renderable for Model {
- {self.child.view()} + {self.child.render()}
} @@ -86,7 +84,7 @@ impl Renderable for Model { } impl Renderable for Child { - fn view(&self) -> Html { + fn render(&self) -> Html { match self { Child::A => html! { <> diff --git a/examples/routing/src/router_button.rs b/examples/routing/src/router_button.rs index efb42807c4f..049dad044ba 100644 --- a/examples/routing/src/router_button.rs +++ b/examples/routing/src/router_button.rs @@ -60,13 +60,12 @@ impl Component for RouterButton { } } } + fn change(&mut self, props: Self::Properties) -> ShouldRender { self.props = props; true } -} -impl Renderable for RouterButton { fn view(&self) -> Html { html! {
} + } + FetchState::Fetching => html! {"Fetching"}, + FetchState::Success(data) => html! {&data}, + FetchState::Failed(err) => html! {&err}, + } + } +} + +#[wasm_bindgen] +pub fn run_app() { + yew::start_app::(); +} diff --git a/src/html/mod.rs b/src/html/mod.rs index 97f0e4637e8..8a32497ae59 100644 --- a/src/html/mod.rs +++ b/src/html/mod.rs @@ -19,6 +19,9 @@ use std::rc::Rc; use stdweb::unstable::TryFrom; use stdweb::web::Node; +#[cfg(all(target_arch = "wasm32", not(cargo_web)))] +use std::future::Future; + /// This type indicates that component should be rendered again. pub type ShouldRender = bool; @@ -407,6 +410,31 @@ where closure.into() } + #[cfg(all(target_arch = "wasm32", not(cargo_web)))] + /// This method processes a Future that returns a message and sends it back to the component's + /// loop. + /// + /// # Panics + /// If the future panics, then the promise will not resolve, and will leak. + pub fn send_future(&self, future: F) + where + F: Future + 'static, + { + use wasm_bindgen::JsValue; + use wasm_bindgen_futures::future_to_promise; + + let mut scope = self.scope.clone(); + + let js_future = async { + let message: COMP::Message = future.await; + // Force movement of the cloned scope into the async block. + let scope_send = move || scope.send_message(message); + scope_send(); + Ok(JsValue::NULL) + }; + future_to_promise(js_future); + } + /// This method sends a message to this component immediately. pub fn send_self(&mut self, msg: COMP::Message) { self.scope.send_message(msg); From c7cc4c2f0e5d0f4bf6205be7dfb02447097d719a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Tusz?= Date: Sun, 10 Nov 2019 21:17:18 -0500 Subject: [PATCH 101/193] Add link to docs in README (#729) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9a448d417f2..b2fac727240 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@

+ Docs + | Examples | Changelog From 98ebc6563759ba9f0f2b4299c34ca002d406cd35 Mon Sep 17 00:00:00 2001 From: Nate Mara Date: Sun, 10 Nov 2019 18:19:16 -0800 Subject: [PATCH 102/193] Improve unimplemented message (#716) * Improve unimplemented message When calling Bridged::bridge, it is possible to get a confusing unimplemented error. This PR adds some explanation and clarity around the error. * Update link to point to latest * cargo fmt --- src/agent.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/agent.rs b/src/agent.rs index 4c3285828df..a5db4ed8496 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -205,7 +205,11 @@ where pub trait Discoverer { /// Spawns an agent and returns `Bridge` implementation. fn spawn_or_join(_callback: Option>) -> Box> { - unimplemented!(); + unimplemented!( + "The Reach type that you tried to use with this Agent does not have +Discoverer properly implemented for it yet. Please see +https://docs.rs/yew/latest/yew/agent/ for other Reach options." + ); } } From 5cc48d060d88fe415dfef889b8d8a69a4d2729d7 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Sun, 10 Nov 2019 21:48:55 -0500 Subject: [PATCH 103/193] Don't force the wasm-bindgen-cli install (#735) --- .travis.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index dda47d4541b..6ba2c412bc2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,10 +30,9 @@ install: - rustup component add rustfmt - rustup component add clippy - rustup target add wasm32-unknown-unknown - # - cargo install cargo-update || true - - cargo install -f wasm-bindgen-cli --version 0.2.54 - # - cargo install-update-config --version =0.2.54 wasm-bindgen-cli - # - cargo install-update wasm-bindgen-cli + - cargo install cargo-update || true + - cargo install-update-config --version =0.2.54 wasm-bindgen-cli + - cargo install-update wasm-bindgen-cli - LATEST_CHROMEDRIVER_VERSION=`curl -s "https://chromedriver.storage.googleapis.com/LATEST_RELEASE"` - curl --retry 5 -LO "https://chromedriver.storage.googleapis.com/${LATEST_CHROMEDRIVER_VERSION}/chromedriver_linux64.zip" - unzip chromedriver_linux64.zip From 1c54ab1784cc61ca7db08de38e3eb6520ca756fd Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Sun, 10 Nov 2019 21:52:56 -0500 Subject: [PATCH 104/193] Add a default-enabled feature gate for agents and services (#684) * Add a default-enabled feature gate for agents and services * cargo fmt * change feature name for agent support from agents to agent --- Cargo.toml | 4 +++- src/lib.rs | 9 +++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b3cf4bc69a5..e88296de3eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,10 +53,12 @@ trybuild = "1.0" rustversion = "0.1" [features] -default = [] +default = ["services", "agent"] doc_test = [] web_test = [] wasm_test = [] +services = [] +agent = [] yaml = ["serde_yaml"] msgpack = ["rmp-serde"] cbor = ["serde_cbor"] diff --git a/src/lib.rs b/src/lib.rs index 36075223621..de60904602d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,17 +75,20 @@ pub mod macros { pub use yew_macro::Properties; } -pub mod agent; pub mod app; pub mod callback; pub mod components; pub mod format; pub mod html; pub mod scheduler; -pub mod services; pub mod utils; pub mod virtual_dom; +#[cfg(feature = "agent")] +pub mod agent; +#[cfg(feature = "services")] +pub mod services; + /// The module that contains all events available in the framework. pub mod events { pub use crate::html::{ChangeData, InputData}; @@ -142,6 +145,7 @@ where /// use yew::prelude::*; /// ``` pub mod prelude { + #[cfg(feature = "agent")] pub use crate::agent::{Bridge, Bridged, Threaded}; pub use crate::app::App; pub use crate::callback::Callback; @@ -154,6 +158,7 @@ pub mod prelude { pub use crate::virtual_dom::Classes; /// Prelude module for creating worker. + #[cfg(feature = "agent")] pub mod worker { pub use crate::agent::{ Agent, AgentLink, Bridge, Bridged, Context, Global, HandlerId, Job, Private, Public, From 0b8d48ec800579c768c5bd4a5e8c83b44660aed6 Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Sun, 10 Nov 2019 22:12:25 -0500 Subject: [PATCH 105/193] Clarify doc comments in ComponentLink about when messages are processed (#728) * Clarify doc comments in ComponentLink about when messages are sent and processed * change doc comment --- src/html/mod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/html/mod.rs b/src/html/mod.rs index 8a32497ae59..ce582acf6cf 100644 --- a/src/html/mod.rs +++ b/src/html/mod.rs @@ -384,7 +384,8 @@ where } } - /// This method sends batch of messages back to the component's loop + /// This method sends batch of messages back to the component's loop when the + /// returned callback is called. pub fn send_back_batch(&mut self, function: F) -> Callback where F: Fn(IN) -> Vec + 'static, @@ -397,7 +398,7 @@ where closure.into() } - /// This method sends messages back to the component's loop. + /// This method sends messages back to the component's loop when the returned callback is called. pub fn send_back(&mut self, function: F) -> Callback where F: Fn(IN) -> COMP::Message + 'static, @@ -435,7 +436,8 @@ where future_to_promise(js_future); } - /// This method sends a message to this component immediately. + /// This method sends a message to this component to be processed immediately after the + /// component has been updated and/or rendered. pub fn send_self(&mut self, msg: COMP::Message) { self.scope.send_message(msg); } From 6bc6a301de30a24c8e3af3666b097bae9c090c71 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Mon, 11 Nov 2019 00:38:54 -0500 Subject: [PATCH 106/193] Update CHANGELOG.md (#736) --- CHANGELOG.md | 145 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 144 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cdf4c31d299..8c40a7fddcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,152 @@ # Changelog -## ✨ **0.10** *(TBD)* +## ✨ **0.10** *(2019-11-11)* - #### ⚡️ Features + - `Future` support :tada: A `Component` can update following the completion of a `Future`. Check out [this example](https://github.com/yewstack/yew/tree/master/examples/futures) to see how it works. This approach was borrowed from a fork of Yew called [`plaster`](https://github.com/carlosdp/plaster) created by [@carlosdp]. [[@hgzimmerman], [#717](https://github.com/yewstack/yew/pull/717)] + - Added the `agent` and `services` features so that this functionality can be disabled (useful if you are switching to using `Future`s). [[@hgzimmerman], [#684](https://github.com/yewstack/yew/pull/684)] + - Add `ref` keyword for allowing a `Component` to have a direct reference to its rendered elements. For example, you can now easily focus an `` element after mounting. [[@jstarry], [#715](https://github.com/yewstack/yew/pull/715)] + + ```rust + use stdweb::web::html_element::InputElement; + use stdweb::web::IHtmlElement; + use yew::*; + + pub struct Input { + node_ref: NodeRef, + } + + impl Component for Input { + type Message = (); + type Properties = (); + + fn create(_: Self::Properties, _: ComponentLink) -> Self { + Input { + node_ref: NodeRef::default(), + } + } + + fn mounted(&mut self) -> ShouldRender { + if let Some(input) = self.node_ref.try_into::() { + input.focus(); + } + false + } + + fn update(&mut self, _: Self::Message) -> ShouldRender { + false + } + + fn view(&self) -> Html { + html! { + + } + } + } + ``` + + - Make `Agent` related types `public` to allow other crates to create custom agents. [[@dunnock], [#721](https://github.com/yewstack/yew/pull/721)] + - `Component::change` will now return `false` for components that have `Component::Properties == ()`. [[@kellytk], [#690](https://github.com/yewstack/yew/pull/690)]] + - Updated `wasm-bindgen` dependency to `0.2.54`. Please update your `wasm-bindgen-cli` tool by running `cargo install --force --version 0.2.54 -- wasm-bindgen-cli`. [[@jstarry], [#730](https://github.com/yewstack/yew/pull/730)], [[@ctaggart], [#681](https://github.com/yewstack/yew/pull/681)] + - #### 🛠 Fixes + - Fixed the mount order of components. The root component will be mounted after all descendants have been mounted. [[@jstarry], [#725](https://github.com/yewstack/yew/pull/725)] + - All public items now implement `Debug`. [[@hgzimmerman], [#673](https://github.com/yewstack/yew/pull/673)] + - #### 🚨 Breaking changes + - Minimum rustc version has been bumped to `1.39.0` for `Future` support. [[@jstarry], [#730](https://github.com/yewstack/yew/pull/730)] + - `Component` now has a required `view` method and automatically implements the `Renderable` trait. The `view` method in the `Renderable` trait has been renamed to `render`. [[@jstarry], [#563](https://github.com/yewstack/yew/pull/563)] + + Before: + ```rust + impl Component for Model { + type Message = Msg; + type Properties = (); + + fn create(_: Self::Properties, _: ComponentLink) -> Self { + Model {} + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + true + } + } + + impl Renderable for Model { + fn view(&self) -> Html { + html! { "hello" } + } + } + ``` + + After: + ```rust + impl Component for Model { + type Message = Msg; + type Properties = (); + + fn create(_: Self::Properties, _: ComponentLink) -> Self { + Model {} + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + true + } + + fn view(&self) -> Html { + html! { "hello" } + } + } + ``` + + - Removed the `Transferable` trait since it did no more than extend the serde `Serialize` and `Deserialize` traits. [[@hgzimmerman], [#319](https://github.com/yewstack/yew/pull/319)] + + Before: + ```rust + impl Transferable for Input {} + #[derive(Serialize, Deserialize)] + pub enum Input { + Connect, + } + ``` + + After: + ```rust + #[derive(Serialize, Deserialize)] + pub enum Input { + Connect, + } + ``` + - `WebSocketService::connect` will now return a `Result` in order to stop panicking on malformed urls. [[@lizhaoxian], [#727](https://github.com/yewstack/yew/pull/727)] + - `VTag` now is boxed within `VNode` to shrink the size of its enum representation. [[@hgzimmerman], [#675](https://github.com/yewstack/yew/pull/675)] + +## ✨ **0.9.2** *(2019-10-12)* + +- #### 🛠 Fixes + + - Fix `yew-macro` dependency version + +## ✨ **0.9.1** *(2019-10-12)* + +Happy Canadian Thanksgiving! 🦃 + +- #### ⚡️ Features + + - Implemented `Default` trait for `VNode` so that `unwrap_or_default` can be called on `Option>`. [[@hgzimmerman], [#672](https://github.com/yewstack/yew/pull/672)] + - Implemented `PartialEq` trait for `Classes` so that is more ergonomic to use `Classes` type in component props. [[@hgzimmerman], [#680](https://github.com/yewstack/yew/pull/680)] + - Updated `wasm-bindgen` dependency to `0.2.50`. Please update your `wasm-bindgen-cli` tool by running `cargo install --force --version 0.2.50 -- wasm-bindgen-cli`. [[@jstarry], [#695](https://github.com/yewstack/yew/pull/695)] + +- #### 🛠 Fixes + + - Fixed issue where text nodes were sometimes rendered out of order. [[@jstarry], [#697](https://github.com/yewstack/yew/pull/697)] + - Fixed regression introduced in 0.9.0 that prevented tag attributes from updating properly. [[@jstarry], [#698](https://github.com/yewstack/yew/pull/698)] + - Fixed emscripten builds by pinning the version for the `ryu` downstream dependency. [[@jstarry], [#703](https://github.com/yewstack/yew/pull/703)] + - Updated `stdweb` to `0.4.20` which fixed emscripten builds and unblocked updating `wasm-bindgen` to `0.2.50`. [[@ctaggart], [@jstarry], [#683](https://github.com/yewstack/yew/pull/683), [#694](https://github.com/yewstack/yew/pull/694)] + - Cleaned up build warnings for missing `dyn` keywords. [[@benreyn], [#687](https://github.com/yewstack/yew/pull/687)] + ## ✨ **0.9** *(2019-09-27)* - #### ⚡️ Features @@ -224,13 +363,17 @@ This release introduces the concept of an `Agent`. Agents are separate activitie [Web Workers API]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API [@astraw]: https://github.com/astraw [@boydjohnson]: https://github.com/boydjohnson +[@carlosdp]: https://github.com/carlosdp [@charvp]: https://github.com/charvp +[@ctaggart]: https://github.com/ctaggart [@davidkna]: https://github.com/davidkna [@DenisKolodin]: https://github.com/DenisKolodin [@dermetfan]: https://github.com/dermetfan +[@dunnock]: https://github.com/dunnock [@hgzimmerman]: https://github.com/hgzimmerman [@jstarry]: https://github.com/jstarry [@kellytk]: https://github.com/kellytk +[@lizhaoxian]: https://github.com/lizhaoxian [@serzhiio]: https://github.com/serzhiio [@stkevintan]: https://github.com/stkevintan [@tiziano88]: https://github.com/tiziano88 From 7c959a53968949c30fe55fe099d2d3d73d63a936 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Mon, 11 Nov 2019 00:58:44 -0500 Subject: [PATCH 107/193] Bump yew from 0.10 to 0.10.1 (#737) --- CHANGELOG.md | 2 ++ Cargo.toml | 4 ++-- crates/macro/Cargo.toml | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c40a7fddcf..bd696e88835 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## ✨ **0.10.1** *(TBD)* + ## ✨ **0.10** *(2019-11-11)* - #### ⚡️ Features diff --git a/Cargo.toml b/Cargo.toml index e88296de3eb..2f4875b836e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yew" -version = "0.10.0" +version = "0.10.1" edition = "2018" authors = [ "Denis Kolodin ", @@ -35,7 +35,7 @@ serde_yaml = { version = "0.8.3", optional = true } slab = "0.4" stdweb = "0.4.20" toml = { version = "0.4", optional = true } -yew-macro = { version = "0.10.0", path = "crates/macro" } +yew-macro = { version = "0.10.1", path = "crates/macro" } [target.'cfg(all(target_arch = "wasm32", not(cargo_web)))'.dependencies] wasm-bindgen = "0.2.54" diff --git a/crates/macro/Cargo.toml b/crates/macro/Cargo.toml index aae6ef7a16c..4f3def4207f 100644 --- a/crates/macro/Cargo.toml +++ b/crates/macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yew-macro" -version = "0.10.0" +version = "0.10.1" edition = "2018" authors = ["Justin Starry "] repository = "https://github.com/yewstack/yew" From 7019dfb6b14bbc901a1329ed72eeccea55caa0b4 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Wed, 13 Nov 2019 22:50:13 -0500 Subject: [PATCH 108/193] Implement Clone for ComponentLink (#741) --- src/html/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/html/mod.rs b/src/html/mod.rs index ce582acf6cf..ded20233086 100644 --- a/src/html/mod.rs +++ b/src/html/mod.rs @@ -369,6 +369,7 @@ impl EmptyBuilder { } /// Link to component's scope for creating callbacks. +#[derive(Clone)] pub struct ComponentLink { scope: Scope, } From f6af516e95fa3b07569472be642a0fe0fe3ac0d6 Mon Sep 17 00:00:00 2001 From: Maxim Vorobjov Date: Mon, 18 Nov 2019 20:32:13 +0200 Subject: [PATCH 109/193] Allow to compile to wasi target without wasm_bindgen (#746) * allow to compile to wasi without wasm-bindgen * allow to compile to wasm32-wasi without wasm_bindgen --- Cargo.toml | 4 ++-- src/html/mod.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2f4875b836e..73d9d40dd78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,11 +37,11 @@ stdweb = "0.4.20" toml = { version = "0.4", optional = true } yew-macro = { version = "0.10.1", path = "crates/macro" } -[target.'cfg(all(target_arch = "wasm32", not(cargo_web)))'.dependencies] +[target.'cfg(all(target_arch = "wasm32", not(target_os="wasi"), not(cargo_web)))'.dependencies] wasm-bindgen = "0.2.54" wasm-bindgen-futures = "0.4.4" -[target.'cfg(all(target_arch = "wasm32", not(cargo_web)))'.dev-dependencies] +[target.'cfg(all(target_arch = "wasm32", not(target_os="wasi"), not(cargo_web)))'.dev-dependencies] wasm-bindgen-test = "0.3.4" [target.'cfg(target_os = "emscripten")'.dependencies] diff --git a/src/html/mod.rs b/src/html/mod.rs index ded20233086..065cb55241d 100644 --- a/src/html/mod.rs +++ b/src/html/mod.rs @@ -412,7 +412,7 @@ where closure.into() } - #[cfg(all(target_arch = "wasm32", not(cargo_web)))] + #[cfg(all(target_arch = "wasm32", not(target_os="wasi"), not(cargo_web)))] /// This method processes a Future that returns a message and sends it back to the component's /// loop. /// From 0f890fd41b061da0fcc827de2ae7c3628dd67daf Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Mon, 18 Nov 2019 21:26:05 -0500 Subject: [PATCH 110/193] Revert "Implement Clone for ComponentLink (#741)" (#747) This reverts commit 7019dfb6b14bbc901a1329ed72eeccea55caa0b4. --- src/html/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/html/mod.rs b/src/html/mod.rs index 065cb55241d..b18cf6b4d7f 100644 --- a/src/html/mod.rs +++ b/src/html/mod.rs @@ -369,7 +369,6 @@ impl EmptyBuilder { } /// Link to component's scope for creating callbacks. -#[derive(Clone)] pub struct ComponentLink { scope: Scope, } From 91d8a893d54c1ddb3ae6761b8979118fc43fe850 Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Mon, 18 Nov 2019 22:30:02 -0500 Subject: [PATCH 111/193] Add send_self_batch to ComponentLink (#748) --- src/html/mod.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/html/mod.rs b/src/html/mod.rs index b18cf6b4d7f..26772ec3a68 100644 --- a/src/html/mod.rs +++ b/src/html/mod.rs @@ -411,7 +411,7 @@ where closure.into() } - #[cfg(all(target_arch = "wasm32", not(target_os="wasi"), not(cargo_web)))] + #[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), not(cargo_web)))] /// This method processes a Future that returns a message and sends it back to the component's /// loop. /// @@ -441,6 +441,15 @@ where pub fn send_self(&mut self, msg: COMP::Message) { self.scope.send_message(msg); } + + /// Sends a batch of messages to the component to be processed immediately after + /// the component has been updated and/or rendered.. + /// + /// All messages will first be processed by `update`, and if _any_ of them return `true`, + /// then re-render will occur. + pub fn send_self_batch(&mut self, msgs: Vec) { + self.scope.send_message_batch(msgs) + } } impl fmt::Debug for ComponentLink { From b4c141560a06c2767867b71aa3d80107aef6a516 Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Mon, 18 Nov 2019 22:30:23 -0500 Subject: [PATCH 112/193] Manually implement Clone for ComponentLink (#749) --- src/html/mod.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/html/mod.rs b/src/html/mod.rs index 26772ec3a68..bf7efc95625 100644 --- a/src/html/mod.rs +++ b/src/html/mod.rs @@ -458,6 +458,14 @@ impl fmt::Debug for ComponentLink { } } +impl Clone for ComponentLink { + fn clone(&self) -> Self { + ComponentLink { + scope: self.scope.clone(), + } + } +} + /// A bridging type for checking `href` attribute value. #[derive(Debug)] pub struct Href { From c439a3d47794bac2934a569bb020f32e20aaafb0 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Sat, 23 Nov 2019 16:50:20 -0500 Subject: [PATCH 113/193] Clean up comments, generics names, and variable names (#752) --- README.md | 9 +-- src/agent.rs | 4 +- src/html/mod.rs | 2 +- src/html/scope.rs | 15 ++-- src/virtual_dom/mod.rs | 4 +- src/virtual_dom/vcomp.rs | 146 +++++++++++++++++++-------------------- src/virtual_dom/vlist.rs | 15 ++-- src/virtual_dom/vnode.rs | 18 +++-- src/virtual_dom/vtag.rs | 6 +- 9 files changed, 113 insertions(+), 106 deletions(-) diff --git a/README.md b/README.md index b2fac727240..80f1d12209a 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,7 @@ impl Agent for Worker { type Input = Request; type Output = Response; - // Create an instance with a link to agent's environment. + // Create an instance with a link to the agent. fn create(link: AgentLink) -> Self { Worker { link } } @@ -271,12 +271,9 @@ html! { } ``` -### Virtual DOM, independent loops, fine updates +### Virtual DOM -Yew uses its own **virtual-dom** implementation. It updates the browser's DOM -with tiny patches when properties of elements have changed. Every component lives -in its own independent loop interacting with the environment (`Scope`) through message passing -and supports a fine control of rendering. +Yew uses its own **virtual-dom** implementation. It updates the browser's DOM with tiny patches when properties of elements have changed. Every component can be interacted with using its (`Scope`) to pass messages and trigger updates. The `ShouldRender` returns the value which informs the loop when the component should be re-rendered: diff --git a/src/agent.rs b/src/agent.rs index a5db4ed8496..92a1975a16a 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -859,8 +859,8 @@ where return; } match self.update { - AgentUpdate::Create(env) => { - this.agent = Some(AGN::create(env)); + AgentUpdate::Create(link) => { + this.agent = Some(AGN::create(link)); } AgentUpdate::Message(msg) => { this.agent diff --git a/src/html/mod.rs b/src/html/mod.rs index bf7efc95625..968957d15fe 100644 --- a/src/html/mod.rs +++ b/src/html/mod.rs @@ -330,7 +330,7 @@ impl NodeRef { } } -/// Should be rendered relative to context and component environment. +/// Trait for rendering virtual DOM elements pub trait Renderable { /// Called by rendering loop. fn render(&self) -> Html; diff --git a/src/html/scope.rs b/src/html/scope.rs index 4ed6996cf7b..75a6d12d5b5 100644 --- a/src/html/scope.rs +++ b/src/html/scope.rs @@ -6,7 +6,7 @@ use std::fmt; use std::rc::Rc; use stdweb::web::Element; -/// Updates for a `Components` instance. Used by scope sender. +/// Updates for a `Component` instance. Used by scope sender. pub(crate) enum ComponentUpdate { /// Wraps messages for a component. Message(COMP::Message), @@ -16,8 +16,7 @@ pub(crate) enum ComponentUpdate { Properties(COMP::Properties), } -/// A context which contains a bridge to send a messages to a loop. -/// Mostly services uses it. +/// A context which allows sending messages to a component. pub struct Scope { shared_state: Shared>, } @@ -60,7 +59,7 @@ impl Scope { let mut scope = self.clone(); let link = ComponentLink::connect(&scope); let ready_state = ReadyState { - env: self.clone(), + scope: self.clone(), element, node_ref, link, @@ -136,7 +135,7 @@ impl fmt::Display for ComponentState { } struct ReadyState { - env: Scope, + scope: Scope, element: Element, node_ref: NodeRef, props: COMP::Properties, @@ -148,7 +147,7 @@ impl ReadyState { fn create(self) -> CreatedState { CreatedState { component: COMP::create(self.props, self.link), - env: self.env, + scope: self.scope, element: self.element, last_frame: self.ancestor, node_ref: self.node_ref, @@ -157,7 +156,7 @@ impl ReadyState { } struct CreatedState { - env: Scope, + scope: Scope, element: Element, component: COMP, last_frame: Option>, @@ -176,7 +175,7 @@ impl CreatedState { fn update(mut self) -> Self { let mut next_frame = self.component.render(); - let node = next_frame.apply(&self.element, None, self.last_frame, &self.env); + let node = next_frame.apply(&self.element, None, self.last_frame, &self.scope); self.node_ref.set(node); self.last_frame = Some(next_frame); self diff --git a/src/virtual_dom/mod.rs b/src/virtual_dom/mod.rs index 6f11ecb2da5..5d558213455 100644 --- a/src/virtual_dom/mod.rs +++ b/src/virtual_dom/mod.rs @@ -162,7 +162,7 @@ pub trait VDiff { /// find where to put the node. /// - `ancestor`: the node that this node will be replacing in the DOM. /// This method will _always_ remove the `ancestor` from the `parent`. - /// - `env`: the `Env`. + /// - `parent_scope`: the parent `Scope` used for passing messages to the parent `Component`. /// /// ### Internal Behavior Notice: /// @@ -177,6 +177,6 @@ pub trait VDiff { parent: &Element, previous_sibling: Option<&Node>, ancestor: Option>, - scope: &Scope, + parent_scope: &Scope, ) -> Option; } diff --git a/src/virtual_dom/vcomp.rs b/src/virtual_dom/vcomp.rs index ed1a6276b7d..754eb0de69e 100644 --- a/src/virtual_dom/vcomp.rs +++ b/src/virtual_dom/vcomp.rs @@ -14,8 +14,8 @@ struct Hidden; type HiddenScope = *mut Hidden; -/// The method generates an instance of a (child) component. -type Generator = dyn FnOnce(GeneratorType, Scope) -> Mounted; +/// The method generates an instance of a component. +type Generator = dyn FnOnce(GeneratorType, Scope) -> Mounted; /// Components can be generated by mounting or by overwriting an old component. enum GeneratorType { @@ -23,19 +23,13 @@ enum GeneratorType { Overwrite(TypeId, HiddenScope), } -/// A reference to unknown scope which will be attached later with a generator function. -pub type ScopeHolder = Rc>>>; +/// A reference to the parent's scope which will be used later to send messages. +pub type ScopeHolder = Rc>>>; /// A virtual component. -pub struct VComp { +pub struct VComp { type_id: TypeId, - state: Rc>>, -} - -impl fmt::Debug for VComp { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("VComp<_>") - } + state: Rc>>, } /// A virtual child component. @@ -48,12 +42,6 @@ pub struct VChild { node_ref: NodeRef, } -impl fmt::Debug for VChild { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("VChild<_,_>") - } -} - impl VChild where SELF: Component, @@ -69,26 +57,26 @@ where } } -impl From> for VComp +impl From> for VComp where - COMP: Component, - CHILD: Component, + SELF: Component, + PARENT: Component, { - fn from(vchild: VChild) -> Self { - VComp::new::(vchild.props, vchild.scope, vchild.node_ref) + fn from(vchild: VChild) -> Self { + VComp::new::(vchild.props, vchild.scope, vchild.node_ref) } } -enum MountState { - Unmounted(Unmounted), +enum MountState { + Unmounted(Unmounted), Mounted(Mounted), Mounting, Detached, Overwritten, } -struct Unmounted { - generator: Box>, +struct Unmounted { + generator: Box>, } struct Mounted { @@ -97,21 +85,21 @@ struct Mounted { destroyer: Box, } -impl VComp { +impl VComp { /// This method prepares a generator to make a new instance of the `Component`. - pub fn new( - props: CHILD::Properties, - scope_holder: ScopeHolder, + pub fn new( + props: SELF::Properties, + scope_holder: ScopeHolder, node_ref: NodeRef, ) -> Self where - CHILD: Component, + SELF: Component, { - let generator = move |generator_type: GeneratorType, parent: Scope| -> Mounted { + let generator = move |generator_type: GeneratorType, parent: Scope| -> Mounted { *scope_holder.borrow_mut() = Some(parent); match generator_type { GeneratorType::Mount(element, ancestor) => { - let scope: Scope = Scope::new(); + let scope: Scope = Scope::new(); // TODO Consider to send ComponentUpdate::Create after `mount_in_place` call let mut scope = scope.mount_in_place( @@ -128,12 +116,12 @@ impl VComp { } } GeneratorType::Overwrite(type_id, scope) => { - if type_id != TypeId::of::() { + if type_id != TypeId::of::() { panic!("tried to overwrite a different type of component"); } let mut scope = unsafe { - let raw: *mut Scope = scope as *mut Scope; + let raw: *mut Scope = scope as *mut Scope; *Box::from_raw(raw) }; @@ -149,7 +137,7 @@ impl VComp { }; VComp { - type_id: TypeId::of::(), + type_id: TypeId::of::(), state: Rc::new(RefCell::new(MountState::Unmounted(Unmounted { generator: Box::new(generator), }))), @@ -157,83 +145,83 @@ impl VComp { } } -/// Converts property and attach empty scope holder which will be activated later. -pub trait Transformer { +/// Transforms properties and attaches a parent scope holder to callbacks for sending messages. +pub trait Transformer { /// Transforms one type to another. - fn transform(scope_holder: ScopeHolder, from: FROM) -> TO; + fn transform(scope_holder: ScopeHolder, from: FROM) -> TO; } -impl Transformer for VComp +impl Transformer for VComp where - COMP: Component, + PARENT: Component, { - fn transform(_: ScopeHolder, from: T) -> T { + fn transform(_: ScopeHolder, from: T) -> T { from } } -impl<'a, COMP, T> Transformer for VComp +impl<'a, PARENT, T> Transformer for VComp where - COMP: Component, + PARENT: Component, T: Clone, { - fn transform(_: ScopeHolder, from: &'a T) -> T { + fn transform(_: ScopeHolder, from: &'a T) -> T { from.clone() } } -impl<'a, COMP> Transformer for VComp +impl<'a, PARENT> Transformer for VComp where - COMP: Component, + PARENT: Component, { - fn transform(_: ScopeHolder, from: &'a str) -> String { + fn transform(_: ScopeHolder, from: &'a str) -> String { from.to_owned() } } -impl<'a, COMP, F, IN> Transformer> for VComp +impl<'a, PARENT, F, IN> Transformer> for VComp where - COMP: Component, - F: Fn(IN) -> COMP::Message + 'static, + PARENT: Component, + F: Fn(IN) -> PARENT::Message + 'static, { - fn transform(scope: ScopeHolder, from: F) -> Callback { + fn transform(scope: ScopeHolder, from: F) -> Callback { let callback = move |arg| { let msg = from(arg); if let Some(ref mut sender) = *scope.borrow_mut() { sender.send_message(msg); } else { - panic!("unactivated callback, parent component have to activate it"); + panic!("Parent component hasn't activated this callback yet"); } }; callback.into() } } -impl<'a, COMP, F, IN> Transformer>> for VComp +impl<'a, PARENT, F, IN> Transformer>> for VComp where - COMP: Component, - F: Fn(IN) -> COMP::Message + 'static, + PARENT: Component, + F: Fn(IN) -> PARENT::Message + 'static, { - fn transform(scope: ScopeHolder, from: F) -> Option> { + fn transform(scope: ScopeHolder, from: F) -> Option> { let callback = move |arg| { let msg = from(arg); if let Some(ref mut sender) = *scope.borrow_mut() { sender.send_message(msg); } else { - panic!("unactivated callback, parent component have to activate it"); + panic!("Parent component hasn't activated this callback yet"); } }; Some(callback.into()) } } -impl Unmounted { - /// mount a virtual component with a generator. +impl Unmounted { + /// Mount a virtual component using a generator. fn mount( self, parent: &T, ancestor: Node, // Any dummy expected - env: Scope, + parent_scope: Scope, ) -> Mounted { let element: Element = parent .as_node() @@ -241,12 +229,12 @@ impl Unmounted { .to_owned() .try_into() .expect("element expected to mount VComp"); - (self.generator)(GeneratorType::Mount(element, ancestor), env) + (self.generator)(GeneratorType::Mount(element, ancestor), parent_scope) } - /// Overwrite an existing virtual component with a generator. - fn replace(self, type_id: TypeId, old: Mounted, env: Scope) -> Mounted { - (self.generator)(GeneratorType::Overwrite(type_id, old.scope), env) + /// Overwrite an existing virtual component using a generator. + fn replace(self, type_id: TypeId, old: Mounted, parent_scope: Scope) -> Mounted { + (self.generator)(GeneratorType::Overwrite(type_id, old.scope), parent_scope) } } @@ -285,7 +273,7 @@ where parent: &Element, previous_sibling: Option<&Node>, ancestor: Option>, - env: &Scope, + parent_scope: &Scope, ) -> Option { match self.state.replace(MountState::Mounting) { MountState::Unmounted(this) => { @@ -312,10 +300,8 @@ where let mounted = match reform { Reform::Keep(type_id, mounted) => { - // Send properties update when component still be rendered. - // But for the first initialization mount gets initial - // properties directly without this channel. - this.replace(type_id, mounted, env.clone()) + // Send properties update when the component is already rendered. + this.replace(type_id, mounted, parent_scope.clone()) } Reform::Before(before) => { // This is a workaround, because component should be mounted @@ -338,7 +324,7 @@ where } } let node = element.as_node().to_owned(); - this.mount(parent, node, env.clone()) + this.mount(parent, node, parent_scope.clone()) } }; @@ -354,8 +340,20 @@ where } } -impl PartialEq for VComp { - fn eq(&self, other: &VComp) -> bool { +impl PartialEq for VComp { + fn eq(&self, other: &VComp) -> bool { self.type_id == other.type_id } } + +impl fmt::Debug for VComp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("VComp<_>") + } +} + +impl fmt::Debug for VChild { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("VChild<_,_>") + } +} diff --git a/src/virtual_dom/vlist.rs b/src/virtual_dom/vlist.rs index 66c7aad39b1..18fd3b16f42 100644 --- a/src/virtual_dom/vlist.rs +++ b/src/virtual_dom/vlist.rs @@ -4,7 +4,7 @@ use crate::html::{Component, Scope}; use stdweb::web::{Element, Node}; /// This struct represents a fragment of the Virtual DOM tree. -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub struct VList { /// The list of children nodes. Which also could have their own children. pub children: Vec>, @@ -46,7 +46,7 @@ impl VDiff for VList { parent: &Element, previous_sibling: Option<&Node>, ancestor: Option>, - env: &Scope, + parent_scope: &Scope, ) -> Option { // Reuse previous_sibling, because fragment reuse parent let mut previous_sibling = previous_sibling.cloned(); @@ -81,11 +81,16 @@ impl VDiff for VList { loop { match (lefts.next(), rights.next()) { (Some(left), Some(right)) => { - previous_sibling = - left.apply(parent, previous_sibling.as_ref(), Some(right), &env); + previous_sibling = left.apply( + parent, + previous_sibling.as_ref(), + Some(right), + &parent_scope, + ); } (Some(left), None) => { - previous_sibling = left.apply(parent, previous_sibling.as_ref(), None, &env); + previous_sibling = + left.apply(parent, previous_sibling.as_ref(), None, &parent_scope); } (None, Some(ref mut right)) => { right.detach(parent); diff --git a/src/virtual_dom/vnode.rs b/src/virtual_dom/vnode.rs index 701763ecd08..cc6ac491c16 100644 --- a/src/virtual_dom/vnode.rs +++ b/src/virtual_dom/vnode.rs @@ -46,13 +46,21 @@ impl VDiff for VNode { parent: &Element, previous_sibling: Option<&Node>, ancestor: Option>, - env: &Scope, + parent_scope: &Scope, ) -> Option { match *self { - VNode::VTag(ref mut vtag) => vtag.apply(parent, previous_sibling, ancestor, env), - VNode::VText(ref mut vtext) => vtext.apply(parent, previous_sibling, ancestor, env), - VNode::VComp(ref mut vcomp) => vcomp.apply(parent, previous_sibling, ancestor, env), - VNode::VList(ref mut vlist) => vlist.apply(parent, previous_sibling, ancestor, env), + VNode::VTag(ref mut vtag) => { + vtag.apply(parent, previous_sibling, ancestor, parent_scope) + } + VNode::VText(ref mut vtext) => { + vtext.apply(parent, previous_sibling, ancestor, parent_scope) + } + VNode::VComp(ref mut vcomp) => { + vcomp.apply(parent, previous_sibling, ancestor, parent_scope) + } + VNode::VList(ref mut vlist) => { + vlist.apply(parent, previous_sibling, ancestor, parent_scope) + } VNode::VRef(ref mut node) => { let sibling = match ancestor { Some(mut n) => n.detach(parent), diff --git a/src/virtual_dom/vtag.rs b/src/virtual_dom/vtag.rs index ee2b146b20b..cc9e4243481 100644 --- a/src/virtual_dom/vtag.rs +++ b/src/virtual_dom/vtag.rs @@ -373,7 +373,7 @@ impl VDiff for VTag { parent: &Element, previous_sibling: Option<&Node>, ancestor: Option>, - env: &Scope, + parent_scope: &Scope, ) -> Option { assert!( self.reference.is_none(), @@ -456,7 +456,7 @@ impl VDiff for VTag { } for mut listener in self.listeners.drain(..) { - let handle = listener.attach(&element, env.clone()); + let handle = listener.attach(&element, parent_scope.clone()); self.captured.push(handle); } @@ -469,7 +469,7 @@ impl VDiff for VTag { match (self_children.next(), ancestor_children.next()) { (Some(left), right) => { previous_sibling = - left.apply(&element, previous_sibling.as_ref(), right, &env); + left.apply(&element, previous_sibling.as_ref(), right, &parent_scope); } (None, Some(ref mut right)) => { right.detach(&element); From 1218c1de335f31a534e944616bb89ded90bb5d0c Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Sat, 23 Nov 2019 16:51:16 -0500 Subject: [PATCH 114/193] Update wasm-bindgen (#753) --- .travis.yml | 2 +- Cargo.toml | 2 +- examples/js_callback/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6ba2c412bc2..e3f98d83399 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,7 +31,7 @@ install: - rustup component add clippy - rustup target add wasm32-unknown-unknown - cargo install cargo-update || true - - cargo install-update-config --version =0.2.54 wasm-bindgen-cli + - cargo install-update-config --version =0.2.55 wasm-bindgen-cli - cargo install-update wasm-bindgen-cli - LATEST_CHROMEDRIVER_VERSION=`curl -s "https://chromedriver.storage.googleapis.com/LATEST_RELEASE"` - curl --retry 5 -LO "https://chromedriver.storage.googleapis.com/${LATEST_CHROMEDRIVER_VERSION}/chromedriver_linux64.zip" diff --git a/Cargo.toml b/Cargo.toml index 73d9d40dd78..d526f99860a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ toml = { version = "0.4", optional = true } yew-macro = { version = "0.10.1", path = "crates/macro" } [target.'cfg(all(target_arch = "wasm32", not(target_os="wasi"), not(cargo_web)))'.dependencies] -wasm-bindgen = "0.2.54" +wasm-bindgen = "0.2.55" wasm-bindgen-futures = "0.4.4" [target.'cfg(all(target_arch = "wasm32", not(target_os="wasi"), not(cargo_web)))'.dev-dependencies] diff --git a/examples/js_callback/Cargo.toml b/examples/js_callback/Cargo.toml index 48dc3fc131e..a17052b755f 100644 --- a/examples/js_callback/Cargo.toml +++ b/examples/js_callback/Cargo.toml @@ -9,4 +9,4 @@ yew = { path = "../.." } stdweb = "^0.4.20" [target.'cfg(all(target_arch = "wasm32", not(cargo_web)))'.dependencies] -wasm-bindgen = "0.2.54" +wasm-bindgen = "0.2.55" From ff042058e50b261742623f07859142f434b55d3c Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Sat, 23 Nov 2019 17:13:13 -0500 Subject: [PATCH 115/193] Disable emscripten in CI for beta/nightly (#731) * Update emscripten * Don't fail on rustup check * Disable emscripten for non-stable builds * Try with cargo-web emscripten * Revert system emscripten stuff --- .travis.yml | 4 ++-- ci/check_examples.sh | 13 +++++++++++-- ci/run_tests.sh | 10 ++++++++-- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index e3f98d83399..33026b0e7d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,8 +15,8 @@ before_cache: - ./ci/clear_cache.sh rust: - - 1.39.0 # min supported - # - stable (enable when 1.40.0 is released) + # - 1.39.0 # min supported (enable when 1.40.0 is released) + - stable - beta - nightly diff --git a/ci/check_examples.sh b/ci/check_examples.sh index c2988904876..4ebfd21d01e 100755 --- a/ci/check_examples.sh +++ b/ci/check_examples.sh @@ -1,12 +1,21 @@ #!/usr/bin/env bash +echo "$(rustup default)" | grep -q "stable" +is_stable=$? set -euxo pipefail # https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/ # Showcase includes all other examples cd examples/showcase + # TODO Can't build some demos with release, need fix -cargo web build --target asmjs-unknown-emscripten -cargo web build --target wasm32-unknown-emscripten + +if [ "$is_stable" == "0" ]; then + # TODO - Emscripten builds are broken on beta/nightly + cargo web build --target asmjs-unknown-emscripten + cargo web build --target wasm32-unknown-emscripten +fi + # TODO showcase doesn't support wasm-bindgen yet cargo web build --target wasm32-unknown-unknown + # Reset cwd cd ../.. diff --git a/ci/run_tests.sh b/ci/run_tests.sh index 5221d0dd024..8d24d7d9379 100755 --- a/ci/run_tests.sh +++ b/ci/run_tests.sh @@ -1,8 +1,14 @@ #!/usr/bin/env bash +echo "$(rustup default)" | grep -q "stable" +is_stable=$? set -euxo pipefail # https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/ -cargo web test --features web_test --target asmjs-unknown-emscripten -cargo web test --features web_test --target wasm32-unknown-emscripten +if [ "$is_stable" == "0" ]; then + # TODO - Emscripten builds are broken on beta/nightly + cargo web test --features web_test --target asmjs-unknown-emscripten + cargo web test --features web_test --target wasm32-unknown-emscripten +fi + cargo test --features wasm_test --target wasm32-unknown-unknown cargo test --test macro_test cargo test --test derive_props_test From 8fd95d867a946d68ed563af7d522e0bba98d022b Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Sun, 24 Nov 2019 13:19:24 -0500 Subject: [PATCH 116/193] Use VList for VTag children (#754) * Use VList for VTag children * Fix placeholder bug * impl Deref for VList * cargo fmt --- crates/macro/src/html_tree/html_iterable.rs | 2 +- crates/macro/src/html_tree/html_list.rs | 1 + src/html/mod.rs | 1 + src/virtual_dom/vlist.rs | 32 ++++++++++++++--- src/virtual_dom/vnode.rs | 4 +-- src/virtual_dom/vtag.rs | 38 +++++++-------------- 6 files changed, 46 insertions(+), 32 deletions(-) diff --git a/crates/macro/src/html_tree/html_iterable.rs b/crates/macro/src/html_tree/html_iterable.rs index 47fac43a8ad..20b82ef40af 100644 --- a/crates/macro/src/html_tree/html_iterable.rs +++ b/crates/macro/src/html_tree/html_iterable.rs @@ -40,7 +40,7 @@ impl ToTokens for HtmlIterable { fn to_tokens(&self, tokens: &mut TokenStream) { let expr = &self.0; let new_tokens = quote_spanned! {expr.span()=> { - let mut __yew_vlist = ::yew::virtual_dom::VList::new(); + let mut __yew_vlist = ::yew::virtual_dom::VList::default(); let __yew_nodes: &mut ::std::iter::Iterator = &mut(#expr); for __yew_node in __yew_nodes.into_iter() { __yew_vlist.add_child(__yew_node.into()); diff --git a/crates/macro/src/html_tree/html_list.rs b/crates/macro/src/html_tree/html_list.rs index 907d9bbe5ab..d15e31a72cf 100644 --- a/crates/macro/src/html_tree/html_list.rs +++ b/crates/macro/src/html_tree/html_list.rs @@ -53,6 +53,7 @@ impl ToTokens for HtmlList { tokens.extend(quote! { ::yew::virtual_dom::VNode::VList( ::yew::virtual_dom::vlist::VList { + no_siblings: false, children: vec![#(#html_trees,)*], } ) diff --git a/src/html/mod.rs b/src/html/mod.rs index 968957d15fe..b2d8658913c 100644 --- a/src/html/mod.rs +++ b/src/html/mod.rs @@ -264,6 +264,7 @@ where { fn render(&self) -> Html { VList { + no_siblings: true, children: self.iter().map(|c| c.into()).collect(), } .into() diff --git a/src/virtual_dom/vlist.rs b/src/virtual_dom/vlist.rs index 18fd3b16f42..304f0e3fa7a 100644 --- a/src/virtual_dom/vlist.rs +++ b/src/virtual_dom/vlist.rs @@ -1,25 +1,49 @@ //! This module contains fragments implementation. use super::{VDiff, VNode, VText}; use crate::html::{Component, Scope}; +use std::ops::{Deref, DerefMut}; use stdweb::web::{Element, Node}; /// This struct represents a fragment of the Virtual DOM tree. -#[derive(Debug, PartialEq)] +#[derive(Debug)] pub struct VList { + /// Whether the fragment has siblings or not. + pub no_siblings: bool, /// The list of children nodes. Which also could have their own children. pub children: Vec>, } +impl Deref for VList { + type Target = Vec>; + + fn deref(&self) -> &Self::Target { + &self.children + } +} + +impl DerefMut for VList { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.children + } +} + +impl PartialEq for VList { + fn eq(&self, other: &Self) -> bool { + self.children == other.children + } +} + impl Default for VList { fn default() -> Self { - VList::new() + VList::new(false) } } impl VList { /// Creates a new empty `VList` instance. - pub fn new() -> Self { + pub fn new(no_siblings: bool) -> Self { VList { + no_siblings, children: Vec::new(), } } @@ -66,7 +90,7 @@ impl VDiff for VList { } }; - if self.children.is_empty() { + if self.children.is_empty() && !self.no_siblings { // Fixes: https://github.com/yewstack/yew/issues/294 // Without a placeholder the next element becomes first // and corrupts the order of rendering diff --git a/src/virtual_dom/vnode.rs b/src/virtual_dom/vnode.rs index cc6ac491c16..482dc66b391 100644 --- a/src/virtual_dom/vnode.rs +++ b/src/virtual_dom/vnode.rs @@ -82,7 +82,7 @@ impl VDiff for VNode { impl Default for VNode { fn default() -> Self { - VNode::VList(VList::new()) + VNode::VList(VList::default()) } } @@ -134,7 +134,7 @@ impl<'a, COMP: Component> From<&'a dyn Renderable> for VNode { impl>> FromIterator for VNode { fn from_iter>(iter: T) -> Self { - let vlist = iter.into_iter().fold(VList::new(), |mut acc, x| { + let vlist = iter.into_iter().fold(VList::default(), |mut acc, x| { acc.add_child(x.into()); acc }); diff --git a/src/virtual_dom/vtag.rs b/src/virtual_dom/vtag.rs index cc9e4243481..e1af1826026 100644 --- a/src/virtual_dom/vtag.rs +++ b/src/virtual_dom/vtag.rs @@ -1,6 +1,6 @@ //! This module contains the implementation of a virtual element node `VTag`. -use super::{Attributes, Classes, Listener, Listeners, Patch, Reform, VDiff, VNode}; +use super::{Attributes, Classes, Listener, Listeners, Patch, Reform, VDiff, VList, VNode}; use crate::html::{Component, NodeRef, Scope}; use log::warn; use std::borrow::Cow; @@ -31,8 +31,8 @@ pub struct VTag { pub listeners: Listeners, /// List of attributes. pub attributes: Attributes, - /// The list of children nodes. Which also could have own children. - pub children: Vec>, + /// List of children nodes + pub children: VList, /// List of attached classes. pub classes: Classes, /// Contains a value of an @@ -65,7 +65,7 @@ impl VTag { attributes: Attributes::new(), listeners: Vec::new(), captured: Vec::new(), - children: Vec::new(), + children: VList::new(true), node_ref: NodeRef::default(), value: None, kind: None, @@ -82,13 +82,13 @@ impl VTag { /// Add `VNode` child. pub fn add_child(&mut self, child: VNode) { - self.children.push(child); + self.children.add_child(child); } /// Add multiple `VNode` children. pub fn add_children(&mut self, children: Vec>) { for child in children { - self.children.push(child); + self.add_child(child); } } @@ -355,9 +355,7 @@ impl VDiff for VTag { .expect("tried to remove not rendered VTag from DOM"); // recursively remove its children - self.children.drain(..).for_each(|mut child| { - child.detach(&node); - }); + self.children.detach(&node); let sibling = node.next_sibling(); if parent.remove_child(&node).is_err() { @@ -461,22 +459,12 @@ impl VDiff for VTag { } // Process children - // Start with an empty previous_sibling, because it put children to itself - let mut previous_sibling = None; - let mut self_children = self.children.iter_mut(); - let mut ancestor_children = ancestor.into_iter().flat_map(|a| a.children); - loop { - match (self_children.next(), ancestor_children.next()) { - (Some(left), right) => { - previous_sibling = - left.apply(&element, previous_sibling.as_ref(), right, &parent_scope); - } - (None, Some(ref mut right)) => { - right.detach(&element); - } - (None, None) => break, - } - } + self.children.apply( + &element, + None, + ancestor.map(|a| a.children.into()), + parent_scope, + ); } let node = self.reference.as_ref().map(|e| e.as_node().to_owned()); self.node_ref.set(node.clone()); From 75f8186a057df8b4c8179b41bed37620e042e78b Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Thu, 28 Nov 2019 12:48:06 -0500 Subject: [PATCH 117/193] Update README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 80f1d12209a..ec7dcc394f4 100644 --- a/README.md +++ b/README.md @@ -49,8 +49,6 @@ and uses a local scheduler attached to a thread for concurrent tasks. This framework is designed to be compiled into modern browsers' runtimes: wasm, asm.js, emscripten. -To prepare the development environment use the installation instruction here: [wasm-and-rust](https://github.com/raphamorim/wasm-and-rust). - ### Architecture inspired by Elm and Redux Yew implements strict application state management based on message passing and updates: @@ -327,7 +325,7 @@ impl Renderable for Model { } ``` -> Some crates don't support the true wasm target (`wasm32-unknown-unknown`) yet. +> Some crates don't support the `wasm32-unknown-unknown` target yet. ### Services From e2ed0953d6a135ad370f8e76b027e1e84fc72e96 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Thu, 28 Nov 2019 14:27:04 -0500 Subject: [PATCH 118/193] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ec7dcc394f4..26f7d2a1686 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@

- Rust / Wasm UI framework + Rust / Wasm client web app framework

@@ -18,7 +18,9 @@

- Docs + Website + | + API Docs | Examples | From 128567016eb56271feff3ee2efd7663001b40e47 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Fri, 29 Nov 2019 00:11:29 -0500 Subject: [PATCH 119/193] VTag cleanup (#760) --- src/virtual_dom/vtag.rs | 45 +++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/src/virtual_dom/vtag.rs b/src/virtual_dom/vtag.rs index e1af1826026..93f073569da 100644 --- a/src/virtual_dom/vtag.rs +++ b/src/virtual_dom/vtag.rs @@ -276,7 +276,9 @@ impl VTag { } } - fn apply_diffs(&mut self, element: &Element, ancestor: &Option>) { + fn apply_diffs(&mut self, ancestor: &Option>) { + let element = self.reference.as_ref().expect("element expected"); + // Update parameters let changes = self.diff_classes(ancestor); for change in changes { @@ -440,32 +442,31 @@ impl VDiff for VTag { } } - let element = self.reference.clone().expect("element expected"); - - { - self.apply_diffs(&element, &ancestor); + self.apply_diffs(&ancestor); - // Every render it removes all listeners and attach it back later - // TODO Compare references of handler to do listeners update better - if let Some(ancestor) = ancestor.as_mut() { - for handle in ancestor.captured.drain(..) { - handle.remove(); - } + // Every render it removes all listeners and attach it back later + // TODO Compare references of handler to do listeners update better + if let Some(ancestor) = ancestor.as_mut() { + for handle in ancestor.captured.drain(..) { + handle.remove(); } + } - for mut listener in self.listeners.drain(..) { - let handle = listener.attach(&element, parent_scope.clone()); - self.captured.push(handle); - } + let element = self.reference.clone().expect("element expected"); - // Process children - self.children.apply( - &element, - None, - ancestor.map(|a| a.children.into()), - parent_scope, - ); + for mut listener in self.listeners.drain(..) { + let handle = listener.attach(&element, parent_scope.clone()); + self.captured.push(handle); } + + // Process children + self.children.apply( + &element, + None, + ancestor.map(|a| a.children.into()), + parent_scope, + ); + let node = self.reference.as_ref().map(|e| e.as_node().to_owned()); self.node_ref.set(node.clone()); node From 231bda8cbe3e609dd1b43a53aef8c8cee4f2e71c Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Fri, 29 Nov 2019 11:31:19 -0500 Subject: [PATCH 120/193] VComp cleanup (#761) --- src/virtual_dom/vcomp.rs | 49 +++++++++++++--------------------------- 1 file changed, 16 insertions(+), 33 deletions(-) diff --git a/src/virtual_dom/vcomp.rs b/src/virtual_dom/vcomp.rs index 754eb0de69e..da12d865790 100644 --- a/src/virtual_dom/vcomp.rs +++ b/src/virtual_dom/vcomp.rs @@ -7,8 +7,7 @@ use std::any::TypeId; use std::cell::RefCell; use std::fmt; use std::rc::Rc; -use stdweb::unstable::TryInto; -use stdweb::web::{document, Element, INode, Node}; +use stdweb::web::{document, Element, INode, Node, TextNode}; struct Hidden; @@ -19,7 +18,7 @@ type Generator = dyn FnOnce(GeneratorType, Scope) -> Mounted; /// Components can be generated by mounting or by overwriting an old component. enum GeneratorType { - Mount(Element, Node), + Mount(Element, TextNode), Overwrite(TypeId, HiddenScope), } @@ -98,13 +97,12 @@ impl VComp { let generator = move |generator_type: GeneratorType, parent: Scope| -> Mounted { *scope_holder.borrow_mut() = Some(parent); match generator_type { - GeneratorType::Mount(element, ancestor) => { + GeneratorType::Mount(element, dummy_node) => { let scope: Scope = Scope::new(); - // TODO Consider to send ComponentUpdate::Create after `mount_in_place` call let mut scope = scope.mount_in_place( element, - Some(VNode::VRef(ancestor)), + Some(VNode::VRef(dummy_node.into())), node_ref.clone(), props, ); @@ -217,19 +215,8 @@ where impl Unmounted { /// Mount a virtual component using a generator. - fn mount( - self, - parent: &T, - ancestor: Node, // Any dummy expected - parent_scope: Scope, - ) -> Mounted { - let element: Element = parent - .as_node() - .as_ref() - .to_owned() - .try_into() - .expect("element expected to mount VComp"); - (self.generator)(GeneratorType::Mount(element, ancestor), parent_scope) + fn mount(self, parent: Element, dummy_node: TextNode, parent_scope: Scope) -> Mounted { + (self.generator)(GeneratorType::Mount(parent, dummy_node), parent_scope) } /// Overwrite an existing virtual component using a generator. @@ -249,7 +236,6 @@ where { type Component = COMP; - /// Remove VComp from parent. fn detach(&mut self, parent: &Element) -> Option { match self.state.replace(MountState::Detached) { MountState::Mounted(this) => { @@ -266,8 +252,6 @@ where } } - /// Renders independent component over DOM `Element`. - /// It compares this with an ancestor `VComp` and overwrites it if it is the same type. fn apply( &mut self, parent: &Element, @@ -279,6 +263,8 @@ where MountState::Unmounted(this) => { let reform = match ancestor { Some(VNode::VComp(mut vcomp)) => { + // If the ancestor is a Component of the same type, don't replace, keep the + // old Component but update the properties. if self.type_id == vcomp.type_id { match vcomp.state.replace(MountState::Overwritten) { MountState::Mounted(mounted) => { @@ -304,27 +290,24 @@ where this.replace(type_id, mounted, parent_scope.clone()) } Reform::Before(before) => { - // This is a workaround, because component should be mounted - // over ancestor element if it exists. - // There is created an empty text node to be replaced with mount call. - let element = document().create_text_node(""); + // Temporary node which will be replaced by a component's root node. + let dummy_node = document().create_text_node(""); if let Some(sibling) = before { parent - .insert_before(&element, &sibling) - .expect("can't insert dummy element for a component"); + .insert_before(&dummy_node, &sibling) + .expect("can't insert dummy node for a component"); } else { let previous_sibling = previous_sibling.and_then(|before| before.next_sibling()); if let Some(previous_sibling) = previous_sibling { parent - .insert_before(&element, &previous_sibling) - .expect("can't insert dummy element before previous_sibling"); + .insert_before(&dummy_node, &previous_sibling) + .expect("can't insert dummy node before previous sibling"); } else { - parent.append_child(&element); + parent.append_child(&dummy_node); } } - let node = element.as_node().to_owned(); - this.mount(parent, node, parent_scope.clone()) + this.mount(parent.to_owned(), dummy_node, parent_scope.clone()) } }; From d03f59185f5a81e5a6aed29cdb56329c5a757228 Mon Sep 17 00:00:00 2001 From: Kelly Thomas Kline Date: Sun, 1 Dec 2019 08:43:21 -0800 Subject: [PATCH 121/193] Add roadmap link to the readme (#764) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 26f7d2a1686..b04a6edc76c 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,8 @@ | Changelog | + Roadmap + | Code of Conduct

From cb671ad4d2008362f4b2003c0fdf62ff9e4361f5 Mon Sep 17 00:00:00 2001 From: Zydnar Date: Mon, 2 Dec 2019 15:32:30 +0100 Subject: [PATCH 122/193] nested_list example fix (#765) (#767) Added missing parameter in two places --- examples/nested_list/src/list.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/nested_list/src/list.rs b/examples/nested_list/src/list.rs index b38ddd5d613..3b370ef195a 100644 --- a/examples/nested_list/src/list.rs +++ b/examples/nested_list/src/list.rs @@ -1,7 +1,7 @@ use crate::{header::Props as HeaderProps, ListHeader}; use crate::{item::Props as ItemProps, ListItem}; use std::fmt; -use yew::html::ChildrenRenderer; +use yew::html::{ChildrenRenderer, NodeRef}; use yew::prelude::*; use yew::virtual_dom::vcomp::ScopeHolder; use yew::virtual_dom::{VChild, VComp, VNode}; @@ -155,8 +155,8 @@ where impl Into> for ListVariant { fn into(self) -> VNode { match self.props { - Variants::Header(props) => VComp::new::(props, self.scope).into(), - Variants::Item(props) => VComp::new::(props, self.scope).into(), + Variants::Header(props) => VComp::new::(props, self.scope, NodeRef::default()).into(), + Variants::Item(props) => VComp::new::(props, self.scope, NodeRef::default()).into(), } } } From fd8c3ee665c7d86b71420790bf81b5d7d418f465 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Tue, 3 Dec 2019 23:06:35 -0500 Subject: [PATCH 123/193] Filter empty string classes (#770) * Filter empty string classes * cargo fmt * feedback * Add tests --- src/virtual_dom/mod.rs | 33 ++++++++++++++++++++++++++++----- tests/vtag_test.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/src/virtual_dom/mod.rs b/src/virtual_dom/mod.rs index 5d558213455..81aaa8f0ce2 100644 --- a/src/virtual_dom/mod.rs +++ b/src/virtual_dom/mod.rs @@ -58,7 +58,9 @@ impl Classes { /// /// Prevents duplication of class names. pub fn push(&mut self, class: &str) { - self.set.insert(class.into()); + if !class.is_empty() { + self.set.insert(class.into()); + } } /// Check the set contains a class. @@ -66,6 +68,11 @@ impl Classes { self.set.contains(class) } + /// Check the set is empty. + pub fn is_empty(&self) -> bool { + self.set.is_empty() + } + /// Adds other classes to this set of classes; returning itself. /// /// Takes the logical union of both `Classes`. @@ -89,28 +96,44 @@ impl ToString for Classes { impl From<&str> for Classes { fn from(t: &str) -> Self { - let set = t.split_whitespace().map(String::from).collect(); + let set = t + .split_whitespace() + .map(String::from) + .filter(|c| !c.is_empty()) + .collect(); Self { set } } } impl From for Classes { fn from(t: String) -> Self { - let set = t.split_whitespace().map(String::from).collect(); + let set = t + .split_whitespace() + .map(String::from) + .filter(|c| !c.is_empty()) + .collect(); Self { set } } } impl From<&String> for Classes { fn from(t: &String) -> Self { - let set = t.split_whitespace().map(String::from).collect(); + let set = t + .split_whitespace() + .map(String::from) + .filter(|c| !c.is_empty()) + .collect(); Self { set } } } impl> From> for Classes { fn from(t: Vec) -> Self { - let set = t.iter().map(|x| x.as_ref().to_string()).collect(); + let set = t + .iter() + .map(|x| x.as_ref().to_string()) + .filter(|c| !c.is_empty()) + .collect(); Self { set } } } diff --git a/tests/vtag_test.rs b/tests/vtag_test.rs index 9135ee27aae..e55e77c2153 100644 --- a/tests/vtag_test.rs +++ b/tests/vtag_test.rs @@ -228,6 +228,33 @@ fn supports_multiple_classes_vec() { } } +#[test] +fn filter_empty_string_classes_vec() { + let mut classes = vec![""]; + classes.push("class-2"); + let a: VNode = html! {
}; + let b: VNode = html! {
}; + let c: VNode = html! {
}; + + if let VNode::VTag(vtag) = a { + assert!(vtag.classes.is_empty()); + } else { + panic!("vtag expected"); + } + + if let VNode::VTag(vtag) = b { + assert!(vtag.classes.is_empty()); + } else { + panic!("vtag expected"); + } + + if let VNode::VTag(vtag) = c { + assert!(vtag.classes.is_empty()); + } else { + panic!("vtag expected"); + } +} + fn assert_vtag(node: &mut VNode) -> &mut VTag { if let VNode::VTag(vtag) = node { return vtag; From 45574e53aec7c9e4b40bb5254039d43539051aa2 Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Wed, 4 Dec 2019 16:00:37 -0500 Subject: [PATCH 124/193] improve speed of to_string for Classes (#772) --- src/virtual_dom/mod.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/virtual_dom/mod.rs b/src/virtual_dom/mod.rs index 81aaa8f0ce2..bb31fd3ed45 100644 --- a/src/virtual_dom/mod.rs +++ b/src/virtual_dom/mod.rs @@ -84,13 +84,11 @@ impl Classes { impl ToString for Classes { fn to_string(&self) -> String { - let mut buf = String::new(); - for class in &self.set { - buf.push_str(class); - buf.push(' '); - } - buf.pop(); - buf + self.set + .iter() + .map(String::as_str) + .collect::>() + .join(" ") } } From e044f73fdb1c8eb3b25f3228f070af9b3351922a Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Sat, 7 Dec 2019 12:42:29 -0500 Subject: [PATCH 125/193] Refactor HiddenScope to clean up code (#778) --- src/html/mod.rs | 2 +- src/html/scope.rs | 29 +++++++++++++++++++++++++++++ src/virtual_dom/vcomp.rs | 38 ++++++++++++-------------------------- 3 files changed, 42 insertions(+), 27 deletions(-) diff --git a/src/html/mod.rs b/src/html/mod.rs index b2d8658913c..91d7c9e6ad7 100644 --- a/src/html/mod.rs +++ b/src/html/mod.rs @@ -7,8 +7,8 @@ mod listener; mod scope; pub use listener::*; -pub(crate) use scope::ComponentUpdate; pub use scope::Scope; +pub(crate) use scope::{ComponentUpdate, HiddenScope}; use crate::callback::Callback; use crate::virtual_dom::{VChild, VList, VNode}; diff --git a/src/html/scope.rs b/src/html/scope.rs index 75a6d12d5b5..0eeedeb889a 100644 --- a/src/html/scope.rs +++ b/src/html/scope.rs @@ -291,3 +291,32 @@ where }); } } + +struct Hidden; + +pub(crate) struct HiddenScope { + type_id: TypeId, + scope: *mut Hidden, +} + +impl From> for HiddenScope { + fn from(scope: Scope) -> Self { + HiddenScope { + type_id: TypeId::of::(), + scope: Box::into_raw(Box::new(scope)) as *mut Hidden, + } + } +} + +impl Into> for HiddenScope { + fn into(self: HiddenScope) -> Scope { + if self.type_id != TypeId::of::() { + panic!("encountered unespected component type"); + } + + unsafe { + let raw: *mut Scope = self.scope as *mut Scope; + *Box::from_raw(raw) + } + } +} diff --git a/src/virtual_dom/vcomp.rs b/src/virtual_dom/vcomp.rs index da12d865790..3a4ec80a000 100644 --- a/src/virtual_dom/vcomp.rs +++ b/src/virtual_dom/vcomp.rs @@ -2,24 +2,20 @@ use super::{VDiff, VNode}; use crate::callback::Callback; -use crate::html::{Component, ComponentUpdate, NodeRef, Scope}; +use crate::html::{Component, ComponentUpdate, HiddenScope, NodeRef, Scope}; use std::any::TypeId; use std::cell::RefCell; use std::fmt; use std::rc::Rc; use stdweb::web::{document, Element, INode, Node, TextNode}; -struct Hidden; - -type HiddenScope = *mut Hidden; - /// The method generates an instance of a component. type Generator = dyn FnOnce(GeneratorType, Scope) -> Mounted; /// Components can be generated by mounting or by overwriting an old component. enum GeneratorType { Mount(Element, TextNode), - Overwrite(TypeId, HiddenScope), + Overwrite(HiddenScope), } /// A reference to the parent's scope which will be used later to send messages. @@ -109,25 +105,17 @@ impl VComp { Mounted { node_ref, - scope: Box::into_raw(Box::new(scope.clone())) as *mut Hidden, + scope: scope.clone().into(), destroyer: Box::new(move || scope.destroy()), } } - GeneratorType::Overwrite(type_id, scope) => { - if type_id != TypeId::of::() { - panic!("tried to overwrite a different type of component"); - } - - let mut scope = unsafe { - let raw: *mut Scope = scope as *mut Scope; - *Box::from_raw(raw) - }; - + GeneratorType::Overwrite(hidden_scope) => { + let mut scope: Scope = hidden_scope.into(); scope.update(ComponentUpdate::Properties(props)); Mounted { node_ref, - scope: Box::into_raw(Box::new(scope.clone())) as *mut Hidden, + scope: scope.clone().into(), destroyer: Box::new(move || scope.destroy()), } } @@ -220,13 +208,13 @@ impl Unmounted { } /// Overwrite an existing virtual component using a generator. - fn replace(self, type_id: TypeId, old: Mounted, parent_scope: Scope) -> Mounted { - (self.generator)(GeneratorType::Overwrite(type_id, old.scope), parent_scope) + fn replace(self, old: Mounted, parent_scope: Scope) -> Mounted { + (self.generator)(GeneratorType::Overwrite(old.scope), parent_scope) } } enum Reform { - Keep(TypeId, Mounted), + Keep(Mounted), Before(Option), } @@ -267,9 +255,7 @@ where // old Component but update the properties. if self.type_id == vcomp.type_id { match vcomp.state.replace(MountState::Overwritten) { - MountState::Mounted(mounted) => { - Reform::Keep(vcomp.type_id, mounted) - } + MountState::Mounted(mounted) => Reform::Keep(mounted), _ => Reform::Before(None), } } else { @@ -285,9 +271,9 @@ where }; let mounted = match reform { - Reform::Keep(type_id, mounted) => { + Reform::Keep(mounted) => { // Send properties update when the component is already rendered. - this.replace(type_id, mounted, parent_scope.clone()) + this.replace(mounted, parent_scope.clone()) } Reform::Before(before) => { // Temporary node which will be replaced by a component's root node. From c583f731c5e248da870c19c81baeaac6a64d49f2 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Sat, 7 Dec 2019 13:42:19 -0500 Subject: [PATCH 126/193] Support passing callbacks to elements (#777) * Support passing callbacks to elements * Fix tests --- crates/macro/src/html_tree/html_component.rs | 4 +- crates/macro/src/html_tree/html_tag/mod.rs | 13 ++- .../src/html_tree/html_tag/tag_attributes.rs | 35 +++---- examples/nested_list/src/list.rs | 3 +- src/html/listener.rs | 43 ++++----- src/html/mod.rs | 2 +- src/html/scope.rs | 3 + src/virtual_dom/mod.rs | 19 ++-- src/virtual_dom/vcomp.rs | 23 +---- src/virtual_dom/vtag.rs | 96 +++++++++++++++---- tests/macro/html-tag-fail.stderr | 15 +-- tests/macro/html-tag-pass.rs | 3 + 12 files changed, 152 insertions(+), 107 deletions(-) diff --git a/crates/macro/src/html_tree/html_component.rs b/crates/macro/src/html_tree/html_component.rs index 398f8ad6b0b..723782400c4 100644 --- a/crates/macro/src/html_tree/html_component.rs +++ b/crates/macro/src/html_tree/html_component.rs @@ -140,7 +140,7 @@ impl ToTokens for HtmlComponent { Props::List(ListProps { props, .. }) => { let set_props = props.iter().map(|HtmlProp { label, value }| { quote_spanned! { value.span()=> - .#label(<::yew::virtual_dom::vcomp::VComp<_> as ::yew::virtual_dom::vcomp::Transformer<_, _, _>>::transform(#vcomp_scope.clone(), #value)) + .#label(<::yew::virtual_dom::vcomp::VComp<_> as ::yew::virtual_dom::Transformer<_, _, _>>::transform(#vcomp_scope.clone(), #value)) } }); @@ -181,7 +181,7 @@ impl ToTokens for HtmlComponent { #validate_props } - let #vcomp_scope: ::yew::virtual_dom::vcomp::ScopeHolder<_> = ::std::default::Default::default(); + let #vcomp_scope: ::yew::html::ScopeHolder<_> = ::std::default::Default::default(); let __yew_node_ref: ::yew::html::NodeRef = #node_ref; ::yew::virtual_dom::VChild::<#ty, _>::new(#init_props, #vcomp_scope, __yew_node_ref) }}); diff --git a/crates/macro/src/html_tree/html_tag/mod.rs b/crates/macro/src/html_tree/html_tag/mod.rs index 59da189b9b6..98a9aa053f4 100644 --- a/crates/macro/src/html_tree/html_tag/mod.rs +++ b/crates/macro/src/html_tree/html_tag/mod.rs @@ -102,6 +102,7 @@ impl ToTokens for HtmlTag { } = &attributes; let vtag = Ident::new("__yew_vtag", tag_name.span()); + let vtag_scope = Ident::new("__yew_vtag_scope", Span::call_site()); let attr_pairs = attributes.iter().map(|TagAttribute { label, value }| { let label_str = label.to_string(); quote_spanned! {value.span() => (#label_str.to_owned(), (#value).to_string()) } @@ -148,9 +149,19 @@ impl ToTokens for HtmlTag { #vtag.node_ref = #node_ref; } }); + let listeners = listeners.iter().map(|(name, callback)| { + quote_spanned! {name.span()=> { + ::yew::html::#name::Wrapper::new( + <::yew::virtual_dom::vtag::VTag<_> as ::yew::virtual_dom::Transformer<_, _, _>>::transform( + #vtag_scope.clone(), #callback + ) + ) + }} + }); tokens.extend(quote! {{ - let mut #vtag = ::yew::virtual_dom::vtag::VTag::new(#name); + let #vtag_scope: ::yew::html::ScopeHolder<_> = ::std::default::Default::default(); + let mut #vtag = ::yew::virtual_dom::vtag::VTag::new_with_scope(#name, #vtag_scope.clone()); #(#set_kind)* #(#set_value)* #(#add_href)* diff --git a/crates/macro/src/html_tree/html_tag/tag_attributes.rs b/crates/macro/src/html_tree/html_tag/tag_attributes.rs index b56d6c874c9..5a6dbb3e933 100644 --- a/crates/macro/src/html_tree/html_tag/tag_attributes.rs +++ b/crates/macro/src/html_tree/html_tag/tag_attributes.rs @@ -9,7 +9,7 @@ use syn::{Expr, ExprClosure, ExprTuple, Ident, Pat}; pub struct TagAttributes { pub attributes: Vec, - pub listeners: Vec, + pub listeners: Vec<(Ident, TokenStream)>, pub classes: Option, pub value: Option, pub kind: Option, @@ -120,14 +120,14 @@ impl TagAttributes { } } - fn map_listener(listener: TagListener) -> ParseResult { + fn map_listener(listener: TagListener) -> ParseResult<(Ident, TokenStream)> { let TagListener { name, event_name, handler, } = listener; - match handler { + let callback: TokenStream = match handler { Expr::Closure(closure) => { let ExprClosure { inputs, @@ -150,29 +150,22 @@ impl TagAttributes { Pat::Wild(pat) => Ok(pat.into_token_stream()), _ => Err(syn::Error::new_spanned(or_span, "invalid closure argument")), }?; - let handler = - Ident::new(&format!("__yew_{}_handler", name.to_string()), name.span()); - let listener = - Ident::new(&format!("__yew_{}_listener", name.to_string()), name.span()); + let callback = + Ident::new(&format!("__yew_{}_callback", name.to_string()), name.span()); let segment = syn::PathSegment { ident: Ident::new(&event_name, name.span()), arguments: syn::PathArguments::None, }; - let var_type = quote! { ::yew::events::#segment }; - let wrapper_type = quote! { ::yew::html::#name::Wrapper }; - let listener_stream = quote_spanned! {name.span()=> { - let #handler = move | #var: #var_type | #body; - let #listener = #wrapper_type::from(#handler); - #listener - }}; - - Ok(listener_stream) + + quote_spanned! {name.span()=> { + let #callback = move | #var: ::yew::events::#segment | #body; + #callback + }} } - _ => Err(syn::Error::new_spanned( - &name, - format!("`{}` attribute value should be a closure", name), - )), - } + callback => callback.into_token_stream(), + }; + + Ok((name, callback)) } } diff --git a/examples/nested_list/src/list.rs b/examples/nested_list/src/list.rs index 3b370ef195a..835404880a6 100644 --- a/examples/nested_list/src/list.rs +++ b/examples/nested_list/src/list.rs @@ -1,9 +1,8 @@ use crate::{header::Props as HeaderProps, ListHeader}; use crate::{item::Props as ItemProps, ListItem}; use std::fmt; -use yew::html::{ChildrenRenderer, NodeRef}; +use yew::html::{ChildrenRenderer, NodeRef, ScopeHolder}; use yew::prelude::*; -use yew::virtual_dom::vcomp::ScopeHolder; use yew::virtual_dom::{VChild, VComp, VNode}; #[derive(Debug)] diff --git a/src/html/listener.rs b/src/html/listener.rs index 31a3d6be5f0..99d51de5c13 100644 --- a/src/html/listener.rs +++ b/src/html/listener.rs @@ -1,4 +1,4 @@ -use super::*; +use crate::callback::Callback; use crate::virtual_dom::Listener; use stdweb::web::html_element::SelectElement; #[allow(unused_imports)] @@ -14,42 +14,33 @@ macro_rules! impl_action { use stdweb::web::event::{IEvent, $type}; use super::*; - /// A wrapper for a callback. - /// Listener extracted from here when attached. - #[allow(missing_debug_implementations)] - pub struct Wrapper(Option); - - /// And event type which keeps the returned type. - pub type Event = $ret; + /// A wrapper for a callback which attaches event listeners to elements. + #[derive(Clone, Debug)] + pub struct Wrapper { + callback: Callback, + } - impl From for Wrapper - where - MSG: 'static, - F: Fn($ret) -> MSG + 'static, - { - fn from(handler: F) -> Self { - Wrapper(Some(handler)) + impl Wrapper { + /// Create a wrapper for an event-typed callback + pub fn new(callback: Callback) -> Self { + Wrapper { callback } } } - impl Listener for Wrapper - where - T: Fn($ret) -> COMP::Message + 'static, - COMP: Component, - { + /// And event type which keeps the returned type. + pub type Event = $ret; + + impl Listener for Wrapper { fn kind(&self) -> &'static str { stringify!($action) } - fn attach(&mut self, element: &Element, mut activator: Scope) - -> EventListenerHandle { - let handler = self.0.take().expect("tried to attach listener twice"); + fn attach(&self, element: &Element) -> EventListenerHandle { let this = element.clone(); + let callback = self.callback.clone(); let listener = move |event: $type| { event.stop_propagation(); - let handy_event: $ret = $convert(&this, event); - let msg = handler(handy_event); - activator.send_message(msg); + callback.emit($convert(&this, event)); }; element.add_event_listener(listener) } diff --git a/src/html/mod.rs b/src/html/mod.rs index 91d7c9e6ad7..507cfa4af63 100644 --- a/src/html/mod.rs +++ b/src/html/mod.rs @@ -7,8 +7,8 @@ mod listener; mod scope; pub use listener::*; -pub use scope::Scope; pub(crate) use scope::{ComponentUpdate, HiddenScope}; +pub use scope::{Scope, ScopeHolder}; use crate::callback::Callback; use crate::virtual_dom::{VChild, VList, VNode}; diff --git a/src/html/scope.rs b/src/html/scope.rs index 0eeedeb889a..f59adb87e44 100644 --- a/src/html/scope.rs +++ b/src/html/scope.rs @@ -16,6 +16,9 @@ pub(crate) enum ComponentUpdate { Properties(COMP::Properties), } +/// A reference to the parent's scope which will be used later to send messages. +pub type ScopeHolder = Rc>>>; + /// A context which allows sending messages to a component. pub struct Scope { shared_state: Shared>, diff --git a/src/virtual_dom/mod.rs b/src/virtual_dom/mod.rs index bb31fd3ed45..1a6caece3fe 100644 --- a/src/virtual_dom/mod.rs +++ b/src/virtual_dom/mod.rs @@ -16,26 +16,25 @@ pub use self::vlist::VList; pub use self::vnode::VNode; pub use self::vtag::VTag; pub use self::vtext::VText; -use crate::html::{Component, Scope}; +use crate::html::{Component, Scope, ScopeHolder}; /// `Listener` trait is an universal implementation of an event listener /// which helps to bind Rust-listener to JS-listener (DOM). -pub trait Listener { +pub trait Listener { /// Returns standard name of DOM's event. fn kind(&self) -> &'static str; - /// Attaches listener to the element and uses scope instance to send - /// prepared event back to the yew main loop. - fn attach(&mut self, element: &Element, scope: Scope) -> EventListenerHandle; + /// Attaches a listener to the element. + fn attach(&self, element: &Element) -> EventListenerHandle; } -impl fmt::Debug for dyn Listener { +impl fmt::Debug for dyn Listener { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Listener {{ kind: {} }}", self.kind()) } } /// A list of event listeners. -type Listeners = Vec>>; +type Listeners = Vec>; /// A map of attributes. type Attributes = HashMap; @@ -201,3 +200,9 @@ pub trait VDiff { parent_scope: &Scope, ) -> Option; } + +/// Transforms properties and attaches a parent scope holder to callbacks for sending messages. +pub trait Transformer { + /// Transforms one type to another. + fn transform(scope_holder: ScopeHolder, from: FROM) -> TO; +} diff --git a/src/virtual_dom/vcomp.rs b/src/virtual_dom/vcomp.rs index 3a4ec80a000..dfffd42b3d8 100644 --- a/src/virtual_dom/vcomp.rs +++ b/src/virtual_dom/vcomp.rs @@ -1,8 +1,8 @@ //! This module contains the implementation of a virtual component `VComp`. -use super::{VDiff, VNode}; +use super::{Transformer, VDiff, VNode}; use crate::callback::Callback; -use crate::html::{Component, ComponentUpdate, HiddenScope, NodeRef, Scope}; +use crate::html::{Component, ComponentUpdate, HiddenScope, NodeRef, Scope, ScopeHolder}; use std::any::TypeId; use std::cell::RefCell; use std::fmt; @@ -18,9 +18,6 @@ enum GeneratorType { Overwrite(HiddenScope), } -/// A reference to the parent's scope which will be used later to send messages. -pub type ScopeHolder = Rc>>>; - /// A virtual component. pub struct VComp { type_id: TypeId, @@ -131,12 +128,6 @@ impl VComp { } } -/// Transforms properties and attaches a parent scope holder to callbacks for sending messages. -pub trait Transformer { - /// Transforms one type to another. - fn transform(scope_holder: ScopeHolder, from: FROM) -> TO; -} - impl Transformer for VComp where PARENT: Component, @@ -189,15 +180,7 @@ where F: Fn(IN) -> PARENT::Message + 'static, { fn transform(scope: ScopeHolder, from: F) -> Option> { - let callback = move |arg| { - let msg = from(arg); - if let Some(ref mut sender) = *scope.borrow_mut() { - sender.send_message(msg); - } else { - panic!("Parent component hasn't activated this callback yet"); - } - }; - Some(callback.into()) + Some(VComp::::transform(scope, from)) } } diff --git a/src/virtual_dom/vtag.rs b/src/virtual_dom/vtag.rs index 93f073569da..1e4ef4b1d75 100644 --- a/src/virtual_dom/vtag.rs +++ b/src/virtual_dom/vtag.rs @@ -1,7 +1,10 @@ //! This module contains the implementation of a virtual element node `VTag`. -use super::{Attributes, Classes, Listener, Listeners, Patch, Reform, VDiff, VList, VNode}; -use crate::html::{Component, NodeRef, Scope}; +use super::{ + Attributes, Classes, Listener, Listeners, Patch, Reform, Transformer, VDiff, VList, VNode, +}; +use crate::callback::Callback; +use crate::html::{Component, NodeRef, Scope, ScopeHolder}; use log::warn; use std::borrow::Cow; use std::cmp::PartialEq; @@ -22,17 +25,17 @@ pub const HTML_NAMESPACE: &str = "http://www.w3.org/1999/xhtml"; /// A type for a virtual /// [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) /// representation. -pub struct VTag { +pub struct VTag { /// A tag of the element. tag: Cow<'static, str>, /// A reference to the `Element`. pub reference: Option, /// List of attached listeners. - pub listeners: Listeners, + pub listeners: Listeners, /// List of attributes. pub attributes: Attributes, /// List of children nodes - pub children: VList, + pub children: VList, /// List of attached classes. pub classes: Classes, /// Contains a value of an @@ -50,14 +53,24 @@ pub struct VTag { pub checked: bool, /// A node reference used for DOM access in Component lifecycle methods pub node_ref: NodeRef, - /// _Service field_. Keeps handler for attached listeners - /// to have an opportunity to drop them later. + /// Keeps handler for attached listeners to have an opportunity to drop them later. captured: Vec, + /// Holds a reference to the parent component scope for callback activation. + scope_holder: ScopeHolder, } -impl VTag { +impl VTag { /// Creates a new `VTag` instance with `tag` name (cannot be changed later in DOM). pub fn new>>(tag: S) -> Self { + Self::new_with_scope(tag, ScopeHolder::default()) + } + + /// Creates a new `VTag` instance with `tag` name (cannot be changed later in DOM) and parent + /// scope holder for callback activation. + pub fn new_with_scope>>( + tag: S, + scope_holder: ScopeHolder, + ) -> Self { VTag { tag: tag.into(), reference: None, @@ -72,6 +85,7 @@ impl VTag { // In HTML node `checked` attribute sets `defaultChecked` parameter, // but we use own field to control real `checked` parameter checked: false, + scope_holder, } } @@ -81,12 +95,12 @@ impl VTag { } /// Add `VNode` child. - pub fn add_child(&mut self, child: VNode) { + pub fn add_child(&mut self, child: VNode) { self.children.add_child(child); } /// Add multiple `VNode` children. - pub fn add_children(&mut self, children: Vec>) { + pub fn add_children(&mut self, children: Vec>) { for child in children { self.add_child(child); } @@ -159,15 +173,15 @@ impl VTag { /// Adds new listener to the node. /// It's boxed because we want to keep it in a single list. - /// Lates `Listener::attach` called to attach actual listener to a DOM node. - pub fn add_listener(&mut self, listener: Box>) { + /// Later `Listener::attach` will attach an actual listener to a DOM node. + pub fn add_listener(&mut self, listener: Box) { self.listeners.push(listener); } /// Adds new listeners to the node. /// They are boxed because we want to keep them in a single list. - /// Lates `Listener::attach` called to attach actual listener to a DOM node. - pub fn add_listeners(&mut self, listeners: Vec>>) { + /// Later `Listener::attach` will attach an actual listener to a DOM node. + pub fn add_listeners(&mut self, listeners: Vec>) { for listener in listeners { self.listeners.push(listener); } @@ -346,8 +360,8 @@ impl VTag { } } -impl VDiff for VTag { - type Component = COMP; +impl VDiff for VTag { + type Component = PARENT; /// Remove VTag from parent. fn detach(&mut self, parent: &Element) -> Option { @@ -454,11 +468,14 @@ impl VDiff for VTag { let element = self.reference.clone().expect("element expected"); - for mut listener in self.listeners.drain(..) { - let handle = listener.attach(&element, parent_scope.clone()); + for listener in self.listeners.drain(..) { + let handle = listener.attach(&element); self.captured.push(handle); } + // Activate scope + *self.scope_holder.borrow_mut() = Some(parent_scope.clone()); + // Process children self.children.apply( &element, @@ -473,7 +490,7 @@ impl VDiff for VTag { } } -impl fmt::Debug for VTag { +impl fmt::Debug for VTag { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "VTag {{ tag: {} }}", self.tag) } @@ -495,8 +512,8 @@ fn set_checked(input: &InputElement, value: bool) { js!( @(no_return) @{input}.checked = @{value}; ); } -impl PartialEq for VTag { - fn eq(&self, other: &VTag) -> bool { +impl PartialEq for VTag { + fn eq(&self, other: &VTag) -> bool { self.tag == other.tag && self.value == other.value && self.kind == other.kind @@ -521,3 +538,40 @@ pub(crate) fn not(option: &Option) -> &Option<()> { &Some(()) } } + +impl Transformer for VTag +where + PARENT: Component, +{ + fn transform(_: ScopeHolder, from: T) -> T { + from + } +} + +impl<'a, PARENT, T> Transformer for VTag +where + PARENT: Component, + T: Clone, +{ + fn transform(_: ScopeHolder, from: &'a T) -> T { + from.clone() + } +} + +impl<'a, PARENT, F, IN> Transformer> for VTag +where + PARENT: Component, + F: Fn(IN) -> PARENT::Message + 'static, +{ + fn transform(scope: ScopeHolder, from: F) -> Callback { + let callback = move |arg| { + let msg = from(arg); + if let Some(ref mut sender) = *scope.borrow_mut() { + sender.send_message(msg); + } else { + panic!("Parent component hasn't activated this callback yet"); + } + }; + callback.into() + } +} diff --git a/tests/macro/html-tag-fail.stderr b/tests/macro/html-tag-fail.stderr index 7ff76430ad4..147ec3cb66d 100644 --- a/tests/macro/html-tag-fail.stderr +++ b/tests/macro/html-tag-fail.stderr @@ -102,12 +102,6 @@ error: only one `class` attribute allowed 23 | html! {
}; | ^^^^^ -error: `onclick` attribute value should be a closure - --> $DIR/html-tag-fail.rs:32:20 - | -32 | html! { }; - | ^^^^^^^ - error: there must be one closure argument --> $DIR/html-tag-fail.rs:33:28 | @@ -190,6 +184,15 @@ error[E0277]: the trait bound `yew::html::Href: std::convert::From<()>` is not s > = note: required because of the requirements on the impl of `std::convert::Into` for `()` +error[E0308]: mismatched types + --> $DIR/html-tag-fail.rs:32:20 + | +32 | html! { }; + | ^^^^^^^ expected struct `yew::callback::Callback`, found integer + | + = note: expected type `yew::callback::Callback` + found type `{integer}` + error[E0599]: no method named `to_string` found for type `NotToString` in the current scope --> $DIR/html-tag-fail.rs:37:27 | diff --git a/tests/macro/html-tag-pass.rs b/tests/macro/html-tag-pass.rs index 122cae3c733..f660b5f96f9 100644 --- a/tests/macro/html-tag-pass.rs +++ b/tests/macro/html-tag-pass.rs @@ -4,6 +4,7 @@ mod helpers; pass_helper! { + let onclick = Callback::from(|_: ClickEvent| ()); let parent_ref = NodeRef::default(); html! {
@@ -37,6 +38,8 @@ pass_helper! { + } } } @@ -362,8 +365,8 @@ impl Component for Model { fn update(&mut self, msg: Self::Message) -> ShouldRender { match msg { Msg::Fire => { - let send_msg = self.link.callback(|_| Msg::Timeout); - self.timeout.spawn(Duration::from_secs(5), send_msg); + let timeout = self.link.callback(|_| Msg::Timeout); + self.timeout.spawn(Duration::from_secs(5), timeout); } Msg::Timeout => { self.console.log("Timeout!"); diff --git a/crates/macro/src/lib.rs b/crates/macro/src/lib.rs index ea3b8687e7f..11eec5e251a 100644 --- a/crates/macro/src/lib.rs +++ b/crates/macro/src/lib.rs @@ -9,7 +9,10 @@ //! # #[macro_use] extern crate yew; //! use yew::prelude::*; //! -//! # struct Component; +//! struct Component { +//! link: ComponentLink, +//! } +//! //! #[derive(Properties)] //! struct Props { //! #[props(required)] @@ -35,7 +38,9 @@ //! //! html! { //!
-//! +//! //! <> //! //! diff --git a/examples/counter/src/lib.rs b/examples/counter/src/lib.rs index 02a5f574ce5..a3b0cdc3d1c 100644 --- a/examples/counter/src/lib.rs +++ b/examples/counter/src/lib.rs @@ -5,6 +5,7 @@ use yew::services::ConsoleService; use yew::{html, Component, ComponentLink, Html, ShouldRender}; pub struct Model { + link: ComponentLink, console: ConsoleService, value: i64, } @@ -19,8 +20,9 @@ impl Component for Model { type Message = Msg; type Properties = (); - fn create(_: Self::Properties, _: ComponentLink) -> Self { + fn create(_: Self::Properties, link: ComponentLink) -> Self { Model { + link, console: ConsoleService::new(), value: 0, } @@ -50,9 +52,15 @@ impl Component for Model { html! {

{ self.value }

{ Date::new().to_string() }

diff --git a/examples/crm/src/lib.rs b/examples/crm/src/lib.rs index fcd7446413f..6bc6a83be7c 100644 --- a/examples/crm/src/lib.rs +++ b/examples/crm/src/lib.rs @@ -8,7 +8,7 @@ mod markdown; use yew::format::Json; use yew::services::storage::Area; use yew::services::{DialogService, StorageService}; -use yew::{html, Component, ComponentLink, Html, Renderable, ShouldRender}; +use yew::{html, Component, ComponentLink, Html, InputData, Renderable, ShouldRender}; const KEY: &'static str = "yew.crm.database"; @@ -42,6 +42,7 @@ pub enum Scene { } pub struct Model { + link: ComponentLink, storage: StorageService, dialog: DialogService, database: Database, @@ -62,13 +63,14 @@ impl Component for Model { type Message = Msg; type Properties = (); - fn create(_: Self::Properties, _: ComponentLink) -> Self { + fn create(_: Self::Properties, link: ComponentLink) -> Self { let storage = StorageService::new(Area::Local); let Json(database) = storage.restore(KEY); let database = database.unwrap_or_else(|_| Database { clients: Vec::new(), }); Model { + link, storage, dialog: DialogService::new(), database, @@ -151,26 +153,26 @@ impl Component for Model {
{ for self.database.clients.iter().map(Renderable::render) }
- - + +
}, Scene::NewClientForm(ref client) => html! {
- { client.view_first_name_input() } - { client.view_last_name_input() } - { client.view_description_textarea() } + { client.view_first_name_input(&self.link) } + { client.view_last_name_input(&self.link) } + { client.view_description_textarea(&self.link) }
- + onclick=self.link.callback(|_| Msg::AddNew)>{ "Add New" } +
}, Scene::Settings => html! {
- - + +
}, } @@ -191,29 +193,29 @@ impl Renderable for Client { } impl Client { - fn view_first_name_input(&self) -> Html { + fn view_first_name_input(&self, link: &ComponentLink) -> Html { html! { + oninput=link.callback(|e: InputData| Msg::UpdateFirstName(e.value)) /> } } - fn view_last_name_input(&self) -> Html { + fn view_last_name_input(&self, link: &ComponentLink) -> Html { html! { + oninput=link.callback(|e: InputData| Msg::UpdateLastName(e.value)) /> } } - fn view_description_textarea(&self) -> Html { + fn view_description_textarea(&self, link: &ComponentLink) -> Html { html! { - -

diff --git a/examples/large_table/src/lib.rs b/examples/large_table/src/lib.rs index a7edf982bfe..5d6701a5f9f 100644 --- a/examples/large_table/src/lib.rs +++ b/examples/large_table/src/lib.rs @@ -4,6 +4,7 @@ use yew::{html, Component, ComponentLink, Html, ShouldRender}; pub struct Model { + link: ComponentLink, selected: Option<(u32, u32)>, } @@ -15,8 +16,11 @@ impl Component for Model { type Message = Msg; type Properties = (); - fn create(_: (), _: ComponentLink) -> Self { - Model { selected: None } + fn create(_: (), link: ComponentLink) -> Self { + Model { + link, + selected: None, + } } // Some details omitted. Explore the examples to get more. @@ -32,33 +36,35 @@ impl Component for Model { fn view(&self) -> Html { html! { - { (0..99).map(|row| view_row(self.selected, row)).collect::>() } + { (0..99).map(|row| self.view_row(row)).collect::>() }
} } } -fn square_class(this: (u32, u32), selected: Option<(u32, u32)>) -> &'static str { - match selected { - Some(xy) if xy == this => "square_green", - _ => "square_red", +impl Model { + fn view_square(&self, row: u32, column: u32) -> Html { + html! { + + + } } -} -fn view_square(selected: Option<(u32, u32)>, row: u32, column: u32) -> Html { - html! { - - + fn view_row(&self, row: u32) -> Html { + html! { + + {for (0..99).map(|column| { + self.view_square(row, column) + })} + + } } } -fn view_row(selected: Option<(u32, u32)>, row: u32) -> Html { - html! { - - {for (0..99).map(|column| { - view_square(selected, row, column) - })} - +fn square_class(this: (u32, u32), selected: Option<(u32, u32)>) -> &'static str { + match selected { + Some(xy) if xy == this => "square_green", + _ => "square_red", } } diff --git a/examples/minimal/src/lib.rs b/examples/minimal/src/lib.rs index d9e1f41afc9..333684a4f17 100644 --- a/examples/minimal/src/lib.rs +++ b/examples/minimal/src/lib.rs @@ -1,6 +1,8 @@ use yew::{html, Component, ComponentLink, Html, ShouldRender}; -pub struct Model {} +pub struct Model { + link: ComponentLink, +} pub enum Msg { Click, @@ -10,8 +12,8 @@ impl Component for Model { type Message = Msg; type Properties = (); - fn create(_: Self::Properties, _: ComponentLink) -> Self { - Model {} + fn create(_: Self::Properties, link: ComponentLink) -> Self { + Model { link } } fn update(&mut self, msg: Self::Message) -> ShouldRender { @@ -24,7 +26,7 @@ impl Component for Model { fn view(&self) -> Html { html! {

- +
} } diff --git a/examples/mount_point/src/lib.rs b/examples/mount_point/src/lib.rs index 7d2b98dc155..6d3d5850f3b 100644 --- a/examples/mount_point/src/lib.rs +++ b/examples/mount_point/src/lib.rs @@ -1,6 +1,7 @@ -use yew::{html, Component, ComponentLink, Html, ShouldRender}; +use yew::{html, Component, ComponentLink, Html, InputData, ShouldRender}; pub struct Model { + link: ComponentLink, name: String, } @@ -12,8 +13,9 @@ impl Component for Model { type Message = Msg; type Properties = (); - fn create(_: Self::Properties, _: ComponentLink) -> Self { + fn create(_: Self::Properties, link: ComponentLink) -> Self { Model { + link, name: "Reversed".to_owned(), } } @@ -30,7 +32,9 @@ impl Component for Model { fn view(&self) -> Html { html! {
- +

{ self.name.chars().rev().collect::() }

} diff --git a/examples/multi_thread/src/lib.rs b/examples/multi_thread/src/lib.rs index 5b8f02e0b66..289c53595a3 100644 --- a/examples/multi_thread/src/lib.rs +++ b/examples/multi_thread/src/lib.rs @@ -9,6 +9,7 @@ use yew::worker::*; use yew::{html, Component, ComponentLink, Html, ShouldRender}; pub struct Model { + link: ComponentLink, worker: Box>, job: Box>, context: Box>, @@ -70,9 +71,9 @@ impl Component for Model { html! {
} diff --git a/examples/node_refs/src/input.rs b/examples/node_refs/src/input.rs index 7eaeab97028..b35ca64b3f5 100644 --- a/examples/node_refs/src/input.rs +++ b/examples/node_refs/src/input.rs @@ -2,6 +2,7 @@ use yew::prelude::*; pub struct InputComponent { props: Props, + link: ComponentLink, } #[derive(Properties)] @@ -18,8 +19,8 @@ impl Component for InputComponent { type Message = Msg; type Properties = Props; - fn create(props: Self::Properties, _: ComponentLink) -> Self { - InputComponent { props } + fn create(props: Self::Properties, link: ComponentLink) -> Self { + InputComponent { props, link } } fn update(&mut self, msg: Self::Message) -> ShouldRender { @@ -33,7 +34,10 @@ impl Component for InputComponent { fn view(&self) -> Html { html! { - + } } } diff --git a/examples/node_refs/src/lib.rs b/examples/node_refs/src/lib.rs index efab09fff32..8f20954dc55 100644 --- a/examples/node_refs/src/lib.rs +++ b/examples/node_refs/src/lib.rs @@ -8,6 +8,7 @@ use stdweb::web::IHtmlElement; use yew::prelude::*; pub struct Model { + link: ComponentLink, refs: Vec, focus_index: usize, } @@ -20,8 +21,9 @@ impl Component for Model { type Message = Msg; type Properties = (); - fn create(_: Self::Properties, _: ComponentLink) -> Self { + fn create(_: Self::Properties, link: ComponentLink) -> Self { Model { + link, focus_index: 0, refs: vec![NodeRef::default(), NodeRef::default()], } @@ -55,11 +57,17 @@ impl Component for Model {
- +
- +
} diff --git a/examples/npm_and_rest/src/lib.rs b/examples/npm_and_rest/src/lib.rs index 3065907da2c..d5f96d98970 100644 --- a/examples/npm_and_rest/src/lib.rs +++ b/examples/npm_and_rest/src/lib.rs @@ -15,6 +15,7 @@ use ccxt::CcxtService; use gravatar::{GravatarService, Profile}; pub struct Model { + link: ComponentLink, gravatar: GravatarService, ccxt: CcxtService, callback: Callback>, @@ -35,6 +36,7 @@ impl Component for Model { fn create(_: Self::Properties, link: ComponentLink) -> Self { Model { + link: link.clone(), gravatar: GravatarService::new(), ccxt: CcxtService::new(), callback: link.callback(Msg::GravatarReady), @@ -73,8 +75,8 @@ impl Component for Model { }; html! {
- - + +
    { for self.exchanges.iter().map(view_exchange) }
diff --git a/examples/routing/src/b_component.rs b/examples/routing/src/b_component.rs index d110f5fa88e..17f95b6b80a 100644 --- a/examples/routing/src/b_component.rs +++ b/examples/routing/src/b_component.rs @@ -1,9 +1,10 @@ use crate::router::{Request, Route, Router}; use log::info; use yew::agent::Bridged; -use yew::{html, Bridge, Component, ComponentLink, Html, ShouldRender}; +use yew::{html, Bridge, Component, ComponentLink, Html, InputData, ShouldRender}; pub struct BModel { + link: ComponentLink, number: Option, sub_path: Option, router: Box>>, @@ -28,6 +29,7 @@ impl Component for BModel { router.send(Request::GetCurrentRoute); BModel { + link, number: None, sub_path: None, router, @@ -111,8 +113,8 @@ impl Component for BModel {
{ self.display_number() } - - + +
{ self.display_subpath_input() } @@ -130,12 +132,12 @@ impl BModel { format!("Number: None") } } - fn display_subpath_input(&self) -> Html { + fn display_subpath_input(&self) -> Html { let sub_path = self.sub_path.clone(); html! { + oninput=self.link.callback(|e: InputData| Msg::Navigate(vec![Msg::UpdateSubpath(e.value)])) /> } } } diff --git a/examples/routing/src/router_button.rs b/examples/routing/src/router_button.rs index 049dad044ba..0d431c6b716 100644 --- a/examples/routing/src/router_button.rs +++ b/examples/routing/src/router_button.rs @@ -9,6 +9,7 @@ use yew::agent::Dispatcher; /// Changes the route when clicked. pub struct RouterButton { + link: ComponentLink, router: Dispatcher>, props: Props, } @@ -34,10 +35,14 @@ impl Component for RouterButton { type Message = Msg; type Properties = Props; - fn create(props: Self::Properties, _link: ComponentLink) -> Self { + fn create(props: Self::Properties, link: ComponentLink) -> Self { let router = Router::dispatcher(); - RouterButton { router, props } + RouterButton { + link, + router, + props, + } } fn update(&mut self, msg: Self::Message) -> ShouldRender { @@ -66,11 +71,11 @@ impl Component for RouterButton { true } - fn view(&self) -> Html { + fn view(&self) -> Html { html! {
{ self.view_scene() } diff --git a/examples/textarea/src/lib.rs b/examples/textarea/src/lib.rs index 0a5864624f0..b766da09c0f 100644 --- a/examples/textarea/src/lib.rs +++ b/examples/textarea/src/lib.rs @@ -1,8 +1,9 @@ #![recursion_limit = "128"] -use yew::{html, Component, ComponentLink, Html, ShouldRender}; +use yew::{html, Component, ComponentLink, Html, InputData, ShouldRender}; pub struct Model { + link: ComponentLink, value: String, } @@ -15,8 +16,11 @@ impl Component for Model { type Message = Msg; type Properties = (); - fn create(_: Self::Properties, _: ComponentLink) -> Self { - Model { value: "".into() } + fn create(_: Self::Properties, link: ComponentLink) -> Self { + Model { + link, + value: "".into(), + } } fn update(&mut self, msg: Self::Message) -> ShouldRender { @@ -37,10 +41,10 @@ impl Component for Model {
- +
{&self.value} diff --git a/examples/timer/src/lib.rs b/examples/timer/src/lib.rs index c705ad19f0a..1964187838b 100644 --- a/examples/timer/src/lib.rs +++ b/examples/timer/src/lib.rs @@ -5,6 +5,7 @@ use yew::services::{ConsoleService, IntervalService, Task, TimeoutService}; use yew::{html, Callback, Component, ComponentLink, Html, ShouldRender}; pub struct Model { + link: ComponentLink, timeout: TimeoutService, interval: IntervalService, console: ConsoleService, @@ -36,6 +37,7 @@ impl Component for Model { let handle = interval.spawn(Duration::from_secs(10), callback.into()); Model { + link: link.clone(), timeout: TimeoutService::new(), interval, console: ConsoleService::new(), @@ -104,9 +106,12 @@ impl Component for Model { let has_job = self.job.is_some(); html! {
- - - + + +
{ for self.messages.iter().map(view_message) }
diff --git a/examples/todomvc/src/lib.rs b/examples/todomvc/src/lib.rs index 3c644bfb98b..84afc62292f 100644 --- a/examples/todomvc/src/lib.rs +++ b/examples/todomvc/src/lib.rs @@ -6,11 +6,12 @@ use strum_macros::{EnumIter, ToString}; use yew::events::IKeyboardEvent; use yew::format::Json; use yew::services::storage::{Area, StorageService}; -use yew::{html, Component, ComponentLink, Href, Html, ShouldRender}; +use yew::{html, Component, ComponentLink, Href, Html, InputData, KeyPressEvent, ShouldRender}; const KEY: &'static str = "yew.todomvc.self"; pub struct Model { + link: ComponentLink, storage: StorageService, state: State, } @@ -48,7 +49,7 @@ impl Component for Model { type Message = Msg; type Properties = (); - fn create(_: Self::Properties, _: ComponentLink) -> Self { + fn create(_: Self::Properties, link: ComponentLink) -> Self { let storage = StorageService::new(Area::Local); let entries = { if let Json(Ok(restored_model)) = storage.restore(KEY) { @@ -63,7 +64,11 @@ impl Component for Model { value: "".into(), edit_value: "".into(), }; - Model { storage, state } + Model { + link, + storage, + state, + } } fn update(&mut self, msg: Self::Message) -> ShouldRender { @@ -125,9 +130,13 @@ impl Component for Model { { self.view_input() }
- +
    - { for self.state.entries.iter().filter(|e| self.state.filter.fit(e)).enumerate().map(view_entry) } + { for self.state.entries.iter().filter(|e| self.state.filter.fit(e)).enumerate().map(|e| self.view_entry(e)) }
@@ -138,7 +147,7 @@ impl Component for Model {
    { for Filter::iter().map(|flt| self.view_filter(flt)) }
-
@@ -154,30 +163,30 @@ impl Component for Model { } impl Model { - fn view_filter(&self, filter: Filter) -> Html { + fn view_filter(&self, filter: Filter) -> Html { let flt = filter.clone(); html! {
  • + onclick=self.link.callback(move |_| Msg::SetFilter(flt.clone()))> { filter }
  • } } - fn view_input(&self) -> Html { + fn view_input(&self) -> Html { html! { // You can use standard Rust comments. One line: //
  • + }) /> /* Or multiline: