Skip to content

Commit

Permalink
generic form fragment context
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidVollmers committed Jul 14, 2023
1 parent 1d410a4 commit 98a93e1
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 18 deletions.
3 changes: 1 addition & 2 deletions examples/BlazorServer/Data/ExampleForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@

namespace BlazorServer.Data;

[RenderAs(typeof(Component<FormComponent, FormFragmentContext>))]
[RenderAs(typeof(Component<FormComponent, FormFragmentContext<ExampleForm>>))]
public class ExampleForm
{
[Display(Name = "Mail Address")]
[Attribute("type", "email")]
[RenderAs(typeof(Component<InputComponent<string>, InputFragmentContext<string>>))]
public string MailAddress { get; set; } = null!;

[Display(Name = "Password")]
[Attribute("type", "password")]
[RenderAs(typeof(Component<InputComponent<string>, InputFragmentContext<string>>))]
public string Password { get; set; } = null!;
Expand Down
6 changes: 4 additions & 2 deletions examples/BlazorServer/Shared/Components/FormComponent.razor
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
<form @onsubmit="@(() => Context.OnSubmitAsync(Context.Model))">
@using BlazorServer.Data
<form @onsubmit="@(() => Context.OnSubmitAsync(Context.Model))">
@foreach (var property in Context.GetProperties())
{
<div class="form-group">
@Label(property)
@Input(Context.Model, property)
</div>
<br/>
}
<div class="form-group">
<button type="submit" class="btn btn-primary">Submit</button>
Expand All @@ -14,5 +16,5 @@
@code
{
[CascadingParameter]
public FormFragmentContext Context { get; set; } = null!;
public FormFragmentContext<ExampleForm> Context { get; set; } = null!;
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
namespace Ignis.Fragments.Abstractions.Builder;

public sealed class FormFragmentContext : FragmentContext
public class FormFragmentContext<T> : FragmentContext where T : class
{
public object Model { get; }
public T Model { get; }

public Func<object, Task> OnSubmitAsync { get; }
public Func<T, Task> OnSubmitAsync { get; }

internal FormFragmentContext(object model, Func<object, Task> onSubmit)
internal FormFragmentContext(T model, Func<T, Task> onSubmit)
{
Model = model;
OnSubmitAsync = onSubmit;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Ignis.Fragments.Extensions;

public static class FormFragmentExtensions
{
public static PropertyInfo[] GetProperties(this FormFragmentContext context)
public static PropertyInfo[] GetProperties<T>(this FormFragmentContext<T> context) where T : class
{
if (context == null) throw new ArgumentNullException(nameof(context));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

namespace Ignis.Fragments.Builder;

internal class DefaultFormFragmentBuilder : IFragmentBuilder<FormFragmentContext>
internal class DefaultFormFragmentBuilder<T> : IFragmentBuilder<FormFragmentContext<T>> where T : class
{
public RenderFragment? BuildFragment(FormFragmentContext context)
public RenderFragment? BuildFragment(FormFragmentContext<T> context)
{
if (context == null) throw new ArgumentNullException(nameof(context));

Expand Down
14 changes: 7 additions & 7 deletions packages/Ignis.Fragments/IgnisFragments.Form.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public static partial class IgnisFragments
public delegate Task OnSubmitAsync<in T>(T model) where T : class;

public static RenderFragment? Form<T>(T model, OnSubmit onSubmit,
IFragmentBuilder<FormFragmentContext>? defaultBuilder = null) where T : class
IFragmentBuilder<FormFragmentContext<T>>? defaultBuilder = null) where T : class
{
return Form(model, () =>
{
Expand All @@ -25,7 +25,7 @@ public static partial class IgnisFragments
}

public static RenderFragment? Form<T>(T model, OnSubmit<T> onSubmit,
IFragmentBuilder<FormFragmentContext>? defaultBuilder = null) where T : class
IFragmentBuilder<FormFragmentContext<T>>? defaultBuilder = null) where T : class
{
return Form(model, m =>
{
Expand All @@ -35,23 +35,23 @@ public static partial class IgnisFragments
}

public static RenderFragment? Form<T>(T model, OnSubmitAsync onSubmit,
IFragmentBuilder<FormFragmentContext>? defaultBuilder = null) where T : class
IFragmentBuilder<FormFragmentContext<T>>? defaultBuilder = null) where T : class
{
return Form(model, async _ => { await onSubmit(); }, defaultBuilder);
}

public static RenderFragment? Form<T>(T model, OnSubmitAsync<T> onSubmit,
IFragmentBuilder<FormFragmentContext>? defaultBuilder = null) where T : class
IFragmentBuilder<FormFragmentContext<T>>? defaultBuilder = null) where T : class
{
if (model == null) throw new ArgumentNullException(nameof(model));

var context = new FormFragmentContext(model, async o => { await onSubmit((T)o); })
var context = new FormFragmentContext<T>(model, async o => { await onSubmit(o); })
{
Attributes = GetAttributes(model)
};

var builder = TryGetFragmentBuilder<FormFragmentContext>(model) ??
defaultBuilder ?? new DefaultFormFragmentBuilder();
var builder = TryGetFragmentBuilder<FormFragmentContext<T>>(model) ??
defaultBuilder ?? new DefaultFormFragmentBuilder<T>();

return builder.BuildFragment(context);
}
Expand Down
92 changes: 92 additions & 0 deletions packages/Ignis.Fragments/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,95 @@ dotnet add package Ignis.Fragments
```

Visit [nuget.org](https://www.nuget.org/packages/Ignis.Fragments) for more information.

## Usage

Imagine you have a class like this:

```csharp
public class LoginModel
{
[Required]
[StringLength(50, MinimumLength = 3)]
public string Username { get; set; }

[Required]
[StringLength(50, MinimumLength = 3)]
public string Password { get; set; }
}
```

And you want to render a form for it. You can do it like this:

```csharp
@Form(new LoginModel(), OnSubmit)

@code {
private void OnSubmit(LoginModel model)
{
// Do something with the model
}
}
```

This will render a form with two inputs and a submit button. The submit button will be disabled if the form is not
valid. If the form is valid, the `OnSubmit` callback will be called with the form as an argument.

Of course, this example does not include any styling. You can add your own styling by adding attributes to your class:

```csharp
public class LoginModel
{
[Required]
[Attribute("type", "email")]
[Attribute("class", "form-control")]
[StringLength(50, MinimumLength = 3)]
public string Username { get; set; }

[Required]
[Attribute("type", "password")]
[Attribute("class", "form-control")]
[StringLength(50, MinimumLength = 3)]
public string Password { get; set; }
}
```

Or if you want to customize the layout and markup used to render your form, you can do it like this:

```csharp
[RenderAs(typeof(Component<MyLoginForm, FormFragmentContext<LoginModel>>))]
public class LoginModel
{
[Required]
[Attribute("type", "email")]
[StringLength(50, MinimumLength = 3)]
public string Username { get; set; }

[Required]
[Attribute("type", "password")]
[StringLength(50, MinimumLength = 3)]
public string Password { get; set; }
}
```

And build the layout and markup in your component:

```csharp
<form @onsubmit="@(() => Context.OnSubmitAsync(Context.Model))">
@foreach (var property in Context.GetProperties())
{
<div class="form-group">
@Label(property)
@Input(Context.Model, property)
</div>
}
<div class="form-group">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>

@code {
[CascadingParameter]
public FormFragmentContext<LoginModel> Context { get; set; }
}
```

0 comments on commit 98a93e1

Please sign in to comment.