Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support passing callbacks to elements #777

Merged
merged 2 commits into from
Dec 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/macro/src/html_tree/html_component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
});

Expand Down Expand Up @@ -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)
}});
Expand Down
13 changes: 12 additions & 1 deletion crates/macro/src/html_tree/html_tag/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()) }
Expand Down Expand Up @@ -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)*
Expand Down
35 changes: 14 additions & 21 deletions crates/macro/src/html_tree/html_tag/tag_attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use syn::{Expr, ExprClosure, ExprTuple, Ident, Pat};

pub struct TagAttributes {
pub attributes: Vec<TagAttribute>,
pub listeners: Vec<TokenStream>,
pub listeners: Vec<(Ident, TokenStream)>,
pub classes: Option<ClassesForm>,
pub value: Option<Expr>,
pub kind: Option<Expr>,
Expand Down Expand Up @@ -120,14 +120,14 @@ impl TagAttributes {
}
}

fn map_listener(listener: TagListener) -> ParseResult<TokenStream> {
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,
Expand All @@ -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))
}
}

Expand Down
3 changes: 1 addition & 2 deletions examples/nested_list/src/list.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down
43 changes: 17 additions & 26 deletions src/html/listener.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::*;
use crate::callback::Callback;
use crate::virtual_dom::Listener;
use stdweb::web::html_element::SelectElement;
#[allow(unused_imports)]
Expand All @@ -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<F>(Option<F>);

/// 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<Event>,
}

impl<F, MSG> From<F> for Wrapper<F>
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<Event>) -> Self {
Wrapper { callback }
}
}

impl<T, COMP> Listener<COMP> for Wrapper<T>
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<COMP>)
-> 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)
}
Expand Down
2 changes: 1 addition & 1 deletion src/html/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
3 changes: 3 additions & 0 deletions src/html/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ pub(crate) enum ComponentUpdate<COMP: Component> {
Properties(COMP::Properties),
}

/// A reference to the parent's scope which will be used later to send messages.
pub type ScopeHolder<PARENT> = Rc<RefCell<Option<Scope<PARENT>>>>;

/// A context which allows sending messages to a component.
pub struct Scope<COMP: Component> {
shared_state: Shared<ComponentState<COMP>>,
Expand Down
19 changes: 12 additions & 7 deletions src/virtual_dom/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<COMP: Component> {
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<COMP>) -> EventListenerHandle;
/// Attaches a listener to the element.
fn attach(&self, element: &Element) -> EventListenerHandle;
}

impl<COMP: Component> fmt::Debug for dyn Listener<COMP> {
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<COMP> = Vec<Box<dyn Listener<COMP>>>;
type Listeners = Vec<Box<dyn Listener>>;

/// A map of attributes.
type Attributes = HashMap<String, String>;
Expand Down Expand Up @@ -201,3 +200,9 @@ pub trait VDiff {
parent_scope: &Scope<Self::Component>,
) -> Option<Node>;
}

/// Transforms properties and attaches a parent scope holder to callbacks for sending messages.
pub trait Transformer<PARENT: Component, FROM, TO> {
/// Transforms one type to another.
fn transform(scope_holder: ScopeHolder<PARENT>, from: FROM) -> TO;
}
23 changes: 3 additions & 20 deletions src/virtual_dom/vcomp.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<PARENT> = Rc<RefCell<Option<Scope<PARENT>>>>;

/// A virtual component.
pub struct VComp<PARENT: Component> {
type_id: TypeId,
Expand Down Expand Up @@ -131,12 +128,6 @@ impl<PARENT: Component> VComp<PARENT> {
}
}

/// Transforms properties and attaches a parent scope holder to callbacks for sending messages.
pub trait Transformer<PARENT: Component, FROM, TO> {
/// Transforms one type to another.
fn transform(scope_holder: ScopeHolder<PARENT>, from: FROM) -> TO;
}

impl<PARENT, T> Transformer<PARENT, T, T> for VComp<PARENT>
where
PARENT: Component,
Expand Down Expand Up @@ -189,15 +180,7 @@ where
F: Fn(IN) -> PARENT::Message + 'static,
{
fn transform(scope: ScopeHolder<PARENT>, from: F) -> Option<Callback<IN>> {
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::<PARENT>::transform(scope, from))
}
}

Expand Down
Loading