Simply let the validation function return the value to be submitted.
value: value.into(), valid, default,
radio_group: false,
unique: false,
- submit_converter: None,
}
}
default: default.into(),
radio_group: props.radio_group,
unique: false,
- submit_converter: None,
}
}
use crate::state::optional_rc_ptr_eq;
-use super::ValidateFn;
+use super::SubmitValidateFn;
/// Basic field options used inside [FormContext].
///
// Field name.
pub name: AttrValue,
/// The validation function
- pub validate: Option<ValidateFn<Value>>,
+ pub validate: Option<SubmitValidateFn<Value>>,
/// Radio group flag. Set this when the field is part of a radio group.
pub radio_group: bool,
/// Do not allow multiple fields with the same name.
pub options: FieldOptions,
/// Field value
pub value: Value,
+ // Submit value (value returned by validate)
+ submit_value: Option<Value>,
/// Field default value.
pub default: Value,
/// Validation result.
pub valid: Result<(), String>,
- /// Optional conversion hook called by [FormContext::get_submit_data]
- ///
- /// Should return `None` if value is invalid.
- pub submit_converter: Option<Callback<Value, Option<Value>>>,
}
impl FieldRegistration {
fn is_dirty(&self) -> bool {
// we need to compare the value that will be submitted
- let submit_value = match &self.submit_converter {
- Some(convert) => {
- match convert.emit(self.value.clone()) {
- Some(v) => v,
- None => return true,
+ match &self.submit_value {
+ Some(submit_value) => &self.default != submit_value,
+ None => true,
+ }
+ }
+
+ fn apply_value(&mut self, value: Value) {
+ let (valid, submit_value);
+ if let Some(validate) = &self.validate {
+ match validate.apply(&value).map_err(|e| e.to_string()) {
+ Ok(value) => {
+ submit_value = Some(value);
+ valid = Ok(());
+ }
+ Err(e) => {
+ submit_value = None;
+ valid = Err(e.to_string());
}
}
- None => self.value.clone(),
- };
- self.default != submit_value
+ } else {
+ submit_value = Some(value.clone());
+ valid = Ok(());
+ }
+ self.value = value;
+ self.valid = valid;
+ self.submit_value = submit_value;
}
}
/// Shared form data ([Rc]<[RefCell]<[FormContextState]>>)
self.write().validate_field_by_slab_key(key);
}
/// Update validation function and trigger re-validation
- pub fn update_validate(&mut self, validate: Option<ValidateFn<Value>>) {
+ pub fn update_validate(&mut self, validate: Option<SubmitValidateFn<Value>>) {
let key = self.key;
self.write()
.update_field_validate_by_slab_key(key, validate);
value: Value,
default: Value,
radio_group: bool,
- validate: Option<ValidateFn<Value>>,
+ validate: Option<SubmitValidateFn<Value>>,
options: FieldOptions,
unique: bool,
- submit_converter: Option<Callback<Value, Option<Value>>>,
) -> FieldHandle {
let key = self.inner.borrow_mut().register_field(
name,
validate,
options,
unique,
- submit_converter,
);
FieldHandle {
value: Value,
default: Value,
radio_group: bool,
- validate: Option<ValidateFn<Value>>,
+ validate: Option<SubmitValidateFn<Value>>,
options: FieldOptions,
unique: bool,
- submit_converter: Option<Callback<Value, Option<Value>>>,
) -> usize {
let name = name.into_prop_value();
let unique = if radio_group { false } else { unique };
- let mut valid = Ok(());
- if let Some(validate) = &validate {
- valid = validate.apply(&value).map_err(|e| e.to_string());
- }
-
- let field = FieldRegistration {
+ let mut field = FieldRegistration {
name: name.clone(),
validate,
radio_group,
unique,
options,
- value,
+ value: Value::Null, // set by apply_value below
+ submit_value: None, // set by apply_value below
default: default.clone(),
- valid,
- submit_converter,
+ valid: Ok(()), // set by apply_value below
};
+ field.apply_value(value);
+
let slab_key;
if unique {
self.version += 1;
}
if value != field.value {
- let mut valid = Ok(());
- if let Some(validate) = &field.validate {
- valid = validate.apply(&value).map_err(|e| e.to_string());
- }
-
- field.value = value;
- field.valid = valid;
-
+ field.apply_value(value);
self.version += 1;
}
}
let field = &mut self.fields[slab_key];
if field.value != field.default {
self.version += 1;
- field.value = field.default.clone();
- if let Some(validate) = &field.validate {
- field.valid = validate.apply(&field.value).map_err(|e| e.to_string());
- } else {
- field.valid = Ok(());
- }
+ field.apply_value(field.default.clone());
}
}
for (_key, field) in self.fields.iter_mut() {
if field.value != field.default {
changes = true;
- field.value = field.default.clone();
- let mut valid = Ok(());
- if let Some(validate) = &field.validate {
- valid = validate.apply(&field.value).map_err(|e| e.to_string());
- }
- field.valid = valid;
+ field.apply_value(field.default.clone());
}
}
if changes {
if field.radio_group {
// fixme: do something ?
} else {
- let mut valid = Ok(());
+ let (valid, submit_value);
if let Some(validate) = &field.validate {
- valid = validate.apply(&field.value).map_err(|e| e.to_string());
+ match validate.apply(&field.value) {
+ Ok(value) => {
+ submit_value = Some(value);
+ valid = Ok(());
+ }
+ Err(e) => {
+ submit_value = None;
+ valid = Err(e.to_string());
+ }
+ }
+ } else {
+ submit_value = Some(field.value.clone());
+ valid = Ok(());
}
if valid != field.valid {
self.version += 1;
field.valid = valid;
}
- }
+ if submit_value != field.submit_value {
+ self.version += 1;
+ field.submit_value = submit_value;
+ }
+ }
}
fn update_field_validate_by_slab_key(
&mut self,
slab_key: usize,
- validate: Option<ValidateFn<Value>>,
+ validate: Option<SubmitValidateFn<Value>>,
) {
let field = &mut self.fields[slab_key];
field.validate = validate;
let field = &self.fields[key];
let submit_empty = field.options.submit_empty;
if field.valid.is_ok() && field.options.submit {
- let mut value = field.value.clone();
- if !submit_empty && value_is_empty(&value) {
- continue;
- }
- if let Some(submit_converter) = &field.submit_converter {
- value = match submit_converter.emit(value) {
- Some(value) => {
- if !submit_empty & value_is_empty(&value) {
- continue;
- }
- value
+ match &field.submit_value {
+ None => continue,
+ Some(value) => {
+ if !submit_empty & value_is_empty(&value) {
+ continue;
}
- None => continue, // should not happen
- };
+ data[name.deref()] = value.clone();
+ }
}
- data[name.deref()] = value;
}
continue;
}
let field = &self.fields[key];
let submit_empty = field.options.submit_empty;
if field.valid.is_ok() && field.options.submit {
- let mut value = field.value.clone();
- if !submit_empty && value_is_empty(&value) {
- continue;
- }
- if let Some(submit_converter) = &field.submit_converter {
- value = match submit_converter.emit(value) {
- Some(value) => {
- if !submit_empty && value_is_empty(&value) {
- continue;
- }
- value
+ match &field.submit_value {
+ None => continue,
+ Some(value) => {
+ if !submit_empty & value_is_empty(&value) {
+ continue;
}
- None => continue, // should not happen
- };
+ list.push(value.clone());
+ }
}
- list.push(value);
}
}
if !list.is_empty() {
}
}
- fn validator(props: &Self::ValidateClosure, value: &Value) -> Result<(), Error> {
+ fn validator(props: &Self::ValidateClosure, value: &Value) -> Result<Value, Error> {
let value = match value {
Value::Null => String::new(),
Value::Number(n) => n.to_string(),
Value::String(v) => v.clone(),
- _ => {
- // should not happen
- log::error!("PwtField: got wrong data type in validate!");
- String::new()
- }
+ _ => return Err(Error::msg(tr!("got wrong data type."))),
};
if value.is_empty() {
if props.required {
return Err(Error::msg(tr!("Field may not be empty.")));
} else {
- return Ok(());
+ return Ok(Value::String(String::new()));
}
}
}
}
- match &props.validate {
- Some(validate) => validate.apply(&value),
- None => Ok(()),
+ if let Some(validate) = &props.validate {
+ validate.apply(&value)?;
}
+
+ Ok(Value::String(value))
}
fn setup(props: &Self::Properties) -> ManagedFieldState {
default,
radio_group: false,
unique: false,
- submit_converter: None,
}
}
default,
radio_group: false,
unique: false,
- submit_converter: None,
}
}
use yew::html::Scope;
use yew::prelude::*;
-use super::{FieldHandle, FieldOptions, FormContext, FormContextObserver, ValidateFn};
+use super::{
+ FieldHandle, FieldOptions, FormContext, FormContextObserver, SubmitValidateFn,
+};
use crate::props::FieldBuilder;
/// Managed field state.
///
/// Instead, use the same state for all of those fields.
pub unique: bool,
-
- /// Optional conversion called by [FormContext::get_submit_data]
- pub submit_converter: Option<Callback<Value, Option<Value>>>,
}
/// Managed field context.
/// The validation function.
///
/// Gets the result of [`validation_args`](Self::validation_args) and the value as parameter.
- fn validator(_props: &Self::ValidateClosure, _value: &Value) -> Result<(), Error> {
- Ok(())
+ /// If valid, it should return the value to be submitted.
+ fn validator(_props: &Self::ValidateClosure, value: &Value) -> Result<Value, Error> {
+ Ok(value.clone())
}
/// Returns the initial field setup.
label_clicked_closure: Option<Closure<dyn Fn()>>,
/// The validation function
- validate: ValidateFn<Value>,
+ validate: SubmitValidateFn<Value>,
}
impl<MF: ManagedField + 'static> ManagedFieldMaster<MF> {
Some(self.validate.clone()),
options,
self.comp_state.unique,
- self.comp_state.submit_converter.clone(),
);
// FormContext may already have field data (i.e for unique fields), so sync back
let props = ctx.props();
let validation_args = MF::validation_args(props);
- let validate = ValidateFn::new(move |value| MF::validator(&validation_args, value));
+ let validate = SubmitValidateFn::new(move |value| MF::validator(&validation_args, value));
let mut comp_state = MF::setup(props);
comp_state.valid = validate
.apply(&comp_state.value)
+ .map(|_| ())
.map_err(|err| err.to_string());
let sub_context = ManagedFieldContext::new(ctx, &comp_state);
}
let value = value.unwrap_or(self.comp_state.value.clone());
- let valid = valid
- .unwrap_or_else(|| self.validate.apply(&value).map_err(|e| e.to_string()));
+ let valid =
+ valid.unwrap_or_else(|| self.validate.apply(&value)
+ .map(|_| ()).map_err(|e| e.to_string()));
let value_changed = value != self.comp_state.value;
let valid_changed = valid != self.comp_state.valid;
true
}
Msg::UpdateValue(value) => {
- let valid = self.validate.apply(&value).map_err(|e| e.to_string());
+ let valid = self.validate.apply(&value).map(|_| ()).map_err(|e| e.to_string());
let value_changed = value != self.comp_state.value;
let valid_changed = valid != self.comp_state.valid;
let valid = self
.validate
.apply(&self.comp_state.value)
+ .map(|_| ())
.map_err(|e| e.to_string());
let valid_changed = valid != self.comp_state.valid;
if validation_args != old_validation_args {
log::info!("UPDATE VF {:?}", props.as_input_props().name);
- let validate = ValidateFn::new(move |value| MF::validator(&validation_args, value));
+ let validate = SubmitValidateFn::new(move |value| MF::validator(&validation_args, value));
refresh1 = true;
self.validate = validate.clone();
if let Some(field_handle) = &mut self.field_handle {
mod textarea;
pub use textarea::{PwtTextArea, TextArea};
+mod submit_validate;
+pub use submit_validate::{IntoSubmitValidateFn, SubmitValidateFn};
+
mod validate;
pub use validate::{IntoValidateFn, ValidateFn};
validate: Option<ValidateFn<T>>,
}
-impl<T: NumberTypeInfo> NumberField<T> {
- // Note: This is called on submit, but only for valid fields
- fn submit_convert(value: Value) -> Option<Value> {
- match &value {
- Value::Number(_) | Value::Null => Some(value),
- Value::String(text) => {
- if text.is_empty() {
- return Some(Value::Null);
- }
- match T::value_to_number(&value) {
- Ok(n) => Some(n.into()),
- Err(err) => {
- log::error!("NumberField: submit_convert failed - {err}");
- None
- }
- }
- }
- _ => None,
- }
- }
-}
-
impl<T: NumberTypeInfo> ManagedField for NumberField<T> {
type Properties = Number<T>;
type Message = Msg;
}
}
- fn validator(props: &Self::ValidateClosure, value: &Value) -> Result<(), Error> {
+ fn validator(props: &Self::ValidateClosure, value: &Value) -> Result<Value, Error> {
let is_empty = match value {
Value::Null => true,
Value::Number(_) => false,
if props.required {
return Err(Error::msg(tr!("Field may not be empty.")));
} else {
- return Ok(());
+ return Ok(Value::Null);
}
}
}
}
- match &props.validate {
- Some(validate) => validate.apply(&number),
- None => Ok(()),
+ if let Some(validate) = &props.validate {
+ validate.apply(&number)?;
}
+ Ok(number.into())
}
fn setup(props: &Self::Properties) -> ManagedFieldState {
default,
radio_group: false,
unique: false,
- submit_converter: Some(Callback::from(Self::submit_convert)),
}
}
}
}
- fn validator(props: &Self::ValidateClosure, value: &Value) -> Result<(), Error> {
+ fn validator(props: &Self::ValidateClosure, value: &Value) -> Result<Value, Error> {
let value = match value {
Value::Null => String::new(),
Value::String(v) => v.clone(),
- _ => {
- // should not happen
- log::error!("PwtField: got wrong data type in validate!");
- String::new()
- }
+ _ => return Err(Error::msg(tr!("got wrong data type."))),
};
if value.is_empty() {
if props.required {
bail!("Field may not be empty.");
} else {
- return Ok(());
+ return Ok(Value::String(String::new()));
}
}
if !props.store.is_empty() {
- match &props.validate {
- Some(validate) => validate.apply(&(value.into(), props.store.clone())),
- None => Ok(()),
+ if let Some(validate) = &props.validate {
+ validate.apply(&(value.clone().into(), props.store.clone()))?;
}
} else {
bail!("no data loaded");
}
+
+ Ok(Value::String(value))
}
fn setup(props: &Self::Properties) -> ManagedFieldState {
default,
radio_group: false,
unique: false,
- submit_converter: None,
}
}
--- /dev/null
+use std::rc::Rc;
+
+use anyhow::Error;
+use serde_json::Value;
+
+/// A [SubmitValidateFn] function is a callback that detrermines if the
+/// passed record is valid, and return the value to be submitted.
+///
+/// Wraps `Rc` around `Fn` so it can be passed as a prop.
+pub struct SubmitValidateFn<T>(Rc<dyn Fn(&T) -> Result<Value, Error>>);
+
+impl<T> Clone for SubmitValidateFn<T> {
+ fn clone(&self) -> Self {
+ Self(Rc::clone(&self.0))
+ }
+}
+
+impl<T> PartialEq for SubmitValidateFn<T> {
+ fn eq(&self, other: &Self) -> bool {
+ Rc::ptr_eq(&self.0, &other.0)
+ }
+}
+
+impl<T> SubmitValidateFn<T> {
+ /// Creates a new [`SubmitValidateFn`]
+ pub fn new(validate: impl 'static + Fn(&T) -> Result<Value, Error>) -> Self {
+ Self(Rc::new(validate))
+ }
+
+ /// Apply the validation function
+ pub fn apply(&self, data: &T) -> Result<Value, Error> {
+ (self.0)(data)
+ }
+}
+
+impl<T> std::fmt::Debug for SubmitValidateFn<T> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "SubmitValidateFn({:p})", self.0)
+ }
+}
+
+impl<T, F: 'static + Fn(&T) -> Result<Value, Error>> From<F> for SubmitValidateFn<T> {
+ fn from(f: F) -> Self {
+ SubmitValidateFn::new(f)
+ }
+}
+
+/// Helper trait to create an optional [SubmitValidateFn] property.
+pub trait IntoSubmitValidateFn<T> {
+ fn into_submit_validate_fn(self) -> Option<SubmitValidateFn<T>>;
+}
+
+impl<T, V: Into<SubmitValidateFn<T>>> IntoSubmitValidateFn<T> for V {
+ fn into_submit_validate_fn(self) -> Option<SubmitValidateFn<T>> {
+ Some(self.into())
+ }
+}
+impl<T, V: Into<SubmitValidateFn<T>>> IntoSubmitValidateFn<T> for Option<V> {
+ fn into_submit_validate_fn(self) -> Option<SubmitValidateFn<T>> {
+ self.map(|v| v.into())
+ }
+}
}
}
- fn validator(props: &Self::ValidateClosure, value: &Value) -> Result<(), Error> {
+ fn validator(props: &Self::ValidateClosure, value: &Value) -> Result<Value, Error> {
let value = match value {
Value::Null => String::new(),
Value::String(v) => v.clone(),
if props.required {
return Err(Error::msg(tr!("Field may not be empty.")));
} else {
- return Ok(());
+ return Ok(Value::String(String::new()));
}
}
- match &props.validate {
- Some(validate) => validate.apply(&value),
- None => Ok(()),
+ if let Some(validate) = &props.validate {
+ validate.apply(&value)?;
}
+
+ Ok(Value::String(value))
}
fn setup(props: &Self::Properties) -> ManagedFieldState {
default,
radio_group: false,
unique: false,
- submit_converter: None,
}
}
fn validation_args(_props: &Self::Properties) -> Self::ValidateClosure {}
- fn validator(_props: &Self::ValidateClosure, value: &Value) -> Result<(), Error> {
+ fn validator(_props: &Self::ValidateClosure, value: &Value) -> Result<Value, Error> {
match value {
- Value::Null | Value::Bool(_) => Ok(()),
+ Value::Null | Value::Bool(_) => Ok(value.clone()),
_ => Err(Error::msg(tr!("Got wrong data type!"))),
}
}
default,
radio_group: false,
unique: false,
- submit_converter: None,
}
}
default: default.into(),
radio_group: props.radio_group,
unique: true,
- submit_converter: None,
}
}