Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce AutoFormView #586

Merged
merged 9 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions demo/UraniumApp/AppShell.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@
<FlyoutItem Title="Validations" Icon="{FontImageSource Glyph={x:Static m:MaterialOutlined.Warning}, FontFamily=MaterialOutlined, Color={AppThemeBinding {StaticResource Primary}, Dark={StaticResource PrimaryDark}}}">
<ShellContent Title="Validations" ContentTemplate="{DataTemplate pages:ValidationsPage}"/>
</FlyoutItem>

<FlyoutItem Title="AutoFormView" Icon="{FontImageSource Glyph={x:Static m:MaterialOutlined.Dynamic_form}, FontFamily=MaterialOutlined, Color={AppThemeBinding {StaticResource Primary}, Dark={StaticResource PrimaryDark}}}">
<ShellContent Title="Blurs" ContentTemplate="{DataTemplate pages:AutoFormViewPage}"/>
</FlyoutItem>

<FlyoutItem Title="Google AutoComplete" Icon="{FontImageSource Glyph={x:Static m:MaterialOutlined.Spellcheck}, FontFamily=MaterialOutlined, Color={AppThemeBinding {StaticResource Primary}, Dark={StaticResource PrimaryDark}}}">
<ShellContent Title="AutoComplete" ContentTemplate="{DataTemplate autocompletes:GoogleAutoCompletePage}"/>
Expand Down
7 changes: 7 additions & 0 deletions demo/UraniumApp/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
using System.Reactive;
using UraniumUI;
using UraniumUI.Dialogs;
using UraniumUI.Options;
using UraniumUI.Validations;

namespace UraniumApp;

Expand All @@ -31,6 +33,11 @@ public static MauiApp CreateMauiApp()
fonts.AddFluentIconFonts();
});

builder.Services.Configure<AutoFormViewOptions>(options =>
{
options.ValidationFactory = DataAnnotationValidation.CreateValidations;
});

RxApp.DefaultExceptionHandler = new AnonymousObserver<Exception>(ex =>
{
App.Current.MainPage.DisplayAlert("Error", ex.Message, "OK");
Expand Down
18 changes: 18 additions & 0 deletions demo/UraniumApp/Pages/AutoFormViewPage.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:uranium="http://schemas.enisn-projects.io/dotnet/maui/uraniumui"
xmlns:material="http://schemas.enisn-projects.io/dotnet/maui/uraniumui/material"
xmlns:vm="clr-namespace:UraniumApp.ViewModels"
BindingContext="{uranium:Inject {Type vm:AutoFormViewPageViewModel}}"
x:DataType="vm:AutoFormViewPageViewModel"
x:Class="UraniumApp.Pages.AutoFormViewPage"
Title="AutoFormViewPage">
<VerticalStackLayout>
<uranium:AutoFormView Source="{Binding .}" ShowMissingProperties="False">
<uranium:AutoFormView.FooterLayout>
<FlexLayout JustifyContent="SpaceEvenly" />
</uranium:AutoFormView.FooterLayout>
</uranium:AutoFormView>
</VerticalStackLayout>
</ContentPage>
9 changes: 9 additions & 0 deletions demo/UraniumApp/Pages/AutoFormViewPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace UraniumApp.Pages;

public partial class AutoFormViewPage : ContentPage
{
public AutoFormViewPage()
{
InitializeComponent();
}
}
23 changes: 23 additions & 0 deletions demo/UraniumApp/ViewModels/AutoFormViewPageViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using DotNurse.Injector.Attributes;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using System.ComponentModel.DataAnnotations;

namespace UraniumApp.ViewModels;

[RegisterAs(typeof(AutoFormViewPageViewModel))]
public class AutoFormViewPageViewModel : ReactiveObject
{
[EmailAddress]
[Required]
[MinLength(5)]
[Reactive] public string Email { get; set; }
[Reactive] public string FullName { get; set; }
[Reactive] public Gender Gender { get; set; }
[Reactive] public DateTime? BirthDate { get; set; }
[Reactive] public TimeSpan? MeetingTime { get; set; }
[Reactive] public int? NumberOfSeats { get; set; }

[Required]
[Reactive] public bool IsTermsAndConditionsAccepted { get; set; }
}
143 changes: 143 additions & 0 deletions docs/en/infrastructure/AutoFormView.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# AutoFormView

The `AutoFormView` is a view that automatically generates a form based on the properties of a model. It is a subclass of `FormView` and uses the same APIs.

## Usage

`AutoFormView` is defined in `UraniumUI.Controls` namespace. You can use it in XAML like this:

```xml
xmlns:uranium="http://schemas.enisn-projects.io/dotnet/maui/uraniumui"
```

Then you can use it like this:

```xml
<uranium:FormView Source="{Binding .}" />
```

### Example

```csharp
public class AutoFormViewPageViewModel : ViewModelBase
{
[Reactive] public string Email { get; set; }
[Reactive] public string FullName { get; set; }
[Reactive] public Gender Gender { get; set; }
[Reactive] public DateTime? BirthDate { get; set; }
[Reactive] public TimeSpan? MeetingTime { get; set; }
[Reactive] public int? NumberOfSeats { get; set; }
[Reactive] public bool IsTermsAndConditionsAccepted { get; set; }
}
```

```xml
<uranium:AutoFormView Source="{Binding .}" />
```

![AutoFormView](images/autoformview-example-dark.png)


## Configuration

AutoFormView can be configured using the `AutoFormViewOptions` in the MauiProgram.cs file. Here is an example of how to configure the `AutoFormView`:

```csharp
builder.Services.Configure<AutoFormViewOptions>(options =>
{
// configure options here
});
```

### DataAnnotations
It's not supported DataAnnotations by default. You can add `UraniumUI.Validations.DataAnnotations` package to project and configure `AutoFormViewOptions` to use DataAnnotations.

```csharp
builder.Services.Configure<AutoFormViewOptions>(options =>
{
options.ValidationFactory = DataAnnotationValidation.CreateValidations;
});
```

### EditorMapping
You can configure the `AutoFormView` to use a specific editor for a type. For example, you can configure the `AutoFormViewOptions` to use a `Editor` for `string` properties.

```csharp
builder.Services.Configure<AutoFormViewOptions>(options =>
{
options.EditorMapping[typeof(string)] = (property, source) =>
{
var editor = new Entry();
editor.SetBinding(Entry.TextProperty, new Binding(property.Name, source: source));
return editor;
};
});
```

> Note: The following types are already mapped by default: `string`, `int`, `float`, `double`, `DateTime`, `TimeSpan`, `bool`, `Enum`, `Keyboard`.


### Property Name Mapping
You can configure the `PropertyNameFactory` property of `AutoFormViewOptions` to use a custom factory to get the property name. For example, you can implement a localization factory to get the property name from a resource file.

```csharp
builder.Services.Configure<AutoFormViewOptions>(options =>
{
options.PropertyNameFactory = property =>
{
return Localize(property.Name);
};
});
```

## Customization

You can customize the `AutoFormView`.


## ItemsLayout
You can customize the `ItemsLayout` of the `AutoFormView` using the `ItemsLayout` property. For example, you can use a `GridLayout` to display the properties in a grid.

> **Note:** It's not the same as the `ItemsLayout` of the `CollectionView`. This is a **real** layout that will be used to place editors into children. Such as `StackLayout`, `Grid`, `FlexLayout`, etc.

```xml
<uranium:AutoFormView Source="{Binding .}">
<uranium:AutoFormView.ItemsLayout>
<uranium:GridLayout ColumnCount="2" RowCount="4" />
</uranium:AutoFormView.ItemsLayout>
</uranium:AutoFormView>
```

![AutoFormView](images/autoformview-itemslayout-grid-dark.png)


## FooterLayout
You can customize the `FooterLayout` of the `AutoFormView` using the `FooterLayout` property. For example, you can use a `HorizontalStackLayout` to display the buttons in a horizontal stack.

```xml
<uranium:AutoFormView Source="{Binding .}" ShowMissingProperties="False">
<uranium:AutoFormView.FooterLayout>
<FlexLayout JustifyContent="SpaceEvenly" />
</uranium:AutoFormView.FooterLayout>
</uranium:AutoFormView>
```

![AutoFormView](images/autoformview-footerlayout-dark.png)

## ShowMissingProperties

You can configure the `AutoFormView` to show missing properties using the `ShowMissingProperties` property. For example, you can set the `ShowMissingProperties` to `true` to show all properties of the model.

```xml
<uranium:AutoFormView Source="{Binding .}" ShowMissingProperties="True" />
```

![AutoFormView](images/autoformview-showmissingproperties-dark.png)


## Other Properties

- `ShowSubmitButton`: Indicates whether the submit button is visible. The default value is `true`.
- `SohwResetButton`: Indicates whether the reset button is visible. The default value is `true`.
- `SubmitButtonText`: The text of the submit button. The default value is `Submit`.
- `ResetButtonText`: The text of the reset button. The default value is `Reset`.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
using InputKit.Shared.Controls;
using System.Reflection;
using UraniumUI.Controls;
using UraniumUI.Extensions;
using UraniumUI.Material.Controls;
using UraniumUI.Options;
using UraniumUI.Resources;

namespace UraniumUI.Material.Extensions;

public static class AutoFormViewMaterialConfigurationExtensions
{
public static MauiAppBuilder ConfigureAutoFormViewForMaterial(this MauiAppBuilder builder)
{
builder.Services.Configure<AutoFormViewOptions>(options =>
{
options.EditorMapping[typeof(string)] = EditorForString;
options.EditorMapping[typeof(int)] = EditorForNumeric;
options.EditorMapping[typeof(double)] = EditorForNumeric;
options.EditorMapping[typeof(float)] = EditorForNumeric;
options.EditorMapping[typeof(bool)] = EditorForBoolean;
options.EditorMapping[typeof(Keyboard)] = EditorForKeyboard;
options.EditorMapping[typeof(Enum)] = EditorForEnum;
options.EditorMapping[typeof(DateTime)] = EditorForDateTime;
options.EditorMapping[typeof(TimeSpan)] = EditorForTimeSpan;
});

return builder;
}

public static View EditorForString(PropertyInfo property, object source)
{
var editor = new TextField();
editor.SetBinding(TextField.TextProperty, new Binding(property.Name, source: source));
editor.AllowClear = true;
editor.Title = property.Name;

return editor;
}

public static View EditorForNumeric(PropertyInfo property, object source)
{
var editor = new TextField();
editor.SetBinding(TextField.TextProperty, new Binding(property.Name, source: source));
editor.Title = property.Name;
editor.AllowClear = false;
editor.Keyboard = Keyboard.Numeric;

return editor;
}

public static View EditorForBoolean(PropertyInfo property, object source)
{
var editor = new UraniumUI.Material.Controls.CheckBox();
editor.SetBinding(UraniumUI.Material.Controls.CheckBox.IsCheckedProperty, new Binding(property.Name, source: source));
editor.Text = property.Name;

return editor;
}

public static View EditorForEnum(PropertyInfo property, object source)
{
var editor = new PickerField();

var values = Enum.GetValues(property.PropertyType.AsNonNullable());
if (values.Length <= 5)
{
return CreateSelectionViewForValues(values, property, source);
}

editor.ItemsSource = values;
editor.SetBinding(PickerField.SelectedItemProperty, new Binding(property.Name, source: source));
editor.Title = property.Name;
editor.AllowClear = false;
return editor;
}

private static View CreateSelectionViewForValues(Array values, PropertyInfo property, object source)
{
var shouldUseSingleColumn = values.Length > 3;
var editor = new SelectionView
{
Color = ColorResource.GetColor("Primary", "PrimaryDark"),
ColumnSpacing = -2,
RowSpacing = shouldUseSingleColumn ? 5 : -2,
SelectionType = shouldUseSingleColumn ? InputKit.Shared.SelectionType.RadioButton : InputKit.Shared.SelectionType.Button,
ColumnNumber = shouldUseSingleColumn ? 1 : values.Length,
ItemsSource = values
};

editor.SetBinding(SelectionView.SelectedItemProperty, new Binding(property.Name, source: source));

return new VerticalStackLayout
{
Spacing = 6,
Children = {
new Label { Text = property.Name },
editor
}
};
}

public static View EditorForKeyboard(PropertyInfo property, object source)
{
var editor = new PickerField();

editor.ItemsSource = typeof(Keyboard)
.GetProperties(BindingFlags.Public | BindingFlags.Static)
.Select(x => x.GetValue(null))
.ToArray();

editor.SetBinding(PickerField.SelectedItemProperty, new Binding(property.Name, source: source));
editor.Title = property.Name;
editor.AllowClear = false;
return editor;
}

public static View EditorForDateTime(PropertyInfo property, object source)
{
var editor = new DatePickerField();
editor.SetBinding(DatePickerField.DateProperty, new Binding(property.Name, source: source));
editor.Title = property.Name;
editor.AllowClear = false;
return editor;
}

public static View EditorForTimeSpan(PropertyInfo property, object source)
{
var editor = new TimePickerField();
editor.SetBinding(TimePickerField.TimeProperty, new Binding(property.Name, source: source));
editor.Title = property.Name;
editor.AllowClear = false;
return editor;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using UraniumUI.Material.Controls;
using UraniumUI.Material.Extensions;
using UraniumUI.Material.Handlers;

namespace UraniumUI;
Expand All @@ -10,7 +11,9 @@ public static MauiAppBuilder UseUraniumUIMaterial(this MauiAppBuilder builder)
{
handlers.AddHandler(typeof(ButtonView), typeof(ButtonViewHandler));
});


builder.ConfigureAutoFormViewForMaterial();

return builder;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
</ItemGroup>

<ItemGroup Condition="$(TargetFramework.Contains('net8'))">
<PackageReference Include="InputKit.Maui" Version="4.4.1" />
<PackageReference Include="InputKit.Maui" Version="4.4.2" />
</ItemGroup>

</Project>