I am at the process of learning WebSharper, and I am struggling with making some of my logic work on the client-side.
I have a few server-side objects with inheritance hierarchy, which I need to expose to the client-side. They deal with generating different parts of the page as doc fragments -- but on the client.
[<JavaScriptExport>]
type [<AbstractClass>] A() =
abstract member Doc: Map<string, A> -> Doc
...
[<JavaScriptExport>]
type [<AbstractClass>] B() =
inherit A()
[<JavaScriptExport>]
type C() =
inherit B()
The post is updated, see history for the older version.
In my server-side code I have a map associating each object with a given name (as in the strategy design pattern):
let mutable objectMap : Map<string, A> = Map.empty
At some point, the map gets filled with data. This happens once only in the application initialization phase, but is a result of the server-side backend logic.
Now, I need to use those objects on the client-side only, like in the overly-simplified snippet below:
[<JavaScriptExport>]
type C() =
inherit B() // or inherit A()
override this.Doc map =
div [] [ map.["innerDoc"].Doc(map)
On the server-side end I would have:
module ServerSide =
let Main ctx endpoint ... =
// this is the mapping that is being generated on the server, derived somehow from the `objectMap ` above:
let serverMap : Map<string, something> = ...
let document =
[
div [ on.afterRender(fun _ -> ClientCode.fromServerMap(serverMap));][client <@ ClientCode.getDoc("someDoc") @>]
] |> Doc.Concat
Page.Content document
The ClientCode
module would be something that gets compiled to JS and would look like this:
[<JavaScript>]
moduel ClientCode =
let _map : Var<Map<string, A>> = Var.Create <| Map.empty
let fromServerMap (serverMap : something) =
let clientMap : Map<string, A> = // TODO: get the information from server map
_map.Set clientMap
let getDoc (docName : string) =
_map.View.Map(fun m -> m.[docName].Doc(m))
|> Doc.EmbedView
So far I've found out that simply returning the map via an Rpc
during the afterRender
would not work -- either generic JS objects are being returned, or I am receiving a serialization error. Looks like this is the expected behavior for the WebSharper remoting and clinet-server communication.
I know I could just implement my ClientModule.obtainObject
by hardCoding the A
instances inside my map and it does work if I do so, but I need to avoid that part. The module I am developing does not have to know the exact mapping or implementation of the types inheriting from A
(like B
and C
for example), nor what names they have been associated with.
What other approaches I need to use to pass the information from the server-side object map to the client? Maybe use something like Quotation.Expr<A>
in my code?
Update 1: I do not necessarily need to instantiate the objects on the server. Maybe there is a way to send the mapping information to the client and let it do the instantiation somehow?
Update 2: Here is a github repo with a simple representation of what I have got working so far
Update 3: An alternative approach would be to keep on the server a mappping that would use the name of my object type instead of an instance of it (Map<string, string>
). Now if my client code sees ClientAode.C
of whatever the full type name is, is it possible to invoke the default constructor of that type entirely from JavaScript?
Here is another take
In this case I create a dictionary called
types
that gives each class a unique identifier based on the file and line number. The server and client versions are slightly different. The server version uses the type name as the key while the client uses the file & line number as a key (Client.fs):On the server side I changed the
_map
that wasMap<string,ClientCode.A>
toMap<string, string>
. The client does the same thing but in reverse.The dictionary
types
acts literally as a dictionary for both the server and the client to translate back and forth between unique name and actual object.(Site.fs):