0

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
Ivaylo Slavov
  • 8,839
  • 12
  • 65
  • 108
  • 1
    I do not think a `Var<>` in the server side can trigger a change in the client side. Your `Var<>`s and `View<>`s need to be maintained and updated all in the client side. If you want to send messages from the server to the client you need to create a different channel of communication between server and client(s) like for instance WebSockets. – AMieres Jan 02 '19 at 12:10
  • @AMieres, this makes sense. Do you know how can I sync the state of a client-side var and a server-side var? My goal is to have part of the page generated serverside (on the initial load) and then process user input/actions mostly on the client-side. I believe I can achieve this by modifying a few dedicated vars but they need to be both accessible on the client and server. Is there anything special I need to do to achieve this? – Ivaylo Slavov Jan 03 '19 at 07:52
  • 1
    The client can always call a function on the server with attribute `[< Rpc >]` to query any value on the server (which should not be in a `Var<>` because they do not make sense in the server) and then store the result into a `Var<>` from which you can construct a `Doc`. That works as long as the event originates in the client. Is that your case or values change in the server without the client knowing? – AMieres Jan 03 '19 at 08:19
  • @AMieres, I believe this illustrates the case indeed. The client currently performs an RPC which modifies the server state. I need the modified state to get back to the client somehow, so I guess I will have to dig deeper into the rpc consuming from the client. As I said, I am new to WebSharper and I was trying to keep thins as simple as possible. However, I got lost with vars -- it was not relly obvious that a server-side var has nothing to do with a client-side one. Thanks for the advice so far. I'd love to give you some score if you put this as an answer. – Ivaylo Slavov Jan 03 '19 at 08:30

1 Answers1

1

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() @> ]) 
                  )                      
AMieres
  • 4,944
  • 1
  • 14
  • 19