Make virtual dom cloneable (#786)

This commit is contained in:
Justin Starry 2019-12-28 20:53:29 -06:00 committed by GitHub
parent 3ffde502a7
commit 84a9ed0e09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 274 additions and 176 deletions

View File

@ -240,7 +240,7 @@ Properties are also pure Rust types with strict type-checking during the compila
```rust ```rust
// my_button.rs // my_button.rs
#[derive(Properties, PartialEq)] #[derive(Clone, Properties, PartialEq)]
pub struct Properties { pub struct Properties {
pub hidden: bool, pub hidden: bool,
#[props(required)] #[props(required)]

View File

@ -119,16 +119,9 @@ impl ToTokens for HtmlComponent {
}; };
let set_children = if !children.is_empty() { let set_children = if !children.is_empty() {
let children_len = children.len();
quote! { quote! {
.children(::yew::html::ChildrenRenderer::new( .children(::yew::html::ChildrenRenderer::new(
#children_len, vec![#(#children.into(),)*]
::std::boxed::Box::new(move || {
#[allow(unused_must_use)]
|| -> ::std::vec::Vec<_> {
vec![#(#children.into(),)*]
}
}()),
)) ))
} }
} else { } else {

View File

@ -172,7 +172,7 @@ impl ToTokens for HtmlTag {
#(#set_classes)* #(#set_classes)*
#(#set_node_ref)* #(#set_node_ref)*
#vtag.add_attributes(vec![#(#attr_pairs),*]); #vtag.add_attributes(vec![#(#attr_pairs),*]);
#vtag.add_listeners(vec![#(::std::boxed::Box::new(#listeners)),*]); #vtag.add_listeners(vec![#(::std::rc::Rc::new(#listeners)),*]);
#vtag.add_children(vec![#(#children),*]); #vtag.add_children(vec![#(#children),*]);
::yew::virtual_dom::VNode::from(#vtag) ::yew::virtual_dom::VNode::from(#vtag)
}}); }});

View File

@ -13,7 +13,7 @@
//! link: ComponentLink<Self>, //! link: ComponentLink<Self>,
//! } //! }
//! //!
//! #[derive(Properties)] //! #[derive(Clone, Properties)]
//! struct Props { //! struct Props {
//! #[props(required)] //! #[props(required)]
//! prop: String, //! prop: String,

View File

@ -12,7 +12,7 @@ pub enum Msg {
ChildClicked, ChildClicked,
} }
#[derive(PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct Props { pub struct Props {
pub limit: u32, pub limit: u32,
#[props(required)] #[props(required)]

View File

@ -10,7 +10,7 @@ pub enum Msg {
Clicked, Clicked,
} }
#[derive(PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct Props { pub struct Props {
pub title: String, pub title: String,
#[props(required)] #[props(required)]

View File

@ -24,7 +24,7 @@ pub enum Msg {
Increase, Increase,
} }
#[derive(PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct Props { pub struct Props {
pub initial: u32, pub initial: u32,
pub color: Color, pub color: Color,

View File

@ -5,4 +5,6 @@ authors = ["Justin Starry <justin.starry@icloud.com>"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
log = "0.4"
web_logger = "0.2"
yew = { path = "../.." } yew = { path = "../.." }

View File

@ -0,0 +1,77 @@
use super::header::ListHeader;
use super::item::ListItem;
use super::list::List;
use super::{Hovered, WeakComponentLink};
use yew::prelude::*;
pub struct App {
link: ComponentLink<Self>,
hovered: Hovered,
}
pub enum Msg {
Hover(Hovered),
}
impl Component for App {
type Message = Msg;
type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
App {
link,
hovered: Hovered::None,
}
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::Hover(hovered) => self.hovered = hovered,
}
true
}
fn view(&self) -> Html {
let on_hover = &self.link.callback(Msg::Hover);
let onmouseenter = &self.link.callback(|_| Msg::Hover(Hovered::None));
let list_link = &WeakComponentLink::<List>::default();
let sub_list_link = &WeakComponentLink::<List>::default();
html! {
<div class="main" onmouseenter=onmouseenter>
<h1>{ "Nested List Demo" }</h1>
<List on_hover=on_hover weak_link=list_link>
<ListHeader text="Calling all Rusties!" on_hover=on_hover list_link=list_link />
<ListItem name="Rustin" on_hover=on_hover />
<ListItem hide={true} name="Rustaroo" on_hover=on_hover />
<ListItem name="Rustifer" on_hover=on_hover>
<div class="sublist">{"Sublist!"}</div>
{
html! {
<List on_hover=on_hover weak_link=sub_list_link>
<ListHeader text="Sub Rusties!" on_hover=on_hover list_link=sub_list_link/>
<ListItem name="Sub Rustin" on_hover=on_hover />
<ListItem hide={true} name="Sub Rustaroo" on_hover=on_hover />
<ListItem name="Sub Rustifer" on_hover=on_hover />
</List>
}
}
</ListItem>
</List>
{self.view_last_hovered()}
</div>
}
}
}
impl App {
fn view_last_hovered(&self) -> Html {
html! {
<div class="last-hovered">
{ "Last hovered:"}
<span class="last-hovered-text">
{ &self.hovered }
</span>
</div>
}
}
}

View File

@ -1,16 +1,19 @@
use crate::Hovered; use super::list::{List, Msg as ListMsg};
use super::{Hovered, WeakComponentLink};
use yew::prelude::*; use yew::prelude::*;
pub struct ListHeader { pub struct ListHeader {
props: Props, props: Props,
} }
#[derive(Properties)] #[derive(Clone, Properties)]
pub struct Props { pub struct Props {
#[props(required)] #[props(required)]
pub on_hover: Callback<Hovered>, pub on_hover: Callback<Hovered>,
#[props(required)] #[props(required)]
pub text: String, pub text: String,
#[props(required)]
pub list_link: WeakComponentLink<List>,
} }
impl Component for ListHeader { impl Component for ListHeader {
@ -26,9 +29,11 @@ impl Component for ListHeader {
} }
fn view(&self) -> Html { fn view(&self) -> Html {
let list_link = self.props.list_link.borrow().clone().unwrap();
let onclick = list_link.callback(|_| ListMsg::HeaderClick);
let onmouseover = self.props.on_hover.reform(|_| Hovered::Header); let onmouseover = self.props.on_hover.reform(|_| Hovered::Header);
html! { html! {
<div class="list-header" onmouseover=onmouseover> <div class="list-header" onmouseover=onmouseover onclick=onclick>
{ &self.props.text } { &self.props.text }
</div> </div>
} }

View File

@ -6,7 +6,7 @@ pub struct ListItem {
props: Props, props: Props,
} }
#[derive(Properties)] #[derive(Clone, Properties)]
pub struct Props { pub struct Props {
pub hide: bool, pub hide: bool,
#[props(required)] #[props(required)]
@ -30,7 +30,10 @@ impl Component for ListItem {
fn view(&self) -> Html { fn view(&self) -> Html {
let name = self.props.name.clone(); let name = self.props.name.clone();
let onmouseover = self.props.on_hover.reform(move |_| Hovered::Item(name.clone())); let onmouseover = self
.props
.on_hover
.reform(move |_| Hovered::Item(name.clone()));
html! { html! {
<div class="list-item" onmouseover=onmouseover> <div class="list-item" onmouseover=onmouseover>
{ &self.props.name } { &self.props.name }

View File

@ -1,19 +1,16 @@
#![recursion_limit = "128"] #![recursion_limit = "512"]
mod app;
mod header; mod header;
mod item; mod item;
mod list; mod list;
use header::ListHeader; pub use app::App;
use item::ListItem; use std::cell::RefCell;
use list::List;
use yew::prelude::*;
use std::fmt; use std::fmt;
use std::rc::Rc;
pub struct Model { use yew::html::ComponentLink;
link: ComponentLink<Self>, pub type WeakComponentLink<COMP> = Rc<RefCell<Option<ComponentLink<COMP>>>>;
hovered: Hovered,
}
#[derive(Debug)] #[derive(Debug)]
pub enum Hovered { pub enum Hovered {
@ -23,60 +20,6 @@ pub enum Hovered {
None, None,
} }
pub enum Msg {
Hover(Hovered),
}
impl Component for Model {
type Message = Msg;
type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
Model { link,
hovered: Hovered::None,
}
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::Hover(hovered) => self.hovered = hovered,
}
true
}
fn view(&self) -> Html {
let on_hover = self.link.callback(Msg::Hover);
html! {
<div class="main">
<h1>{ "Nested List Demo" }</h1>
<List on_hover=on_hover.clone()>
<ListHeader text="Calling all Rusties!" on_hover=on_hover.clone() />
<ListItem name="Rustin" on_hover=on_hover.clone() />
<ListItem hide={true} name="Rustaroo" on_hover=on_hover.clone() />
<ListItem name="Rustifer" on_hover=on_hover.clone()>
<span>{"Hello!"}</span>
</ListItem>
</List>
{self.view_last_hovered()}
</div>
}
}
}
impl Model {
fn view_last_hovered(&self) -> Html {
html! {
<div class="last-hovered">
{ "Last hovered:"}
<span class="last-hovered-text">
{ &self.hovered }
</span>
</div>
}
}
}
impl fmt::Display for Hovered { impl fmt::Display for Hovered {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!( write!(

View File

@ -1,10 +1,11 @@
use crate::{header::Props as HeaderProps, ListHeader}; use super::{Hovered, WeakComponentLink};
use crate::{item::Props as ItemProps, ListItem}; use crate::{header::ListHeader, header::Props as HeaderProps};
use crate::Hovered; use crate::{item::ListItem, item::Props as ItemProps};
use yew::html::{ChildrenRenderer, NodeRef}; use yew::html::{ChildrenRenderer, NodeRef};
use yew::prelude::*; use yew::prelude::*;
use yew::virtual_dom::{VChild, VComp, VNode}; use yew::virtual_dom::{VChild, VComp, VNode};
#[derive(Clone)]
pub enum Variants { pub enum Variants {
Item(<ListItem as Component>::Properties), Item(<ListItem as Component>::Properties),
Header(<ListHeader as Component>::Properties), Header(<ListHeader as Component>::Properties),
@ -22,40 +23,58 @@ impl From<HeaderProps> for Variants {
} }
} }
#[derive(Clone)]
pub struct ListVariant { pub struct ListVariant {
props: Variants, props: Variants,
} }
#[derive(Properties)] #[derive(Clone, Properties)]
pub struct Props { pub struct Props {
#[props(required)] #[props(required)]
pub children: ChildrenRenderer<ListVariant>, pub children: ChildrenRenderer<ListVariant>,
#[props(required)] #[props(required)]
pub on_hover: Callback<Hovered>, pub on_hover: Callback<Hovered>,
#[props(required)]
pub weak_link: WeakComponentLink<List>,
} }
pub struct List { pub struct List {
props: Props, props: Props,
inactive: bool,
}
pub enum Msg {
HeaderClick,
} }
impl Component for List { impl Component for List {
type Message = (); type Message = Msg;
type Properties = Props; type Properties = Props;
fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self { fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
List { props } *props.weak_link.borrow_mut() = Some(link);
List {
props,
inactive: false,
}
} }
fn update(&mut self, _msg: Self::Message) -> ShouldRender { fn update(&mut self, msg: Self::Message) -> ShouldRender {
false match msg {
Msg::HeaderClick => {
self.inactive = !self.inactive;
true
}
}
} }
fn view(&self) -> Html { fn view(&self) -> Html {
let inactive = if self.inactive { "inactive" } else { "" };
let onmouseover = self.props.on_hover.reform(|_| Hovered::List); let onmouseover = self.props.on_hover.reform(|_| Hovered::List);
let onmouseout = self.props.on_hover.reform(|_| Hovered::None); let onmouseout = self.props.on_hover.reform(|_| Hovered::None);
html! { html! {
<div class="list-container" onmouseout=onmouseout onmouseover=onmouseover> <div class="list-container" onmouseout=onmouseout onmouseover=onmouseover>
<div class="list"> <div class=vec!["list", inactive]>
{self.view_header()} {self.view_header()}
<div class="items"> <div class="items">
{self.view_items()} {self.view_items()}

View File

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

View File

@ -35,10 +35,15 @@ html, body {
min-width: 30vw; min-width: 30vw;
} }
.list.inactive {
opacity: 0.5;
}
.list-header { .list-header {
background: #FEECAA; background: #FEECAA;
border-bottom: 1px solid #666; border-bottom: 1px solid #666;
padding: 10px; padding: 10px;
cursor: pointer;
} }
.list-header:hover { .list-header:hover {
@ -65,6 +70,7 @@ html, body {
border-radius: 3px; border-radius: 3px;
margin-top: 10px; margin-top: 10px;
padding: 10px; padding: 10px;
text-align: center;
} }
.last-hovered { .last-hovered {
@ -75,3 +81,17 @@ html, body {
color: #666; color: #666;
margin-left: 5px; margin-left: 5px;
} }
.list-item-details .list-container {
margin-top: 0px;
padding: 15px;
}
.sublist {
display: inline-block;
background: #FFF;
border: 1px solid #666;
border-radius: 3px;
margin: 5px;
padding: 5px 20px;
}

View File

@ -5,7 +5,7 @@ pub struct InputComponent {
link: ComponentLink<Self>, link: ComponentLink<Self>,
} }
#[derive(Properties)] #[derive(Clone, Properties)]
pub struct Props { pub struct Props {
#[props(required)] #[props(required)]
pub on_hover: Callback<()>, pub on_hover: Callback<()>,

View File

@ -64,7 +64,7 @@ pub type Html = VNode;
/// In this example, the `Wrapper` component is used to wrap other elements. /// In this example, the `Wrapper` component is used to wrap other elements.
/// ``` /// ```
///# use yew::{Children, Html, Properties, Component, ComponentLink, html}; ///# use yew::{Children, Html, Properties, Component, ComponentLink, html};
///# #[derive(Properties)] ///# #[derive(Clone, Properties)]
///# struct WrapperProps { ///# struct WrapperProps {
///# children: Children, ///# children: Children,
///# } ///# }
@ -92,7 +92,7 @@ pub type Html = VNode;
/// children property can be used to render the wrapped elements. /// children property can be used to render the wrapped elements.
/// ``` /// ```
///# use yew::{Children, Html, Properties, Renderable, Component, ComponentLink, html}; ///# use yew::{Children, Html, Properties, Renderable, Component, ComponentLink, html};
/// #[derive(Properties)] /// #[derive(Clone, Properties)]
/// struct WrapperProps { /// struct WrapperProps {
/// children: Children, /// children: Children,
/// } /// }
@ -124,7 +124,7 @@ pub type Children = ChildrenRenderer<Html>;
/// ``` /// ```
///# use yew::{html, Component, Renderable, Html, ComponentLink, ChildrenWithProps, Properties}; ///# use yew::{html, Component, Renderable, Html, ComponentLink, ChildrenWithProps, Properties};
///# ///#
///# #[derive(Properties)] ///# #[derive(Clone, Properties)]
///# struct ListProps { ///# struct ListProps {
///# children: ChildrenWithProps<ListItem>, ///# children: ChildrenWithProps<ListItem>,
///# } ///# }
@ -136,7 +136,7 @@ pub type Children = ChildrenRenderer<Html>;
///# fn update(&mut self, msg: Self::Message) -> bool {unimplemented!()} ///# fn update(&mut self, msg: Self::Message) -> bool {unimplemented!()}
///# fn view(&self) -> Html {unimplemented!()} ///# fn view(&self) -> Html {unimplemented!()}
///# } ///# }
///# #[derive(Properties)] ///# #[derive(Clone, Properties)]
///# struct ListItemProps { ///# struct ListItemProps {
///# value: String ///# value: String
///# } ///# }
@ -166,7 +166,7 @@ pub type Children = ChildrenRenderer<Html>;
/// ``` /// ```
///# use yew::{html, Component, Html, ChildrenWithProps, ComponentLink, Properties}; ///# use yew::{html, Component, Html, ChildrenWithProps, ComponentLink, Properties};
///# ///#
/// #[derive(Properties)] /// #[derive(Clone, Properties)]
/// struct ListProps { /// struct ListProps {
/// children: ChildrenWithProps<ListItem>, /// children: ChildrenWithProps<ListItem>,
/// } /// }
@ -188,7 +188,7 @@ pub type Children = ChildrenRenderer<Html>;
/// } /// }
/// } /// }
///# ///#
///# #[derive(Properties)] ///# #[derive(Clone, Properties)]
///# struct ListItemProps { ///# struct ListItemProps {
///# value: String ///# value: String
///# } ///# }
@ -205,30 +205,33 @@ pub type Children = ChildrenRenderer<Html>;
pub type ChildrenWithProps<CHILD> = ChildrenRenderer<VChild<CHILD>>; pub type ChildrenWithProps<CHILD> = ChildrenRenderer<VChild<CHILD>>;
/// A type used for rendering children html. /// A type used for rendering children html.
#[derive(Clone)]
pub struct ChildrenRenderer<T> { pub struct ChildrenRenderer<T> {
len: usize, children: Vec<T>,
boxed_render: Box<dyn Fn() -> Vec<T>>,
} }
impl<T> ChildrenRenderer<T> { impl<T> ChildrenRenderer<T>
where
T: Clone + Into<VNode>,
{
/// Create children /// Create children
pub fn new(len: usize, boxed_render: Box<dyn Fn() -> Vec<T>>) -> Self { pub fn new(children: Vec<T>) -> Self {
Self { len, boxed_render } Self { children }
} }
/// Children list is empty /// Children list is empty
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.len == 0 self.children.is_empty()
} }
/// Number of children elements /// Number of children elements
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.len self.children.len()
} }
/// Build children components and return `Vec` /// Build children components and return `Vec`
pub fn to_vec(&self) -> Vec<T> { pub fn to_vec(&self) -> Vec<T> {
(&self.boxed_render)() self.children.clone()
} }
/// Render children components and return `Iterator` /// Render children components and return `Iterator`
@ -239,13 +242,8 @@ impl<T> ChildrenRenderer<T> {
impl<T> Default for ChildrenRenderer<T> { impl<T> Default for ChildrenRenderer<T> {
fn default() -> Self { fn default() -> Self {
// False positive: https://github.com/rust-lang/rust-clippy/issues/4002
#[allow(clippy::redundant_closure)]
let boxed_render = Box::new(|| Vec::new());
Self { Self {
len: 0, children: Vec::new(),
boxed_render,
} }
} }
} }
@ -258,7 +256,7 @@ impl<T> fmt::Debug for ChildrenRenderer<T> {
impl<T> Renderable for ChildrenRenderer<T> impl<T> Renderable for ChildrenRenderer<T>
where where
T: Into<VNode>, T: Clone + Into<VNode>,
{ {
fn render(&self) -> Html { fn render(&self) -> Html {
VList::new_with_children(self.iter().map(|c| c.into()).collect()).into() VList::new_with_children(self.iter().map(|c| c.into()).collect()).into()
@ -338,7 +336,7 @@ impl<COMP: Component> Renderable for COMP {
} }
/// Trait for building properties for a component /// Trait for building properties for a component
pub trait Properties { pub trait Properties: Clone {
/// Builder that will be used to construct properties /// Builder that will be used to construct properties
type Builder; type Builder;

View File

@ -9,6 +9,7 @@ pub mod vtext;
use indexmap::set::IndexSet; use indexmap::set::IndexSet;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt; use std::fmt;
use std::rc::Rc;
use stdweb::web::{Element, EventListenerHandle, Node}; use stdweb::web::{Element, EventListenerHandle, Node};
pub use self::vcomp::{VChild, VComp}; pub use self::vcomp::{VChild, VComp};
@ -33,7 +34,7 @@ impl fmt::Debug for dyn Listener {
} }
/// A list of event listeners. /// A list of event listeners.
type Listeners = Vec<Box<dyn Listener>>; type Listeners = Vec<Rc<dyn Listener>>;
/// A map of attributes. /// A map of attributes.
type Attributes = HashMap<String, String>; type Attributes = HashMap<String, String>;

View File

@ -9,7 +9,7 @@ use std::rc::Rc;
use stdweb::web::{document, Element, INode, Node, TextNode}; use stdweb::web::{document, Element, INode, Node, TextNode};
/// The method generates an instance of a component. /// The method generates an instance of a component.
type Generator = dyn FnOnce(GeneratorType) -> Mounted; type Generator = dyn Fn(GeneratorType) -> Mounted;
/// Components can be generated by mounting or by overwriting an old component. /// Components can be generated by mounting or by overwriting an old component.
enum GeneratorType { enum GeneratorType {
@ -23,6 +23,15 @@ pub struct VComp {
state: Rc<RefCell<MountState>>, state: Rc<RefCell<MountState>>,
} }
impl Clone for VComp {
fn clone(&self) -> Self {
VComp {
type_id: self.type_id,
state: self.state.clone(),
}
}
}
/// A virtual child component. /// A virtual child component.
pub struct VChild<COMP: Component> { pub struct VChild<COMP: Component> {
/// The component properties /// The component properties
@ -31,6 +40,15 @@ pub struct VChild<COMP: Component> {
node_ref: NodeRef, node_ref: NodeRef,
} }
impl<COMP: Component> Clone for VChild<COMP> {
fn clone(&self) -> Self {
VChild {
props: self.props.clone(),
node_ref: self.node_ref.clone(),
}
}
}
impl<COMP> VChild<COMP> impl<COMP> VChild<COMP>
where where
COMP: Component, COMP: Component,
@ -83,21 +101,21 @@ impl VComp {
element, element,
Some(VNode::VRef(dummy_node.into())), Some(VNode::VRef(dummy_node.into())),
node_ref.clone(), node_ref.clone(),
props, props.clone(),
); );
Mounted { Mounted {
node_ref, node_ref: node_ref.clone(),
scope: scope.clone().into(), scope: scope.clone().into(),
destroyer: Box::new(move || scope.destroy()), destroyer: Box::new(move || scope.destroy()),
} }
} }
GeneratorType::Overwrite(hidden_scope) => { GeneratorType::Overwrite(hidden_scope) => {
let mut scope: Scope<COMP> = hidden_scope.into(); let mut scope: Scope<COMP> = hidden_scope.into();
scope.update(ComponentUpdate::Properties(props)); scope.update(ComponentUpdate::Properties(props.clone()));
Mounted { Mounted {
node_ref, node_ref: node_ref.clone(),
scope: scope.clone().into(), scope: scope.clone().into(),
destroyer: Box::new(move || scope.destroy()), destroyer: Box::new(move || scope.destroy()),
} }

View File

@ -4,7 +4,7 @@ use std::ops::{Deref, DerefMut};
use stdweb::web::{Element, Node}; use stdweb::web::{Element, Node};
/// This struct represents a fragment of the Virtual DOM tree. /// This struct represents a fragment of the Virtual DOM tree.
#[derive(Debug, PartialEq, Default)] #[derive(Clone, Debug, PartialEq, Default)]
pub struct VList { pub struct VList {
/// The list of children nodes. /// The list of children nodes.
pub children: Vec<VNode>, pub children: Vec<VNode>,

View File

@ -8,6 +8,7 @@ use std::iter::FromIterator;
use stdweb::web::{Element, INode, Node}; use stdweb::web::{Element, INode, Node};
/// Bind virtual element to a DOM reference. /// Bind virtual element to a DOM reference.
#[derive(Clone)]
pub enum VNode { pub enum VNode {
/// A bind between `VTag` and `Element`. /// A bind between `VTag` and `Element`.
VTag(Box<VTag>), VTag(Box<VTag>),

View File

@ -8,6 +8,7 @@ use log::warn;
use std::borrow::Cow; use std::borrow::Cow;
use std::cmp::PartialEq; use std::cmp::PartialEq;
use std::fmt; use std::fmt;
use std::rc::Rc;
use stdweb::unstable::TryFrom; use stdweb::unstable::TryFrom;
use stdweb::web::html_element::InputElement; use stdweb::web::html_element::InputElement;
use stdweb::web::html_element::TextAreaElement; use stdweb::web::html_element::TextAreaElement;
@ -56,6 +57,24 @@ pub struct VTag {
captured: Vec<EventListenerHandle>, captured: Vec<EventListenerHandle>,
} }
impl Clone for VTag {
fn clone(&self) -> Self {
VTag {
tag: self.tag.clone(),
reference: None,
listeners: self.listeners.clone(),
attributes: self.attributes.clone(),
children: self.children.clone(),
classes: self.classes.clone(),
value: self.value.clone(),
kind: self.kind.clone(),
checked: self.checked,
node_ref: self.node_ref.clone(),
captured: Vec::new(),
}
}
}
impl VTag { impl VTag {
/// Creates a new `VTag` instance with `tag` name (cannot be changed later in DOM). /// Creates a new `VTag` instance with `tag` name (cannot be changed later in DOM).
pub fn new<S: Into<Cow<'static, str>>>(tag: S) -> Self { pub fn new<S: Into<Cow<'static, str>>>(tag: S) -> Self {
@ -161,14 +180,14 @@ impl VTag {
/// Adds new listener to the node. /// Adds new listener to the node.
/// It's boxed because we want to keep it in a single list. /// It's boxed because we want to keep it in a single list.
/// Later `Listener::attach` will attach an actual listener to a DOM node. /// Later `Listener::attach` will attach an actual listener to a DOM node.
pub fn add_listener(&mut self, listener: Box<dyn Listener>) { pub fn add_listener(&mut self, listener: Rc<dyn Listener>) {
self.listeners.push(listener); self.listeners.push(listener);
} }
/// Adds new listeners to the node. /// Adds new listeners to the node.
/// They are boxed because we want to keep them in a single list. /// They are boxed because we want to keep them in a single list.
/// Later `Listener::attach` will attach an actual listener to a DOM node. /// Later `Listener::attach` will attach an actual listener to a DOM node.
pub fn add_listeners(&mut self, listeners: Vec<Box<dyn Listener>>) { pub fn add_listeners(&mut self, listeners: Vec<Rc<dyn Listener>>) {
for listener in listeners { for listener in listeners {
self.listeners.push(listener); self.listeners.push(listener);
} }

View File

@ -9,6 +9,7 @@ use stdweb::web::{document, Element, INode, Node, TextNode};
/// A type for a virtual /// A type for a virtual
/// [`TextNode`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createTextNode) /// [`TextNode`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createTextNode)
/// representation. /// representation.
#[derive(Clone)]
pub struct VText { pub struct VText {
/// Contains a text of the node. /// Contains a text of the node.
pub text: String, pub text: String,

View File

@ -4,8 +4,9 @@ use yew::prelude::*;
mod t1 { mod t1 {
use super::*; use super::*;
#[derive(Clone)]
struct Value; struct Value;
#[derive(Properties)] #[derive(Clone, Properties)]
pub struct Props { pub struct Props {
// ERROR: optional params must implement default // ERROR: optional params must implement default
value: Value, value: Value,
@ -14,7 +15,7 @@ mod t1 {
mod t2 { mod t2 {
use super::*; use super::*;
#[derive(Properties)] #[derive(Clone, Properties)]
pub struct Props { pub struct Props {
// ERROR: optional is not a tag // ERROR: optional is not a tag
#[props(optional)] #[props(optional)]
@ -24,7 +25,7 @@ mod t2 {
mod t3 { mod t3 {
use super::*; use super::*;
#[derive(Properties)] #[derive(Clone, Properties)]
pub struct Props { pub struct Props {
#[props(required)] #[props(required)]
value: String, value: String,
@ -37,7 +38,7 @@ mod t3 {
mod t4 { mod t4 {
use super::*; use super::*;
#[derive(Properties)] #[derive(Clone, Properties)]
pub struct Props { pub struct Props {
b: i32, b: i32,
#[props(required)] #[props(required)]

View File

@ -1,31 +1,31 @@
error: expected `props(required)` error: expected `props(required)`
--> $DIR/fail.rs:20:11 --> $DIR/fail.rs:21:11
| |
20 | #[props(optional)] 21 | #[props(optional)]
| ^^^^^ | ^^^^^
error[E0277]: the trait bound `t1::Value: std::default::Default` is not satisfied error[E0277]: the trait bound `t1::Value: std::default::Default` is not satisfied
--> $DIR/fail.rs:8:14 --> $DIR/fail.rs:9:21
| |
8 | #[derive(Properties)] 9 | #[derive(Clone, Properties)]
| ^^^^^^^^^^ the trait `std::default::Default` is not implemented for `t1::Value` | ^^^^^^^^^^ the trait `std::default::Default` is not implemented for `t1::Value`
| |
= note: required by `std::default::Default::default` = note: required by `std::default::Default::default`
error[E0599]: no method named `build` found for type `t3::PropsBuilder<t3::PropsBuilderStep_missing_required_prop_value>` in the current scope error[E0599]: no method named `build` found for type `t3::PropsBuilder<t3::PropsBuilderStep_missing_required_prop_value>` in the current scope
--> $DIR/fail.rs:34:26 --> $DIR/fail.rs:35:26
| |
27 | #[derive(Properties)] 28 | #[derive(Clone, Properties)]
| - method `build` not found for this | - method `build` not found for this
... ...
34 | Props::builder().build(); 35 | Props::builder().build();
| ^^^^^ method not found in `t3::PropsBuilder<t3::PropsBuilderStep_missing_required_prop_value>` | ^^^^^ method not found in `t3::PropsBuilder<t3::PropsBuilderStep_missing_required_prop_value>`
error[E0599]: no method named `b` found for type `t4::PropsBuilder<t4::PropsBuilderStep_missing_required_prop_a>` in the current scope error[E0599]: no method named `b` found for type `t4::PropsBuilder<t4::PropsBuilderStep_missing_required_prop_a>` in the current scope
--> $DIR/fail.rs:48:26 --> $DIR/fail.rs:49:26
| |
40 | #[derive(Properties)] 41 | #[derive(Clone, Properties)]
| - method `b` not found for this | - method `b` not found for this
... ...
48 | Props::builder().b(1).a(2).build(); 49 | Props::builder().b(1).a(2).build();
| ^ help: there is a method with a similar name: `a` | ^ help: there is a method with a similar name: `a`

View File

@ -5,8 +5,8 @@ use yew::prelude::*;
mod t1 { mod t1 {
use super::*; use super::*;
#[derive(Properties)] #[derive(Clone, Properties)]
pub struct Props<T: Default> { pub struct Props<T: Clone + Default> {
value: T, value: T,
} }
@ -19,9 +19,10 @@ mod t1 {
mod t2 { mod t2 {
use super::*; use super::*;
#[derive(Clone)]
struct Value; struct Value;
#[derive(Properties)] #[derive(Clone, Properties)]
pub struct Props<T> { pub struct Props<T: Clone> {
#[props(required)] #[props(required)]
value: T, value: T,
} }
@ -34,7 +35,7 @@ mod t2 {
mod t3 { mod t3 {
use super::*; use super::*;
#[derive(Properties)] #[derive(Clone, Properties)]
pub struct Props { pub struct Props {
#[props(required)] #[props(required)]
b: i32, b: i32,
@ -50,10 +51,10 @@ mod t3 {
mod t4 { mod t4 {
use super::*; use super::*;
#[derive(Properties)] #[derive(Clone, Properties)]
pub struct Props<T> pub struct Props<T>
where where
T: Default, T: Clone + Default,
{ {
value: T, value: T,
} }
@ -67,8 +68,8 @@ mod t4 {
mod t5 { mod t5 {
use super::*; use super::*;
#[derive(Properties)] #[derive(Clone, Properties)]
pub struct Props<'a, T: Default + 'a> { pub struct Props<'a, T: Clone + Default + 'a> {
static_value: &'static str, static_value: &'static str,
#[props(required)] #[props(required)]
value: &'a T, value: &'a T,

View File

@ -2,7 +2,7 @@
use yew::prelude::*; use yew::prelude::*;
#[derive(Properties, PartialEq)] #[derive(Clone, Properties, PartialEq)]
pub struct ChildProperties { pub struct ChildProperties {
pub string: String, pub string: String,
#[props(required)] #[props(required)]
@ -27,7 +27,7 @@ impl Component for Child {
} }
} }
#[derive(Properties)] #[derive(Clone, Properties)]
pub struct ChildContainerProperties { pub struct ChildContainerProperties {
pub children: ChildrenWithProps<Child>, pub children: ChildrenWithProps<Child>,
} }

View File

@ -109,8 +109,8 @@ error[E0609]: no field `unknown` on type `ChildProperties`
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:65:20 --> $DIR/html-component-fail.rs:65:20
| |
5 | #[derive(Properties, PartialEq)] 5 | #[derive(Clone, Properties, PartialEq)]
| - method `unknown` not found for this | - method `unknown` not found for this
... ...
65 | html! { <Child unknown="unknown" /> }; 65 | html! { <Child unknown="unknown" /> };
| ^^^^^^^ method not found in `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` | ^^^^^^^ method not found in `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>`
@ -170,8 +170,8 @@ help: you can convert an `u32` to `i32` and panic if the converted value wouldn'
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:73:20 --> $DIR/html-component-fail.rs:73:20
| |
5 | #[derive(Properties, PartialEq)] 5 | #[derive(Clone, Properties, PartialEq)]
| - method `string` not found for this | - method `string` not found for this
... ...
73 | html! { <Child string="abc" /> }; 73 | html! { <Child string="abc" /> };
| ^^^^^^ method not found in `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` | ^^^^^^ method not found in `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>`
@ -179,8 +179,8 @@ error[E0599]: no method named `string` found for type `ChildPropertiesBuilder<Ch
error[E0599]: no method named `children` found for type `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope 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 --> $DIR/html-component-fail.rs:77:5
| |
5 | #[derive(Properties, PartialEq)] 5 | #[derive(Clone, Properties, PartialEq)]
| - method `children` not found for this | - method `children` not found for this
... ...
77 | html! { <Child>{ "Not allowed" }</Child> }; 77 | html! { <Child>{ "Not allowed" }</Child> };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ method not found in `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ method not found in `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>`

View File

@ -3,7 +3,7 @@
use yew::prelude::*; use yew::prelude::*;
use yew::html::ChildrenRenderer; use yew::html::ChildrenRenderer;
#[derive(Properties, Default, PartialEq)] #[derive(Clone, Properties, Default, PartialEq)]
pub struct ChildProperties { pub struct ChildProperties {
pub string: String, pub string: String,
#[props(required)] #[props(required)]
@ -30,7 +30,7 @@ impl Component for Child {
} }
} }
#[derive(Properties, Default)] #[derive(Clone, Properties, Default)]
pub struct ContainerProperties { pub struct ContainerProperties {
#[props(required)] #[props(required)]
pub int: i32, pub int: i32,
@ -55,7 +55,7 @@ impl Component for Container {
} }
} }
#[derive(Properties, Default)] #[derive(Clone, Properties, Default)]
pub struct ChildContainerProperties { pub struct ChildContainerProperties {
#[props(required)] #[props(required)]
pub int: i32, pub int: i32,
@ -165,12 +165,7 @@ fn compile_pass() {
</scoped::Container> </scoped::Container>
<Container int=1 children=ChildrenRenderer::new( <Container int=1 children=ChildrenRenderer::new(
1, vec![html!{ "String" }]
::std::boxed::Box::new(move || {
|| -> ::std::vec::Vec<_> {
vec![html!{ "String" }]
}
}()),
) /> ) />
</> </>
}; };

View File

@ -1,6 +1,6 @@
use yew::prelude::*; use yew::prelude::*;
#[derive(Properties, PartialEq)] #[derive(Clone, Properties, PartialEq)]
pub struct TestProperties { pub struct TestProperties {
pub string: String, pub string: String,
pub int: i32, pub int: i32,

View File

@ -8,7 +8,7 @@ wasm_bindgen_test_configure!(run_in_browser);
struct Comp; struct Comp;
#[derive(PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
struct Props { struct Props {
field_1: u32, field_1: u32,
field_2: u32, field_2: u32,
@ -33,19 +33,19 @@ impl Component for Comp {
#[test] #[test]
fn set_properties_to_component() { fn set_properties_to_component() {
let _ = html! { html! {
<Comp /> <Comp />
}; };
let _ = html! { html! {
<Comp field_1=1 /> <Comp field_1=1 />
}; };
let _ = html! { html! {
<Comp field_2=2 /> <Comp field_2=2 />
}; };
let _ = html! { html! {
<Comp field_1=1 field_2=2 /> <Comp field_1=1 field_2=2 />
}; };
@ -54,7 +54,7 @@ fn set_properties_to_component() {
field_2: 1, field_2: 1,
}; };
let _ = html! { html! {
<Comp with props /> <Comp with props />
}; };
} }