diff --git a/yew-macro/tests/macro/html-tag-fail.stderr b/yew-macro/tests/macro/html-tag-fail.stderr index 1571cb83e14..d13496e52fd 100644 --- a/yew-macro/tests/macro/html-tag-fail.stderr +++ b/yew-macro/tests/macro/html-tag-fail.stderr @@ -204,10 +204,10 @@ error[E0308]: mismatched types --> $DIR/html-tag-fail.rs:32:20 | 32 | html! { }; - | ^^^^^^^ expected struct `yew::callback::Callback`, found integer + | ^^^^^^^ expected enum `yew::callback::Callback`, found integer | - = note: expected struct `yew::callback::Callback` - found type `{integer}` + = note: expected enum `yew::callback::Callback` + 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 @@ -216,8 +216,8 @@ error[E0308]: mismatched types 33 | html! { }; | ^^^^^^^ expected struct `web_sys::features::gen_MouseEvent::MouseEvent`, found struct `std::string::String` | - = note: expected struct `yew::callback::Callback` - found struct `yew::callback::Callback` + = note: expected enum `yew::callback::Callback` + found enum `yew::callback::Callback` = 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 diff --git a/yew/src/callback.rs b/yew/src/callback.rs index 6be69d9c97a..eb466bf39c8 100644 --- a/yew/src/callback.rs +++ b/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; @@ -10,36 +11,73 @@ use std::rc::Rc; /// Callbacks should be used from JS callbacks or `setTimeout` calls. /// /// `Rc` wrapper used to make it clonable. -pub struct Callback(Rc); +pub enum Callback { + /// A callback that can be called multiple times + Callback(Rc), + /// A callback that will only be called once. Panics if it is called again + CallbackOnce(Rc>), +} + +type CallbackOnce = RefCell>>; impl From for Callback { fn from(func: F) -> Self { - Callback(Rc::new(func)) + Callback::Callback(Rc::new(func)) } } impl Clone for Callback { 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 PartialEq for Callback { fn eq(&self, other: &Callback) -> 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 fmt::Debug for Callback { 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 Callback { /// 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(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 diff --git a/yew/src/html/scope.rs b/yew/src/html/scope.rs index 25b8111beb7..3b57d1c33b7 100644 --- a/yew/src/html/scope.rs +++ b/yew/src/html/scope.rs @@ -119,7 +119,7 @@ impl Scope { 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(&self, function: F) -> Callback where @@ -134,8 +134,23 @@ impl Scope { 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(&self, function: F) -> Callback + where + M: Into, + 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(&self, function: F) -> Callback where F: Fn(IN) -> Vec + 'static,