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

docs(feed): Doc for 2.3 release #879

Merged
merged 4 commits into from
Oct 31, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 67 additions & 15 deletions doc/Overview/Reactive/in-apps.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,23 @@ uid: Overview.Reactive.InApps
---
# Usage in applications of feeds

The recommended use for feeds is only in view models.
The recommended use for feeds is only in models.
dr1rrb marked this conversation as resolved.
Show resolved Hide resolved

The reactive framework allows you to design state-less view models, focusing on the presentation logic.
Your view models only have to request in their constructors the user _ inputs_ that are expected from the view. Inputs could be:
* An `IInput<T>` to get _data_ from the view (e.g. for 2-way bindings);
* An `ICommandBuilder` for the "trigger" inputs.

In order to easily interact with the binding engine in a performant way, a `BindableXXX` class is automatically generated. It is this class that will hold the state and which **has to be set as `DataContext` of your page**.
The reactive framework allows you to design state-less _models_, focusing on the presentation logic.
dr1rrb marked this conversation as resolved.
Show resolved Hide resolved
They are expected to expose some `IFeed`, `IListFeed`, `IState` or `IListState`.
A bindable friendly and higly performant _view model_ is then automatically generated by the reactive framework.
dr1rrb marked this conversation as resolved.
Show resolved Hide resolved
It is this class that will hold the state and which **has to be set as `DataContext` of your page**.

> [!NOTE]
> The bindable counterpart of a class will be created when the class name ends with "ViewModel".
> You can customize that behavior using the `[ReactiveBindable]` attribute.
> The _ViewModel_ of a _model_ will be created when the class name matches the regex "Model$".
dr1rrb marked this conversation as resolved.
Show resolved Hide resolved
> You can customize that behavior using the `[ImplicitBindables("MyApp\.Presentation\.\.*Model$")]` on the assembly
> or the `[ReactiveBindable]` attribute on you model itself.

> [!NOTE]
> For easier View Model creation, public properties will also be accessible on the `BindableXXX` class.
> You can also access the view model itself through the `Model` property.
> To ease _model_ declaration, public properties will also be accessible on the _view model_ class.
dr1rrb marked this conversation as resolved.
Show resolved Hide resolved
> You can also access to the model itself through the `Model` property.
dr1rrb marked this conversation as resolved.
Show resolved Hide resolved

## Display some data in the UI (VM to View)
## Display some data in the UI (Model to View)

To render a _feed_ in the UI, you only have to expose it through a public property:
```csharp
Expand Down Expand Up @@ -131,11 +130,64 @@ so you can enhance the UX of the pagination, like display a loading indicator wh
</uer:FeedView>
```

## Selection

The reactive framework has embedded support of selected item of a `ListFeed` data-bound to a `Selector`, like the `ListView`.
It means that you don't have to data-bind (and synchronize) the `ListView.SelectedItem`, it will instead be automatically pushed from and to the view.
dr1rrb marked this conversation as resolved.
Show resolved Hide resolved

```xml
<uer:FeedView Source="{Binding Items}">
<DataTemplate>
<ListView ItemsSource="{Binding Data}" />
</DataTemplate>
</uer:FeedView>
```

And in your model:
dr1rrb marked this conversation as resolved.
Show resolved Hide resolved

```csharp
public IListState<int> Items => ListView
.Value(this, () => ImmutableList.Create(1, 2, 3, 4, 5))
.Selection(SelectedItem);

public IState<int> SelectedItem => State.Value(this, () => 3);
```

### Project selected item into another entity

You can also synchronize the selected item _key_ into another aggregate root object, for instance, assuming `Profile` and `Country` records:
dr1rrb marked this conversation as resolved.
Show resolved Hide resolved

```csharp
public record Profile(string? FirstName, string? CountryId);

public record Country(string Id, string Name);
```

You can automatically store the selected item into the `Profile.CountyId` by doing:
dr1rrb marked this conversation as resolved.
Show resolved Hide resolved
```csharp
public IState<Profile> Profile => State.Async(this, _svc.GetProfile);

public IListState<Country> Countries => ListState
.Async(this, _svc.GetCountries)
.Selection(Profile, p => p.CountryId);
```

```xml
<uer:FeedView Source="{Binding Countries}">
<DataTemplate>
<ComboBox ItemsSource="{Binding Data}" />
</DataTemplate>
</uer:FeedView>
```

> [!NOTE]
> As the `IState<Porfile> Profile` state might be `None` when an item is being selected, the `Profile` class
> must have either a parameter-less contructor, either a constructor that accepts only nullable parameters.

## Commands
The generated bindable counterpart of a class will automatically re-expose as `ICommand` public methods that are compatible (cf. "general rules" below).
The generated _view model_ of a _model_ will automatically re-expose as `ICommand` public methods that are compatible (cf. "general rules" below).

For instance, in your ViewModel:
For instance, in your _model_:

```csharp
public async ValueTask Share()
Expand All @@ -152,7 +204,7 @@ This will be exposed into an `ICommand` that can be data-bound to the `Command`

By default, if the method has a parameter `T myValue` and there is a property `Feed<T> MyValue` on the same class (matching type and name), that parameter will automatically be filled from that feed.

For instance, in your ViewModel:
For instance, in your _model_:

```csharp
public IFeed<string> Message { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,9 @@ public static string Replace(this string text, char[] chars, char replacement)

return result.ToString();
}

public static string TrimEnd(this string text, string value, StringComparison comparison)
=> text.EndsWith(value, comparison)
? text.Substring(0, text.Length - value.Length)
: text;
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,12 @@ private static string GetViewModelName(INamedTypeSymbol type)
var isLegacyMode = type.ContainingAssembly.FindAttribute<ImplicitBindablesAttribute>() is { Patterns.Length: 1 } config
&& config.Patterns[0] is ImplicitBindablesAttribute.LegacyPattern;

return !isLegacyMode && type.Name.IndexOf("ViewModel", StringComparison.OrdinalIgnoreCase) < 0
? $"{type.Name}ViewModel"
: $"Bindable{type.Name}";
return isLegacyMode switch
{
true => $"Bindable{type.Name}",
_ when type.Name.EndsWith("ViewModel", StringComparison.OrdinalIgnoreCase) => $"Bindable{type.Name}",
_ => $"{type.Name.TrimEnd("Model", StringComparison.OrdinalIgnoreCase)}ViewModel",
};
}

private string Generate(INamedTypeSymbol model)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,12 @@ public void NestedInheritingViewModel()
=> Assert.IsNotNull(GetBindable(typeof(NestedSubViewModel)));

private Type? GetBindable(Type vmType)
=> vmType.GetNestedType(vmType.Name.Contains("ViewModel") ? $"Bindable{vmType.Name}" : $"{vmType.Name}ViewModel");
=> vmType.GetNestedType(vmType.Name switch
{
{ } name when name.EndsWith("ViewModel", StringComparison.OrdinalIgnoreCase) => $"Bindable{vmType.Name}",
{ } name when name.EndsWith("Model", StringComparison.OrdinalIgnoreCase) => $"{name.Substring(0, name.Length - "Model".Length)}ViewModel",
{ } name => $"{name}ViewModel",
});

private Type? GetBindable(string vmType)
{
Expand Down