You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
-[Example use of `mapModel` and `mapMsg`](#example-use-of-mapModel-and-mapMsg)
54
+
-[Theory behind `mapModel` and `mapMsg`](#theory-behind-mapModel-and-mapMsg)
53
55
*[Additional resources](#additional-resources)
54
56
55
57
The MVU (Elm/Elmish) architecture
@@ -728,66 +730,58 @@ Elmish.WPF provides two helpers you can often use as the `equals` parameter: `re
728
730
729
731
You may pass any function you want for `equals`; it does not have to be one of the above. For example, if you want structural comparison (note the caveat above however), you can pass `(=)`.
*Note: This is an advanced optimization that should not be necessary in most cases.*
735
+
Sometimes duplicate mapping code exists across several bindings. The duplicate mappings could be from the parent model to a common child model or it could be the wrapping of a child message in a parent message, which might even depend on the parent model. The duplicate mapping code can be extracted and written once using the mapping functions `mapModel`, `mapMsg`, and `mapModelWithMsg`.
734
736
735
-
Occasionally you may want to limit the frequency of dispatches from a particular binding. You may therefore want to apply some kind of throttling or debouncing (dispatch at most one message every X millisecond, or only dispatch the latest message after there have been no messages for at least X milliseconds).
737
+
#### Example use of `mapModel` and `mapMsg`
736
738
737
-
To facilitate this, all `twoWay` and `cmd` bindings as well as `subModelSelectedItem` have an optional `wrapDispatch` parameter with the signature `Dispatch<'msg> -> Dispatch<'msg>`.
739
+
Here is a simple example that uses these model and message types.
740
+
```F#
741
+
type ChildModel =
742
+
{ GrandChild1: GrandChild1
743
+
GrandChild2: GrandChild2 }
738
744
739
-
This is completely general and allows you to implement all manner of dispatch modifications. There are currently no built-in throttling or debouncing wrappers, but you can write your own.
745
+
type ChildMsg =
746
+
| SetGrandChild1 of GrandChild1
747
+
| SetGrandChild2 of GrandChild2
740
748
741
-
To show you how you can write them from scratch, here is a throttling wrapper that dispatches the latest message every X milliseconds:
742
-
743
-
```f#
744
-
let throttle (durationMs: int) (dispatch: Dispatch<'msg>) : Dispatch<'msg> =
745
-
let locker = obj()
746
-
let mutable lastMessage = ValueNone
747
-
let timer = new System.Timers.Timer(Interval = float durationMs)
748
-
timer.Elapsed.Add (fun _ ->
749
-
lock locker (fun () ->
750
-
lastMessage |> ValueOption.iter dispatch
751
-
lastMessage <- ValueNone
752
-
)
753
-
)
754
-
timer.Start()
755
-
fun msg ->
756
-
async {
757
-
lock locker (fun () ->
758
-
lastMessage <- ValueSome msg
759
-
)
760
-
} |> Async.Start
749
+
type ParentModel =
750
+
{ Child: ChildModel }
751
+
752
+
type ParentMsg =
753
+
| ChildMsg of ChildMsg
761
754
```
762
755
763
-
Note the use of `Async.Start`. It seems to be required in order to avoid deadlocks, see [#114 (comment)](https://github.com/elmish/Elmish.WPF/issues/114#issuecomment-532481275).
764
-
765
-
If you want a more general solution, you can make use of [FSharp.Control.Reactive](http://fsprojects.github.io/FSharp.Control.Reactive/) (which provides an F#-friendly wrapper over [System.Reactive](https://www.nuget.org/packages/System.Reactive), which you can also just use directly). You can write a general helper function to convert any `IObservable` combinator/extension to a dispatch wrapper:
756
+
It is possible to create bindings from the parent to the two grandchild fields, but there is duplicate mapping code.
let parentBindings () : Binding<ParentModel, ParentMsg> list =
773
+
childBindings ()
774
+
|> Bindings.mapModel (fun parent -> parent.Child)
775
+
|> Bindings.mapMsg ChildMsg
786
776
```
787
777
788
-
Note that since the `binding` function is only called once (or once per sub-model for the sub-model bindings), you can inline the wrapper creation as shown above. (For a “normal” Elmish architecture where `view` is called for each update, you’d have to define it outside).
778
+
See the `SubModelSeq` sample for a another example use of `mapModel` and a use of `mapModelWithMsg`.
779
+
780
+
#### Theory behind `mapModel` and `mapMsg`
789
781
790
-
Note also that throttling as demonstrated above may cause suboptimal behavior for two-way bindings due to the value shown in the UI being locked to the value returned by `get`, which isn’t updated until the message is dispatched. For sliders, the slider will “lag” behind the mouse cursor when dragging it, only moving when a message is dispatched and the model updated. For text boxes, throttling will make the cursor jump to the start of the text box while typing, and only some of the characters will be entered.
782
+
A binding in Elmish.WPF is represented by an instance of type `Binding<'model, 'msg>`. It is a functor in both type parameters. More specifically,
783
+
- it a contravariant functor in `'model`, and `mapModel` is the corresponding mapping function for this functor; and
784
+
- it is a covariant functor in `'msg`, and `mapMsg` is the corresponding mapping function for this functor.
0 commit comments