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

Convert Callback to be an enum to support Fn and FnOnce. #1125

Merged
merged 8 commits into from Apr 25, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 5 additions & 5 deletions yew-macro/tests/macro/html-tag-fail.stderr
Expand Up @@ -204,10 +204,10 @@ error[E0308]: mismatched types
--> $DIR/html-tag-fail.rs:32:20
|
32 | html! { <input onclick=1 /> };
| ^^^^^^^ expected struct `yew::callback::Callback`, found integer
| ^^^^^^^ expected enum `yew::callback::Callback`, found integer
|
= note: expected struct `yew::callback::Callback<web_sys::features::gen_MouseEvent::MouseEvent>`
found type `{integer}`
= note: expected enum `yew::callback::Callback<web_sys::features::gen_MouseEvent::MouseEvent>`
found type `{integer}`
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0308]: mismatched types
Expand All @@ -216,8 +216,8 @@ error[E0308]: mismatched types
33 | html! { <input onclick=Callback::from(|a: String| ()) /> };
| ^^^^^^^ expected struct `web_sys::features::gen_MouseEvent::MouseEvent`, found struct `std::string::String`
|
= note: expected struct `yew::callback::Callback<web_sys::features::gen_MouseEvent::MouseEvent>`
found struct `yew::callback::Callback<std::string::String>`
= note: expected enum `yew::callback::Callback<web_sys::features::gen_MouseEvent::MouseEvent>`
found enum `yew::callback::Callback<std::string::String>`
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0599]: no method named `to_string` found for struct `NotToString` in the current scope
Expand Down
52 changes: 45 additions & 7 deletions yew/src/callback.rs
@@ -1,5 +1,6 @@
//! This module contains structs to interact with `Scope`s.
//! This module contains data types for interacting with `Scope`s.

use std::cell::RefCell;
use std::fmt;
use std::rc::Rc;

Expand All @@ -10,36 +11,73 @@ use std::rc::Rc;
/// Callbacks should be used from JS callbacks or `setTimeout` calls.
/// </aside>
/// `Rc` wrapper used to make it clonable.
pub struct Callback<IN>(Rc<dyn Fn(IN)>);
pub enum Callback<IN> {
/// A callback that can be called multiple times
Callback(Rc<dyn Fn(IN)>),
/// A callback that will only be called once. Panics if it is called again
CallbackOnce(Rc<CallbackOnce<IN>>),
}

type CallbackOnce<IN> = RefCell<Option<Box<dyn FnOnce(IN)>>>;

impl<IN, F: Fn(IN) + 'static> From<F> for Callback<IN> {
fn from(func: F) -> Self {
Callback(Rc::new(func))
Callback::Callback(Rc::new(func))
}
}

impl<IN> Clone for Callback<IN> {
fn clone(&self) -> Self {
Callback(self.0.clone())
match self {
Callback::Callback(cb) => Callback::Callback(cb.clone()),
Callback::CallbackOnce(cb) => Callback::CallbackOnce(cb.clone()),
}
}
}

impl<IN> PartialEq for Callback<IN> {
fn eq(&self, other: &Callback<IN>) -> bool {
Rc::ptr_eq(&self.0, &other.0)
match (&self, &other) {
(Callback::Callback(cb), Callback::Callback(other_cb)) => Rc::ptr_eq(cb, other_cb),
(Callback::CallbackOnce(cb), Callback::CallbackOnce(other_cb)) => {
Rc::ptr_eq(cb, other_cb)
}
_ => false,
}
}
}

impl<IN> fmt::Debug for Callback<IN> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Callback<_>")
let data = match self {
Callback::Callback(_) => "Callback<_>",
Callback::CallbackOnce(_) => "CallbackOnce<_>",
};

f.write_str(data)
}
}

impl<IN> Callback<IN> {
/// This method calls the actual callback.
pub fn emit(&self, value: IN) {
(self.0)(value);
match self {
Callback::Callback(cb) => cb(value),
Callback::CallbackOnce(rc) => {
let cb = rc.replace(None);
let f = cb.expect("callback in CallbackOnce has already been used");
f(value)
}
};
}

/// Creates a callback from a FnOnce. You are responsible for ensuring
/// the callback is only called once otherwise it will panic.
pub fn once<F>(func: F) -> Self
where
F: FnOnce(IN) + 'static,
{
Callback::CallbackOnce(Rc::new(RefCell::new(Some(Box::new(func)))))
}

/// Creates a no-op callback which can be used when it is not suitable to use an
Expand Down
21 changes: 18 additions & 3 deletions yew/src/html/scope.rs
Expand Up @@ -119,7 +119,7 @@ impl<COMP: Component> Scope<COMP> {
self.update(ComponentUpdate::MessageBatch(messages));
}

/// This method creates a `Callback` which will send a message to the linked component's
/// Creates a `Callback` which will send a message to the linked component's
/// update method when invoked.
pub fn callback<F, IN, M>(&self, function: F) -> Callback<IN>
where
Expand All @@ -134,8 +134,23 @@ impl<COMP: Component> Scope<COMP> {
closure.into()
}

/// This method creates a `Callback` which will send a batch of messages back to the linked
/// component's update method when called.
/// Creates a `Callback` from a FnOnce which will send a message to the linked
/// component's update method when invoked.
pub fn callback_once<F, IN, M>(&self, function: F) -> Callback<IN>
where
M: Into<COMP::Message>,
F: FnOnce(IN) -> M + 'static,
{
let scope = self.clone();
let closure = move |input| {
let output = function(input);
scope.send_message(output);
};
Callback::once(closure)
}

/// Creates a `Callback` which will send a batch of messages back to the linked
/// component's update method when invoked.
pub fn batch_callback<F, IN>(&self, function: F) -> Callback<IN>
where
F: Fn(IN) -> Vec<COMP::Message> + 'static,
Expand Down