diff --git a/crates/macro/src/html_tree/html_component.rs b/crates/macro/src/html_tree/html_component.rs
index 398f8ad6b0b..723782400c4 100644
--- a/crates/macro/src/html_tree/html_component.rs
+++ b/crates/macro/src/html_tree/html_component.rs
@@ -140,7 +140,7 @@ impl ToTokens for HtmlComponent {
Props::List(ListProps { props, .. }) => {
let set_props = props.iter().map(|HtmlProp { label, value }| {
quote_spanned! { value.span()=>
- .#label(<::yew::virtual_dom::vcomp::VComp<_> as ::yew::virtual_dom::vcomp::Transformer<_, _, _>>::transform(#vcomp_scope.clone(), #value))
+ .#label(<::yew::virtual_dom::vcomp::VComp<_> as ::yew::virtual_dom::Transformer<_, _, _>>::transform(#vcomp_scope.clone(), #value))
}
});
@@ -181,7 +181,7 @@ impl ToTokens for HtmlComponent {
#validate_props
}
- let #vcomp_scope: ::yew::virtual_dom::vcomp::ScopeHolder<_> = ::std::default::Default::default();
+ let #vcomp_scope: ::yew::html::ScopeHolder<_> = ::std::default::Default::default();
let __yew_node_ref: ::yew::html::NodeRef = #node_ref;
::yew::virtual_dom::VChild::<#ty, _>::new(#init_props, #vcomp_scope, __yew_node_ref)
}});
diff --git a/crates/macro/src/html_tree/html_tag/mod.rs b/crates/macro/src/html_tree/html_tag/mod.rs
index 59da189b9b6..98a9aa053f4 100644
--- a/crates/macro/src/html_tree/html_tag/mod.rs
+++ b/crates/macro/src/html_tree/html_tag/mod.rs
@@ -102,6 +102,7 @@ impl ToTokens for HtmlTag {
} = &attributes;
let vtag = Ident::new("__yew_vtag", tag_name.span());
+ let vtag_scope = Ident::new("__yew_vtag_scope", Span::call_site());
let attr_pairs = attributes.iter().map(|TagAttribute { label, value }| {
let label_str = label.to_string();
quote_spanned! {value.span() => (#label_str.to_owned(), (#value).to_string()) }
@@ -148,9 +149,19 @@ impl ToTokens for HtmlTag {
#vtag.node_ref = #node_ref;
}
});
+ let listeners = listeners.iter().map(|(name, callback)| {
+ quote_spanned! {name.span()=> {
+ ::yew::html::#name::Wrapper::new(
+ <::yew::virtual_dom::vtag::VTag<_> as ::yew::virtual_dom::Transformer<_, _, _>>::transform(
+ #vtag_scope.clone(), #callback
+ )
+ )
+ }}
+ });
tokens.extend(quote! {{
- let mut #vtag = ::yew::virtual_dom::vtag::VTag::new(#name);
+ let #vtag_scope: ::yew::html::ScopeHolder<_> = ::std::default::Default::default();
+ let mut #vtag = ::yew::virtual_dom::vtag::VTag::new_with_scope(#name, #vtag_scope.clone());
#(#set_kind)*
#(#set_value)*
#(#add_href)*
diff --git a/crates/macro/src/html_tree/html_tag/tag_attributes.rs b/crates/macro/src/html_tree/html_tag/tag_attributes.rs
index b56d6c874c9..5a6dbb3e933 100644
--- a/crates/macro/src/html_tree/html_tag/tag_attributes.rs
+++ b/crates/macro/src/html_tree/html_tag/tag_attributes.rs
@@ -9,7 +9,7 @@ use syn::{Expr, ExprClosure, ExprTuple, Ident, Pat};
pub struct TagAttributes {
pub attributes: Vec,
- pub listeners: Vec,
+ pub listeners: Vec<(Ident, TokenStream)>,
pub classes: Option,
pub value: Option,
pub kind: Option,
@@ -120,14 +120,14 @@ impl TagAttributes {
}
}
- fn map_listener(listener: TagListener) -> ParseResult {
+ fn map_listener(listener: TagListener) -> ParseResult<(Ident, TokenStream)> {
let TagListener {
name,
event_name,
handler,
} = listener;
- match handler {
+ let callback: TokenStream = match handler {
Expr::Closure(closure) => {
let ExprClosure {
inputs,
@@ -150,29 +150,22 @@ impl TagAttributes {
Pat::Wild(pat) => Ok(pat.into_token_stream()),
_ => Err(syn::Error::new_spanned(or_span, "invalid closure argument")),
}?;
- let handler =
- Ident::new(&format!("__yew_{}_handler", name.to_string()), name.span());
- let listener =
- Ident::new(&format!("__yew_{}_listener", name.to_string()), name.span());
+ let callback =
+ Ident::new(&format!("__yew_{}_callback", name.to_string()), name.span());
let segment = syn::PathSegment {
ident: Ident::new(&event_name, name.span()),
arguments: syn::PathArguments::None,
};
- let var_type = quote! { ::yew::events::#segment };
- let wrapper_type = quote! { ::yew::html::#name::Wrapper };
- let listener_stream = quote_spanned! {name.span()=> {
- let #handler = move | #var: #var_type | #body;
- let #listener = #wrapper_type::from(#handler);
- #listener
- }};
-
- Ok(listener_stream)
+
+ quote_spanned! {name.span()=> {
+ let #callback = move | #var: ::yew::events::#segment | #body;
+ #callback
+ }}
}
- _ => Err(syn::Error::new_spanned(
- &name,
- format!("`{}` attribute value should be a closure", name),
- )),
- }
+ callback => callback.into_token_stream(),
+ };
+
+ Ok((name, callback))
}
}
diff --git a/examples/nested_list/src/list.rs b/examples/nested_list/src/list.rs
index 3b370ef195a..835404880a6 100644
--- a/examples/nested_list/src/list.rs
+++ b/examples/nested_list/src/list.rs
@@ -1,9 +1,8 @@
use crate::{header::Props as HeaderProps, ListHeader};
use crate::{item::Props as ItemProps, ListItem};
use std::fmt;
-use yew::html::{ChildrenRenderer, NodeRef};
+use yew::html::{ChildrenRenderer, NodeRef, ScopeHolder};
use yew::prelude::*;
-use yew::virtual_dom::vcomp::ScopeHolder;
use yew::virtual_dom::{VChild, VComp, VNode};
#[derive(Debug)]
diff --git a/src/html/listener.rs b/src/html/listener.rs
index 31a3d6be5f0..99d51de5c13 100644
--- a/src/html/listener.rs
+++ b/src/html/listener.rs
@@ -1,4 +1,4 @@
-use super::*;
+use crate::callback::Callback;
use crate::virtual_dom::Listener;
use stdweb::web::html_element::SelectElement;
#[allow(unused_imports)]
@@ -14,42 +14,33 @@ macro_rules! impl_action {
use stdweb::web::event::{IEvent, $type};
use super::*;
- /// A wrapper for a callback.
- /// Listener extracted from here when attached.
- #[allow(missing_debug_implementations)]
- pub struct Wrapper(Option);
-
- /// And event type which keeps the returned type.
- pub type Event = $ret;
+ /// A wrapper for a callback which attaches event listeners to elements.
+ #[derive(Clone, Debug)]
+ pub struct Wrapper {
+ callback: Callback,
+ }
- impl From for Wrapper
- where
- MSG: 'static,
- F: Fn($ret) -> MSG + 'static,
- {
- fn from(handler: F) -> Self {
- Wrapper(Some(handler))
+ impl Wrapper {
+ /// Create a wrapper for an event-typed callback
+ pub fn new(callback: Callback) -> Self {
+ Wrapper { callback }
}
}
- impl Listener for Wrapper
- where
- T: Fn($ret) -> COMP::Message + 'static,
- COMP: Component,
- {
+ /// And event type which keeps the returned type.
+ pub type Event = $ret;
+
+ impl Listener for Wrapper {
fn kind(&self) -> &'static str {
stringify!($action)
}
- fn attach(&mut self, element: &Element, mut activator: Scope)
- -> EventListenerHandle {
- let handler = self.0.take().expect("tried to attach listener twice");
+ fn attach(&self, element: &Element) -> EventListenerHandle {
let this = element.clone();
+ let callback = self.callback.clone();
let listener = move |event: $type| {
event.stop_propagation();
- let handy_event: $ret = $convert(&this, event);
- let msg = handler(handy_event);
- activator.send_message(msg);
+ callback.emit($convert(&this, event));
};
element.add_event_listener(listener)
}
diff --git a/src/html/mod.rs b/src/html/mod.rs
index 91d7c9e6ad7..507cfa4af63 100644
--- a/src/html/mod.rs
+++ b/src/html/mod.rs
@@ -7,8 +7,8 @@ mod listener;
mod scope;
pub use listener::*;
-pub use scope::Scope;
pub(crate) use scope::{ComponentUpdate, HiddenScope};
+pub use scope::{Scope, ScopeHolder};
use crate::callback::Callback;
use crate::virtual_dom::{VChild, VList, VNode};
diff --git a/src/html/scope.rs b/src/html/scope.rs
index 0eeedeb889a..f59adb87e44 100644
--- a/src/html/scope.rs
+++ b/src/html/scope.rs
@@ -16,6 +16,9 @@ pub(crate) enum ComponentUpdate {
Properties(COMP::Properties),
}
+/// A reference to the parent's scope which will be used later to send messages.
+pub type ScopeHolder = Rc>>>;
+
/// A context which allows sending messages to a component.
pub struct Scope {
shared_state: Shared>,
diff --git a/src/virtual_dom/mod.rs b/src/virtual_dom/mod.rs
index bb31fd3ed45..1a6caece3fe 100644
--- a/src/virtual_dom/mod.rs
+++ b/src/virtual_dom/mod.rs
@@ -16,26 +16,25 @@ pub use self::vlist::VList;
pub use self::vnode::VNode;
pub use self::vtag::VTag;
pub use self::vtext::VText;
-use crate::html::{Component, Scope};
+use crate::html::{Component, Scope, ScopeHolder};
/// `Listener` trait is an universal implementation of an event listener
/// which helps to bind Rust-listener to JS-listener (DOM).
-pub trait Listener {
+pub trait Listener {
/// Returns standard name of DOM's event.
fn kind(&self) -> &'static str;
- /// Attaches listener to the element and uses scope instance to send
- /// prepared event back to the yew main loop.
- fn attach(&mut self, element: &Element, scope: Scope) -> EventListenerHandle;
+ /// Attaches a listener to the element.
+ fn attach(&self, element: &Element) -> EventListenerHandle;
}
-impl fmt::Debug for dyn Listener {
+impl fmt::Debug for dyn Listener {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Listener {{ kind: {} }}", self.kind())
}
}
/// A list of event listeners.
-type Listeners = Vec>>;
+type Listeners = Vec>;
/// A map of attributes.
type Attributes = HashMap;
@@ -201,3 +200,9 @@ pub trait VDiff {
parent_scope: &Scope,
) -> Option;
}
+
+/// Transforms properties and attaches a parent scope holder to callbacks for sending messages.
+pub trait Transformer {
+ /// Transforms one type to another.
+ fn transform(scope_holder: ScopeHolder, from: FROM) -> TO;
+}
diff --git a/src/virtual_dom/vcomp.rs b/src/virtual_dom/vcomp.rs
index 3a4ec80a000..dfffd42b3d8 100644
--- a/src/virtual_dom/vcomp.rs
+++ b/src/virtual_dom/vcomp.rs
@@ -1,8 +1,8 @@
//! This module contains the implementation of a virtual component `VComp`.
-use super::{VDiff, VNode};
+use super::{Transformer, VDiff, VNode};
use crate::callback::Callback;
-use crate::html::{Component, ComponentUpdate, HiddenScope, NodeRef, Scope};
+use crate::html::{Component, ComponentUpdate, HiddenScope, NodeRef, Scope, ScopeHolder};
use std::any::TypeId;
use std::cell::RefCell;
use std::fmt;
@@ -18,9 +18,6 @@ enum GeneratorType {
Overwrite(HiddenScope),
}
-/// A reference to the parent's scope which will be used later to send messages.
-pub type ScopeHolder = Rc>>>;
-
/// A virtual component.
pub struct VComp {
type_id: TypeId,
@@ -131,12 +128,6 @@ impl VComp {
}
}
-/// Transforms properties and attaches a parent scope holder to callbacks for sending messages.
-pub trait Transformer {
- /// Transforms one type to another.
- fn transform(scope_holder: ScopeHolder, from: FROM) -> TO;
-}
-
impl Transformer for VComp
where
PARENT: Component,
@@ -189,15 +180,7 @@ where
F: Fn(IN) -> PARENT::Message + 'static,
{
fn transform(scope: ScopeHolder, from: F) -> Option> {
- let callback = move |arg| {
- let msg = from(arg);
- if let Some(ref mut sender) = *scope.borrow_mut() {
- sender.send_message(msg);
- } else {
- panic!("Parent component hasn't activated this callback yet");
- }
- };
- Some(callback.into())
+ Some(VComp::::transform(scope, from))
}
}
diff --git a/src/virtual_dom/vtag.rs b/src/virtual_dom/vtag.rs
index 93f073569da..1e4ef4b1d75 100644
--- a/src/virtual_dom/vtag.rs
+++ b/src/virtual_dom/vtag.rs
@@ -1,7 +1,10 @@
//! This module contains the implementation of a virtual element node `VTag`.
-use super::{Attributes, Classes, Listener, Listeners, Patch, Reform, VDiff, VList, VNode};
-use crate::html::{Component, NodeRef, Scope};
+use super::{
+ Attributes, Classes, Listener, Listeners, Patch, Reform, Transformer, VDiff, VList, VNode,
+};
+use crate::callback::Callback;
+use crate::html::{Component, NodeRef, Scope, ScopeHolder};
use log::warn;
use std::borrow::Cow;
use std::cmp::PartialEq;
@@ -22,17 +25,17 @@ pub const HTML_NAMESPACE: &str = "http://www.w3.org/1999/xhtml";
/// A type for a virtual
/// [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element)
/// representation.
-pub struct VTag {
+pub struct VTag {
/// A tag of the element.
tag: Cow<'static, str>,
/// A reference to the `Element`.
pub reference: Option,
/// List of attached listeners.
- pub listeners: Listeners,
+ pub listeners: Listeners,
/// List of attributes.
pub attributes: Attributes,
/// List of children nodes
- pub children: VList,
+ pub children: VList,
/// List of attached classes.
pub classes: Classes,
/// Contains a value of an
@@ -50,14 +53,24 @@ pub struct VTag {
pub checked: bool,
/// A node reference used for DOM access in Component lifecycle methods
pub node_ref: NodeRef,
- /// _Service field_. Keeps handler for attached listeners
- /// to have an opportunity to drop them later.
+ /// Keeps handler for attached listeners to have an opportunity to drop them later.
captured: Vec,
+ /// Holds a reference to the parent component scope for callback activation.
+ scope_holder: ScopeHolder,
}
-impl VTag {
+impl VTag {
/// Creates a new `VTag` instance with `tag` name (cannot be changed later in DOM).
pub fn new>>(tag: S) -> Self {
+ Self::new_with_scope(tag, ScopeHolder::default())
+ }
+
+ /// Creates a new `VTag` instance with `tag` name (cannot be changed later in DOM) and parent
+ /// scope holder for callback activation.
+ pub fn new_with_scope>>(
+ tag: S,
+ scope_holder: ScopeHolder,
+ ) -> Self {
VTag {
tag: tag.into(),
reference: None,
@@ -72,6 +85,7 @@ impl VTag {
// In HTML node `checked` attribute sets `defaultChecked` parameter,
// but we use own field to control real `checked` parameter
checked: false,
+ scope_holder,
}
}
@@ -81,12 +95,12 @@ impl VTag {
}
/// Add `VNode` child.
- pub fn add_child(&mut self, child: VNode) {
+ pub fn add_child(&mut self, child: VNode) {
self.children.add_child(child);
}
/// Add multiple `VNode` children.
- pub fn add_children(&mut self, children: Vec>) {
+ pub fn add_children(&mut self, children: Vec>) {
for child in children {
self.add_child(child);
}
@@ -159,15 +173,15 @@ impl VTag {
/// Adds new listener to the node.
/// It's boxed because we want to keep it in a single list.
- /// Lates `Listener::attach` called to attach actual listener to a DOM node.
- pub fn add_listener(&mut self, listener: Box>) {
+ /// Later `Listener::attach` will attach an actual listener to a DOM node.
+ pub fn add_listener(&mut self, listener: Box) {
self.listeners.push(listener);
}
/// Adds new listeners to the node.
/// They are boxed because we want to keep them in a single list.
- /// Lates `Listener::attach` called to attach actual listener to a DOM node.
- pub fn add_listeners(&mut self, listeners: Vec>>) {
+ /// Later `Listener::attach` will attach an actual listener to a DOM node.
+ pub fn add_listeners(&mut self, listeners: Vec>) {
for listener in listeners {
self.listeners.push(listener);
}
@@ -346,8 +360,8 @@ impl VTag {
}
}
-impl VDiff for VTag {
- type Component = COMP;
+impl VDiff for VTag {
+ type Component = PARENT;
/// Remove VTag from parent.
fn detach(&mut self, parent: &Element) -> Option {
@@ -454,11 +468,14 @@ impl VDiff for VTag {
let element = self.reference.clone().expect("element expected");
- for mut listener in self.listeners.drain(..) {
- let handle = listener.attach(&element, parent_scope.clone());
+ for listener in self.listeners.drain(..) {
+ let handle = listener.attach(&element);
self.captured.push(handle);
}
+ // Activate scope
+ *self.scope_holder.borrow_mut() = Some(parent_scope.clone());
+
// Process children
self.children.apply(
&element,
@@ -473,7 +490,7 @@ impl VDiff for VTag {
}
}
-impl fmt::Debug for VTag {
+impl fmt::Debug for VTag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "VTag {{ tag: {} }}", self.tag)
}
@@ -495,8 +512,8 @@ fn set_checked(input: &InputElement, value: bool) {
js!( @(no_return) @{input}.checked = @{value}; );
}
-impl PartialEq for VTag {
- fn eq(&self, other: &VTag) -> bool {
+impl PartialEq for VTag {
+ fn eq(&self, other: &VTag) -> bool {
self.tag == other.tag
&& self.value == other.value
&& self.kind == other.kind
@@ -521,3 +538,40 @@ pub(crate) fn not(option: &Option) -> &Option<()> {
&Some(())
}
}
+
+impl Transformer for VTag
+where
+ PARENT: Component,
+{
+ fn transform(_: ScopeHolder, from: T) -> T {
+ from
+ }
+}
+
+impl<'a, PARENT, T> Transformer for VTag
+where
+ PARENT: Component,
+ T: Clone,
+{
+ fn transform(_: ScopeHolder, from: &'a T) -> T {
+ from.clone()
+ }
+}
+
+impl<'a, PARENT, F, IN> Transformer> for VTag
+where
+ PARENT: Component,
+ F: Fn(IN) -> PARENT::Message + 'static,
+{
+ fn transform(scope: ScopeHolder, from: F) -> Callback {
+ let callback = move |arg| {
+ let msg = from(arg);
+ if let Some(ref mut sender) = *scope.borrow_mut() {
+ sender.send_message(msg);
+ } else {
+ panic!("Parent component hasn't activated this callback yet");
+ }
+ };
+ callback.into()
+ }
+}