# DevTalk.net

ActiveMesa's software development blog. MathSharp, R2P, X2C and more!

## RESTful Web Conversations 2

Having created a back-end using the new WCF Web API, I decided it was time to create a front-end and, not being a particular fan of JavaScript, went with WebSharper instead. Here’s a brief experience report.

Source code is, as always, available here: https://bitbucket.org/nesteruk/restfulwebconversation

### JSONP, again…

Before doing anything, I mirrored the conversation data structure using the F# record type:

type ConversationItem = {
Id : int
PartyId : int
Speech : string
Responses : ConversationItem list
}


So to try things out, I wrote a very simple function that simply did a GET of some data from the server:

[<JavaScript>]
let serverUrl = "http://localhost:51688/"
[<JavaScript>]
let Update(id:string) =
JQuery.GetJSON(serverUrl + id,
fun (result:ConversationItem, msg) ->
JQuery.Of("#serverItem").Text(msg)) |> ignore


This raised the all too familiar error message:

XMLHttpRequest cannot load http://localhost:51688/. Origin null is not allowed by Access-Control-Allow-Origin.


For a moment I thought that maybe I’ve gone crazy and I’d have to implement JSONP support manually like I did for WCF REST (in a pre-.Net 4 scenario), so I tried to adjust the query string to explicitly invoke the callback:

[<JavaScript>]
let Update(id:string) =
JQuery.GetJSON(serverUrl + id + "?callback=?",
fun (result:ConversationItem, msg) ->
JQuery.Of("#serverItem").Text(msg)) |> ignore


Now that I got this working, I hit another problem: the result I got from the service was XML! Now I know that JSON works fine with the new API, but when I opened Fiddler, what I saw is that the header had an Accept: */* instead! As it turns out, with JSONP you cannot even customize it, which means that my nice, resource-oriented server must be forced to return JSON by default.

While waiting for clarification on how to do this (typical fiddling in Web.config didn’t help one bit), I turned off Chrome web security and went with a JSON (as opposed to JSONP) approach. I suspect that the lack of JSONP support us due to the nature of the Web API, which isn’t ready yet.

(On a side note, I noticed that the entity names of my lists in XML had a wrong name. So using XML would have been impossible anyway without further fiddling the XML serializer.)

### Consuming the Service

So to start with I create a bare-minimum HTML structure to house my UI:

[<JavaScript>]
let Conversation() =
Div [
P [Id "serverItem"] -< [Text "Server text here"]
Div [Id "clientItems"]
Button [Text "Hello"] |>! OnClick (fun x y -> Update(""))
]


And I subsequently try to project each piece of data onto the UI using the following piece of code:

[<JavaScript>]
let Update(id:string) =
JQuery.GetJSON(serverUrl + id,
fun (item:ConversationItem, msg) ->
// what the server says
JQuery.Of("#serverItem").Text(item.Speech).Ignore
// what the client can respond with
if not (List.isEmpty item.Responses) then
let listItems =
item.Responses |> List.map (fun r -> LI [ A [HRef "#"] -< [Text r.Speech]])
let wholeList = OL [] -< listItems
JQuery.Of("#clientItems").Empty().Append(wholeList.Dom).Ignore
) |> ignore


Now, we can turn to the next and most painful part of our site – the handling of links.

Okay, so what should those links contain? Common sense dictates that the URL they point to should contain the link to the conversation item that has just been pressed, e.g., http://mysite.com/123 where 123 is the ID of what I just said.

That’s all well and good but gets us nowhere, because we’re not interested in postbacks (it’s pure AJAX) and because even if we get the data at the URL, we’d still need to render it. Anyways, we can agree that we need the link, if only to do another AJAX call.

The moral of the story here is that you don’t get nice permalinks, you cannot add a bookmark to a conversation point (largely because the conversation has a context which we cannot preserve), but you can define AJAX-based link handling actions en masse. Essentially, we make the Update() function recursive and then wire up each link to call it:

let listItems =
item.Responses |> List.map
(fun r -> LI [ A [HRef "#"] -< [Text r.Speech]]
|>! OnClick(fun x y -> Update(r.Id.ToString()))
)


This works, but what about the progressive enabling/disabling of links? Our Empty() JavaScript call wipes all existing links but they must, presumably, be stored somewhere on the client in order to be useful.

If you remember my previous post, we agreed to make the list of enabled options a string such as 1,2,3. The server then repopulates the set of links each time we get a response, taking into account the links we already have. Now, how can we put together this string without creating any extra data structures? The answer is, of course, to use jQuery.

The agreement here is to assign each link its ID

fun r -> LI [ A [HRef "#"; Id (r.Id.ToString())] -< [Text r.Speech]]
...


and subsequently iterate over all of them, creating the query string. Now, due to F#‘s statelessness, the code below uses ref cells and is a bit ugly:

let enabledOptions = ref ""

JQuery.Of("#clientItems").Find("a").Each(
fun (e:Dom.Element) -> let id = JQuery.Of(e).Attr("id")
enabledOptions := !enabledOptions + id + ",").Ignore


And then, of course, we pass enabledOptions as a parameter to the URL. Note that this could be considered bad by some REST purists used to clean URLs. However, this is something I can live with.

### Conclusion

As you can see, I am not posting the full example online yet. There are a few reasons why I am not doing this:

• I am still not sure what the situation with JSONP is right now. I hope that I’m just missing something in the new API, as opposed to having to implement this myself. Same goes for having JSON as the default output type for the service.

• I am still looking for a way to define conversations more efficiently. The main problem right now is that the way I’m doing it with enable/disable list causes it to be a ’poor man’s HSM’ instead of a proper one. Perhaps the Stateless library could be of some use here.

• Another problem I’m having is that, due to the fact that HTML is generated via JavaScript, I can’t see a place where I can initialize the content of my HTML after it has been returned. This is mainly due to my lack of knowledge of WebSharper, but I hope to figure it out before I post the next part.

Overall though, I’m happy to say that I didn’t write a single line of JavaScript to get it all working. To be continued!

Written by Dmitri Nesteruk

May 13th, 2011 at 2:55 pm

Posted in FSharp

Tagged with , , ,