-
Notifications
You must be signed in to change notification settings - Fork 10.2k
/
Copy pathEditForm.cs
144 lines (124 loc) · 6.2 KB
/
EditForm.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// 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);
}
}
}
}
}