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/mount_point",
"examples/multi_thread",
"examples/nested_list",
"examples/npm_and_rest",
"examples/routing",
"examples/server",

View File

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

View File

@ -170,13 +170,29 @@ impl TryFrom<Field> for PropField {
impl PartialOrd for PropField {
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 {
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::HtmlPropSuffix;
use super::HtmlTreeNested;
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};
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<Props>,
children: Vec<HtmlTreeNested>,
}
impl PeekValue<()> for HtmlComponent {
fn peek(cursor: Cursor) -> Option<()> {
let (punct, cursor) = cursor.punct()?;
(punct.as_char() == '<').as_option()?;
HtmlComponent::peek_type(cursor)
HtmlComponent::peek_type(cursor)?;
Some(())
}
}
impl Parse for HtmlComponent {
fn parse(input: ParseStream) -> ParseResult<Self> {
let lt = input.parse::<Token![<]>()?;
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::<HtmlComponentClose>() {
Ok(close) => Err(syn::Error::new_spanned(
close,
"this close tag has no corresponding open tag",
)),
Err(err) => Err(err),
};
}
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 open = input.parse::<HtmlComponentOpen>()?;
// 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(),
});
}
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 {
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 +91,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 +112,30 @@ impl ToTokens for HtmlComponent {
quote! {
#unallocated_prop_ref
#check_children
#(#check_props)*
}
} else {
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 {
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()
}
};
@ -117,9 +179,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)
}});
}
}
@ -135,13 +195,18 @@ impl HtmlComponent {
Some(cursor)
}
fn peek_type(mut cursor: Cursor) -> Option<()> {
fn peek_type(mut cursor: Cursor) -> Option<Type> {
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 +214,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,46 +229,96 @@ 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,
},
}))
}
}
pub struct HtmlComponentInner {
struct HtmlComponentOpen {
lt: Token![<],
ty: Type,
props: Option<Props>,
}
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![<],
div: Option<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) {
let HtmlComponentTag { lt, gt } = self;
let HtmlComponentOpen { lt, gt, .. } = self;
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 {
List,
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>);
impl Parse for ListProps {
fn parse(input: ParseStream) -> ParseResult<Self> {
@ -242,10 +372,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))

View File

@ -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<HtmlType> 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,
};
html_tree_el.to_tokens(tokens);
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<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};
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.
pub type ShouldRender = bool;
@ -48,6 +48,128 @@ pub trait Component: Sized + 'static {
/// A type which expected as a result of `view` function implementation.
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.
pub trait Renderable<COMP: Component> {
/// Called by rendering loop.

View File

@ -149,7 +149,8 @@ pub mod prelude {
pub use crate::callback::Callback;
pub use crate::events::*;
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::virtual_dom::Classes;

View File

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

View File

@ -31,6 +31,35 @@ pub struct VComp<COMP: Component> {
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> {
Unmounted(Unmounted<COMP>),
Mounted(Mounted),

View File

@ -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;
@ -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> {
fn from(value: T) -> Self {
VNode::VText(VText::new(value.to_string()))

View File

@ -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<TestComponent> for TestComponent {
fn view(&self) -> Html<Self> {

View File

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

View File

@ -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 | <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
--> $DIR/html-block-fail.rs:12:16
|
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
|
= help: the trait `std::fmt::Display` is not implemented for `()`

View File

@ -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>) -> Self {
ChildComponent
Child
}
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> {
unimplemented!()
}
}
fn compile_fail() {
html! { <ChildComponent> };
html! { <ChildComponent:: /> };
html! { <ChildComponent with /> };
html! { <ChildComponent props /> };
html! { <ChildComponent with props > };
html! { <ChildComponent with blah /> };
html! { <ChildComponent with props () /> };
html! { <ChildComponent type=0 /> };
html! { <ChildComponent invalid-prop-name=0 /> };
html! { <ChildComponent unknown="unknown" /> };
html! { <ChildComponent string= /> };
html! { <ChildComponent int=1 string={} /> };
html! { <ChildComponent int=1 string=3 /> };
html! { <ChildComponent int=1 string={3} /> };
html! { <ChildComponent int=0u32 /> };
html! { <ChildComponent string="abc" /> };
html! { <Child> };
html! { <Child:: /> };
html! { <Child with /> };
html! { <Child props /> };
html! { <Child with props > };
html! { <Child with blah /> };
html! { <Child with props () /> };
html! { <Child type=0 /> };
html! { <Child invalid-prop-name=0 /> };
html! { <Child unknown="unknown" /> };
html! { <Child string= /> };
html! { <Child int=1 string={} /> };
html! { <Child int=1 string=3 /> };
html! { <Child int=1 string={3} /> };
html! { <Child int=0u32 /> };
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() {}

View File

@ -1,127 +1,231 @@
error: expected component tag be of form `< .. />`
--> $DIR/html-component-fail.rs:33:13
error: this open tag has no corresponding close tag
--> $DIR/html-component-fail.rs:58:13
|
33 | html! { <ChildComponent> };
| ^^^^^^^^^^^^^^^^
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 () /> };
| ^^
58 | html! { <Child> };
| ^^^^^^^
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
--> $DIR/html-component-fail.rs:41:29
error: this open tag has no corresponding close tag
--> $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
--> $DIR/html-component-fail.rs:43:37
error: expected type, found `/`
--> $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
--> $DIR/html-component-fail.rs:38:34
--> $DIR/html-component-fail.rs:63:25
|
38 | html! { <ChildComponent with blah /> };
| ^^^^ not found in this scope
63 | html! { <Child with blah /> };
| ^^^^ 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`
--> $DIR/html-component-fail.rs:42:29
--> $DIR/html-component-fail.rs:67:20
|
42 | html! { <ChildComponent unknown="unknown" /> };
| ^^^^^^^ unknown field
67 | html! { <Child unknown="unknown" /> };
| ^^^^^^^ unknown field
|
= 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
--> $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! { <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
--> $DIR/html-component-fail.rs:44:42
--> $DIR/html-component-fail.rs:69:33
|
44 | html! { <ChildComponent int=1 string={} /> };
| ^^ expected struct `std::string::String`, found ()
69 | html! { <Child int=1 string={} /> };
| ^^ 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! { <ChildComponent int=1 string=3 /> };
| ^
| |
| expected struct `std::string::String`, found integer
| help: try using a conversion method: `3.to_string()`
70 | html! { <Child int=1 string=3 /> };
| ^
| |
| 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! { <ChildComponent int=1 string={3} /> };
| ^^^
| |
| expected struct `std::string::String`, found integer
| help: try using a conversion method: `{3}.to_string()`
71 | html! { <Child int=1 string={3} /> };
| ^^^
| |
| 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! { <ChildComponent int=0u32 /> };
| ^^^^ expected i32, found u32
72 | html! { <Child int=0u32 /> };
| ^^^^ expected i32, found u32
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)]
| - method `string` not found for this
...
48 | html! { <ChildComponent string="abc" /> };
| ^^^^^^
73 | html! { <Child string="abc" /> };
| ^^^^^^
Some errors occurred: E0308, E0425, E0599, E0609.
For more information about an error, try `rustc --explain E0308`.
error[E0599]: no method named `children` found for type `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
--> $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]
mod helpers;
use yew::html::ChildrenRenderer;
#[derive(Properties, Default, PartialEq)]
pub struct ChildProperties {
pub string: String,
@ -12,13 +14,13 @@ pub struct ChildProperties {
pub optional_callback: Option<Callback<()>>,
}
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>) -> Self {
ChildComponent
Child
}
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> {
unimplemented!()
}
}
mod scoped {
pub use super::ChildComponent;
pub use super::Child;
pub use super::Container;
}
pass_helper! {
html! { <ChildComponent int=1 /> };
html! { <Child int=1 /> };
// backwards compat
html! { <ChildComponent: int=1 /> };
html! { <Child: int=1 /> };
html! {
<>
<ChildComponent int=1 />
<scoped::ChildComponent int=1 />
<Child int=1 />
<scoped::Child int=1 />
// backwards compat
<ChildComponent: int=1 />
<scoped::ChildComponent: int=1 />
<Child: int=1 />
<scoped::Child: int=1 />
</>
};
let props = <ChildComponent as Component>::Properties::default();
let props2 = <ChildComponent as Component>::Properties::default();
let props = <Child as Component>::Properties::default();
let props2 = <Child as Component>::Properties::default();
html! {
<>
<ChildComponent with props />
<Child with props />
// backwards compat
<ChildComponent: with props2, />
<Child: with props2, />
</>
};
html! {
<>
<ChildComponent int=1 string="child" />
<ChildComponent int=1 />
<ChildComponent int={1+1} />
<ChildComponent int=1 vec={vec![1]} />
<ChildComponent string={String::from("child")} int=1 />
<Child int=1 string="child" />
<Child int=1 />
<Child int={1+1} />
<Child int=1 vec={vec![1]} />
<Child string={String::from("child")} int=1 />
// backwards compat
<ChildComponent: string="child", int=3, />
<Child: string="child", int=3, />
</>
};
let name_expr = "child";
html! {
<ChildComponent int=1 string=name_expr />
<Child int=1 string=name_expr />
};
html! {
<>
<ChildComponent int=1 />
<ChildComponent int=1 optional_callback=|_| () />
<Child int=1 />
<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>
</>
};
}