I've figured it out. Here is my solution, which may be of interest to others. I'm using Zurb Foundation, hence the various attributes.

First some helpers:

1
2
3
type attr with
    static member cls = attr.``class``
    static member typ = attr.``type``

Now the actual code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
let username = Var.Create ""
let password = Var.Create ""
let submit = Var.Create ()

let loginData = View.Map2 (fun u p -> { Username = u; Password = p }) username.View password.View

let showError = Var.Create true
// Set 'showError' to false whenever the user types.
// This view only exists for its side effect.
let hideError = loginData |> View.Map (fun _ -> Var.Set showError false)

let loginError = // View<string option>
    loginData
    |> View.Map Some
    |> View.SnapshotOn None submit.View
    |> View.MapAsync (function
        | None -> async { return None }
        | Some data ->
            async {
                let! res = login data
                // Reset 'showError' so that the error message (if Some) is
                // displayed until the user types some more.
                Var.Set showError true
                return res
            })

let loginErrorDoc =
    View.Map2 (fun a b -> a,b) loginError showError.View
    |> View.Map (function
        // Only show the login error message if 'showError' is true.
        | Some message, true ->
            divAttr [attr.cls "alert-box alert"; Attr.Create "data-alert" ""] [
                text message
                aAttr [attr.cls "close"; attr.href "#"] [text "×"]
            ] :> Doc
        | _ -> Doc.Empty)

divAttr [attr.cls "row"] [
    divAttr [attr.cls "small-4 small-centered columns"] [
        form [
            Doc.Input [attr.typ "text"; attr.placeholder "Username"] username
            Doc.Input [attr.typ "password"; attr.placeholder "Password"] password
            Doc.Button "Sign in" [attr.cls "button"] (Var.Set submit)
            Doc.EmbedView loginErrorDoc

            // 'hideError' view only exists for its side effect, but we
            // need to sink it, otherwise it will never be demanded.
            // Embed empty doc, because I'm paranoid about using View.Sink
            // from what I've read in various places.
            hideError |> View.Map (fun _ -> Doc.Empty) |> Doc.EmbedView
        ]
    ]
]

Is this the right way to do it?

Notice that I'm not using View.Sink, because I'm paranoid about memory leaks. I'm embedding an empty Doc instead, which achieves the same thing. Is that a good idea?

By on 8/28/2015 5:31 AM ()

I think that's pretty much how I would do it. I have also occasionally used the "map to Doc.Empty and embed" trick. There's a good chance that we will soon provide a helper for that, something that would have type View<'T> -> ('T -> unit) -> Attr.

By on 8/28/2015 8:50 AM ()
IntelliFactory Offices Copyright (c) 2011-2012 IntelliFactory. All rights reserved.
Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy
Built with WebSharper