Skip to content

Commit

Permalink
ReactiveElmish is now its own NuGet package and also supports C#. Add…
Browse files Browse the repository at this point in the history
…ed a WpfExample project in C#.
  • Loading branch information
JordanMarr committed Feb 1, 2024
1 parent 2b62dcb commit a331e2c
Show file tree
Hide file tree
Showing 18 changed files with 475 additions and 82 deletions.
2 changes: 1 addition & 1 deletion src/ReactiveElmish.Avalonia/ReactiveElmish.Avalonia.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/JordanMarr/ReactiveElmish.Avalonia</PackageProjectUrl>
<PackageTags>Avalonia F# fsharp Elmish Elm</PackageTags>
<Version>1.1.1</Version>
<Version>1.1.2</Version>
<!--Turn on warnings for unused values (arguments and let bindings) -->
<OtherFlags>$(OtherFlags) --warnon:1182</OtherFlags>
<TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage</TargetsForTfmSpecificBuildOutput>
Expand Down
7 changes: 7 additions & 0 deletions src/ReactiveElmish.sln
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "ReactiveElmish", "ReactiveE
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "ReactiveElmish.Wpf", "ReactiveElmish.Wpf\ReactiveElmish.Wpf.fsproj", "{8E7D1F76-A45C-4151-96BD-BFEAECAA4419}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfExample", "WpfExample\WpfExample.csproj", "{E612993E-238C-40C7-A39C-DA021DBF6D9F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -41,12 +43,17 @@ Global
{8E7D1F76-A45C-4151-96BD-BFEAECAA4419}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8E7D1F76-A45C-4151-96BD-BFEAECAA4419}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8E7D1F76-A45C-4151-96BD-BFEAECAA4419}.Release|Any CPU.Build.0 = Release|Any CPU
{E612993E-238C-40C7-A39C-DA021DBF6D9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E612993E-238C-40C7-A39C-DA021DBF6D9F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E612993E-238C-40C7-A39C-DA021DBF6D9F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E612993E-238C-40C7-A39C-DA021DBF6D9F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{B5F6F996-01DD-410D-9D3B-A1E91A3D5CF4} = {B1F3D2D8-D07C-4115-ACAD-5CD90ED1A04A}
{E612993E-238C-40C7-A39C-DA021DBF6D9F} = {B1F3D2D8-D07C-4115-ACAD-5CD90ED1A04A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E4A43E5C-8FF0-434B-8C5E-AB656A275C4F}
Expand Down
87 changes: 87 additions & 0 deletions src/ReactiveElmish/ReactiveBindingsCS.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
namespace ReactiveElmish

open System
open System.Runtime.CompilerServices
open System.Runtime.InteropServices
open DynamicData

/// Allows using an IStore<'Model> + ReactiveElmishViewModel binding methods from an existing view model using C# Action and Func delegates.
type ReactiveBindingsCS(onPropertyChanged: Action<string>) =
let vm = new ReactiveElmishViewModel(onPropertyChanged.Invoke)
let opt = Option.ofObj

member this.Bind<'Model, 'ModelProjection>(
store: IStore<'Model>,
modelProjection: Func<'Model, 'ModelProjection>,
[<CallerMemberName; Optional; DefaultParameterValue("")>] vmPropertyName
) = vm.Bind(store, modelProjection.Invoke, vmPropertyName)

member this.BindOnChanged<'Model, 'OnChanged, 'ModelProjection>(
store: IStore<'Model>,
onChanged: Func<'Model, 'OnChanged>,
modelProjection: Func<'Model, 'ModelProjection>,
[<CallerMemberName; Optional; DefaultParameterValue("")>] vmPropertyName
) = vm.BindOnChanged(store, onChanged.Invoke, modelProjection.Invoke, vmPropertyName)

member this.BindList<'Model, 'ModelProjection>(
store: IStore<'Model>,
modelProjectionSeq: Func<'Model, 'ModelProjection seq>,
[<CallerMemberName; Optional; DefaultParameterValue("")>] vmPropertyName
) = vm.BindList(store, modelProjectionSeq.Invoke, vmPropertyName)

member this.BindList<'Model, 'ModelProjection, 'Mapped>(
store: IStore<'Model>,
modelProjectionSeq: Func<'Model, 'ModelProjection seq>,
map: Func<'ModelProjection, 'Mapped>,
[<CallerMemberName; Optional; DefaultParameterValue("")>] vmPropertyName
) = vm.BindList(store, modelProjectionSeq.Invoke, map.Invoke, vmPropertyName)

member this.BindKeyedList<'Model, 'Key, 'Value, 'Mapped when 'Value : equality and 'Mapped : not struct and 'Key : comparison>(
store: IStore<'Model>,
modelProjection: Func<'Model, Map<'Key, 'Value>>,
map: Func<'Value, 'Mapped>,
getKey: Func<'Mapped, 'Key>,
[<Optional>] update: Action<'Value, 'Mapped>,
[<Optional>] sortBy: Func<'Mapped, IComparable>,
[<CallerMemberName; Optional; DefaultParameterValue("")>] vmPropertyName
) = vm.BindKeyedList(store, modelProjection.Invoke, map.Invoke, getKey.Invoke, ?update = opt update, ?sortBy = opt sortBy, vmPropertyName = vmPropertyName)

member this.BindKeyedList<'Model, 'Key, 'Value when 'Value: equality and 'Value : not struct and 'Key : comparison>(
store: IStore<'Model>,
modelProjection: Func<'Model, Map<'Key, 'Value>>,
getKey: Func<'Value, 'Key>,
[<Optional>] update: Action<'Value, 'Value>,
[<Optional>] sortBy: Func<'Value, IComparable>,
[<CallerMemberName; Optional; DefaultParameterValue("")>] vmPropertyName: string
) = vm.BindKeyedList(store, modelProjection.Invoke, getKey.Invoke, ?update = opt update, ?sortBy = opt sortBy, vmPropertyName = vmPropertyName)

member this.BindSourceList<'T>(
sourceList: ISourceList<'T>,
[<CallerMemberName; Optional; DefaultParameterValue("")>] vmPropertyName
) = vm.BindSourceList(sourceList, vmPropertyName)

member this.BindSourceList<'T, 'Mapped>(
sourceList: ISourceList<'T>,
map: Func<'T, 'Mapped>,
[<CallerMemberName; Optional; DefaultParameterValue("")>] vmPropertyName
) = vm.BindSourceList(sourceList, map.Invoke, vmPropertyName)

member this.BindSourceCache<'Value, 'Key>(
sourceCache: IObservableCache<'Value, 'Key>,
[<Optional>] sortBy,
[<CallerMemberName; Optional; DefaultParameterValue("")>] vmPropertyName
) = vm.BindSourceCache(sourceCache, ?sortBy = opt sortBy, vmPropertyName = vmPropertyName)

member this.BindSourceCache<'Value, 'Key, 'Mapped when 'Value : not struct and 'Mapped : not struct>(
sourceCache: IObservableCache<'Value, 'Key>,
map: Func<'Value, 'Mapped>,
[<Optional>] update: Action<'Value, 'Mapped>,
[<Optional>] sortBy,
[<CallerMemberName; Optional; DefaultParameterValue("")>] vmPropertyName
) = vm.BindSourceCache(sourceCache, map.Invoke, ?update = opt update, ?sortBy = opt sortBy, vmPropertyName = vmPropertyName)

member this.Subscribe(observable, handler) =
vm.Subscribe(observable, handler)

interface IDisposable with
member this.Dispose() = (vm :> IDisposable).Dispose()
4 changes: 3 additions & 1 deletion src/ReactiveElmish/ReactiveElmish.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,17 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/JordanMarr/ReactiveElmish</PackageProjectUrl>
<PackageTags>F# fsharp Elmish MVU MVVM</PackageTags>
<Version>1.0.0-beta</Version>
<Version>1.1.0-beta.5</Version>
<!--Turn on warnings for unused values (arguments and let bindings) -->
<OtherFlags>$(OtherFlags) --warnon:1182</OtherFlags>
</PropertyGroup>

<ItemGroup>
<Compile Include="DynamicData.fs" />
<Compile Include="ReactiveElmishStore.fs" />
<Compile Include="ReactiveStore.fs" />
<Compile Include="ReactiveElmishViewModel.fs" />
<Compile Include="ReactiveBindingsCS.fs" />
</ItemGroup>

<ItemGroup>
Expand Down
14 changes: 11 additions & 3 deletions src/ReactiveElmish/ReactiveElmishStore.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,23 @@ open System.Reactive.Subjects
open System.Reactive.Linq
open System

type IStore<'Model, 'Msg> =
type IStore<'Model> =
inherit IDisposable
abstract member Dispatch: 'Msg -> unit
abstract member Model: 'Model with get
abstract member Observable: IObservable<'Model>

type IStore<'Model, 'Msg> =
inherit IStore<'Model>
abstract member Dispatch: 'Msg -> unit

type IHasSubject<'Model> =
abstract member Subject: Subject<'Model> with get

module Design =
/// Stubs a constructor injected dependency in design mode.
let stub<'T> = Unchecked.defaultof<'T>

/// An Elmish reactive store that can be used to store and update a model and send out an Rx stream of the model.
type ReactiveElmishStore<'Model, 'Msg> () =
let _modelSubject = new Subject<'Model>()
let mutable _model: 'Model = Unchecked.defaultof<'Model>
Expand All @@ -26,7 +33,8 @@ type ReactiveElmishStore<'Model, 'Msg> () =
member this.Model = _model
member this.Observable = _modelSubject.AsObservable()

member internal this.Subject = _modelSubject
interface IHasSubject<'Model> with
member this.Subject = _modelSubject

member this.Dispatcher
with get() = _dispatch
Expand Down
Loading

0 comments on commit a331e2c

Please sign in to comment.