From 2541c89161c03970c8134153061c3b1dc44d865d Mon Sep 17 00:00:00 2001 From: Thomas Gideon Date: Mon, 19 Oct 2020 11:27:35 -0400 Subject: [PATCH] Re-factor to separate Option/String handling from conditional render. --- Cargo.toml | 2 +- src/prelude/mod.rs | 67 ++++++++++++++++++++++------------------------ 2 files changed, 33 insertions(+), 36 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c2883d2..c85f807 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bootstrap-rs" -version = "0.3.0" +version = "0.4.0" authors = ["Thomas Gideon "] edition = "2018" diff --git a/src/prelude/mod.rs b/src/prelude/mod.rs index 2e8731b..3d3be8f 100644 --- a/src/prelude/mod.rs +++ b/src/prelude/mod.rs @@ -7,25 +7,35 @@ mod padding; pub use self::{border::Border, color::Color, edge::Edge, margin::Margin, padding::Padding}; use yew::prelude::*; +/// A newtype that can be helpful to work with `String` values the represent direct user input. +/// +/// This type is convertible into `Option` which makes it helpful for wrapping user input +/// for optional fields when sent to the server. Leaving the wrapped type as a `String` value while +/// handling input and validation helps preserve the user's text as entered so they can continue to +/// edit or correct as needed. +pub struct InputString>(pub S); + +impl> Into> for InputString { + fn into(self) -> Option { + let InputString(input) = self; + if input.as_ref().is_empty() { + None + } else { + Some(input.as_ref().to_owned()) + } + } +} + +/// A convenience method for determining `ShouldRender` based on whether a value has changed, i.e. +/// the existing and new values are not equal, and its side effect of assigning `argument` to `assign_to` for the same condition. +/// +/// Be careful if you choose to logically `or` calls of this function together for multiple +/// properties or state fields since short circuiting of any compound conditional may yield +/// surprising results, specifically failure to assign some fields. For this use case, collect the +/// results of each call into a collection then use reduce that collection to produce the result of +/// applying `or` to all its members. pub fn render_if_ne(assign_to: &mut T, argument: T) -> ShouldRender { - render_if(assign_to, argument, identity, is_ne) -} - -pub fn render_if_opt_str>( - assign_to: &mut Option, - argument: S, -) -> ShouldRender { - render_if(assign_to, argument, opt_from_str, is_ne) -} - -pub fn render_if( - assign_to: &mut T, - argument: A, - map: impl Fn(A) -> T, - assign_render: impl Fn(&mut T, &T) -> bool, -) -> ShouldRender { - let argument = map(argument); - if assign_render(assign_to, &argument) { + if assign_to != &argument { *assign_to = argument; true } else { @@ -33,6 +43,9 @@ pub fn render_if( } } +/// A convenience method for determining which validation class to use for some input field +/// including one that has not yet been validating, represented by the argument having a `None` +/// value. pub fn valid_as_class(v: &Option) -> &'static str { match v { None => "", @@ -41,22 +54,6 @@ pub fn valid_as_class(v: &Option) -> &'static str { } } -fn identity(t: T) -> T { - t -} - -fn opt_from_str>(a: S) -> Option { - if a.as_ref().is_empty() { - None - } else { - Some(a.as_ref().to_owned()) - } -} - -fn is_ne(t: &mut T, a: &T) -> bool { - t != a -} - #[cfg(test)] mod test { use super::*; @@ -80,7 +77,7 @@ mod test { #[test] fn render_if_opt() { let mut t = Some(String::from("test")); - let should = render_if_opt_str(&mut t, String::default()); + let should = render_if_ne(&mut t, InputString(String::default()).into()); assert!(should, "Should have determined to render"); assert_eq!(t, None); }