Skip to content
This repository has been archived by the owner on Oct 29, 2024. It is now read-only.

Commit

Permalink
Add basic validated controls
Browse files Browse the repository at this point in the history
  • Loading branch information
Dreamescaper committed Dec 23, 2020
1 parent 86d19b7 commit 42d9b13
Show file tree
Hide file tree
Showing 12 changed files with 815 additions and 0 deletions.
15 changes: 15 additions & 0 deletions MobileBlazorBindings.sln
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HybridMsalAuthApp.macOS", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HybridMsalAuthApp.Windows", "samples\HybridMsalAuthSample\HybridMsalAuthApp.Windows\HybridMsalAuthApp.Windows.csproj", "{7F72AEB6-7077-4F58-A3EB-BC668E8EEECF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.MobileBlazorBindings.Forms", "src\Microsoft.MobileBlazorBindings.Forms\Microsoft.MobileBlazorBindings.Forms.csproj", "{4A24D736-ED4E-4C61-8C24-2B8CEFC1B263}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -921,6 +923,18 @@ Global
{7F72AEB6-7077-4F58-A3EB-BC668E8EEECF}.Release|iPhone.Build.0 = Release|Any CPU
{7F72AEB6-7077-4F58-A3EB-BC668E8EEECF}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{7F72AEB6-7077-4F58-A3EB-BC668E8EEECF}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{4A24D736-ED4E-4C61-8C24-2B8CEFC1B263}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4A24D736-ED4E-4C61-8C24-2B8CEFC1B263}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4A24D736-ED4E-4C61-8C24-2B8CEFC1B263}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{4A24D736-ED4E-4C61-8C24-2B8CEFC1B263}.Debug|iPhone.Build.0 = Debug|Any CPU
{4A24D736-ED4E-4C61-8C24-2B8CEFC1B263}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{4A24D736-ED4E-4C61-8C24-2B8CEFC1B263}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{4A24D736-ED4E-4C61-8C24-2B8CEFC1B263}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4A24D736-ED4E-4C61-8C24-2B8CEFC1B263}.Release|Any CPU.Build.0 = Release|Any CPU
{4A24D736-ED4E-4C61-8C24-2B8CEFC1B263}.Release|iPhone.ActiveCfg = Release|Any CPU
{4A24D736-ED4E-4C61-8C24-2B8CEFC1B263}.Release|iPhone.Build.0 = Release|Any CPU
{4A24D736-ED4E-4C61-8C24-2B8CEFC1B263}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{4A24D736-ED4E-4C61-8C24-2B8CEFC1B263}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -995,6 +1009,7 @@ Global
{D3B0F2E0-E1A6-4F9F-8B64-5EB147B1B817} = {EDBF7495-7557-457A-96FF-DB68D4B57C2F}
{84EBDBF2-0E83-4D20-BCEA-F00FF716F2F6} = {EDBF7495-7557-457A-96FF-DB68D4B57C2F}
{7F72AEB6-7077-4F58-A3EB-BC668E8EEECF} = {EDBF7495-7557-457A-96FF-DB68D4B57C2F}
{4A24D736-ED4E-4C61-8C24-2B8CEFC1B263} = {175AB6E2-5FB5-4C15-94C2-DCA2EE6B0703}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A645A7FF-3F09-414D-A391-7E50C3597F05}
Expand Down
71 changes: 71 additions & 0 deletions src/Microsoft.MobileBlazorBindings.Forms/CascadingEditContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Rendering;
using System;

namespace Microsoft.MobileBlazorBindings.Forms
{
public class CascadingEditContext : ComponentBase
{
private EditContext _editContext;
private bool _hasSetEditContextExplicitly;

[Parameter]
public EditContext EditContext
{
get => _editContext;
set
{
_editContext = value;
_hasSetEditContextExplicitly = value != null;
}
}

[Parameter] public object Model { get; set; }

[Parameter] public RenderFragment<EditContext> ChildContent { get; set; }

protected override void OnParametersSet()
{
if (_hasSetEditContextExplicitly && Model != null)
{
throw new InvalidOperationException($"{nameof(CascadingEditContext)} requires a {nameof(Model)} " +
$"parameter, or an {nameof(EditContext)} parameter, but not both.");
}
else if (!_hasSetEditContextExplicitly && Model == null)
{
throw new InvalidOperationException($"{nameof(CascadingEditContext)} requires either a {nameof(Model)} " +
$"parameter, or an {nameof(EditContext)} parameter, please provide one of these.");
}

// Update _editContext if we don't have one yet, or if they are supplying a
// potentially new EditContext, or if they are supplying a different Model
if (Model != null && Model != _editContext?.Model)
{
_editContext = new EditContext(Model!);
}
}

protected override void BuildRenderTree(RenderTreeBuilder builder)
{
if (builder is null)
throw new ArgumentNullException(nameof(builder));

// If _editContext changes, tear down and recreate all descendants.
// This is so we can safely use the IsFixed optimization on CascadingValue,
// optimizing for the common case where _editContext never changes.
builder.OpenRegion(_editContext.GetHashCode());

builder.OpenComponent<CascadingValue<EditContext>>(0);
builder.AddAttribute(1, "IsFixed", true);
builder.AddAttribute(2, "Value", _editContext);
builder.AddAttribute(3, "ChildContent", ChildContent?.Invoke(_editContext));
builder.CloseComponent();

builder.CloseRegion();
}
}
}
84 changes: 84 additions & 0 deletions src/Microsoft.MobileBlazorBindings.Forms/InputViewWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

using Microsoft.AspNetCore.Components;
using System.Collections.Generic;
using System.Linq;
using XF = Xamarin.Forms;

namespace Microsoft.MobileBlazorBindings.Forms
{
public abstract class InputViewWrapper : ViewWrapper
{
/// <summary>
/// Gets or sets a value that indicates the number of device-independent units that should be in between characters in the text displayed by the Entry. Applies to Text and Placeholder.
/// </summary>
/// <value>
/// The number of device-independent units that should be in between characters in the text.
/// </value>
[Parameter] public double? CharacterSpacing { get; set; }
/// <summary>
/// Gets or sets a value that indicates whether user should be prevented from modifying the text. Default is <see langword="false" />.
/// </summary>
/// <value>
/// If <see langword="true" />, user cannot modify text. Else, <see langword="false" />.
/// </value>
[Parameter] public bool? IsReadOnly { get; set; }
/// <summary>
/// Gets or sets a value that controls whether spell checking is enabled.
/// </summary>
/// <value>
/// <see langword="true" /> if spell checking is enabled. Otherwise <see langword="false" />.
/// </value>
[Parameter] public bool? IsSpellCheckEnabled { get; set; }
/// <summary>
/// Gets or sets the maximum allowed length of input.
/// </summary>
/// <value>
/// An integer in the interval [0,<c>int.MaxValue</c>]. The default value is <c>Int.MaxValue</c>.
/// </value>
[Parameter] public int? MaxLength { get; set; }
/// <summary>
/// Gets or sets the text that is displayed when the control is empty.
/// </summary>
/// <value>
/// The text that is displayed when the control is empty.
/// </value>
[Parameter] public string Placeholder { get; set; }
/// <summary>
/// Gets or sets the color of the placeholder text.
/// </summary>
/// <value>
/// The color of the placeholder text.
/// </value>
[Parameter] public XF.Color? PlaceholderColor { get; set; }
/// <summary>
/// Gets or sets the text color.
/// </summary>
[Parameter] public XF.Color? TextColor { get; set; }
[Parameter] public XF.TextTransform? TextTransform { get; set; }

private IEnumerable<KeyValuePair<string, object>> InputViewProperties
{
get
{
yield return new KeyValuePair<string, object>(nameof(CharacterSpacing), CharacterSpacing);
yield return new KeyValuePair<string, object>(nameof(IsReadOnly), IsReadOnly);
yield return new KeyValuePair<string, object>(nameof(IsSpellCheckEnabled), IsSpellCheckEnabled);
yield return new KeyValuePair<string, object>(nameof(MaxLength), MaxLength);
yield return new KeyValuePair<string, object>(nameof(Placeholder), Placeholder);
yield return new KeyValuePair<string, object>(nameof(PlaceholderColor), PlaceholderColor);
yield return new KeyValuePair<string, object>(nameof(TextColor), TextColor);
yield return new KeyValuePair<string, object>(nameof(TextTransform), TextTransform);
}
}

protected override IEnumerable<KeyValuePair<string, object>> WrappedProperties
{
get
{
return base.WrappedProperties.Concat(InputViewProperties);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RazorLangVersion>3.0</RazorLangVersion>
<Description>Forms and validation support for Experimental Mobile Blazor Bindings.</Description>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Microsoft.MobileBlazorBindings\Microsoft.MobileBlazorBindings.csproj" />
</ItemGroup>

</Project>
34 changes: 34 additions & 0 deletions src/Microsoft.MobileBlazorBindings.Forms/SubmitButton.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
@inherits ViewWrapper

<Button OnClick="OnButtonClick" @attributes="Properties" />

@code {
[CascadingParameter] EditContext CascadedEditContext { get; set; }

[Parameter] public EventCallback<EditContext> OnValidSubmit { get; set; }
[Parameter] public EventCallback<EditContext> OnInvalidSubmit { get; set; }

protected override void OnInitialized()
{
if (CascadedEditContext is null)
{
throw new InvalidOperationException($"{GetType()} requires a cascading parameter " +
$"of type {nameof(EditContext)}. For example, you can use {GetType().FullName} inside " +
$"an {nameof(CascadedEditContext)}.");
}
}

private async Task OnButtonClick()
{
CascadedEditContext.Validate();

if (CascadedEditContext.GetValidationMessages().Any())
{
await OnInvalidSubmit.InvokeAsync(CascadedEditContext).ConfigureAwait(false);
}
else
{
await OnValidSubmit.InvokeAsync(CascadedEditContext).ConfigureAwait(false);
}
}
}
92 changes: 92 additions & 0 deletions src/Microsoft.MobileBlazorBindings.Forms/SubmitButton.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

using Microsoft.AspNetCore.Components;
using System.Collections.Generic;
using XF = Xamarin.Forms;

namespace Microsoft.MobileBlazorBindings.Forms
{
public partial class SubmitButton : ViewWrapper
{
/// <summary>
/// Gets or sets a color that describes the border stroke color of the button. This is a bindable property.
/// </summary>
/// <value>
/// The color that is used as the border stroke color; the default is <see cref="XF.Color.Default" />.
/// </value>
[Parameter] public XF.Color? BorderColor { get; set; }
/// <summary>
/// Gets or sets the width of the border. This is a bindable property.
/// </summary>
/// <value>
/// The width of the button border; the default is 0.
/// </value>
[Parameter] public double? BorderWidth { get; set; }
[Parameter] public double? CharacterSpacing { get; set; }
/// <summary>
/// Gets or sets the corner radius for the button, in device-independent units.
/// </summary>
/// <value>
/// The corner radius for the button, in device-independent units.
/// </value>
[Parameter] public int? CornerRadius { get; set; }
/// <summary>
/// Gets a value that indicates whether the font for the button text is bold, italic, or neither.
/// </summary>
[Parameter] public XF.FontAttributes? FontAttributes { get; set; }
/// <summary>
/// Gets the font family to which the font for the button text belongs.
/// </summary>
[Parameter] public string FontFamily { get; set; }
/// <summary>
/// Gets or sets the size of the font of the button text.
/// </summary>
[Parameter] public double? FontSize { get; set; }
/// <summary>
/// Allows you to display a bitmap image on the Button.
/// </summary>
[Parameter] public XF.ImageSource ImageSource { get; set; }
/// <summary>
/// Gets or sets the padding for the button.
/// </summary>
/// <value>
/// The padding for the button.
/// </value>
[Parameter] public XF.Thickness? Padding { get; set; }
/// <summary>
/// Gets or sets the Text displayed as the content of the button. This is a bindable property.
/// </summary>
/// <value>
/// The text displayed in the button. The default value is <see langword="null" />.
/// </value>
[Parameter] public string Text { get; set; }
/// <summary>
/// Gets or sets the <see cref="XF.Color" /> for the text of the button. This is a bindable property.
/// </summary>
/// <value>
/// The <see cref="XF.Color" /> value.
/// </value>
[Parameter] public XF.Color? TextColor { get; set; }
[Parameter] public XF.TextTransform? TextTransform { get; set; }

protected override IEnumerable<KeyValuePair<string, object>> AdditionalProperties
{
get
{
yield return new KeyValuePair<string, object>(nameof(BorderColor), BorderColor);
yield return new KeyValuePair<string, object>(nameof(BorderWidth), BorderWidth);
yield return new KeyValuePair<string, object>(nameof(CharacterSpacing), CharacterSpacing);
yield return new KeyValuePair<string, object>(nameof(CornerRadius), CornerRadius);
yield return new KeyValuePair<string, object>(nameof(FontAttributes), FontAttributes);
yield return new KeyValuePair<string, object>(nameof(FontFamily), FontFamily);
yield return new KeyValuePair<string, object>(nameof(FontSize), FontSize);
yield return new KeyValuePair<string, object>(nameof(ImageSource), ImageSource);
yield return new KeyValuePair<string, object>(nameof(Padding), Padding);
yield return new KeyValuePair<string, object>(nameof(Text), Text);
yield return new KeyValuePair<string, object>(nameof(TextColor), TextColor);
yield return new KeyValuePair<string, object>(nameof(TextTransform), TextTransform);
}
}
}
}
59 changes: 59 additions & 0 deletions src/Microsoft.MobileBlazorBindings.Forms/ValidatedEntry.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
@inherits InputViewWrapper

<Entry @bind-Text="@CurrentText"
OnUnfocused="OnUnfocused"
@attributes="Properties" />

@code
{
private FieldIdentifier _fieldIdentifier;
[CascadingParameter] EditContext CascadedEditContext { get; set; }

[Parameter] public string Text { get; set; }
[Parameter] public EventCallback<string> TextChanged { get; set; }
[Parameter] public Expression<Func<string>> TextExpression { get; set; }

public bool IsValid => !CascadedEditContext.GetValidationMessages(_fieldIdentifier).Any();

private string CurrentText
{
get => Text;
set
{
if (Text != value)
{
Text = value;
_ = TextChanged.InvokeAsync(value);

// Reflect changes immediately only if field already has validations
if (!IsValid)
{
CascadedEditContext.NotifyFieldChanged(_fieldIdentifier);
}
}
}
}

private void OnUnfocused()
{
CascadedEditContext.NotifyFieldChanged(_fieldIdentifier);
}

protected override void OnInitialized()
{
if (CascadedEditContext is null)
{
throw new InvalidOperationException($"{GetType()} requires a cascading parameter " +
$"of type {nameof(EditContext)}. For example, you can use {GetType().FullName} inside " +
$"an {nameof(CascadedEditContext)}.");
}

if (TextExpression is null)
{
throw new InvalidOperationException($"{GetType()} requires a value for the 'TextExpression' " +
$"parameter. Normally this is provided automatically when using 'bind-Text'.");
}

_fieldIdentifier = FieldIdentifier.Create(TextExpression);
}
}
Loading

0 comments on commit 42d9b13

Please sign in to comment.