Skip to content

Commit

Permalink
Allow skipping a callback when reforming (#2864)
Browse files Browse the repository at this point in the history
* Allow skipping a callback when reforming

This adds a method to the callback, similar to Rust's filter_map, which
allows to reform a callback, but also suppress the emit in case
the reform function returns `None`.

* Update packages/yew/src/callback.rs

Co-authored-by: WorldSEnder <WorldSEnder@users.noreply.github.com>

* Implement filter_reform for all Callback output types

As suggested in the PR, this implements filter_reform for all output
types of callbacks by mapping to `Option<OUT>`.

* Fix clippy error

Although I believe that the code is more readable/understandable with
an explicit map, I am not sure adding a clippy exception just for that
is justified. So I applied the clippy suggestion.

Co-authored-by: WorldSEnder <WorldSEnder@users.noreply.github.com>
  • Loading branch information
ctron and WorldSEnder committed Sep 14, 2022
1 parent 81f7ea4 commit beb0157
Showing 1 changed file with 63 additions and 1 deletion.
64 changes: 63 additions & 1 deletion packages/yew/src/callback.rs
Expand Up @@ -68,7 +68,7 @@ impl<IN> Default for Callback<IN> {

impl<IN: 'static, OUT: 'static> Callback<IN, OUT> {
/// Creates a new callback from another callback and a function
/// That when emited will call that function and will emit the original callback
/// That when emitted will call that function and will emit the original callback
pub fn reform<F, T>(&self, func: F) -> Callback<T, OUT>
where
F: Fn(T) -> IN + 'static,
Expand All @@ -80,6 +80,68 @@ impl<IN: 'static, OUT: 'static> Callback<IN, OUT> {
};
Callback::from(func)
}

/// Creates a new callback from another callback and a function.
/// When emitted will call the function and, only if it returns `Some(value)`, will emit
/// `value` to the original callback.
pub fn filter_reform<F, T>(&self, func: F) -> Callback<T, Option<OUT>>
where
F: Fn(T) -> Option<IN> + 'static,
{
let this = self.clone();
let func = move |input| func(input).map(|output| this.emit(output));
Callback::from(func)
}
}

impl<IN, OUT> ImplicitClone for Callback<IN, OUT> {}

#[cfg(test)]
mod test {
use std::sync::Mutex;

use super::*;

/// emit the callback with the provided value
fn emit<T, I, R: 'static + Clone, F, OUT>(values: I, f: F) -> Vec<R>
where
I: IntoIterator<Item = T>,
F: FnOnce(Callback<R, ()>) -> Callback<T, OUT>,
{
let result = Rc::new(Mutex::new(Vec::new()));
let cb_result = result.clone();
let cb = f(Callback::<R, ()>::from(move |v| {
cb_result.lock().unwrap().push(v);
}));
for value in values {
cb.emit(value);
}
let x = result.lock().unwrap().clone();
x
}

#[test]
fn test_callback() {
assert_eq!(*emit([true, false], |cb| cb), vec![true, false]);
}

#[test]
fn test_reform() {
assert_eq!(
*emit([true, false], |cb| cb.reform(|v: bool| !v)),
vec![false, true]
);
}

#[test]
fn test_filter_reform() {
assert_eq!(
*emit([1, 2, 3], |cb| cb.filter_reform(|v| match v {
1 => Some(true),
2 => Some(false),
_ => None,
})),
vec![true, false]
);
}
}

0 comments on commit beb0157

Please sign in to comment.