Skip to content
Permalink
c79002bf38
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
144 lines (124 sloc) 6.2 KB
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Rendering;
namespace Microsoft.AspNetCore.Components.Forms
{
/// <summary>
/// Renders a form element that cascades an <see cref="EditContext"/> to descendants.
/// </summary>
public class EditForm : ComponentBase
{
private readonly Func<Task> _handleSubmitDelegate; // Cache to avoid per-render allocations
private EditContext _fixedEditContext;
/// <summary>
/// Constructs an instance of <see cref="EditForm"/>.
/// </summary>
public EditForm()
{
_handleSubmitDelegate = HandleSubmitAsync;
}
/// <summary>
/// Gets or sets a collection of additional attributes that will be applied to the created <c>form</c> element.
/// </summary>
[Parameter(CaptureUnmatchedValues = true)] public IReadOnlyDictionary<string, object> AdditionalAttributes { get; set; }
/// <summary>
/// Supplies the edit context explicitly. If using this parameter, do not
/// also supply <see cref="Model"/>, since the model value will be taken
/// from the <see cref="EditContext.Model"/> property.
/// </summary>
[Parameter] public EditContext EditContext { get; set; }
/// <summary>
/// Specifies the top-level model object for the form. An edit context will
/// be constructed for this model. If using this parameter, do not also supply
/// a value for <see cref="EditContext"/>.
/// </summary>
[Parameter] public object Model { get; set; }
/// <summary>
/// Specifies the content to be rendered inside this <see cref="EditForm"/>.
/// </summary>
[Parameter] public RenderFragment<EditContext> ChildContent { get; set; }
/// <summary>
/// A callback that will be invoked when the form is submitted.
///
/// If using this parameter, you are responsible for triggering any validation
/// manually, e.g., by calling <see cref="EditContext.Validate"/>.
/// </summary>
[Parameter] public EventCallback<EditContext> OnSubmit { get; set; }
/// <summary>
/// A callback that will be invoked when the form is submitted and the
/// <see cref="EditContext"/> is determined to be valid.
/// </summary>
[Parameter] public EventCallback<EditContext> OnValidSubmit { get; set; }
/// <summary>
/// A callback that will be invoked when the form is submitted and the
/// <see cref="EditContext"/> is determined to be invalid.
/// </summary>
[Parameter] public EventCallback<EditContext> OnInvalidSubmit { get; set; }
/// <inheritdoc />
protected override void OnParametersSet()
{
if ((EditContext == null) == (Model == null))
{
throw new InvalidOperationException($"{nameof(EditForm)} requires a {nameof(Model)} " +
$"parameter, or an {nameof(EditContext)} parameter, but not both.");
}
// If you're using OnSubmit, it becomes your responsibility to trigger validation manually
// (e.g., so you can display a "pending" state in the UI). In that case you don't want the
// system to trigger a second validation implicitly, so don't combine it with the simplified
// OnValidSubmit/OnInvalidSubmit handlers.
if (OnSubmit.HasDelegate && (OnValidSubmit.HasDelegate || OnInvalidSubmit.HasDelegate))
{
throw new InvalidOperationException($"When supplying an {nameof(OnSubmit)} parameter to " +
$"{nameof(EditForm)}, do not also supply {nameof(OnValidSubmit)} or {nameof(OnInvalidSubmit)}.");
}
// Update _fixedEditContext 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 (_fixedEditContext == null || EditContext != null || Model != _fixedEditContext.Model)
{
_fixedEditContext = EditContext ?? new EditContext(Model);
}
}
/// <inheritdoc />
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
// If _fixedEditContext 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 _fixedEditContext never changes.
builder.OpenRegion(_fixedEditContext.GetHashCode());
builder.OpenElement(0, "form");
builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttribute(2, "onsubmit", _handleSubmitDelegate);
builder.OpenComponent<CascadingValue<EditContext>>(3);
builder.AddAttribute(4, "IsFixed", true);
builder.AddAttribute(5, "Value", _fixedEditContext);
builder.AddAttribute(6, "ChildContent", ChildContent?.Invoke(_fixedEditContext));
builder.CloseComponent();
builder.CloseElement();
builder.CloseRegion();
}
private async Task HandleSubmitAsync()
{
if (OnSubmit.HasDelegate)
{
// When using OnSubmit, the developer takes control of the validation lifecycle
await OnSubmit.InvokeAsync(_fixedEditContext);
}
else
{
// Otherwise, the system implicitly runs validation on form submission
var isValid = _fixedEditContext.Validate(); // This will likely become ValidateAsync later
if (isValid && OnValidSubmit.HasDelegate)
{
await OnValidSubmit.InvokeAsync(_fixedEditContext);
}
if (!isValid && OnInvalidSubmit.HasDelegate)
{
await OnInvalidSubmit.InvokeAsync(_fixedEditContext);
}
}
}
}
}