589: Allow components to accept children elements r=jstarry a=jstarry

Fixes: https://github.com/yewstack/yew/issues/537

#### Terminology
- (B) Base component that renders components nested inside each other
- (P) Parent component that has a `children` property and can render those children
- (C) Child component that is nested inside parent and included inside the Parent's `children`

#### Todo
- [x] Add example for nested components
- [x] Support arbitrary html nested inside component tags
- [x] Support nested components inside component tags
- [x] Allow modifying & accessing (C) props when rendering (P)
- [x] Allow filtering (C) components when rendering (P)
- [x] Children prop be required or optional
- [x] Clean up nested component example
- [x] Fix parser for generic component type
- [x] Write tests
- [x] Update documentation and README
- [x] ~~Investigate passing required properties from (P) -> (C)~~
- [x] ~~Allow sending messages from (C) -> (B)~~


Co-authored-by: Justin Starry <jstarry@users.noreply.github.com>
This commit is contained in:
bors[bot] 2019-09-10 14:53:24 +00:00 committed by GitHub
commit 24d39f97e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 1210 additions and 217 deletions

View File

@ -72,6 +72,7 @@ members = [
"examples/minimal", "examples/minimal",
"examples/mount_point", "examples/mount_point",
"examples/multi_thread", "examples/multi_thread",
"examples/nested_list",
"examples/npm_and_rest", "examples/npm_and_rest",
"examples/routing", "examples/routing",
"examples/server", "examples/server",

View File

@ -218,6 +218,9 @@ html! {
<nav class="menu"> <nav class="menu">
<MyButton title="First Button" /> <MyButton title="First Button" />
<MyButton title="Second Button "/> <MyButton title="Second Button "/>
<MyList name="Grocery List">
<MyListItem text="Apples" />
</MyList>
</nav> </nav>
} }
``` ```

View File

@ -170,13 +170,29 @@ impl TryFrom<Field> for PropField {
impl PartialOrd for PropField { impl PartialOrd for PropField {
fn partial_cmp(&self, other: &PropField) -> Option<Ordering> { fn partial_cmp(&self, other: &PropField) -> Option<Ordering> {
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 { impl Ord for PropField {
fn cmp(&self, other: &PropField) -> Ordering { 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)
}
} }
} }

View File

@ -1,53 +1,88 @@
use super::HtmlProp; use super::HtmlProp;
use super::HtmlPropSuffix; use super::HtmlPropSuffix;
use super::HtmlTreeNested;
use crate::PeekValue; use crate::PeekValue;
use boolinator::Boolinator; use boolinator::Boolinator;
use proc_macro2::Span; use proc_macro2::Span;
use quote::{quote, quote_spanned, ToTokens}; use quote::{quote, quote_spanned, ToTokens};
use std::cmp::Ordering;
use syn::buffer::Cursor; use syn::buffer::Cursor;
use syn::parse; use syn::parse;
use syn::parse::{Parse, ParseStream, Result as ParseResult}; use syn::parse::{Parse, ParseStream, Result as ParseResult};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned; 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<Props>,
children: Vec<HtmlTreeNested>,
}
impl PeekValue<()> for HtmlComponent { impl PeekValue<()> for HtmlComponent {
fn peek(cursor: Cursor) -> Option<()> { fn peek(cursor: Cursor) -> Option<()> {
let (punct, cursor) = cursor.punct()?; let (punct, cursor) = cursor.punct()?;
(punct.as_char() == '<').as_option()?; (punct.as_char() == '<').as_option()?;
HtmlComponent::peek_type(cursor) HtmlComponent::peek_type(cursor)?;
Some(())
} }
} }
impl Parse for HtmlComponent { impl Parse for HtmlComponent {
fn parse(input: ParseStream) -> ParseResult<Self> { fn parse(input: ParseStream) -> ParseResult<Self> {
let lt = input.parse::<Token![<]>()?; if HtmlComponentClose::peek(input.cursor()).is_some() {
let HtmlPropSuffix { stream, div, gt } = input.parse()?; return match input.parse::<HtmlComponentClose>() {
if div.is_none() { Ok(close) => Err(syn::Error::new_spanned(
return Err(syn::Error::new_spanned( close,
HtmlComponentTag { lt, gt }, "this close tag has no corresponding open tag",
"expected component tag be of form `< .. />`", )),
)); Err(err) => Err(err),
};
} }
match parse(stream) { let open = input.parse::<HtmlComponentOpen>()?;
Ok(comp) => Ok(HtmlComponent(comp)), // Return early if it's a self-closing tag
Err(err) => { if open.div.is_some() {
if err.to_string().starts_with("unexpected end of input") { return Ok(HtmlComponent {
Err(syn::Error::new_spanned(div, err.to_string())) ty: open.ty,
} else { props: open.props,
Err(err) children: Vec::new(),
} });
}
} }
let mut children: Vec<HtmlTreeNested> = vec![];
loop {
if input.is_empty() {
return Err(syn::Error::new_spanned(
open,
"this open tag has no corresponding close tag",
));
}
if HtmlComponentClose::peek(input.cursor()).is_some() {
break;
}
children.push(input.parse()?);
}
input.parse::<HtmlComponentClose>()?;
Ok(HtmlComponent {
ty: open.ty,
props: open.props,
children,
})
} }
} }
impl ToTokens for HtmlComponent { impl ToTokens for HtmlComponent {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 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 vcomp_scope = Ident::new("__yew_vcomp_scope", Span::call_site());
let validate_props = if let Some(Props::List(ListProps(vec_props))) = props { let validate_props = if let Some(Props::List(ListProps(vec_props))) = props {
@ -56,6 +91,12 @@ impl ToTokens for HtmlComponent {
quote! { #prop_ref.#label; } 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 // 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 // struct so that attributes can be checked against it
@ -71,12 +112,30 @@ impl ToTokens for HtmlComponent {
quote! { quote! {
#unallocated_prop_ref #unallocated_prop_ref
#check_children
#(#check_props)* #(#check_props)*
} }
} else { } else {
quote! {} quote! {}
}; };
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 {
quote! {}
};
let init_props = if let Some(props) = props { let init_props = if let Some(props) = props {
match props { match props {
Props::List(ListProps(vec_props)) => { Props::List(ListProps(vec_props)) => {
@ -89,6 +148,7 @@ impl ToTokens for HtmlComponent {
quote! { quote! {
<<#ty as ::yew::html::Component>::Properties as ::yew::html::Properties>::builder() <<#ty as ::yew::html::Component>::Properties as ::yew::html::Properties>::builder()
#(#set_props)* #(#set_props)*
#set_children
.build() .build()
} }
} }
@ -96,7 +156,9 @@ impl ToTokens for HtmlComponent {
} }
} else { } else {
quote! { 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()
} }
}; };
@ -117,9 +179,7 @@ impl ToTokens for HtmlComponent {
} }
let #vcomp_scope: ::yew::virtual_dom::vcomp::ScopeHolder<_> = ::std::default::Default::default(); let #vcomp_scope: ::yew::virtual_dom::vcomp::ScopeHolder<_> = ::std::default::Default::default();
::yew::virtual_dom::VNode::VComp( ::yew::virtual_dom::VChild::<#ty, _>::new(#init_props, #vcomp_scope)
::yew::virtual_dom::VComp::new::<#ty>(#init_props, #vcomp_scope)
)
}}); }});
} }
} }
@ -135,13 +195,18 @@ impl HtmlComponent {
Some(cursor) Some(cursor)
} }
fn peek_type(mut cursor: Cursor) -> Option<()> { fn peek_type(mut cursor: Cursor) -> Option<Type> {
let mut colons_optional = true; let mut colons_optional = true;
let mut last_ident = None; let mut last_ident = None;
let mut leading_colon = None;
let mut segments = Punctuated::new();
loop { loop {
let mut post_colons_cursor = cursor; let mut post_colons_cursor = cursor;
if let Some(c) = Self::double_colon(post_colons_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; post_colons_cursor = c;
} else if !colons_optional { } else if !colons_optional {
break; break;
@ -149,7 +214,11 @@ impl HtmlComponent {
if let Some((ident, c)) = post_colons_cursor.ident() { if let Some((ident, c)) = post_colons_cursor.ident() {
cursor = c; cursor = c;
last_ident = Some(ident); last_ident = Some(ident.clone());
segments.push(PathSegment {
ident,
arguments: PathArguments::None,
});
} else { } else {
break; break;
} }
@ -160,46 +229,96 @@ impl HtmlComponent {
let type_str = last_ident?.to_string(); let type_str = last_ident?.to_string();
type_str.is_ascii().as_option()?; 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,
},
}))
} }
} }
pub struct HtmlComponentInner { struct HtmlComponentOpen {
lt: Token![<],
ty: Type, ty: Type,
props: Option<Props>, props: Option<Props>,
} div: Option<Token![/]>,
impl Parse for HtmlComponentInner {
fn parse(input: ParseStream) -> ParseResult<Self> {
let ty = input.parse()?;
// backwards compat
let _ = input.parse::<Token![:]>();
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 })
}
}
struct HtmlComponentTag {
lt: Token![<],
gt: Token![>], gt: Token![>],
} }
impl ToTokens for HtmlComponentTag { impl PeekValue<Type> for HtmlComponentOpen {
fn peek(cursor: Cursor) -> Option<Type> {
let (punct, cursor) = cursor.punct()?;
(punct.as_char() == '<').as_option()?;
HtmlComponent::peek_type(cursor)
}
}
impl Parse for HtmlComponentOpen {
fn parse(input: ParseStream) -> ParseResult<Self> {
let lt = input.parse::<Token![<]>()?;
let ty = input.parse()?;
// backwards compat
let _ = input.parse::<Token![:]>();
let HtmlPropSuffix { stream, div, gt } = input.parse()?;
let props: Option<Props> = parse(stream).ok();
Ok(HtmlComponentOpen {
lt,
ty,
props,
div,
gt,
})
}
}
impl ToTokens for HtmlComponentOpen {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let HtmlComponentTag { lt, gt } = self; let HtmlComponentOpen { lt, gt, .. } = self;
tokens.extend(quote! {#lt#gt}); tokens.extend(quote! {#lt#gt});
} }
} }
struct HtmlComponentClose {
lt: Token![<],
div: Token![/],
ty: Type,
gt: Token![>],
}
impl PeekValue<Type> for HtmlComponentClose {
fn peek(cursor: Cursor) -> Option<Type> {
let (punct, cursor) = cursor.punct()?;
(punct.as_char() == '<').as_option()?;
let (punct, cursor) = cursor.punct()?;
(punct.as_char() == '/').as_option()?;
HtmlComponent::peek_type(cursor)
}
}
impl Parse for HtmlComponentClose {
fn parse(input: ParseStream) -> ParseResult<Self> {
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 HtmlComponentClose { lt, div, ty, gt } = self;
tokens.extend(quote! {#lt#div#ty#gt});
}
}
enum PropType { enum PropType {
List, List,
With, With,
@ -223,6 +342,17 @@ impl PeekValue<PropType> for Props {
} }
} }
impl Parse for Props {
fn parse(input: ParseStream) -> ParseResult<Self> {
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<HtmlProp>); struct ListProps(Vec<HtmlProp>);
impl Parse for ListProps { impl Parse for ListProps {
fn parse(input: ParseStream) -> ParseResult<Self> { fn parse(input: ParseStream) -> ParseResult<Self> {
@ -242,10 +372,18 @@ impl Parse for ListProps {
// alphabetize // alphabetize
props.sort_by(|a, b| { props.sort_by(|a, b| {
a.label if a.label == b.label {
.to_string() Ordering::Equal
.partial_cmp(&b.label.to_string()) } else if a.label.to_string() == "children" {
.unwrap() 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)) Ok(ListProps(props))

View File

@ -18,7 +18,7 @@ use html_prop::HtmlProp;
use html_prop::HtmlPropSuffix; use html_prop::HtmlPropSuffix;
use html_tag::HtmlTag; use html_tag::HtmlTag;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::ToTokens; use quote::{quote, ToTokens};
use syn::buffer::Cursor; use syn::buffer::Cursor;
use syn::parse::{Parse, ParseStream, Result}; use syn::parse::{Parse, ParseStream, Result};
@ -105,17 +105,36 @@ impl PeekValue<HtmlType> for HtmlTree {
impl ToTokens for HtmlTree { impl ToTokens for HtmlTree {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let empty_html_el = HtmlList(Vec::new()); let node = self.token_stream();
let html_tree_el: &dyn ToTokens = match self { tokens.extend(quote! {
HtmlTree::Empty => &empty_html_el, ::yew::virtual_dom::VNode::from(#node)
HtmlTree::Component(comp) => comp, });
HtmlTree::Tag(tag) => tag, }
HtmlTree::List(list) => list, }
HtmlTree::Node(node) => node,
HtmlTree::Iterable(iterable) => iterable, impl HtmlTree {
HtmlTree::Block(block) => block, fn token_stream(&self) -> proc_macro2::TokenStream {
}; match self {
HtmlTree::Empty => HtmlList(Vec::new()).into_token_stream(),
html_tree_el.to_tokens(tokens); 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<Self> {
Ok(HtmlTreeNested(HtmlTree::parse(input)?))
}
}
impl ToTokens for HtmlTreeNested {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
tokens.extend(self.0.token_stream());
} }
} }

View File

@ -0,0 +1,8 @@
[package]
name = "nested_list"
version = "0.1.0"
authors = ["Justin Starry <justin.starry@icloud.com>"]
edition = "2018"
[dependencies]
yew = { path = "../.." }

View File

@ -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<Hovered>,
#[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>) -> 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<ListHeader> for ListHeader {
fn view(&self) -> Html<Self> {
html! {
<div class="list-header" onmouseover=|_| Msg::Hover>
{ &self.props.text }
</div>
}
}
}

View File

@ -0,0 +1,66 @@
use crate::list::Hovered;
use yew::html::Children;
use yew::prelude::*;
pub struct ListItem {
props: Props,
}
#[derive(Properties)]
pub struct Props {
pub hide: bool,
#[props(required)]
pub on_hover: Callback<Hovered>,
#[props(required)]
pub name: String,
pub children: Children<ListItem>,
}
pub enum Msg {
Hover,
}
impl Component for ListItem {
type Message = Msg;
type Properties = Props;
fn create(props: Self::Properties, _: ComponentLink<Self>) -> 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<ListItem> for ListItem {
fn view(&self) -> Html<Self> {
html! {
<div class="list-item" onmouseover=|_| Msg::Hover>
{ &self.props.name }
{ self.view_details() }
</div>
}
}
}
impl ListItem {
fn view_details(&self) -> Html<Self> {
if self.props.children.is_empty() {
return html! {};
}
html! {
<div class="list-item-details">
{ self.props.children.view() }
</div>
}
}
}

View File

@ -0,0 +1,43 @@
#![recursion_limit = "128"]
mod header;
mod item;
mod list;
use header::ListHeader;
use item::ListItem;
use list::{List, Msg as ListMsg};
use yew::prelude::*;
pub struct Model;
impl Component for Model {
type Message = ();
type Properties = ();
fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
Model
}
fn update(&mut self, _: Self::Message) -> ShouldRender {
true
}
}
impl Renderable<Model> for Model {
fn view(&self) -> Html<Self> {
html! {
<div class="main">
<h1>{ "Nested List Demo" }</h1>
<List>
<ListHeader text="Calling all Rusties!" on_hover=ListMsg::Hover />
<ListItem name="Rustin" on_hover=ListMsg::Hover />
<ListItem hide={true} name="Rustaroo" on_hover=ListMsg::Hover />
<ListItem name="Rustifer" on_hover=ListMsg::Hover>
<span>{"Hello!"}</span>
</ListItem>
</List>
</div>
}
}
}

View File

@ -0,0 +1,164 @@
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,
}
pub enum Msg {
Hover(Hovered),
}
pub enum Variants {
Item(<ListItem as Component>::Properties),
Header(<ListHeader as Component>::Properties),
}
impl From<ItemProps> for Variants {
fn from(props: ItemProps) -> Self {
Variants::Item(props)
}
}
impl From<HeaderProps> for Variants {
fn from(props: HeaderProps) -> Self {
Variants::Header(props)
}
}
pub struct ListVariant {
props: Variants,
scope: ScopeHolder<List>,
}
#[derive(Properties)]
pub struct Props {
#[props(required)]
pub children: ChildrenRenderer<ListVariant>,
}
pub struct List {
props: Props,
hovered: Hovered,
}
impl Component for List {
type Message = Msg;
type Properties = Props;
fn create(props: Self::Properties, _: ComponentLink<Self>) -> 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<List> for List {
fn view(&self) -> Html<Self> {
html! {
<div
class="list-container"
onmouseout=|_| Msg::Hover(Hovered::None)
onmouseover=|_| Msg::Hover(Hovered::List)
>
<div class="list">
{self.view_header()}
<div class="items">
{self.view_items()}
</div>
</div>
{self.view_last_hovered()}
</div>
}
}
}
impl List {
fn view_header(&self) -> Html<Self> {
html! {{
for self.props.children.iter().filter(|c| match c.props {
Variants::Header(_) => true,
_ => false
})
}}
}
fn view_items(&self) -> Html<Self> {
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<Self> {
html! {
<div class="last-hovered">
{ "Last hovered:"}
<span class="last-hovered-text">
{ &self.hovered }
</span>
</div>
}
}
}
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<CHILD> From<VChild<CHILD, List>> for ListVariant
where
CHILD: Component + Renderable<CHILD>,
CHILD::Properties: Into<Variants>,
{
fn from(vchild: VChild<CHILD, List>) -> Self {
ListVariant {
props: vchild.props.into(),
scope: vchild.scope,
}
}
}
impl Into<VNode<List>> for ListVariant {
fn into(self) -> VNode<List> {
match self.props {
Variants::Header(props) => VComp::new::<ListHeader>(props, self.scope).into(),
Variants::Item(props) => VComp::new::<ListItem>(props, self.scope).into(),
}
}
}

View File

@ -0,0 +1,3 @@
fn main() {
yew::start_app::<nested_list::Model>();
}

View File

@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Yew • Nested List</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<script src="/nested_list.js"></script>
</body>
</html>

View File

@ -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;
}

View File

@ -11,7 +11,7 @@ pub(crate) use scope::ComponentUpdate;
pub use scope::{NodeCell, Scope}; pub use scope::{NodeCell, Scope};
use crate::callback::Callback; use crate::callback::Callback;
use crate::virtual_dom::VNode; use crate::virtual_dom::{VChild, VList, VNode};
/// This type indicates that component should be rendered again. /// This type indicates that component should be rendered again.
pub type ShouldRender = bool; pub type ShouldRender = bool;
@ -48,6 +48,128 @@ pub trait Component: Sized + 'static {
/// A type which expected as a result of `view` function implementation. /// A type which expected as a result of `view` function implementation.
pub type Html<MSG> = VNode<MSG>; pub type Html<MSG> = VNode<MSG>;
/// 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!{
/// <Wrapper>
/// <h4> {"Hi"} </h4>
/// <div> {"Hello"} </div>
/// </Wrapper>
/// }
/// ```
///
/// **`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<Wrapper>,
/// }
///
/// html!{
/// <div id="container">
/// { self.props.children.view() }
/// </div>
/// }
/// ```
pub type Children<T> = ChildrenRenderer<Html<T>>;
/// 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>
/// <ListItem value="a" />
/// <ListItem value="b" />
/// <ListItem value="c" />
/// </List>
/// }
/// ```
///
/// **`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<ListItem, List>,
/// }
///
/// html!{{
/// for self.props.children.iter().map(|mut item| {
/// item.props.value = format!("item-{}", item.props.value);
/// item
/// })
/// }}
/// ```
pub type ChildrenWithProps<C, P> = ChildrenRenderer<VChild<C, P>>;
/// A type used for rendering children html.
pub struct ChildrenRenderer<T> {
len: usize,
boxed_render: Box<dyn Fn() -> Vec<T>>,
}
impl<T> ChildrenRenderer<T> {
/// Create children
pub fn new(len: usize, boxed_render: Box<dyn Fn() -> Vec<T>>) -> 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`
pub fn to_vec(&self) -> Vec<T> {
(&self.boxed_render)()
}
/// Render children components and return `Iterator`
pub fn iter(&self) -> impl Iterator<Item = T> {
(&self.boxed_render)().into_iter()
}
}
impl<T> Default for ChildrenRenderer<T> {
fn default() -> Self {
Self {
len: 0,
boxed_render: Box::new(|| Vec::new()),
}
}
}
impl<T, COMP: Component> Renderable<COMP> for ChildrenRenderer<T>
where
T: Into<VNode<COMP>>,
{
fn view(&self) -> Html<COMP> {
VList {
childs: self.iter().map(|c| c.into()).collect(),
}
.into()
}
}
/// Should be rendered relative to context and component environment. /// Should be rendered relative to context and component environment.
pub trait Renderable<COMP: Component> { pub trait Renderable<COMP: Component> {
/// Called by rendering loop. /// Called by rendering loop.

View File

@ -149,7 +149,8 @@ pub mod prelude {
pub use crate::callback::Callback; pub use crate::callback::Callback;
pub use crate::events::*; pub use crate::events::*;
pub use crate::html::{ pub use crate::html::{
Component, ComponentLink, Href, Html, Properties, Renderable, ShouldRender, Children, ChildrenWithProps, Component, ComponentLink, Href, Html, Properties, Renderable,
ShouldRender,
}; };
pub use crate::macros::*; pub use crate::macros::*;
pub use crate::virtual_dom::Classes; pub use crate::virtual_dom::Classes;

View File

@ -11,7 +11,7 @@ use std::collections::HashMap;
use std::fmt; use std::fmt;
use stdweb::web::{Element, EventListenerHandle, Node}; 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::vlist::VList;
pub use self::vnode::VNode; pub use self::vnode::VNode;
pub use self::vtag::VTag; pub use self::vtag::VTag;

View File

@ -31,6 +31,35 @@ pub struct VComp<COMP: Component> {
state: Rc<RefCell<MountState<COMP>>>, state: Rc<RefCell<MountState<COMP>>>,
} }
/// A virtual child component.
pub struct VChild<SELF: Component, PARENT: Component> {
/// The component properties
pub props: SELF::Properties,
/// The parent component scope
pub scope: ScopeHolder<PARENT>,
}
impl<SELF, PARENT> VChild<SELF, PARENT>
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<PARENT>) -> Self {
Self { props, scope }
}
}
impl<COMP, CHILD> From<VChild<CHILD, COMP>> for VComp<COMP>
where
COMP: Component,
CHILD: Component + Renderable<CHILD>,
{
fn from(vchild: VChild<CHILD, COMP>) -> Self {
VComp::new::<CHILD>(vchild.props, vchild.scope)
}
}
enum MountState<COMP: Component> { enum MountState<COMP: Component> {
Unmounted(Unmounted<COMP>), Unmounted(Unmounted<COMP>),
Mounted(Mounted), Mounted(Mounted),

View File

@ -1,6 +1,6 @@
//! This module contains the implementation of abstract virtual node. //! 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 crate::html::{Component, Renderable, Scope};
use std::cmp::PartialEq; use std::cmp::PartialEq;
use std::fmt; use std::fmt;
@ -96,6 +96,16 @@ impl<COMP: Component> From<VComp<COMP>> for VNode<COMP> {
} }
} }
impl<COMP, CHILD> From<VChild<CHILD, COMP>> for VNode<COMP>
where
COMP: Component,
CHILD: Component + Renderable<CHILD>,
{
fn from(vchild: VChild<CHILD, COMP>) -> Self {
VNode::VComp(VComp::from(vchild))
}
}
impl<COMP: Component, T: ToString> From<T> for VNode<COMP> { impl<COMP: Component, T: ToString> From<T> for VNode<COMP> {
fn from(value: T) -> Self { fn from(value: T) -> Self {
VNode::VText(VText::new(value.to_string())) VNode::VText(VText::new(value.to_string()))

View File

@ -12,7 +12,6 @@ macro_rules! pass_helper {
( $($content:tt)* ) => { ( $($content:tt)* ) => {
mod test_component; mod test_component;
use test_component::TestComponent; use test_component::TestComponent;
// #[allow(unused_imports)]
use yew::prelude::*; use yew::prelude::*;
impl Renderable<TestComponent> for TestComponent { impl Renderable<TestComponent> for TestComponent {
fn view(&self) -> Html<Self> { fn view(&self) -> Html<Self> {

View File

@ -1,7 +1,11 @@
use yew::prelude::*; use yew::prelude::*;
fn compile_fail() { fn compile_fail() {
html! { () }; html! {
<>
{ () }
</>
};
let not_tree = || (); let not_tree = || ();
html! { html! {

View File

@ -1,8 +1,8 @@
error[E0277]: `()` doesn't implement `std::fmt::Display` error[E0277]: `()` doesn't implement `std::fmt::Display`
--> $DIR/html-block-fail.rs:4:13 --> $DIR/html-block-fail.rs:6:15
| |
4 | html! { () }; 6 | { () }
| ^^ `()` cannot be formatted with the default formatter | ^^ `()` cannot be formatted with the default formatter
| |
= help: the trait `std::fmt::Display` is not implemented for `()` = 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: 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` = note: required by `std::convert::From::from`
error[E0277]: `()` doesn't implement `std::fmt::Display` error[E0277]: `()` doesn't implement `std::fmt::Display`
--> $DIR/html-block-fail.rs:8:16 --> $DIR/html-block-fail.rs:12:16
|
8 | <div>{ not_tree() }</div>
| ^^^^^^^^ `()` 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
| |
11 | <>{ for (0..3).map(|_| not_tree()) }</> 12 | <div>{ not_tree() }</div>
| ^^^^^^^^ `()` 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:15:17
|
15 | <>{ for (0..3).map(|_| not_tree()) }</>
| ^^^^^^ `()` cannot be formatted with the default formatter | ^^^^^^ `()` cannot be formatted with the default formatter
| |
= help: the trait `std::fmt::Display` is not implemented for `()` = help: the trait `std::fmt::Display` is not implemented for `()`

View File

@ -9,13 +9,13 @@ pub struct ChildProperties {
pub int: i32, pub int: i32,
} }
pub struct ChildComponent; pub struct Child;
impl Component for ChildComponent { impl Component for Child {
type Message = (); type Message = ();
type Properties = ChildProperties; type Properties = ChildProperties;
fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self { fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self {
ChildComponent Child
} }
fn update(&mut self, _: Self::Message) -> ShouldRender { fn update(&mut self, _: Self::Message) -> ShouldRender {
@ -23,29 +23,63 @@ impl Component for ChildComponent {
} }
} }
impl Renderable<ChildComponent> for ChildComponent { impl Renderable<Self> for Child {
fn view(&self) -> Html<Self> {
unimplemented!()
}
}
#[derive(Properties)]
pub struct ChildContainerProperties {
pub children: ChildrenWithProps<Child, ChildContainer>,
}
pub struct ChildContainer;
impl Component for ChildContainer {
type Message = ();
type Properties = ChildContainerProperties;
fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self {
ChildContainer
}
fn update(&mut self, _: Self::Message) -> ShouldRender {
unimplemented!()
}
}
impl Renderable<Self> for ChildContainer {
fn view(&self) -> Html<Self> { fn view(&self) -> Html<Self> {
unimplemented!() unimplemented!()
} }
} }
fn compile_fail() { fn compile_fail() {
html! { <ChildComponent> }; html! { <Child> };
html! { <ChildComponent:: /> }; html! { <Child:: /> };
html! { <ChildComponent with /> }; html! { <Child with /> };
html! { <ChildComponent props /> }; html! { <Child props /> };
html! { <ChildComponent with props > }; html! { <Child with props > };
html! { <ChildComponent with blah /> }; html! { <Child with blah /> };
html! { <ChildComponent with props () /> }; html! { <Child with props () /> };
html! { <ChildComponent type=0 /> }; html! { <Child type=0 /> };
html! { <ChildComponent invalid-prop-name=0 /> }; html! { <Child invalid-prop-name=0 /> };
html! { <ChildComponent unknown="unknown" /> }; html! { <Child unknown="unknown" /> };
html! { <ChildComponent string= /> }; html! { <Child string= /> };
html! { <ChildComponent int=1 string={} /> }; html! { <Child int=1 string={} /> };
html! { <ChildComponent int=1 string=3 /> }; html! { <Child int=1 string=3 /> };
html! { <ChildComponent int=1 string={3} /> }; html! { <Child int=1 string={3} /> };
html! { <ChildComponent int=0u32 /> }; html! { <Child int=0u32 /> };
html! { <ChildComponent string="abc" /> }; html! { <Child string="abc" /> };
html! { </Child> };
html! { <Child><Child></Child> };
html! { <Child></Child><Child></Child> };
html! { <Child>{ "Not allowed" }</Child> };
html! { <ChildContainer>{ "Not allowed" }</ChildContainer> };
html! { <ChildContainer><></></ChildContainer> };
html! { <ChildContainer><ChildContainer /></ChildContainer> };
html! { <ChildContainer><ChildContainer /></ChildContainer> };
html! { <ChildContainer><Child int=1 /><other /></ChildContainer> };
} }
fn main() {} fn main() {}

View File

@ -1,127 +1,231 @@
error: expected component tag be of form `< .. />` 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! { <ChildComponent> }; 58 | html! { <Child> };
| ^^^^^^^^^^^^^^^^ | ^^^^^^^
error: unexpected end of input, expected identifier
--> $DIR/html-component-fail.rs:34:31
|
34 | html! { <ChildComponent:: /> };
| ^
error: unexpected end of input, expected identifier
--> $DIR/html-component-fail.rs:35:34
|
35 | html! { <ChildComponent with /> };
| ^
error: unexpected token
--> $DIR/html-component-fail.rs:36:29
|
36 | html! { <ChildComponent props /> };
| ^^^^^
error: expected component tag be of form `< .. />`
--> $DIR/html-component-fail.rs:37:13
|
37 | html! { <ChildComponent with props > };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: unexpected token
--> $DIR/html-component-fail.rs:39:40
|
39 | html! { <ChildComponent with props () /> };
| ^^
error: expected identifier error: expected identifier
--> $DIR/html-component-fail.rs:40:29 --> $DIR/html-component-fail.rs:59:22
| |
40 | html! { <ChildComponent type=0 /> }; 59 | html! { <Child:: /> };
| ^^^^ | ^
error: expected identifier error: this open tag has no corresponding close tag
--> $DIR/html-component-fail.rs:41:29 --> $DIR/html-component-fail.rs:62:13
| |
41 | html! { <ChildComponent invalid-prop-name=0 /> }; 62 | html! { <Child with props > };
| ^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^
error: unexpected end of input, expected expression error: expected type, found `/`
--> $DIR/html-component-fail.rs:43:37 --> $DIR/html-component-fail.rs:74:14
| |
43 | html! { <ChildComponent string= /> }; 74 | html! { </Child> };
| ^ | ^
error: this open tag has no corresponding close tag
--> $DIR/html-component-fail.rs:75:13
|
75 | html! { <Child><Child></Child> };
| ^^^^^^^
error: only one root html element allowed
--> $DIR/html-component-fail.rs:76:28
|
76 | html! { <Child></Child><Child></Child> };
| ^^^^^^^^^^^^^^^
error[E0425]: cannot find value `blah` in this scope 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! { <ChildComponent with blah /> }; 63 | html! { <Child with blah /> };
| ^^^^ not found in this scope | ^^^^ not found in this scope
error[E0599]: no method named `build` found for type `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
--> $DIR/html-component-fail.rs:60:5
|
5 | #[derive(Properties, PartialEq)]
| - method `build` not found for this
...
60 | html! { <Child with /> };
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= 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<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
--> $DIR/html-component-fail.rs:61:5
|
5 | #[derive(Properties, PartialEq)]
| - method `build` not found for this
...
61 | html! { <Child props /> };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= 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<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
--> $DIR/html-component-fail.rs:64:5
|
5 | #[derive(Properties, PartialEq)]
| - method `build` not found for this
...
64 | html! { <Child with props () /> };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= 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<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
--> $DIR/html-component-fail.rs:65:5
|
5 | #[derive(Properties, PartialEq)]
| - method `build` not found for this
...
65 | html! { <Child type=0 /> };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= 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<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
--> $DIR/html-component-fail.rs:66:5
|
5 | #[derive(Properties, PartialEq)]
| - method `build` not found for this
...
66 | html! { <Child invalid-prop-name=0 /> };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= 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` error[E0609]: no field `unknown` on type `ChildProperties`
--> $DIR/html-component-fail.rs:42:29 --> $DIR/html-component-fail.rs:67:20
| |
42 | html! { <ChildComponent unknown="unknown" /> }; 67 | html! { <Child unknown="unknown" /> };
| ^^^^^^^ unknown field | ^^^^^^^ unknown field
| |
= note: available fields are: `string`, `int` = note: available fields are: `string`, `int`
error[E0599]: no method named `unknown` found for type `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope error[E0599]: no method named `unknown` found for type `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
--> $DIR/html-component-fail.rs:42:29 --> $DIR/html-component-fail.rs:67:20
| |
5 | #[derive(Properties, PartialEq)] 5 | #[derive(Properties, PartialEq)]
| - method `unknown` not found for this | - method `unknown` not found for this
... ...
42 | html! { <ChildComponent unknown="unknown" /> }; 67 | html! { <Child unknown="unknown" /> };
| ^^^^^^^ | ^^^^^^^
error[E0599]: no method named `build` found for type `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
--> $DIR/html-component-fail.rs:68:5
|
5 | #[derive(Properties, PartialEq)]
| - method `build` not found for this
...
68 | html! { <Child string= /> };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= 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 error[E0308]: mismatched types
--> $DIR/html-component-fail.rs:44:42 --> $DIR/html-component-fail.rs:69:33
| |
44 | html! { <ChildComponent int=1 string={} /> }; 69 | html! { <Child int=1 string={} /> };
| ^^ expected struct `std::string::String`, found () | ^^ expected struct `std::string::String`, found ()
| |
= note: expected type `std::string::String` = note: expected type `std::string::String`
found type `()` found type `()`
error[E0308]: mismatched types error[E0308]: mismatched types
--> $DIR/html-component-fail.rs:45:42 --> $DIR/html-component-fail.rs:70:33
| |
45 | html! { <ChildComponent int=1 string=3 /> }; 70 | html! { <Child int=1 string=3 /> };
| ^ | ^
| | | |
| expected struct `std::string::String`, found integer | expected struct `std::string::String`, found integer
| help: try using a conversion method: `3.to_string()` | help: try using a conversion method: `3.to_string()`
| |
= note: expected type `std::string::String` = note: expected type `std::string::String`
found type `{integer}` found type `{integer}`
error[E0308]: mismatched types error[E0308]: mismatched types
--> $DIR/html-component-fail.rs:46:42 --> $DIR/html-component-fail.rs:71:33
| |
46 | html! { <ChildComponent int=1 string={3} /> }; 71 | html! { <Child int=1 string={3} /> };
| ^^^ | ^^^
| | | |
| expected struct `std::string::String`, found integer | expected struct `std::string::String`, found integer
| help: try using a conversion method: `{3}.to_string()` | help: try using a conversion method: `{3}.to_string()`
| |
= note: expected type `std::string::String` = note: expected type `std::string::String`
found type `{integer}` found type `{integer}`
error[E0308]: mismatched types error[E0308]: mismatched types
--> $DIR/html-component-fail.rs:47:33 --> $DIR/html-component-fail.rs:72:24
| |
47 | html! { <ChildComponent int=0u32 /> }; 72 | html! { <Child int=0u32 /> };
| ^^^^ expected i32, found u32 | ^^^^ expected i32, found u32
error[E0599]: no method named `string` found for type `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope error[E0599]: no method named `string` found for type `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
--> $DIR/html-component-fail.rs:48:29 --> $DIR/html-component-fail.rs:73:20
| |
5 | #[derive(Properties, PartialEq)] 5 | #[derive(Properties, PartialEq)]
| - method `string` not found for this | - method `string` not found for this
... ...
48 | html! { <ChildComponent string="abc" /> }; 73 | html! { <Child string="abc" /> };
| ^^^^^^ | ^^^^^^
Some errors occurred: E0308, E0425, E0599, E0609. error[E0599]: no method named `children` found for type `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
For more information about an error, try `rustc --explain E0308`. --> $DIR/html-component-fail.rs:77:5
|
5 | #[derive(Properties, PartialEq)]
| - method `children` not found for this
...
77 | html! { <Child>{ "Not allowed" }</Child> };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= 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<Child, ChildContainer>: std::convert::From<yew::virtual_dom::vnode::VNode<_>>` is not satisfied
--> $DIR/html-component-fail.rs:78:5
|
78 | html! { <ChildContainer>{ "Not allowed" }</ChildContainer> };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From<yew::virtual_dom::vnode::VNode<_>>` is not implemented for `yew::virtual_dom::vcomp::VChild<Child, ChildContainer>`
|
= note: required because of the requirements on the impl of `std::convert::Into<yew::virtual_dom::vcomp::VChild<Child, ChildContainer>>` 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<Child, ChildContainer>: std::convert::From<yew::virtual_dom::vnode::VNode<_>>` is not satisfied
--> $DIR/html-component-fail.rs:79:5
|
79 | html! { <ChildContainer><></></ChildContainer> };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From<yew::virtual_dom::vnode::VNode<_>>` is not implemented for `yew::virtual_dom::vcomp::VChild<Child, ChildContainer>`
|
= note: required because of the requirements on the impl of `std::convert::Into<yew::virtual_dom::vcomp::VChild<Child, ChildContainer>>` 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<Child, ChildContainer>: std::convert::From<yew::virtual_dom::vcomp::VChild<ChildContainer, _>>` is not satisfied
--> $DIR/html-component-fail.rs:80:5
|
80 | html! { <ChildContainer><ChildContainer /></ChildContainer> };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From<yew::virtual_dom::vcomp::VChild<ChildContainer, _>>` is not implemented for `yew::virtual_dom::vcomp::VChild<Child, ChildContainer>`
|
= note: required because of the requirements on the impl of `std::convert::Into<yew::virtual_dom::vcomp::VChild<Child, ChildContainer>>` for `yew::virtual_dom::vcomp::VChild<ChildContainer, _>`
= 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<Child, ChildContainer>: std::convert::From<yew::virtual_dom::vcomp::VChild<ChildContainer, _>>` is not satisfied
--> $DIR/html-component-fail.rs:81:5
|
81 | html! { <ChildContainer><ChildContainer /></ChildContainer> };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From<yew::virtual_dom::vcomp::VChild<ChildContainer, _>>` is not implemented for `yew::virtual_dom::vcomp::VChild<Child, ChildContainer>`
|
= note: required because of the requirements on the impl of `std::convert::Into<yew::virtual_dom::vcomp::VChild<Child, ChildContainer>>` for `yew::virtual_dom::vcomp::VChild<ChildContainer, _>`
= 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<Child, ChildContainer>: std::convert::From<yew::virtual_dom::vnode::VNode<_>>` is not satisfied
--> $DIR/html-component-fail.rs:82:5
|
82 | html! { <ChildContainer><Child int=1 /><other /></ChildContainer> };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From<yew::virtual_dom::vnode::VNode<_>>` is not implemented for `yew::virtual_dom::vcomp::VChild<Child, ChildContainer>`
|
= note: required because of the requirements on the impl of `std::convert::Into<yew::virtual_dom::vcomp::VChild<Child, ChildContainer>>` 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: E0277, E0308, E0425, E0599, E0609.
For more information about an error, try `rustc --explain E0277`.

View File

@ -1,8 +1,10 @@
#![recursion_limit = "128"] #![recursion_limit = "256"]
#[macro_use] #[macro_use]
mod helpers; mod helpers;
use yew::html::ChildrenRenderer;
#[derive(Properties, Default, PartialEq)] #[derive(Properties, Default, PartialEq)]
pub struct ChildProperties { pub struct ChildProperties {
pub string: String, pub string: String,
@ -12,13 +14,13 @@ pub struct ChildProperties {
pub optional_callback: Option<Callback<()>>, pub optional_callback: Option<Callback<()>>,
} }
pub struct ChildComponent; pub struct Child;
impl Component for ChildComponent { impl Component for Child {
type Message = (); type Message = ();
type Properties = ChildProperties; type Properties = ChildProperties;
fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self { fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
ChildComponent Child
} }
fn update(&mut self, _: Self::Message) -> ShouldRender { fn update(&mut self, _: Self::Message) -> ShouldRender {
@ -26,66 +28,159 @@ impl Component for ChildComponent {
} }
} }
impl Renderable<ChildComponent> for ChildComponent { impl Renderable<Child> for Child {
fn view(&self) -> Html<Self> {
unimplemented!()
}
}
#[derive(Properties, Default)]
pub struct ContainerProperties {
#[props(required)]
pub int: i32,
pub children: Children<Container>,
}
pub struct Container;
impl Component for Container {
type Message = ();
type Properties = ContainerProperties;
fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
Container
}
fn update(&mut self, _: Self::Message) -> ShouldRender {
unimplemented!()
}
}
impl Renderable<Self> for Container {
fn view(&self) -> Html<Self> {
unimplemented!()
}
}
#[derive(Properties, Default)]
pub struct ChildContainerProperties {
#[props(required)]
pub int: i32,
pub children: ChildrenWithProps<Child, ChildContainer>,
}
pub struct ChildContainer;
impl Component for ChildContainer {
type Message = ();
type Properties = ChildContainerProperties;
fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
ChildContainer
}
fn update(&mut self, _: Self::Message) -> ShouldRender {
unimplemented!()
}
}
impl Renderable<Self> for ChildContainer {
fn view(&self) -> Html<Self> { fn view(&self) -> Html<Self> {
unimplemented!() unimplemented!()
} }
} }
mod scoped { mod scoped {
pub use super::ChildComponent; pub use super::Child;
pub use super::Container;
} }
pass_helper! { pass_helper! {
html! { <ChildComponent int=1 /> }; html! { <Child int=1 /> };
// backwards compat // backwards compat
html! { <ChildComponent: int=1 /> }; html! { <Child: int=1 /> };
html! { html! {
<> <>
<ChildComponent int=1 /> <Child int=1 />
<scoped::ChildComponent int=1 /> <scoped::Child int=1 />
// backwards compat // backwards compat
<ChildComponent: int=1 /> <Child: int=1 />
<scoped::ChildComponent: int=1 /> <scoped::Child: int=1 />
</> </>
}; };
let props = <ChildComponent as Component>::Properties::default(); let props = <Child as Component>::Properties::default();
let props2 = <ChildComponent as Component>::Properties::default(); let props2 = <Child as Component>::Properties::default();
html! { html! {
<> <>
<ChildComponent with props /> <Child with props />
// backwards compat // backwards compat
<ChildComponent: with props2, /> <Child: with props2, />
</> </>
}; };
html! { html! {
<> <>
<ChildComponent int=1 string="child" /> <Child int=1 string="child" />
<ChildComponent int=1 /> <Child int=1 />
<ChildComponent int={1+1} /> <Child int={1+1} />
<ChildComponent int=1 vec={vec![1]} /> <Child int=1 vec={vec![1]} />
<ChildComponent string={String::from("child")} int=1 /> <Child string={String::from("child")} int=1 />
// backwards compat // backwards compat
<ChildComponent: string="child", int=3, /> <Child: string="child", int=3, />
</> </>
}; };
let name_expr = "child"; let name_expr = "child";
html! { html! {
<ChildComponent int=1 string=name_expr /> <Child int=1 string=name_expr />
}; };
html! { html! {
<> <>
<ChildComponent int=1 /> <Child int=1 />
<ChildComponent int=1 optional_callback=|_| () /> <Child int=1 optional_callback=|_| () />
</>
};
let props = <Container as Component>::Properties::default();
html! {
<>
<Container int=1 />
<Container int=1></Container>
<Container with props>
<></>
</Container>
<Container int=1>
<Child int=2 />
</Container>
<scoped::Container int=1>
<scoped::Container int=2/>
</scoped::Container>
<Container int=1 children=ChildrenRenderer::new(
1,
::std::boxed::Box::new(move || {
|| -> ::std::vec::Vec<_> {
vec![html!{ "String" }]
}
}()),
) />
</>
};
html! {
<>
<ChildContainer int=1 />
<ChildContainer int=1></ChildContainer>
<ChildContainer int=1><Child int = 2 /></ChildContainer>
<ChildContainer int=1><Child int = 2 /><Child int = 2 /></ChildContainer>
</> </>
}; };
} }