Skip to content

Manipulating javascript objects in FSharp with WebSharper

In a WebSharper app I’m using Bootstrap tooltips, and those need to be initialised explicitly. As shown in the doc this is easily done. For example this code will make the tooltip work as expected for the element with id elementId, so that hovering that element will trigger the tooltip to be shown:

var el = document.getElementById(elementId)
new bootstrap.Tooltip(el)

However, in my case, the element for which a tooltip should be shown is dynamically added and removed from the document following user actions. And the tooltip didn’t hide immediately, but only if the user scrolled the page, leading to confusion as the tooltip shown was about an element of the page that is not present anymore. The solution in my case was to call .dispose() on the tooltip object constructed on the last line of the previous code excerpt. Let’s see how this can be done with WebSharper.

First, we need to get a handle to the tooltip objects when it is initialised. This code does initialise the tooltip on element with id tooltipId and assigns it to the variable tooltip:

let tooltip = JavaScript.JS.Inline("""
                  var el = document.getElementById($0)
                  var tt = new bootstrap.Tooltip(el)
                  return tt
              """, tooltipId)

Now, F# is statically typed, and tooltip is of the type WebSharper.JavaScript.HTMLElement which does not have the method dispose(). Two solutions are available.

Unsafe access with ?

This is the simplest solution, but that should be used only for the simplest case as type safety is lost. If you open WebSharper.JavaScript, you get the unsafe operator ? which lets you call dispose like this: tooltip?dispose(). This works, but there’s no compile-time checks and you risk runtime errors. This makes this solution to be sparsely used.

Define a binding

You can easily bindings to javascript objects, and only cover the methods you use. In our example, we only access one method, so we will only include that one on the stub.

[<Stub>]
type BSTooltip() =
    [<Name "dispose">]
    member this.Dispose() = WebSharper.JavaScript.Interop.X<_>

What we do:

  1. Define a type that we annotate with [<Stub>]

  2. Define a member for the method we want to access. We annotate this method with

    • [<Name "dispose">]: This indicates the name of the javascript method our method is binding.
    • The [<Stub>] attribute is inherited, so no need to repeat it here.

    The body of the method is WebSharper.JavaScript.Interop.X<_> (which is an alias for Unchecked.defaultof<_>) just to please the type checker.There’s no need to provide a body, as it will simply call the javascript method we mention in the [<Name>] annotation. It will however raise a ClientSideOnly exception if you try to call this method at the server side.

When we initialise the tooltip, we can now explicitly set the type of our variable to the stub type we defined:

let tooltip:BSTooltip = JavaScript.JS.Inline("""
                            var el = document.getElementById($0)
                            var tt = new bootstrap.Tooltip(el)
                            return tt
                        """, tooltipId)

With that being done, we can call the method:

tooltip.Dispose()

The method Dispose is known by the typechecker, and is even proposed in code completion, making it the preferred solution for more complex or intensive uses.

Thanks to @Jand42 for the feedback on this post.