Blog moved to http://www.andrevdm.com/

Tuesday 18 November 2014

F# FunScript with NancyFx and Ractive


Getting started


I just started with FunScript and got stuck with a few of the basics. Here is how I got it all working

FunScript is a library that compiles F# to JavaScript. This lets you write strongly typed client side code in F#.  It takes advantage of many of the F# features, async workflows (no callback) etc. Take a look at the FunScript page for more information.

Ractive.JS is a template drive reactive UI library created initially at the Guardian. The Ractive tutorials are very clear and definitely worth using

NancyFx is a lightweight HTTP framework for .NET. It is less popular in the F# world than C# but still works very well from F#.


Read the official docs at http://funscript.info/. The introduction and tutorials are very good and will give you a good background in what FunScript is and how it works.

The code below is based on these demos as well as one of Alfonso's projects https://github.com/alfonsogarciacaro/PinkBubbles.Informa


In this post I'll demonstrate the following
  1. Serving data as JSON with NancyFx
  2. Declaring types in F# and using them in FunScript code
  3. Reading the JSON with FunScript and casting to the strongly typed type
  4. Rendering a basic Ractor template
Although I'm using NancyFx here any web framework should work much the same way.


Creating the solution


  1. Open Visual Studio
  2. Create a new F# console project
  3. Add the following nuget references
    • FunScript
    • funscript.TypeScript.binding.lib
    • nancy.hosting.self
  4. Enable type providers when prompted
  5. Get FunScript.HTML.dll and FunScript.HTML.Ractive.dll.
    I got them from Alfonso's github demo, they are included in my github repo
  6. Download and store the two DLLs in a local folder (e.g. lib)
  7. Add reference to the two DLLs

NancyFx - Serving some data

The code to serve a simple page over nancy fx
Run the projet. If you get permission errors, you will need to run studio as an administrator. Browse to http://localhost:6543/ping This snippet
  1. Creates a nancyFx module "IndexModule"
  2. Defines a route for GET /ping

    This may look a bit strange but all it does is define that whenever a GET request is received for /ping the current DateTime string should be returned. 'box' is called to box the result as on object as that is what NancyFx is returning
  3. Starts the host on port 6543


F# Types


As a simple example all types in the current assembly will be returned. Below is the function that does this as well as the AssemblyTypes record for this data.

Serving the data as JSON



The /data route has been added. It calls the getAssemblyTypes function defined above and returns the data as JSON.
To format the data as JSON you call NancyFx's FormatterExtensions.AsJson method
If you run the project and open http://localhost:6543/data in your browser you should see the JSON data being returned

FunScript - Index page and template


At this point we have a simple HTTP server that can serve JSON data. Now the  scaffolding to get FunScript working needs to be added Create an index.html page that will be used to display the data
This page contains the following items
  • A "ractive-container" div into which the template will be rendered
  • A "ractive-template" script block containing the template
  • A script include for ractive
  • A script include for the generate Javascript from the FunScript compiler. I'll show how this works below

Add the NancyFx route for the index page. I'm loading the page using a File.ReadAllText(). (NancyFx can serve static pages but I'm avoiding the discussion about bootstrappers, resource directories etc here. You should read the NancyFx docs about this if you are using this in a real project)



FunScript - Compiling F# to JavaScript


Here is the F# code that compiles the F# to JavaScript. I'll comment on each section of the code below
Since this is the F# that needs to be compile by the FunScript compiler to JavaScript it needs the ReflectedDefinition attribute on the module
The compile function does the actual compilation. This should be familiar from the FunScript guide
The start function does the following
  •   Creates a Ractive instance
  •   Creates the initial application state
  •   Starts a Ractive loop (mainLoop function)

The application state is stored in record defined like this
Here is the start function. Note the creation of the initial RactiveState. Also note that JavaScript is expecting an array not a list or a seq.

That is the main scaffolding for getting FunScript and Ractive working. Next the mainLoop function which contains the F# code that is going to run in the browser.

This function does the following
  • Check if a reload of the data was requested
  • Call our /data endpoint to get the JSON
  • Cast the JSON data to the stronly typed F# type
  • Mark as updated


This looks a little complex but the core of it is the following
  1. A WebRequest instance is created.
  2. AsyncGetJSON<> is called and casts the JSON to our F# type.
    This is the real magic of FunScript. The AsyncGetJSON makes an AJAX call from the browser for you and then lets you work with your data in a strongly typed way.
  3. The two Globals.console.log calls show the typed data being used.
  4. Finally an updated state is returned


Serving app.js



All that is left is to get the FunScript compile F# served as app.js
Not terribly pretty but simple enough. A text response object is create a containing the javascript compiled from the F# (by the Web.compile function). Then the content type is changed to application/javascript. Finally the response object is returned

If you run the project and browse to http://localhost:6543 you will see your data being displayed. The code itself is mostly scaffolding and is pretty simple. With the basics working you can now continue with the tutorials from Ractive and FunScript to take things further.

Code


The full code is on github https://github.com/andrevdm/FunScriptRactorAndNancyDemo

Feel free to suggest any improvements

No comments:

Post a Comment