Search code examples
f#websharper

Unable to embed document fragment into a WebSharper page trough a `Var<Doc>`


I am quite new to using WebSharper and I might be doing things the wrong way. My goal is to be able to update the contents of my page as a result of user actions by updating a Var<Doc> variable representing a portion of the page to be updated. I'd be happy to know if I could update a Var<Doc> from server-side code and have it reflect in the user's browser.

Below is a quick example:

let TestPage ctx =
    let clientPart = Var.Create <| Doc.Empty
    clientPart .Value <- div [] [ text "This content is dynamically inserted" ]

    Templating.Main ctx EndPoint.Home "Home" [
        h1 [] [text "Below is a dynamically inserted content:"]
        div [] [ client <@ clientPart .View |> Doc.EmbedView @> ]
    ]

The error I receive is:

System.Exception: Error during RPC JSON conversion ---> System.Exception: Failed to look up translated field name for write' in type WebSharper.UI.Elt with fields: docNode, elt, rvUpdates, updates

The WebSharper 4 documentation regarding Views also states:

It will only be run while the resulting View is included in the document using one of these methods:

  • Doc.BindView
  • Doc.EmbedView
  • textView

and etc.

A similar error is produced if I try this instead:

type SomeTemplate = Template<"SomeTemplate.html">
clientDoc.Value <- SomeTemplate().Doc()

In the above code, Templating.Main is the same as in the default WebSharper project:

module Templating =
    ...
    let Main ctx action (title: string) (body: Doc list) =
        let t = MainTemplate().Title(title).MenuBar(MenuBar ctx action).With("Body", body)
        let doc : Doc = t.Doc()
        doc |> Content.Page

Solution

  • Here is an example calling an RPC on the server side and storing it into a client Var<>:

    module ServerFunctions =
    
        let mutable ServerState = ("Zero", 0)
    
        let [< Rpc >] addToState n = async {
            let state, counter = ServerState
            let newCounter = counter + n
            let newState   = if newCounter = 0 then "Zero" else "NonZero"
            ServerState <- newState, newCounter
            return newState
        }
    
    [< JavaScript >]
    module ClientFunctions =
        open WebSharper
        open WebSharper.UI
        open WebSharper.UI.Html
        open ServerFunctions
    
        let zeroState = Var.Create "do not know"
    
        let clientDoc() = 
            div [] [
                h1 [] [ text "State of zero on server:" ]
                h2 [] [ text zeroState.V ]
                Doc.Button "increment" [] (fun () -> async { let! state = addToState  1
                                                             zeroState.Set state 
                                                            } |> Async.Start)
                Doc.Button "decrement" [] (fun () -> async { let! state = addToState -1
                                                             zeroState.Set state 
                                                            } |> Async.Start)
            ]
    
    module Server =
        open global.Owin
        open Microsoft.Owin.Hosting
        open Microsoft.Owin.StaticFiles
        open Microsoft.Owin.FileSystems
        open WebSharper.Owin
        open WebSharper.UI.Server
        open WebSharper.UI.Html
    
        type EndPointServer = 
            | [< EndPoint "/" >] Hello
            | About
    
        let url = "http://localhost:9006/"
        let rootdir = @"..\website"
        let site()  = WebSharper.Application.MultiPage(fun context (s:EndPointServer) -> 
                        printfn "Serving page: %A" s
                        Content.Page(
                            Title= ( sprintf "Test %A" s)
                          , Body = [ h1 [] [ text <| sprintf "%A" s ] 
                                     Html.client <@  ClientFunctions.clientDoc() @> ]) 
                      )