Skip to content

Commit

Permalink
Added a bit more to output
Browse files Browse the repository at this point in the history
  • Loading branch information
ReedCopsey committed Jun 17, 2016
1 parent 6442e0b commit 62e43a8
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 41 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -187,3 +187,4 @@ docs/content/license.md
docs/content/release-notes.md
.fake
docs/tools/FSharp.Formatting.svclog
Settings.FSharpLint
52 changes: 30 additions & 22 deletions samples/WpfSimpleMvvmApplication/ViewModels.fs
Expand Up @@ -15,11 +15,16 @@ with

module VM =
let createMainViewModel (nameIn : IObservable<NameModel>) initialValue =
// Create an observable binding source equivelent to https://github.com/fsprojects/FsXaml/blob/master/demos/WpfSimpleMvvmApplication/MainViewModel.fs
// Create an observable binding source
let bindingSource = Binding.createObservableSource ()

// Map our incoming IObservable to a signal. If we didn't want to use an input IObservable, we could just use Signal.constant or Mutable.create
let source = bindingSource.ObservableToSignal initialValue nameIn
// This is optional, but lets us track changes easily
let source = Mutable.create initialValue

// Copy incoming changes from our input observable into our mutable
nameIn
|> Observable.Subscription.copyTo source
|> bindingSource.AddDisposable

// Create the "properties" we want to bind to - this could be mutables, signals (for read-only), or commands
let first =
Expand All @@ -33,27 +38,31 @@ module VM =
Signal.map2 (fun f l -> f + " " + l) first.RawInput last.RawInput
|> Binding.toViewValidated bindingSource "Full" (notEqual "Ree Copsey" >> fixErrorsWithMessage "That is a poor choice of names")

// This is our "result" from the UI (includes invalid results)
// As the user types, this constantly updates
let name' =
Signal.mapValidated2 (fun f l -> {First = f; Last = l}) first.Output last.Output
|> Signal.choose id source.Value
// TODO: Replace with UserOutput.map2 [to write]?
// Or should we leave mapValidated* in place?
// This is our "result" from the UI
// As the user types, this constantly updates whenever the output is valid and doesn't match the last known value
let userChanges =
Signal.mapOption2 (fun f l -> {First = f; Last = l}) first.Output last.Output
|> Observable.filterSome
|> Observable.filter (fun v -> v <> source.Value)

// We're storing the last "good" name from the user. Initializes using input value
let lastGoodName =
Signal.Subscription.fromObservable source.Value userChanges
|> bindingSource.AddDisposable2

// Create a "toggle" which we can use to toggle whether to push automatically to the backend
let pushAutomatically = Mutable.create false

// Bind it directly and push changes back to the input mutable
// and bind it directly and push changes back to the input mutable
let pushAutomatically = Mutable.create false
bindingSource.MutateToFromView (pushAutomatically, "PushAutomatically")

let pushManually = Signal.not pushAutomatically

// Create a command that will only execute if
// 1) We're valid, 2) we're not pusing automatically, and 3) our name has changed from the input
// 1) Our name has changed from the input
// 2) We're pushing manually
// 3) We're not executing an async operation currently, and
// 4) We're valid
let canExecute =
Signal.notEqual source name'
|> Signal.both pushManually
Signal.notEqual lastGoodName source
|> Signal.both (Signal.not pushAutomatically)
|> Signal.both bindingSource.IdleTracker
|> Signal.both bindingSource.Valid
let okCommand = bindingSource |> Binding.createCommandChecked "OkCommand" canExecute
Expand Down Expand Up @@ -87,16 +96,15 @@ module VM =
// Note that we use FilterValid to guarantee that all validation is completed before
// the final signal is sent through. This isn't a problem with the command approach,
// but guarantees our validation to always be up to date before it's queried in the filter
name'
|> bindingSource.FilterValid
userChanges
|> Observable.filter (fun _ -> pushAutomatically.Value)
// Command-triggered updates
let commandUpdates =
// In this case, we can use our command to map the right value out when it's clicked
// Since the command already is only enabled when we're valid, we don't need a validity filter here
okCommand
|> Observable.filterBy pushManually
|> Observable.map (fun _ -> name'.Value)
|> Observable.filterBy (Signal.not pushAutomatically)
|> Observable.map (fun _ -> lastGoodName.Value)

// Combine our automatic and manual updates into one signal, and push them to the backing observable
Observable.merge automaticUpdates commandUpdates
Expand Down
13 changes: 12 additions & 1 deletion src/Gjallarhorn/Observable.fs
Expand Up @@ -12,6 +12,12 @@ module Observable =
input
|> Observable.filter (fun _ -> condition.Value)

/// Filters out observables of options to only pass through
/// "Some" values
let filterSome (provider: IObservable<'a option>) =
provider
|> Observable.choose id

/// Maps the input observable through an async workflow.
let mapAsync (mapping : 'a -> Async<'b>) (provider : IObservable<'a>) =
let evt = Event<_>()
Expand Down Expand Up @@ -40,4 +46,9 @@ module Observable =
let! result = mapping v
return result
}
mapAsync trackingMap provider
mapAsync trackingMap provider

module Subscription =
/// Create a subscription to an observable which copies its value upon change into a mutable
let copyTo (target : IMutatable<'a>) (provider : IObservable<'a>) =
provider.Subscribe(fun v -> target.Value <- v)
36 changes: 18 additions & 18 deletions src/Gjallarhorn/Signal.fs
Expand Up @@ -166,54 +166,54 @@ module Signal =
/// Combines two signals of options, such as validation results,
/// using a specified mapping function
/// If either is None, the result is None
let mapValidated2 (mapping : 'a -> 'b -> 'c) (provider1 : ISignal<'a option>) (provider2 : ISignal<'b option>) =
let mapOption2 (mapping : 'a -> 'b -> 'c) (provider1 : ISignal<'a option>) (provider2 : ISignal<'b option>) =
map2 (optionMap2 mapping) provider1 provider2

// Lift function for options
let private liftO f a b c =
let f' a bc = f a (fst bc) (snd bc)
let bc = mapValidated2 (fun b c -> b,c) b c
let bc = mapOption2 (fun b c -> b,c) b c
f', bc

/// Combines three signals of options using a specified mapping function
let mapValidated3 f v1 v2 v3 =
let mapOption3 f v1 v2 v3 =
let f1, bc = liftO f v1 v2 v3
mapValidated2 f1 v1 bc
mapOption2 f1 v1 bc

/// Combines four signals of options using a specified mapping function
let mapValidated4 f v1 v2 v3 v4 =
let mapOption4 f v1 v2 v3 v4 =
let f1, bc = liftO f v1 v2 v3
mapValidated3 f1 v1 bc v4
mapOption3 f1 v1 bc v4

/// Combines five signals of options using a specified mapping function
let mapValidated5 f v1 v2 v3 v4 v5 =
let mapOption5 f v1 v2 v3 v4 v5 =
let f1, bc = liftO f v1 v2 v3
mapValidated4 f1 v1 bc v4 v5
mapOption4 f1 v1 bc v4 v5

/// Combines six signals of options using a specified mapping function
let mapValidated6 f v1 v2 v3 v4 v5 v6 =
let mapOption6 f v1 v2 v3 v4 v5 v6 =
let f1, bc = liftO f v1 v2 v3
mapValidated5 f1 v1 bc v4 v5 v6
mapOption5 f1 v1 bc v4 v5 v6

/// Combines seven signals of options using a specified mapping function
let mapValidated7 f v1 v2 v3 v4 v5 v6 v7=
let mapOption7 f v1 v2 v3 v4 v5 v6 v7=
let f1, bc = liftO f v1 v2 v3
mapValidated6 f1 v1 bc v4 v5 v6 v7
mapOption6 f1 v1 bc v4 v5 v6 v7

/// Combines eight signals of options using a specified mapping function
let mapValidated8 f v1 v2 v3 v4 v5 v6 v7 v8 =
let mapOption8 f v1 v2 v3 v4 v5 v6 v7 v8 =
let f1, bc = liftO f v1 v2 v3
mapValidated7 f1 v1 bc v4 v5 v6 v7 v8
mapOption7 f1 v1 bc v4 v5 v6 v7 v8

/// Combines nine signals of options using a specified mapping function
let mapValidated9 f v1 v2 v3 v4 v5 v6 v7 v8 v9 =
let mapOption9 f v1 v2 v3 v4 v5 v6 v7 v8 v9 =
let f1, bc = liftO f v1 v2 v3
mapValidated8 f1 v1 bc v4 v5 v6 v7 v8 v9
mapOption8 f1 v1 bc v4 v5 v6 v7 v8 v9

/// Combines ten signals of options using a specified mapping function
let mapValidated10 f v1 v2 v3 v4 v5 v6 v7 v8 v9 v10 =
let mapOption10 f v1 v2 v3 v4 v5 v6 v7 v8 v9 v10 =
let f1, bc = liftO f v1 v2 v3
mapValidated9 f1 v1 bc v4 v5 v6 v7 v8 v9 v10
mapOption9 f1 v1 bc v4 v5 v6 v7 v8 v9 v10

/// Filters the signal, so only values matching the predicate are cached and propogated onwards.
/// If the provider's value doesn't match the predicate, the resulting signal begins with the provided defaultValue.
Expand Down

0 comments on commit 62e43a8

Please sign in to comment.