Skip to content

Websharper super power with Vars

#WebSharper’s super power with Vars

Using WebSharper allows you to develop both the client side and the server side of a web application in C# or F#, with client to server communication abstracted as RPC calls. There’s no need to handle low-level concerns like XMLHttpRequest or data serialisation. There’s great Javascript interoperability of course, allowing to use javascript libraries from your F# code.

But still, what I enjoy most when developing a WebSharper app is its reactive layer built on Vars and Views. From Websharper’s documentation:

Vars are similar to F# ref<'T> in that they store a value of type 'T
that you can get or set using the Value property.
But they can additionally be reactively observed or two-way bound to
HTML input elements.

A View has a value computed from Vars and/or other Views, with its value continuously kept up to date. All HTML elements using a View are updated when the View’s value changes. Views are deeply integrated in WebSharper’s UI library, making it trivial to have dynamic user interfaces.

Introductory example

Here’s a minimal example to grasp the concept of Vars and Views. It defines a Var<string>, a Var containing a string, by initiating it with the string "a". It also defines html content with a button, which when clicked will append an additional a to the string stored in the accumulator. Finally,we display the accumulator value in the page.

// Define a Var with the value "a"
let accumulator = Var.Create "a"
div
    []
    [
        button
            [
                on.click
                    (fun el ev ->
                        accumulator.Set (accumulator.Value + "a"))
            ]
            [ text "Append 'a'" ]
        br [] []
        span
            []
            [textView accumulator.View ]
    ]

You’ll notice there’s no code handling the update of the value displayed. We include the view in the output, and WebSharper hanles all updates for us.

This is a toy example illustrating how things work, but let’s now look at a real world example.

Real world example

I wanted to toggle the display of an element in the page when a button was clicked. To give an idea of the conciseness and approachability of the code, here it is (we will examine it below):

let linkableVisible = Var.Create false
let linkableVisibilityClass =
    linkableVisible.View
    |> View.Map
        (fun isVisible ->
            if isVisible then
                "d-block"
            else
                "d-none"
        )
let buttonText =
    linkableVisible.View
    |> View.Map (fun visible->
        if visible then
            "Hide element"
        else
            "Show element"

    )

div []
    [
        button
            [ attr.``class`` "btn-primary"
              on.clickView
                    linkableVisible.View
                    (fun _ev _el isVisible->
                        linkableVisible.Set (not isVisible))
            ]
            [   buttonText.V
            ]
        div [attr.classDyn linkableVisibilityClass ]
            [
                div [ ]
                    [
                        text "toggled element"
                    ]
            ]
    ]

We define one Var named linkableVisible, holding a booling value indicating if the element is currently visible. This is the Var on which the solution will be built.

We hide the element by giving it the CSS class d-none from Bootstrap. So what we want is map the value of linkableVisible to "d-none" (resp. "d-block") if its value is false(resp. true). But of course, when the value of linkableVisible is changed, we want the CSS class to be updated accordingly. This indicates the need of defining a View derived from linkableVisible. This is how it is done:

let linkableVisibilityClass =
    // Take the view of linkableVisible
    linkableVisible.View
    // Map is to the css class
    |> View.Map
        // This function gets the current value of linkableVisible
        (fun isVisible ->
            if isVisible then
                "d-block"
            else
                "d-none"
        )

Similarly, we want the button text to be dependent of the visibility of the toggled element. If the element is visible (linkableVisible is true), we want the button text to be Hide element, and if the element is hidden, the button text should be Show element. Of course the button text should update accordingly, which again suggests the use of a view. And indeed here again we map a view of linkableVisible to a text view with the right value with this code:

let buttonText =
    linkableVisible.View
    |> View.Map (fun visible->
        if visible then
            t.tt "Hide element"
        else
            t.tt "Show element"

    )

And that’s all there is to do on the logic side. We now just have to integrate these views in the html generation. Let’s first take a look at the button:

button
    [ attr.``class`` "btn-primary"
      on.clickView
            linkableVisible.View
            (fun _ev _el isVisible->
                linkableVisible.Set (not isVisible))
    ]
    [   buttonText.V
    ]

We set the class btn-primary provided by Bootstrap, and set the click handler, which simply toggles the boolean value present in the Var linkableVisible. Setting the Var’s new value is the only time our code touches the Var, all other code is based on Views. We never read the value from the Var itself, but access it through its View.

Including the toggle element is done as easily:

div [attr.classDyn linkableVisibilityClass ]
    [
        div [ ]
            [
                text "toggled element"
            ]
    ]

WebSharper provides the helper attr.classDyn to set the class string attribute of an element, taking a View<string> and setting its value as the class of said element. When the value of the View changes, so does the class of the element in the DOM.

You can run this code online here and its real world application here.

Going further

You might say, toggling an element visibility is good, but it means it has to be present in the DOM. What if I don’t want to include it in the DOM right away, because it is the result of an RPC call to get information from the server? Well we can easily build further on what we have. Let’s look at the changes needed.

We first define a function that will issue the RPC and get information from the server and return a Doc based on it. We don’t issue an RPC here to make it runnable on try.websharper.com, but as it is async it replicates the situation correctly with the Async.Sleep.

let callRPCAndReturnDoc() = async {
    do! Async.Sleep 300
    return div [] [text "my info from server"]
}

At initial page render time, we won’t have a doc available to include in the DOM, so we create a Var containing an option type. When we have the Doc built with data from the server, we will store it in this Var:

let linkableDocOption:Var<Option<Doc>> = Var.Create None

In the HTML, we have to include a Doc anyway, so we build a View from this Var, mapping a None value to Doc.Empty which can be included in the HTML document:

let linkableDocView =
    linkableDocOption.View
    |> View.Map(
        function
        | None -> Doc.Empty
        | Some doc -> doc
    )

Our onclick handler will now have to act according to 2 criteria: the visibility and the availability of the Doc build with the result of the RPC:

  • visible -> hide the element, whatever the Doc status
  • hidden, linkableDocOption is None -> issue RPC and show element
  • hidden, linkableDocOption is Some -> show element.

But to achieve this, we need a view giving the visibility and RPC status. We map to a View<bool,bool> (a pair of boolean values):

let isVisibleIsRetrieved =
            // Map2's mapping function takes 2 arguments, corresponding to the current values
            // of the 2 views also passed as arguments to Map2
            View.Map2 (fun visible (doc:Option<Doc>) -> (visible,doc.IsSome))
                linkableVisible.View
                linkableDocOption.View

With this view defined, writing our click handler follows its description nearly literally:

let displayLinkableTable (visible,retrieved) = async {
    match visible,retrieved with
    // If visible, just hide it. It must have been retrieved at that point.
    | true, _ ->
        linkableVisible.Set (not visible)
    // if it was alredy retrieved (by a previous click on the button), just make it visible again
    | false, true ->
        linkableVisible.Set (not visible)
    | false, false ->
            // Call the thunk we got to setup the linkable table, we get a Doc value
            let! docBuiltFromRPC = callRPCAndReturnDoc()
            // Update the Vars accordingly
            linkableDocOption.Set (Some docBuiltFromRPC)
            linkableVisible.Set (not visible)
}

Updates to the HTML to be displayed are minimal. Update the click handler to use isVisibleIsRetrieved, and start the Async to issue the RPC:

[ on.clickView
        isVisibleIsRetrieved
        (fun _ev _el isVisibleIsRetrieved->
            (displayLinkableTable isVisibleIsRetrieved)|> Async.StartImmediate)
]

and include the linkableDoc view in the HTML:

div [ ]
    [
        linkableDocView.V
    ]

It uses the V shortcut easing the use of Views in the Doc building functions. And that’s all there is. You can run this code online here. You’ll notice that the first time you click on the button the element is shown with a small delay, because the RPC was issued (simulated here by the Async.Sleep). Subsequent displays of the element are immediate though. The real world application of this can be found here.

The changes needed to issue the RPC at first display are minimal, but bring a big improvement. And if you want to improve the solution further it can be done easily:

  • want to display a spinner while the RPC is done? Replace Doc.Empty by the spinner.
  • want to execute the RPC every time the element is shown? This is easily done by updating slightly displayLinkableTable. And displaying the spinner can be done by changing linkableDocOption before issuing the RPC.
  • want to issue the RPC only if the previous RPC was issued more than 10 seconds away? Put the time of the last RPC call in a Var and use it to decide if an RPC should be issued.

Possibilities are endless, all while keeping the code maintainable and staying in F#. What more is there to ask? :-)