From 393fb7240fe26164c86c8c0dd84a83d4829d6d3d Mon Sep 17 00:00:00 2001 From: Damien Kake Date: Thu, 5 May 2022 14:49:09 +0200 Subject: [PATCH 1/7] created the validation rules project --- src/Plugin.Reactive.ValidationRules/Class1.cs | 8 +++++ .../Plugin.Reactive.ValidationRules.csproj | 7 +++++ src/Plugin.ValidationRules.sln | 31 +++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 src/Plugin.Reactive.ValidationRules/Class1.cs create mode 100644 src/Plugin.Reactive.ValidationRules/Plugin.Reactive.ValidationRules.csproj create mode 100644 src/Plugin.ValidationRules.sln diff --git a/src/Plugin.Reactive.ValidationRules/Class1.cs b/src/Plugin.Reactive.ValidationRules/Class1.cs new file mode 100644 index 0000000..dfa7ec8 --- /dev/null +++ b/src/Plugin.Reactive.ValidationRules/Class1.cs @@ -0,0 +1,8 @@ +using System; + +namespace Plugin.Reactive.ValidationRules +{ + public class Class1 + { + } +} diff --git a/src/Plugin.Reactive.ValidationRules/Plugin.Reactive.ValidationRules.csproj b/src/Plugin.Reactive.ValidationRules/Plugin.Reactive.ValidationRules.csproj new file mode 100644 index 0000000..9f5c4f4 --- /dev/null +++ b/src/Plugin.Reactive.ValidationRules/Plugin.Reactive.ValidationRules.csproj @@ -0,0 +1,7 @@ + + + + netstandard2.0 + + + diff --git a/src/Plugin.ValidationRules.sln b/src/Plugin.ValidationRules.sln new file mode 100644 index 0000000..af5c125 --- /dev/null +++ b/src/Plugin.ValidationRules.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.32228.343 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plugin.ValidationRules", "ValidationRules\Plugin.ValidationRules.csproj", "{424D34D8-A9A4-49F2-B958-324C41AA66C2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin.Reactive.ValidationRules", "Plugin.Reactive.ValidationRules\Plugin.Reactive.ValidationRules.csproj", "{1ABEA0B9-9EA6-42DD-9EA7-8B5F7542E1E9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {424D34D8-A9A4-49F2-B958-324C41AA66C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {424D34D8-A9A4-49F2-B958-324C41AA66C2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {424D34D8-A9A4-49F2-B958-324C41AA66C2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {424D34D8-A9A4-49F2-B958-324C41AA66C2}.Release|Any CPU.Build.0 = Release|Any CPU + {1ABEA0B9-9EA6-42DD-9EA7-8B5F7542E1E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1ABEA0B9-9EA6-42DD-9EA7-8B5F7542E1E9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1ABEA0B9-9EA6-42DD-9EA7-8B5F7542E1E9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1ABEA0B9-9EA6-42DD-9EA7-8B5F7542E1E9}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {599BD14E-621D-4CC6-BF59-F8DD48595F45} + EndGlobalSection +EndGlobal From d3f59069c0c84ee6ed04a6275ef8ffb194a720df Mon Sep 17 00:00:00 2001 From: Damien Kake Date: Thu, 5 May 2022 15:01:10 +0200 Subject: [PATCH 2/7] Added a bit of boiler plate code. --- src/Plugin.Reactive.ValidationRules/Class1.cs | 8 -------- .../Plugin.Reactive.ValidationRules.csproj | 10 +++++++++- src/Plugin.Reactive.ValidationRules/README.MD | 4 ++++ .../ReactiveValidatable.cs | 10 ++++++++++ 4 files changed, 23 insertions(+), 9 deletions(-) delete mode 100644 src/Plugin.Reactive.ValidationRules/Class1.cs create mode 100644 src/Plugin.Reactive.ValidationRules/README.MD create mode 100644 src/Plugin.Reactive.ValidationRules/ReactiveValidatable.cs diff --git a/src/Plugin.Reactive.ValidationRules/Class1.cs b/src/Plugin.Reactive.ValidationRules/Class1.cs deleted file mode 100644 index dfa7ec8..0000000 --- a/src/Plugin.Reactive.ValidationRules/Class1.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; - -namespace Plugin.Reactive.ValidationRules -{ - public class Class1 - { - } -} diff --git a/src/Plugin.Reactive.ValidationRules/Plugin.Reactive.ValidationRules.csproj b/src/Plugin.Reactive.ValidationRules/Plugin.Reactive.ValidationRules.csproj index 9f5c4f4..30b3a46 100644 --- a/src/Plugin.Reactive.ValidationRules/Plugin.Reactive.ValidationRules.csproj +++ b/src/Plugin.Reactive.ValidationRules/Plugin.Reactive.ValidationRules.csproj @@ -1,7 +1,15 @@ - netstandard2.0 + netstandard2.0;net6.0 + + + + + + + + diff --git a/src/Plugin.Reactive.ValidationRules/README.MD b/src/Plugin.Reactive.ValidationRules/README.MD new file mode 100644 index 0000000..8a3bc0e --- /dev/null +++ b/src/Plugin.Reactive.ValidationRules/README.MD @@ -0,0 +1,4 @@ +# Reactive Validation Rules +This plugin is an extension of the Validation rules plugin, with +a reactive programming approach to validation. It leverages +Reactive UI to perform input validation. diff --git a/src/Plugin.Reactive.ValidationRules/ReactiveValidatable.cs b/src/Plugin.Reactive.ValidationRules/ReactiveValidatable.cs new file mode 100644 index 0000000..5297b5a --- /dev/null +++ b/src/Plugin.Reactive.ValidationRules/ReactiveValidatable.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Plugin.Reactive.ValidationRules +{ + public class ReactiveValidatable + { + } +} From c0d1cf8edef88a15a8151d08d6fa3eb6d636bdf5 Mon Sep 17 00:00:00 2001 From: damiendoumer Date: Thu, 5 May 2022 16:28:21 +0200 Subject: [PATCH 3/7] Populated the reactive validatable class with all of its reactive functionalities, and validation logic. --- .../Plugin.Reactive.ValidationRules.csproj | 6 +- .../ReactiveRules/BaseReactiveRule.cs | 7 + .../ReactiveValidatable.cs | 210 +++++++++++++++++- 3 files changed, 221 insertions(+), 2 deletions(-) create mode 100644 src/Plugin.Reactive.ValidationRules/ReactiveRules/BaseReactiveRule.cs diff --git a/src/Plugin.Reactive.ValidationRules/Plugin.Reactive.ValidationRules.csproj b/src/Plugin.Reactive.ValidationRules/Plugin.Reactive.ValidationRules.csproj index 30b3a46..d5d6eca 100644 --- a/src/Plugin.Reactive.ValidationRules/Plugin.Reactive.ValidationRules.csproj +++ b/src/Plugin.Reactive.ValidationRules/Plugin.Reactive.ValidationRules.csproj @@ -1,7 +1,7 @@ - netstandard2.0;net6.0 + netstandard2.0;net6.0 @@ -12,4 +12,8 @@ + + + + diff --git a/src/Plugin.Reactive.ValidationRules/ReactiveRules/BaseReactiveRule.cs b/src/Plugin.Reactive.ValidationRules/ReactiveRules/BaseReactiveRule.cs new file mode 100644 index 0000000..6d553fb --- /dev/null +++ b/src/Plugin.Reactive.ValidationRules/ReactiveRules/BaseReactiveRule.cs @@ -0,0 +1,7 @@ +namespace Plugin.Reactive.ValidationRules.ReactiveRules +{ + public class BaseReactiveRule + { + + } +} \ No newline at end of file diff --git a/src/Plugin.Reactive.ValidationRules/ReactiveValidatable.cs b/src/Plugin.Reactive.ValidationRules/ReactiveValidatable.cs index 5297b5a..fa2697b 100644 --- a/src/Plugin.Reactive.ValidationRules/ReactiveValidatable.cs +++ b/src/Plugin.Reactive.ValidationRules/ReactiveValidatable.cs @@ -1,10 +1,218 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; +using Plugin.ValidationRules.Interfaces; +using ReactiveUI; namespace Plugin.Reactive.ValidationRules { - public class ReactiveValidatable + /// + /// A validatable instance, that leverages the power of + /// reactive programming to automatically validate its value when + /// a change is made to it, and generates error messages based on + /// the validation rules it is given. + /// + /// + public class ReactiveValidatable : ReactiveObject, IValidity, IDisposable { + public List> _validations; + protected IDisposable _valueDisposable; + private bool _disposed; + + private T _value; + /// + /// The value to be validated. + /// NOTE: This object automatically validates + /// this value when it changes. No check has to + /// be done by external code. + /// + public T Value + { + get { return _value; } + set { this.RaiseAndSetIfChanged(ref _value, value); } + } + + private bool _isValid; + /// + /// Determines if the Value is valid or not. + /// + public bool IsValid + { + get { return _isValid; } + set { this.RaiseAndSetIfChanged(ref _isValid, value); } + } + + private string _errorText; + /// + /// A simple error message displayed by + /// the UI when validation fails. + /// + public string ErrorMessage + { + get { return _errorText; } + set { this.RaiseAndSetIfChanged(ref _errorText, value); } + } + private List _errors; + /// + /// List of errors users have before can save the record + /// + public List Errors + { + get => _errors; + set + { + Error = value?.Count > 0 ? value.FirstOrDefault() : string.Empty; + this.RaiseAndSetIfChanged(ref _errors, value); + } + } + private bool _hasErrors; + /// + /// The value indicating whether the validation has errors. + /// + public bool HasErrors + { + get => _hasErrors; + set => this.RaiseAndSetIfChanged(ref _hasErrors, value); + } + public string Error { get; set; } + /// + /// Formats the list of error messages + /// into a simple error message understandable + /// in one line. + /// + public Func, string> ErrorMessageFormatter { get; set; } + + /// + /// Create an instance of a reactive validatable object. + /// + /// The value to be watched and validated. + public ReactiveValidatable(T value) + { + ErrorMessageFormatter = (errorMessages) => + { + if (errorMessages != null && errorMessages.Any()) + { + var builder = new StringBuilder(); + for (int i = 0; i < errorMessages.Count;i++) + { + builder.Append(errorMessages[i]); + if (i < errorMessages.Count - 1) + { + builder.Append(Environment.NewLine); + } + } + + return builder.ToString(); + } + + return string.Empty; + }; + Value = value; + _validations = new List>(); + Errors = new List(); + _valueDisposable = this.WhenAnyValue(obj => obj.Value).Subscribe((val) => + { + Validate(); + }); + } + + /// + /// Create an instance of a reactive validatable object. + /// + /// The value to be watched and validated. + /// The validation rules that the value should be checked for. + public ReactiveValidatable(T value, params IValidationRule[] validations) : this(value) + { + _validations.AddRange(validations); + } + + /// + /// Create an instance of a reactive validatable object. + /// + /// The value to be watched and validated. + /// A function to format the list of errors to be displayed in a single string on the UI. + /// The validation rules that the value should be checked for. + public ReactiveValidatable(T value, Func, string> errorMessageFormatter, + params IValidationRule[] validations) : this(value, validations) + { + ErrorMessageFormatter = errorMessageFormatter; + } + + /// + /// Validate the value of this property + /// each time it changes. + /// + /// + public bool Validate() + { + IsValid = TryValidate(Value); + if (Errors.Any()) + { + HasErrors = true; + ErrorMessage = ErrorMessageFormatter(Errors); + } + else + { + ErrorMessage = string.Empty; + } + return IsValid; + } + + /// + /// Tells if the object is valid without + /// setting its state + /// + /// + public bool TryValidate(T val) + { + Errors.Clear(); + Errors = new List(_validations.Where(v => !v.Check(val)) + .Select(v => v.ValidationMessage)); + + return !Errors.Any(); + } + + #region Disposing + + private void ReleaseManagedResources() + { + // Release resources + _validations?.Clear(); + _errors?.Clear(); + _value = default(T); + _valueDisposable.Dispose(); + } + + public void Dispose() + { + // If this function is being called the user wants to release the + // resources. lets call the Dispose which will do this for us. + Dispose(true); + + // Now since we have done the cleanup already there is nothing left + // for the Finalizer to do. So lets tell the GC not to call it later. + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + ReleaseManagedResources(); + } + + _disposed = true; + } + } + + ~ReactiveValidatable() + { + Dispose(false); + } + #endregion + } } From a9dac7dbd59e0def8b3682c4a32503b92b2a36ef Mon Sep 17 00:00:00 2001 From: damiendoumer Date: Tue, 17 May 2022 17:01:35 +0200 Subject: [PATCH 4/7] Started the addition of reactive validation rules example. --- samples/Xamarin.Forms/ValidationRulesTest.sln | 26 ++++++++ .../ValidationRulesTest.csproj | 1 + .../ReactiveValidationExample1ViewModel.cs | 65 +++++++++++++++++++ .../ValidationRulesTest/Views/Example1.xaml | 11 ++-- .../Views/ReactiveValidationExample1.xaml | 15 +++++ .../Views/ReactiveValidationExample1.xaml.cs | 23 +++++++ .../ReactiveValidatable.cs | 18 +++-- 7 files changed, 148 insertions(+), 11 deletions(-) create mode 100644 samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/ViewModels/ReactiveValidationExample1ViewModel.cs create mode 100644 samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml create mode 100644 samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml.cs diff --git a/samples/Xamarin.Forms/ValidationRulesTest.sln b/samples/Xamarin.Forms/ValidationRulesTest.sln index b008074..9eceb37 100644 --- a/samples/Xamarin.Forms/ValidationRulesTest.sln +++ b/samples/Xamarin.Forms/ValidationRulesTest.sln @@ -11,6 +11,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ValidationRulesTest", "Vali EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plugin.ValidationRules", "..\..\src\ValidationRules\Plugin.ValidationRules.csproj", "{1DA953F3-E1A6-425A-B823-629006A45561}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin.Reactive.ValidationRules", "..\..\src\Plugin.Reactive.ValidationRules\Plugin.Reactive.ValidationRules.csproj", "{AAC08845-4F44-4851-B512-372380949518}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Ad-Hoc|Any CPU = Ad-Hoc|Any CPU @@ -159,6 +161,30 @@ Global {1DA953F3-E1A6-425A-B823-629006A45561}.Release|iPhone.Build.0 = Release|Any CPU {1DA953F3-E1A6-425A-B823-629006A45561}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {1DA953F3-E1A6-425A-B823-629006A45561}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.AppStore|iPhone.Build.0 = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Debug|iPhone.Build.0 = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Release|Any CPU.Build.0 = Release|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Release|iPhone.ActiveCfg = Release|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Release|iPhone.Build.0 = Release|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/ValidationRulesTest.csproj b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/ValidationRulesTest.csproj index 71c7ed3..3968f8a 100644 --- a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/ValidationRulesTest.csproj +++ b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/ValidationRulesTest.csproj @@ -14,6 +14,7 @@ + diff --git a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/ViewModels/ReactiveValidationExample1ViewModel.cs b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/ViewModels/ReactiveValidationExample1ViewModel.cs new file mode 100644 index 0000000..536131c --- /dev/null +++ b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/ViewModels/ReactiveValidationExample1ViewModel.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using Plugin.Reactive.ValidationRules; +using Plugin.ValidationRules.Extensions; +using Plugin.ValidationRules.Interfaces; +using Plugin.ValidationRules.Rules; +using ValidationRulesTest.Validations; +using EmailRule = Plugin.ValidationRules.Rules.EmailRule; + +namespace ValidationRulesTest.ViewModels +{ + public class ReactiveValidationExample1ViewModel : ExtendedPropertyChanged + { + ReactiveValidatable _name; + public ReactiveValidatable Name + { + get => _name; + set => SetProperty(ref _name, value); + } + + public ReactiveValidatable _email; + public ReactiveValidatable Email + { + get => _email; + set => SetProperty(ref _email, value); + } + + public ReactiveValidatable _password; + public ReactiveValidatable Password + { + get => _password; + set => SetProperty(ref _password, value); + } + + public ReactiveValidatable _age; + public ReactiveValidatable Age + { + get => _age; + set => SetProperty(ref _age, value); + } + + public ReactiveValidationExample1ViewModel() + { + Name = new ReactiveValidatable( + string.Empty, new NotEmptyRule(string.Empty) + { + ValidationMessage = "Name is required" + }); + + Email = new ReactiveValidatable(string.Empty, + new EmailRule() + { + ValidationMessage = "Email wrongly formatted" + }, + new NotEmptyRule(string.Empty) + { + ValidationMessage = "Email is required" + }); + + Password = new ReactiveValidatable(string.Empty, new PasswordRule(), new NotEmptyRule(string.Empty) + { + ValidationMessage = "Password is required" + }); + } + } +} \ No newline at end of file diff --git a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/Example1.xaml b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/Example1.xaml index 9db1620..dc8f8d1 100644 --- a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/Example1.xaml +++ b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/Example1.xaml @@ -1,10 +1,9 @@  - + diff --git a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml new file mode 100644 index 0000000..ce6dae6 --- /dev/null +++ b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml @@ -0,0 +1,15 @@ + + + + + + + + \ No newline at end of file diff --git a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml.cs b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml.cs new file mode 100644 index 0000000..17d555a --- /dev/null +++ b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ValidationRulesTest.ViewModels; +using Xamarin.Forms; +using Xamarin.Forms.Xaml; + +namespace ValidationRulesTest.Views +{ + [XamlCompilation(XamlCompilationOptions.Compile)] + public partial class ReactiveValidationExample1 : ContentPage + { + ReactiveValidationExample1ViewModel _viewModel; + public ReactiveValidationExample1() + { + InitializeComponent(); + _viewModel = new ReactiveValidationExample1ViewModel(); + BindingContext = _viewModel; + } + } +} \ No newline at end of file diff --git a/src/Plugin.Reactive.ValidationRules/ReactiveValidatable.cs b/src/Plugin.Reactive.ValidationRules/ReactiveValidatable.cs index fa2697b..65aad4c 100644 --- a/src/Plugin.Reactive.ValidationRules/ReactiveValidatable.cs +++ b/src/Plugin.Reactive.ValidationRules/ReactiveValidatable.cs @@ -84,10 +84,9 @@ public bool HasErrors public Func, string> ErrorMessageFormatter { get; set; } /// - /// Create an instance of a reactive validatable object. + /// Creates a new instance of the validatable object. /// - /// The value to be watched and validated. - public ReactiveValidatable(T value) + public ReactiveValidatable() { ErrorMessageFormatter = (errorMessages) => { @@ -108,15 +107,24 @@ public ReactiveValidatable(T value) return string.Empty; }; - Value = value; + _validations = new List>(); Errors = new List(); - _valueDisposable = this.WhenAnyValue(obj => obj.Value).Subscribe((val) => + _valueDisposable = this.WhenAnyValue(x => x.Value).Subscribe(x => { Validate(); }); } + /// + /// Create an instance of a reactive validatable object. + /// + /// The value to be watched and validated. + public ReactiveValidatable(T value) : this() + { + Value = value; + } + /// /// Create an instance of a reactive validatable object. /// From 7ee7e29c848f8d44fa8c0efb1c8bae09aa02c443 Mon Sep 17 00:00:00 2001 From: damiendoumer Date: Tue, 17 May 2022 17:24:29 +0200 Subject: [PATCH 5/7] Implemented Reactive validation example step 1. --- .../ReactiveValidationExample1ViewModel.cs | 17 ++--- .../ValidationRulesTest/Views/Examples.xaml | 6 ++ .../Views/Examples.xaml.cs | 5 ++ .../Views/ReactiveValidationExample1.xaml | 71 +++++++++++++++++-- .../Views/ReactiveValidationExample1.xaml.cs | 6 ++ 5 files changed, 90 insertions(+), 15 deletions(-) diff --git a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/ViewModels/ReactiveValidationExample1ViewModel.cs b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/ViewModels/ReactiveValidationExample1ViewModel.cs index 536131c..6018fc8 100644 --- a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/ViewModels/ReactiveValidationExample1ViewModel.cs +++ b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/ViewModels/ReactiveValidationExample1ViewModel.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Plugin.Reactive.ValidationRules; using Plugin.ValidationRules.Extensions; @@ -8,7 +9,7 @@ namespace ValidationRulesTest.ViewModels { - public class ReactiveValidationExample1ViewModel : ExtendedPropertyChanged + public class ReactiveValidationExample1ViewModel : ExtendedPropertyChanged, IDisposable { ReactiveValidatable _name; public ReactiveValidatable Name @@ -31,13 +32,6 @@ public ReactiveValidatable Password set => SetProperty(ref _password, value); } - public ReactiveValidatable _age; - public ReactiveValidatable Age - { - get => _age; - set => SetProperty(ref _age, value); - } - public ReactiveValidationExample1ViewModel() { Name = new ReactiveValidatable( @@ -61,5 +55,12 @@ public ReactiveValidationExample1ViewModel() ValidationMessage = "Password is required" }); } + + public void Dispose() + { + _name?.Dispose(); + _email?.Dispose(); + _password?.Dispose(); + } } } \ No newline at end of file diff --git a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml index 9ecd20f..0504edd 100644 --- a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml +++ b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml @@ -51,6 +51,12 @@ x:Name="example8" Clicked="example8_Clicked" Text="Example 8" /> + + diff --git a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml.cs b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml.cs index ba2373f..370a280 100644 --- a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml.cs +++ b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml.cs @@ -56,5 +56,10 @@ private void example8_Clicked(object sender, EventArgs e) { Navigation.PushAsync(new Example8()); } + + private void ReactiveValidationExample1_Clicked(object sender, EventArgs e) + { + Navigation.PushAsync(new ReactiveValidationExample1()); + } } } \ No newline at end of file diff --git a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml index ce6dae6..b90b02e 100644 --- a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml +++ b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml @@ -5,11 +5,68 @@ xmlns:viewModels="clr-namespace:ValidationRulesTest.ViewModels;assembly=ValidationRulesTest" x:Class="ValidationRulesTest.Views.ReactiveValidationExample1" x:DataType="viewModels:ReactiveValidationExample1ViewModel"> - - - - + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml.cs b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml.cs index 17d555a..492a25d 100644 --- a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml.cs +++ b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml.cs @@ -19,5 +19,11 @@ public ReactiveValidationExample1() _viewModel = new ReactiveValidationExample1ViewModel(); BindingContext = _viewModel; } + + protected override void OnDisappearing() + { + _viewModel.Dispose(); + base.OnDisappearing(); + } } } \ No newline at end of file From 3f05a38fcef07f3b9a046e4cad099212b10ded33 Mon Sep 17 00:00:00 2001 From: Damien Doumer Date: Tue, 31 May 2022 23:35:52 +0200 Subject: [PATCH 6/7] Continued MAUi reactive validation rules integration --- .../ValidationRulesTest.sln | 6 ++ .../ValidationRulesTest.csproj | 1 + .../ReactiveValidationExample1ViewModel.cs | 66 ++++++++++++++ .../ValidationRulesTest/Views/Examples.xaml | 6 ++ .../Views/Examples.xaml.cs | 4 + .../Views/ReactiveValidationExample1.xaml | 86 +++++++++++++++++++ .../Views/ReactiveValidationExample1.xaml.cs | 23 +++++ .../Views/ReactiveValidationExample1.xaml | 1 - 8 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 samples/Maui/ValidationRulesTest/ValidationRulesTest/ViewModels/ReactiveValidationExample1ViewModel.cs create mode 100644 samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml create mode 100644 samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml.cs diff --git a/samples/Maui/ValidationRulesTest/ValidationRulesTest.sln b/samples/Maui/ValidationRulesTest/ValidationRulesTest.sln index d009e7a..8dbb4eb 100644 --- a/samples/Maui/ValidationRulesTest/ValidationRulesTest.sln +++ b/samples/Maui/ValidationRulesTest/ValidationRulesTest.sln @@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ValidationRulesTest", "Vali EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plugin.ValidationRules", "..\..\..\src\ValidationRules\Plugin.ValidationRules.csproj", "{74CB3C79-59A7-4AAA-A94F-EDA9D278847E}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plugin.Reactive.ValidationRules", "..\..\..\src\Plugin.Reactive.ValidationRules\Plugin.Reactive.ValidationRules.csproj", "{44FC6F88-FC72-4DC9-B6EB-E76B896A3861}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -23,6 +25,10 @@ Global {74CB3C79-59A7-4AAA-A94F-EDA9D278847E}.Debug|Any CPU.Build.0 = Debug|Any CPU {74CB3C79-59A7-4AAA-A94F-EDA9D278847E}.Release|Any CPU.ActiveCfg = Release|Any CPU {74CB3C79-59A7-4AAA-A94F-EDA9D278847E}.Release|Any CPU.Build.0 = Release|Any CPU + {44FC6F88-FC72-4DC9-B6EB-E76B896A3861}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {44FC6F88-FC72-4DC9-B6EB-E76B896A3861}.Debug|Any CPU.Build.0 = Debug|Any CPU + {44FC6F88-FC72-4DC9-B6EB-E76B896A3861}.Release|Any CPU.ActiveCfg = Release|Any CPU + {44FC6F88-FC72-4DC9-B6EB-E76B896A3861}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/samples/Maui/ValidationRulesTest/ValidationRulesTest/ValidationRulesTest.csproj b/samples/Maui/ValidationRulesTest/ValidationRulesTest/ValidationRulesTest.csproj index 17c6f3f..f6d5791 100644 --- a/samples/Maui/ValidationRulesTest/ValidationRulesTest/ValidationRulesTest.csproj +++ b/samples/Maui/ValidationRulesTest/ValidationRulesTest/ValidationRulesTest.csproj @@ -50,6 +50,7 @@ + diff --git a/samples/Maui/ValidationRulesTest/ValidationRulesTest/ViewModels/ReactiveValidationExample1ViewModel.cs b/samples/Maui/ValidationRulesTest/ValidationRulesTest/ViewModels/ReactiveValidationExample1ViewModel.cs new file mode 100644 index 0000000..93b72a9 --- /dev/null +++ b/samples/Maui/ValidationRulesTest/ValidationRulesTest/ViewModels/ReactiveValidationExample1ViewModel.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using Plugin.Reactive.ValidationRules; +using Plugin.ValidationRules.Extensions; +using Plugin.ValidationRules.Interfaces; +using Plugin.ValidationRules.Rules; +using ValidationRulesTest.Validations; +using EmailRule = Plugin.ValidationRules.Rules.EmailRule; + +namespace ValidationRulesTest.ViewModels +{ + public class ReactiveValidationExample1ViewModel : ExtendedPropertyChanged, IDisposable + { + ReactiveValidatable _name; + public ReactiveValidatable Name + { + get => _name; + set => SetProperty(ref _name, value); + } + + public ReactiveValidatable _email; + public ReactiveValidatable Email + { + get => _email; + set => SetProperty(ref _email, value); + } + + public ReactiveValidatable _password; + public ReactiveValidatable Password + { + get => _password; + set => SetProperty(ref _password, value); + } + + public ReactiveValidationExample1ViewModel() + { + Name = new ReactiveValidatable( + string.Empty, new NotEmptyRule(string.Empty) + { + ValidationMessage = "Name is required" + }); + + Email = new ReactiveValidatable(string.Empty, + new EmailRule() + { + ValidationMessage = "Email wrongly formatted" + }, + new NotEmptyRule(string.Empty) + { + ValidationMessage = "Email is required" + }); + + Password = new ReactiveValidatable(string.Empty, new PasswordRule(), new NotEmptyRule(string.Empty) + { + ValidationMessage = "Password is required" + }); + } + + public void Dispose() + { + _name?.Dispose(); + _email?.Dispose(); + _password?.Dispose(); + } + } +} diff --git a/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml b/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml index f97d39b..b80b9a2 100644 --- a/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml +++ b/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml @@ -52,6 +52,12 @@ Clicked="example8_Clicked" Text="Example 8" /> + + diff --git a/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml.cs b/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml.cs index 5e5842c..a6347f8 100644 --- a/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml.cs +++ b/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml.cs @@ -47,5 +47,9 @@ private void example8_Clicked(object sender, EventArgs e) { Navigation.PushAsync(new Example8()); } + private void ReactiveValidationExample1_Clicked(object sender, EventArgs e) + { + Navigation.PushAsync(new ReactiveValidationExample1()); + } } } \ No newline at end of file diff --git a/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml b/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml new file mode 100644 index 0000000..392295f --- /dev/null +++ b/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml.cs b/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml.cs new file mode 100644 index 0000000..4dc378b --- /dev/null +++ b/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml.cs @@ -0,0 +1,23 @@ +using ValidationRulesTest.ViewModels; + +namespace ValidationRulesTest.Views +{ + [XamlCompilation(XamlCompilationOptions.Compile)] + public partial class ReactiveValidationExample1 : ContentPage + { + ReactiveValidationExample1ViewModel _viewModel; + + public ReactiveValidationExample1() + { + InitializeComponent(); + _viewModel = new ReactiveValidationExample1ViewModel(); + BindingContext = _viewModel; + } + + protected override void OnDisappearing() + { + _viewModel.Dispose(); + base.OnDisappearing(); + } + } +} \ No newline at end of file diff --git a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml index b90b02e..b6c1e15 100644 --- a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml +++ b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml @@ -1,5 +1,4 @@ - Date: Thu, 2 Jun 2022 08:30:33 +0200 Subject: [PATCH 7/7] Fixed #23 - Added a reactive validation rule mechanism based on the original validation rules plugin - Made a demo of how these reactive validation rules function on Xamarin.Forms and MAUI --- .../Views/ReactiveValidationExample1.xaml | 52 +++--- .../Resources/Resource.designer.cs | 2 +- .../ValidationRulesTest.csproj | 5 +- .../ReactiveValidationExample1ViewModel.cs | 8 +- .../Views/ReactiveValidationExample1.xaml | 164 +++++++++++------- .../Views/ReactiveValidationExample1.xaml.cs | 23 +-- 6 files changed, 153 insertions(+), 101 deletions(-) diff --git a/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml b/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml index 392295f..3f034c7 100644 --- a/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml +++ b/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml @@ -22,19 +22,23 @@ - - @@ -42,33 +46,32 @@ - - - - diff --git a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest.Android/Resources/Resource.designer.cs b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest.Android/Resources/Resource.designer.cs index 963aa61..6e7591e 100644 --- a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest.Android/Resources/Resource.designer.cs +++ b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest.Android/Resources/Resource.designer.cs @@ -14,7 +14,7 @@ namespace ValidationRulesTest.Droid { - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "12.3.99.43")] public partial class Resource { diff --git a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/ValidationRulesTest.csproj b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/ValidationRulesTest.csproj index 3968f8a..a99db12 100644 --- a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/ValidationRulesTest.csproj +++ b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/ValidationRulesTest.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -19,6 +19,9 @@ + + ReactiveValidationExample1.xaml + Example1.xaml diff --git a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/ViewModels/ReactiveValidationExample1ViewModel.cs b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/ViewModels/ReactiveValidationExample1ViewModel.cs index 6018fc8..02814d9 100644 --- a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/ViewModels/ReactiveValidationExample1ViewModel.cs +++ b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/ViewModels/ReactiveValidationExample1ViewModel.cs @@ -24,7 +24,7 @@ public ReactiveValidatable Email get => _email; set => SetProperty(ref _email, value); } - + public ReactiveValidatable _password; public ReactiveValidatable Password { @@ -39,17 +39,17 @@ public ReactiveValidationExample1ViewModel() { ValidationMessage = "Name is required" }); - + Email = new ReactiveValidatable(string.Empty, new EmailRule() { ValidationMessage = "Email wrongly formatted" - }, + }, new NotEmptyRule(string.Empty) { ValidationMessage = "Email is required" }); - + Password = new ReactiveValidatable(string.Empty, new PasswordRule(), new NotEmptyRule(string.Empty) { ValidationMessage = "Password is required" diff --git a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml index b6c1e15..cc51424 100644 --- a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml +++ b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml @@ -1,71 +1,117 @@ - - + - - - - - - + + + + + + + + + + + + - - - - - + + + + + + + + + + + - + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + - - - + + + + + + + - - - \ No newline at end of file + + + diff --git a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml.cs b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml.cs index 492a25d..f193cec 100644 --- a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml.cs +++ b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml.cs @@ -1,29 +1,24 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using ValidationRulesTest.ViewModels; +using System; using Xamarin.Forms; -using Xamarin.Forms.Xaml; +using ValidationRulesTest.ViewModels; -namespace ValidationRulesTest.Views +namespace ValidationRulesTest { - [XamlCompilation(XamlCompilationOptions.Compile)] public partial class ReactiveValidationExample1 : ContentPage { - ReactiveValidationExample1ViewModel _viewModel; + ReactiveValidationExample1ViewModel _context; public ReactiveValidationExample1() { InitializeComponent(); - _viewModel = new ReactiveValidationExample1ViewModel(); - BindingContext = _viewModel; + + _context = new ReactiveValidationExample1ViewModel(); + BindingContext = _context; } protected override void OnDisappearing() { - _viewModel.Dispose(); + _context.Dispose(); base.OnDisappearing(); } } -} \ No newline at end of file +}